]> git.saurik.com Git - apple/securityd.git/blob - src/acl_keychain.cpp
securityd-32661.tar.gz
[apple/securityd.git] / src / acl_keychain.cpp
1 /*
2 * Copyright (c) 2000-2004,2007 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 //
26 // acl_keychain - a subject type for the protected-path
27 // keychain prompt interaction model.
28 //
29 // Arguments in CSSM_LIST form:
30 // list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure
31 // list[2] = CssmData: Descriptive String (presented to user in protected dialogs)
32 // For legacy compatibility, we accept a single-entry form
33 // list[1] = CssmData: Descriptive String
34 // which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value.
35 // This is never produced by current code, and is considered purely a legacy feature.
36 //
37 // On-disk (flattened) representation:
38 // In order to accommodate legacy formats nicely, we use the binary-versioning feature
39 // of the ACL machinery. Version 0 is the legacy format (storing only the description
40 // string), while Version 1 contains both selector and description. We are now always
41 // writing version-1 data, but will continue to recognize version-0 data indefinitely
42 // for really, really old keychain items.
43 //
44 #include "acl_keychain.h"
45 #include "agentquery.h"
46 #include "acls.h"
47 #include "connection.h"
48 #include "database.h"
49 #include "server.h"
50 #include "osxcodewrap.h"
51 #include <security_utilities/debugging.h>
52 #include <security_utilities/logging.h>
53 #include <security_cdsa_utilities/osxverifier.h>
54 #include <algorithm>
55
56
57 #define ACCEPT_LEGACY_FORM 1
58
59
60 //
61 // The default for the selector structure.
62 //
63 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = {
64 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version
65 0 // flags
66 };
67
68
69 //
70 // Validate a credential set against this subject.
71 //
72 bool KeychainPromptAclSubject::validate(const AclValidationContext &context,
73 const TypedList &sample) const
74 {
75 if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
76 Process &process = Server::process();
77 secdebug("kcacl", "Keychain query for process %d (UID %d)", process.pid(), process.uid());
78
79 // assemble the effective validity mode mask
80 uint32_t mode = Maker::defaultMode;
81 const uint16_t &flags = selector.flags;
82 if (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT)
83 mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED);
84 if (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT)
85 mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID);
86
87 // determine signed/validity status of client, without reference to any particular Code Requirement
88 SecCodeRef clientCode = process.currentGuest();
89 Server::active().longTermActivity();
90 OSStatus validation = clientCode ? SecCodeCheckValidity(clientCode, kSecCSDefaultFlags, NULL) : errSecCSStaticCodeNotFound;
91 switch (validation) {
92 case noErr: // client is signed and valid
93 secdebug("kcacl", "client is valid, proceeding");
94 break;
95 case errSecCSUnsigned: // client is not signed
96 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) {
97 secdebug("kcacl", "client is unsigned, suppressing prompt");
98 return false;
99 }
100 break;
101 case errSecCSSignatureFailed: // client signed but signature is broken
102 case errSecCSGuestInvalid: // client signed but dynamically invalid
103 case errSecCSStaticCodeNotFound: // client not on disk (or unreadable)
104 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) {
105 secdebug("kcacl", "client is invalid, suppressing prompt");
106 Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)",
107 process.getPath().c_str(), process.pid());
108 return false;
109 }
110 Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)",
111 process.getPath().c_str(), process.pid());
112 break;
113 default: // something else went wrong
114 secdebug("kcacl", "client validation failed rc=%ld, suppressing prompt", validation);
115 return false;
116 }
117
118 // At this point, we're committed to try to Pop The Question. Now, how?
119
120 // does the user need to type in the passphrase?
121 const Database *db = env->database;
122 bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE);
123
124 // an application (i.e. Keychain Access.app :-) can force this option
125 if (clientCode) {
126 CFRef<CFDictionaryRef> dict;
127 if (!SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &dict.aref()))
128 if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList)))
129 needPassphrase |=
130 (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL);
131 }
132
133 // pop The Question
134 QueryKeychainUse query(needPassphrase, db);
135 query.inferHints(Server::process());
136 query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation));
137 if (query.queryUser(db ? db->dbName() : NULL,
138 description.c_str(), context.authorization()) != SecurityAgent::noReason)
139 return false;
140
141 // process an "always allow..." response
142 if (query.remember && clientCode) {
143 RefPointer<OSXCode> clientXCode = new OSXCodeWrap(clientCode);
144 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(clientXCode));
145 SecurityServerAcl::addToStandardACL(context, subject);
146 }
147
148 // finally, return the actual user response
149 return query.allow;
150 }
151 return false; // default to deny without prejudice
152 }
153
154
155 //
156 // Make a copy of this subject in CSSM_LIST form
157 //
158 CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const
159 {
160 // always issue new (non-legacy) form
161 return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
162 new(alloc) ListElement(alloc, CssmData::wrap(selector)),
163 new(alloc) ListElement(alloc, description));
164 }
165
166
167 //
168 // Create a KeychainPromptAclSubject
169 //
170 uint32_t KeychainPromptAclSubject::Maker::defaultMode;
171
172 KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const
173 {
174 switch (list.length()) {
175 #if ACCEPT_LEGACY_FORM
176 case 2: // legacy case: just description
177 {
178 ListElement *params[1];
179 crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM);
180 return new KeychainPromptAclSubject(*params[0], defaultSelector);
181 }
182 #endif //ACCEPT_LEGACY_FORM
183 case 3: // standard case: selector + description
184 {
185 ListElement *params[2];
186 crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM);
187 return new KeychainPromptAclSubject(*params[1],
188 *params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE));
189 }
190 default:
191 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
192 }
193 }
194
195 KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version,
196 Reader &pub, Reader &) const
197 {
198 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector;
199 const char *description;
200 switch (version) {
201 case pumaVersion:
202 selector = defaultSelector;
203 pub(description);
204 break;
205 case jaguarVersion:
206 pub(selector);
207 selector.version = n2h(selector.version);
208 selector.flags = n2h(selector.flags);
209 pub(description);
210 break;
211 default:
212 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
213 }
214 return new KeychainPromptAclSubject(description, selector);
215 }
216
217 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr,
218 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel)
219 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT),
220 selector(sel), description(descr)
221 {
222 // check selector version
223 if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION)
224 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
225
226 // always use the latest binary version
227 version(currentVersion);
228 }
229
230
231 //
232 // Export the subject to a memory blob
233 //
234 void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv)
235 {
236 if (version() != 0) {
237 selector.version = h2n (selector.version);
238 selector.flags = h2n (selector.flags);
239 pub(selector);
240 }
241
242 pub.insert(description.size() + 1);
243 }
244
245 void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv)
246 {
247 if (version() != 0) {
248 selector.version = h2n (selector.version);
249 selector.flags = h2n (selector.flags);
250 pub(selector);
251 }
252 pub(description.c_str());
253 }
254
255
256 #ifdef DEBUGDUMP
257
258 void KeychainPromptAclSubject::debugDump() const
259 {
260 Debug::dump("KeychainPrompt:%s(%s)",
261 description.c_str(),
262 (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard");
263 }
264
265 #endif //DEBUGDUMP