]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
1 | /* |
2 | * Copyright (c) 2000-2004,2006-2009,2012-2013 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 <security_utilities/debugging.h> | |
51 | #include <security_utilities/logging.h> | |
52 | #include <security_cdsa_utilities/osxverifier.h> | |
53 | #include <algorithm> | |
54 | ||
5c19dc3a | 55 | #include <Security/AuthorizationTagsPriv.h> |
d8f41ccd A |
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 = NULL; | |
89 | OSStatus validation = errSecCSStaticCodeNotFound; | |
90 | { | |
91 | StLock<Mutex> _(process); | |
92 | Server::active().longTermActivity(); | |
93 | clientCode = process.currentGuest(); | |
94 | if (clientCode) { | |
95 | validation = SecCodeCheckValidity(clientCode, kSecCSDefaultFlags, NULL); | |
96 | } | |
97 | ||
98 | switch (validation) | |
99 | { | |
100 | case noErr: // client is signed and valid | |
101 | { | |
102 | bool forceAllow = false; | |
103 | secdebug("kcacl", "client is valid, proceeding"); | |
104 | CFDictionaryRef codeDictionary = NULL; | |
105 | if (errSecSuccess == SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &codeDictionary)) { | |
106 | CFTypeRef entitlementsDictionary = NULL; | |
107 | entitlementsDictionary = CFDictionaryGetValue(codeDictionary, kSecCodeInfoEntitlementsDict); | |
108 | if (NULL != entitlementsDictionary) { | |
109 | if (CFGetTypeID(entitlementsDictionary) == CFDictionaryGetTypeID()) { | |
110 | CFTypeRef migrationEntitlement = CFDictionaryGetValue((CFDictionaryRef)entitlementsDictionary, CFSTR("com.apple.private.security.allow-migration")); | |
111 | if (NULL != migrationEntitlement) { | |
112 | if (CFGetTypeID(migrationEntitlement) == CFBooleanGetTypeID()) { | |
113 | if (migrationEntitlement == kCFBooleanTrue) { | |
114 | secdebug("kcacl", "client has migration entitlement, allowing"); | |
115 | forceAllow = true; | |
116 | } | |
117 | } | |
118 | } | |
119 | } | |
120 | } | |
121 | CFRelease(codeDictionary); | |
122 | } | |
123 | if (forceAllow) { | |
124 | return true; | |
125 | } | |
126 | } | |
127 | break; | |
128 | ||
129 | case errSecCSUnsigned: | |
130 | { // client is not signed | |
131 | if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) { | |
132 | secdebug("kcacl", "client is unsigned, suppressing prompt"); | |
133 | return false; | |
134 | } | |
135 | } | |
136 | break; | |
137 | ||
138 | case errSecCSSignatureFailed: // client signed but signature is broken | |
139 | case errSecCSGuestInvalid: // client signed but dynamically invalid | |
140 | case errSecCSStaticCodeNotFound: // client not on disk (or unreadable) | |
141 | { | |
142 | if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) { | |
143 | secdebug("kcacl", "client is invalid, suppressing prompt"); | |
144 | Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", | |
145 | process.getPath().c_str(), process.pid()); | |
146 | return false; | |
147 | } | |
148 | Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", | |
149 | process.getPath().c_str(), process.pid()); | |
150 | } | |
151 | break; | |
152 | ||
153 | default: // something else went wrong | |
154 | secdebug("kcacl", "client validation failed rc=%d, suppressing prompt", int32_t(validation)); | |
155 | return false; | |
156 | } | |
157 | } | |
158 | ||
159 | // At this point, we're committed to try to Pop The Question. Now, how? | |
160 | ||
161 | // does the user need to type in the passphrase? | |
162 | const Database *db = env->database; | |
163 | bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); | |
164 | ||
165 | // an application (i.e. Keychain Access.app :-) can force this option | |
166 | if (clientCode && validation == noErr) { | |
167 | StLock<Mutex> _(process); | |
168 | CFRef<CFDictionaryRef> dict; | |
169 | if (SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &dict.aref()) == noErr) | |
170 | if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList))) | |
171 | needPassphrase |= | |
172 | (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL); | |
173 | } | |
174 | ||
175 | // pop The Question | |
176 | if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { | |
177 | QueryKeychainAuth query; | |
178 | query.inferHints(Server::process()); | |
179 | if (query(db ? db->dbName() : NULL, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason) | |
180 | return false; | |
181 | return true; | |
182 | } else { | |
183 | QueryKeychainUse query(needPassphrase, db); | |
184 | query.inferHints(Server::process()); | |
185 | query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation)); | |
186 | if (query.queryUser(db ? db->dbName() : NULL, | |
187 | description.c_str(), context.authorization()) != SecurityAgent::noReason) | |
188 | return false; | |
189 | ||
190 | // process an "always allow..." response | |
191 | if (query.remember && clientCode) { | |
192 | StLock<Mutex> _(process); | |
193 | RefPointer<OSXCode> clientXCode = new OSXCodeWrap(clientCode); | |
194 | RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(clientXCode)); | |
195 | SecurityServerAcl::addToStandardACL(context, subject); | |
196 | } | |
197 | ||
198 | // finally, return the actual user response | |
199 | return query.allow; | |
200 | } | |
201 | } | |
202 | return false; // default to deny without prejudice | |
203 | } | |
204 | ||
205 | ||
206 | // | |
207 | // Make a copy of this subject in CSSM_LIST form | |
208 | // | |
209 | CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const | |
210 | { | |
211 | // always issue new (non-legacy) form | |
212 | return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, | |
213 | new(alloc) ListElement(alloc, CssmData::wrap(selector)), | |
214 | new(alloc) ListElement(alloc, description)); | |
215 | } | |
216 | ||
217 | // | |
218 | // Has the caller recently authorized in such a way as to render unnecessary | |
219 | // the usual QueryKeychainAuth dialog? (The right is specific to Keychain | |
220 | // Access' way of editing a system keychain.) | |
221 | // | |
222 | bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const | |
223 | { | |
224 | string rightString = "system.keychain.modify"; | |
225 | return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/); | |
226 | } | |
227 | ||
228 | ||
229 | ||
230 | // | |
231 | // Create a KeychainPromptAclSubject | |
232 | // | |
233 | uint32_t KeychainPromptAclSubject::Maker::defaultMode; | |
234 | ||
235 | KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const | |
236 | { | |
237 | switch (list.length()) { | |
238 | #if ACCEPT_LEGACY_FORM | |
239 | case 2: // legacy case: just description | |
240 | { | |
241 | ListElement *params[1]; | |
242 | crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); | |
243 | return new KeychainPromptAclSubject(*params[0], defaultSelector); | |
244 | } | |
245 | #endif //ACCEPT_LEGACY_FORM | |
246 | case 3: // standard case: selector + description | |
247 | { | |
248 | ListElement *params[2]; | |
249 | crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); | |
250 | return new KeychainPromptAclSubject(*params[1], | |
251 | *params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); | |
252 | } | |
253 | default: | |
254 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
255 | } | |
256 | } | |
257 | ||
258 | KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, | |
259 | Reader &pub, Reader &) const | |
260 | { | |
261 | CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; | |
262 | const char *description; | |
263 | switch (version) { | |
264 | case pumaVersion: | |
265 | selector = defaultSelector; | |
266 | pub(description); | |
267 | break; | |
268 | case jaguarVersion: | |
269 | pub(selector); | |
270 | selector.version = n2h(selector.version); | |
271 | selector.flags = n2h(selector.flags); | |
272 | pub(description); | |
273 | break; | |
274 | default: | |
275 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
276 | } | |
277 | return new KeychainPromptAclSubject(description, selector); | |
278 | } | |
279 | ||
280 | KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, | |
281 | const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) | |
282 | : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT), | |
283 | selector(sel), description(descr) | |
284 | { | |
285 | // check selector version | |
286 | if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) | |
287 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
288 | ||
289 | // always use the latest binary version | |
290 | version(currentVersion); | |
291 | } | |
292 | ||
293 | ||
294 | // | |
295 | // Export the subject to a memory blob | |
296 | // | |
297 | void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) | |
298 | { | |
299 | if (version() != 0) { | |
300 | selector.version = h2n (selector.version); | |
301 | selector.flags = h2n (selector.flags); | |
302 | pub(selector); | |
303 | } | |
304 | ||
305 | pub.insert(description.size() + 1); | |
306 | } | |
307 | ||
308 | void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) | |
309 | { | |
310 | if (version() != 0) { | |
311 | selector.version = h2n (selector.version); | |
312 | selector.flags = h2n (selector.flags); | |
313 | pub(selector); | |
314 | } | |
315 | pub(description.c_str()); | |
316 | } | |
317 | ||
318 | ||
319 | #ifdef DEBUGDUMP | |
320 | ||
321 | void KeychainPromptAclSubject::debugDump() const | |
322 | { | |
323 | Debug::dump("KeychainPrompt:%s(%s)", | |
324 | description.c_str(), | |
325 | (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); | |
326 | } | |
327 | ||
328 | #endif //DEBUGDUMP |