2 * Copyright (c) 2000-2004,2006-2009,2012-2013,2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 // acl_keychain - a subject type for the protected-path
27 // keychain prompt interaction model.
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.
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.
44 #include "acl_keychain.h"
45 #include "agentquery.h"
47 #include "connection.h"
50 #include <security_utilities/debugging.h>
51 #include <security_utilities/logging.h>
52 #include <security_cdsa_utilities/osxverifier.h>
56 #include <Security/AuthorizationTagsPriv.h>
58 #define ACCEPT_LEGACY_FORM 1
61 // Initialize static memory.
63 uint32_t KeychainPromptAclSubject::promptsValidated
= 0;
67 // The default for the selector structure.
69 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
KeychainPromptAclSubject::defaultSelector
= {
70 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
, // version
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.
80 bool KeychainPromptAclSubject::validates(const AclValidationContext
&ctx
) const
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
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)
93 return SimpleAclSubject::validates(ctx
);
98 // Validate a credential set against this subject.
100 bool KeychainPromptAclSubject::validates(const AclValidationContext
&context
,
101 const TypedList
&sample
) const
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
);
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
);
122 bool KeychainPromptAclSubject::validateExplicitly(const AclValidationContext
&context
, void (^alwaysAllow
)()) const
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());
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
);
136 // determine signed/validity status of client, without reference to any particular Code Requirement
137 OSStatus validation
= errSecCSStaticCodeNotFound
;
139 StLock
<Mutex
> _(process
);
140 Server::active().longTermActivity();
142 validation
= process
.checkValidity(kSecCSDefaultFlags
, NULL
);
146 case noErr
: // client is signed and valid
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
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());
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)
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());
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());
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
);
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());
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
);
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
)))
205 (CFDictionaryGetValue(info
, CFSTR("SecForcePassphrasePrompt")) != NULL
);
209 if (db
&& db
->belongsToSystem() && !hasAuthorizedForSystemKeychain()) {
210 QueryKeychainAuth query
;
211 query
.inferHints(Server::process());
212 if (query(db
? db
->dbName() : NULL
, description
.c_str(), context
.authorization(), NULL
) != SecurityAgent::noReason
)
216 QueryKeychainUse
query(needPassphrase
, db
);
217 query
.inferHints(Server::process());
218 query
.addHint(AGENT_HINT_CLIENT_VALIDITY
, &validation
, sizeof(validation
));
219 if (query
.queryUser(db
? db
->dbName() : NULL
,
220 description
.c_str(), context
.authorization()) != SecurityAgent::noReason
)
223 // process an "always allow..." response
224 if (query
.remember
&& validation
!= errSecCSStaticCodeNotFound
) {
228 // finally, return the actual user response
232 return false; // default to deny without prejudice
237 // Make a copy of this subject in CSSM_LIST form
239 CssmList
KeychainPromptAclSubject::toList(Allocator
&alloc
) const
241 // always issue new (non-legacy) form
242 return TypedList(alloc
, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
,
243 new(alloc
) ListElement(alloc
, CssmData::wrap(selector
)),
244 new(alloc
) ListElement(alloc
, description
));
248 // Has the caller recently authorized in such a way as to render unnecessary
249 // the usual QueryKeychainAuth dialog? (The right is specific to Keychain
250 // Access' way of editing a system keychain.)
252 bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const
254 // string rightString = "system.keychain.modify";
255 // return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/);
262 // Create a KeychainPromptAclSubject
264 uint32_t KeychainPromptAclSubject::Maker::defaultMode
;
266 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(const TypedList
&list
) const
268 switch (list
.length()) {
269 #if ACCEPT_LEGACY_FORM
270 case 2: // legacy case: just description
272 ListElement
*params
[1];
273 crack(list
, 1, params
, CSSM_LIST_ELEMENT_DATUM
);
274 return new KeychainPromptAclSubject(*params
[0], defaultSelector
);
276 #endif //ACCEPT_LEGACY_FORM
277 case 3: // standard case: selector + description
279 ListElement
*params
[2];
280 crack(list
, 2, params
, CSSM_LIST_ELEMENT_DATUM
, CSSM_LIST_ELEMENT_DATUM
);
281 return new KeychainPromptAclSubject(*params
[1],
282 *params
[0]->data().interpretedAs
<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
));
285 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
289 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(Version version
,
290 Reader
&pub
, Reader
&) const
292 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector
;
293 const char *description
;
296 selector
= defaultSelector
;
301 selector
.version
= n2h(selector
.version
);
302 selector
.flags
= n2h(selector
.flags
);
306 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
308 return new KeychainPromptAclSubject(description
, selector
);
311 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr
,
312 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
&sel
)
313 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
),
314 selector(sel
), description(descr
)
316 // check selector version
317 if (selector
.version
!= CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
)
318 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
320 // always use the latest binary version
321 version(currentVersion
);
326 // Export the subject to a memory blob
328 void KeychainPromptAclSubject::exportBlob(Writer::Counter
&pub
, Writer::Counter
&priv
)
330 if (version() != 0) {
331 selector
.version
= h2n (selector
.version
);
332 selector
.flags
= h2n (selector
.flags
);
336 pub
.insert(description
.size() + 1);
339 void KeychainPromptAclSubject::exportBlob(Writer
&pub
, Writer
&priv
)
341 if (version() != 0) {
342 selector
.version
= h2n (selector
.version
);
343 selector
.flags
= h2n (selector
.flags
);
346 pub(description
.c_str());
352 void KeychainPromptAclSubject::debugDump() const
354 Debug::dump("KeychainPrompt:%s(%s)",
356 (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
) ? "passphrase" : "standard");
362 uint32_t KeychainPromptAclSubject::getPromptAttempts() {
363 if (csr_check(CSR_ALLOW_APPLE_INTERNAL
)) {
364 // Not an internal install; don't answer
367 return KeychainPromptAclSubject::promptsValidated
;
371 void KeychainPromptAclSubject::addPromptAttempt() {