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