]>
Commit | Line | Data |
---|---|---|
d8f41ccd | 1 | /* |
fa7225c8 A |
2 | * Copyright (c) 2000-2004,2006-2009,2012-2013,2016 Apple Inc. All Rights Reserved. |
3 | * | |
d8f41ccd | 4 | * @APPLE_LICENSE_HEADER_START@ |
fa7225c8 | 5 | * |
d8f41ccd A |
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. | |
fa7225c8 | 12 | * |
d8f41ccd A |
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. | |
fa7225c8 | 20 | * |
d8f41ccd A |
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> | |
fa7225c8 | 54 | #include <sys/csr.h> |
d8f41ccd | 55 | |
5c19dc3a | 56 | #include <Security/AuthorizationTagsPriv.h> |
d8f41ccd A |
57 | |
58 | #define ACCEPT_LEGACY_FORM 1 | |
59 | ||
fa7225c8 A |
60 | // |
61 | // Initialize static memory. | |
62 | // | |
63 | uint32_t KeychainPromptAclSubject::promptsValidated = 0; | |
64 | ||
d8f41ccd A |
65 | |
66 | // | |
67 | // The default for the selector structure. | |
68 | // | |
69 | CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = { | |
70 | CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version | |
71 | 0 // flags | |
72 | }; | |
73 | ||
74 | ||
e3d460c9 A |
75 | // |
76 | // If we have a KeychainPromptAclSubject, we want KeychainMigrator to have | |
77 | // access even if we don't have the "pop ui" credential. Do the code signing | |
78 | // check first, then process this ACL as normal. | |
79 | // | |
80 | bool KeychainPromptAclSubject::validates(const AclValidationContext &ctx) const | |
81 | { | |
82 | Process &process = Server::process(); | |
83 | if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) { | |
84 | Syslog::info("bypassing keychain prompt for keychain migrator"); | |
fa7225c8 | 85 | secnotice("kcacl", "bypassing keychain prompt for keychain migrator"); |
e3d460c9 A |
86 | return true; // migrator client -> automatic win |
87 | } | |
88 | ||
fa7225c8 A |
89 | // Also, mark down that we evaluated a prompt ACL. We want to record this for testing even if the client did not pass credentials for UI |
90 | // (so that tests can disable prompts but still detect if one would have popped) | |
91 | promptsValidated++; | |
92 | ||
e3d460c9 A |
93 | return SimpleAclSubject::validates(ctx); |
94 | } | |
95 | ||
96 | ||
d8f41ccd A |
97 | // |
98 | // Validate a credential set against this subject. | |
99 | // | |
e3d460c9 | 100 | bool KeychainPromptAclSubject::validates(const AclValidationContext &context, |
d8f41ccd | 101 | const TypedList &sample) const |
e3d460c9 A |
102 | { |
103 | return validateExplicitly(context, ^{ | |
104 | if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) { | |
fa7225c8 | 105 | Process& process = Server::process(); |
e3d460c9 | 106 | StLock<Mutex> _(process); |
fa7225c8 | 107 | RefPointer<AclSubject> subject = process.copyAclSubject(); |
e3d460c9 | 108 | if (SecurityServerAcl::addToStandardACL(context, subject)) { |
fa7225c8 | 109 | if(env->database && env->database->dbVersion() >= CommonBlob::version_partition) { |
e3d460c9 A |
110 | env->acl.addClientPartitionID(process); |
111 | } | |
112 | } | |
113 | } | |
114 | }); | |
115 | } | |
116 | ||
117 | bool KeychainPromptAclSubject::validateExplicitly(const AclValidationContext &context, void (^alwaysAllow)()) const | |
d8f41ccd A |
118 | { |
119 | if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) { | |
120 | Process &process = Server::process(); | |
fa7225c8 | 121 | secnotice("kcacl", "Keychain query for process %d (UID %d)", process.pid(), process.uid()); |
d8f41ccd A |
122 | |
123 | // assemble the effective validity mode mask | |
124 | uint32_t mode = Maker::defaultMode; | |
125 | const uint16_t &flags = selector.flags; | |
126 | if (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT) | |
127 | mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED); | |
128 | if (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT) | |
129 | mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID); | |
fa7225c8 | 130 | |
d8f41ccd | 131 | // determine signed/validity status of client, without reference to any particular Code Requirement |
d8f41ccd A |
132 | OSStatus validation = errSecCSStaticCodeNotFound; |
133 | { | |
134 | StLock<Mutex> _(process); | |
135 | Server::active().longTermActivity(); | |
fa7225c8 A |
136 | |
137 | validation = process.checkValidity(kSecCSDefaultFlags, NULL); | |
138 | ||
d8f41ccd A |
139 | switch (validation) |
140 | { | |
141 | case noErr: // client is signed and valid | |
142 | { | |
fa7225c8 | 143 | secnotice("kcacl", "client is valid, proceeding"); |
e3d460c9 A |
144 | // This should almost always be handled by the check in KeychainPromptAclSubject::validate, but check again just in case |
145 | if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) { | |
146 | Syslog::info("bypassing keychain prompt for keychain migrator"); | |
fa7225c8 | 147 | secnotice("kcacl", "bypassing keychain prompt for keychain migrator"); |
e3d460c9 A |
148 | return true; // migrator client -> automatic win |
149 | } | |
d8f41ccd A |
150 | } |
151 | break; | |
fa7225c8 | 152 | |
d8f41ccd A |
153 | case errSecCSUnsigned: |
154 | { // client is not signed | |
155 | if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) { | |
e3d460c9 | 156 | Syslog::info("supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid()); |
fa7225c8 | 157 | secnotice("kcacl", "supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid()); |
d8f41ccd A |
158 | return false; |
159 | } | |
160 | } | |
161 | break; | |
fa7225c8 | 162 | |
d8f41ccd A |
163 | case errSecCSSignatureFailed: // client signed but signature is broken |
164 | case errSecCSGuestInvalid: // client signed but dynamically invalid | |
165 | case errSecCSStaticCodeNotFound: // client not on disk (or unreadable) | |
166 | { | |
167 | if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) { | |
fa7225c8 | 168 | secnotice("kcacl", "client is invalid, suppressing prompt"); |
e3d460c9 | 169 | Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); |
fa7225c8 | 170 | secnotice("kcacl", "suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); |
d8f41ccd A |
171 | return false; |
172 | } | |
e3d460c9 | 173 | Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); |
fa7225c8 | 174 | secnotice("kcacl", "attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); |
d8f41ccd A |
175 | } |
176 | break; | |
177 | ||
178 | default: // something else went wrong | |
e3d460c9 | 179 | Syslog::info("suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation); |
fa7225c8 | 180 | secnotice("kcacl", "suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation); |
d8f41ccd A |
181 | return false; |
182 | } | |
183 | } | |
fa7225c8 | 184 | |
d8f41ccd | 185 | // At this point, we're committed to try to Pop The Question. Now, how? |
e3d460c9 | 186 | Syslog::info("displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid()); |
fa7225c8 A |
187 | secnotice("kcacl", "displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid()); |
188 | ||
d8f41ccd A |
189 | // does the user need to type in the passphrase? |
190 | const Database *db = env->database; | |
191 | bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); | |
192 | ||
193 | // an application (i.e. Keychain Access.app :-) can force this option | |
fa7225c8 | 194 | if (validation == noErr) { |
d8f41ccd A |
195 | StLock<Mutex> _(process); |
196 | CFRef<CFDictionaryRef> dict; | |
fa7225c8 | 197 | if (process.copySigningInfo(kSecCSDefaultFlags, &dict.aref()) == noErr) |
d8f41ccd A |
198 | if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList))) |
199 | needPassphrase |= | |
200 | (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL); | |
201 | } | |
202 | ||
203 | // pop The Question | |
204 | if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { | |
205 | QueryKeychainAuth query; | |
206 | query.inferHints(Server::process()); | |
207 | if (query(db ? db->dbName() : NULL, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason) | |
208 | return false; | |
209 | return true; | |
210 | } else { | |
211 | QueryKeychainUse query(needPassphrase, db); | |
212 | query.inferHints(Server::process()); | |
213 | query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation)); | |
fa7225c8 | 214 | if (query.queryUser(db ? db->dbName() : NULL, |
d8f41ccd A |
215 | description.c_str(), context.authorization()) != SecurityAgent::noReason) |
216 | return false; | |
217 | ||
218 | // process an "always allow..." response | |
fa7225c8 | 219 | if (query.remember && validation != errSecCSStaticCodeNotFound) { |
e3d460c9 | 220 | alwaysAllow(); |
d8f41ccd A |
221 | } |
222 | ||
223 | // finally, return the actual user response | |
224 | return query.allow; | |
225 | } | |
226 | } | |
227 | return false; // default to deny without prejudice | |
228 | } | |
229 | ||
230 | ||
231 | // | |
232 | // Make a copy of this subject in CSSM_LIST form | |
233 | // | |
234 | CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const | |
235 | { | |
236 | // always issue new (non-legacy) form | |
237 | return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, | |
238 | new(alloc) ListElement(alloc, CssmData::wrap(selector)), | |
239 | new(alloc) ListElement(alloc, description)); | |
240 | } | |
241 | ||
242 | // | |
243 | // Has the caller recently authorized in such a way as to render unnecessary | |
fa7225c8 A |
244 | // the usual QueryKeychainAuth dialog? (The right is specific to Keychain |
245 | // Access' way of editing a system keychain.) | |
d8f41ccd A |
246 | // |
247 | bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const | |
248 | { | |
fa7225c8 A |
249 | // string rightString = "system.keychain.modify"; |
250 | // return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/); | |
251 | return false; | |
d8f41ccd A |
252 | } |
253 | ||
254 | ||
255 | ||
256 | // | |
257 | // Create a KeychainPromptAclSubject | |
258 | // | |
259 | uint32_t KeychainPromptAclSubject::Maker::defaultMode; | |
260 | ||
261 | KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const | |
262 | { | |
263 | switch (list.length()) { | |
264 | #if ACCEPT_LEGACY_FORM | |
265 | case 2: // legacy case: just description | |
266 | { | |
267 | ListElement *params[1]; | |
268 | crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); | |
269 | return new KeychainPromptAclSubject(*params[0], defaultSelector); | |
270 | } | |
271 | #endif //ACCEPT_LEGACY_FORM | |
272 | case 3: // standard case: selector + description | |
273 | { | |
274 | ListElement *params[2]; | |
275 | crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); | |
276 | return new KeychainPromptAclSubject(*params[1], | |
277 | *params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); | |
278 | } | |
279 | default: | |
280 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
281 | } | |
282 | } | |
283 | ||
284 | KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, | |
285 | Reader &pub, Reader &) const | |
286 | { | |
287 | CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; | |
288 | const char *description; | |
289 | switch (version) { | |
290 | case pumaVersion: | |
291 | selector = defaultSelector; | |
292 | pub(description); | |
293 | break; | |
294 | case jaguarVersion: | |
295 | pub(selector); | |
296 | selector.version = n2h(selector.version); | |
297 | selector.flags = n2h(selector.flags); | |
298 | pub(description); | |
299 | break; | |
300 | default: | |
301 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
302 | } | |
303 | return new KeychainPromptAclSubject(description, selector); | |
304 | } | |
305 | ||
306 | KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, | |
307 | const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) | |
308 | : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT), | |
309 | selector(sel), description(descr) | |
310 | { | |
311 | // check selector version | |
312 | if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) | |
313 | CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); | |
314 | ||
315 | // always use the latest binary version | |
316 | version(currentVersion); | |
317 | } | |
318 | ||
319 | ||
320 | // | |
321 | // Export the subject to a memory blob | |
322 | // | |
323 | void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) | |
324 | { | |
325 | if (version() != 0) { | |
326 | selector.version = h2n (selector.version); | |
327 | selector.flags = h2n (selector.flags); | |
328 | pub(selector); | |
329 | } | |
fa7225c8 | 330 | |
d8f41ccd A |
331 | pub.insert(description.size() + 1); |
332 | } | |
333 | ||
334 | void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) | |
335 | { | |
336 | if (version() != 0) { | |
337 | selector.version = h2n (selector.version); | |
338 | selector.flags = h2n (selector.flags); | |
339 | pub(selector); | |
340 | } | |
341 | pub(description.c_str()); | |
342 | } | |
343 | ||
344 | ||
345 | #ifdef DEBUGDUMP | |
346 | ||
347 | void KeychainPromptAclSubject::debugDump() const | |
348 | { | |
349 | Debug::dump("KeychainPrompt:%s(%s)", | |
350 | description.c_str(), | |
351 | (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); | |
352 | } | |
353 | ||
354 | #endif //DEBUGDUMP | |
fa7225c8 A |
355 | |
356 | ||
357 | uint32_t KeychainPromptAclSubject::getPromptAttempts() { | |
358 | if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) { | |
359 | // Not an internal install; don't answer | |
360 | return 0; | |
361 | } else { | |
362 | return KeychainPromptAclSubject::promptsValidated; | |
363 | } | |
364 | } | |
365 | ||
366 | void KeychainPromptAclSubject::addPromptAttempt() { | |
367 | promptsValidated++; | |
368 | } |