]> git.saurik.com Git - apple/security.git/blame - securityd/src/acl_keychain.cpp
Security-58286.51.6.tar.gz
[apple/security.git] / securityd / src / acl_keychain.cpp
CommitLineData
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//
63uint32_t KeychainPromptAclSubject::promptsValidated = 0;
64
d8f41ccd
A
65
66//
67// The default for the selector structure.
68//
69CSSM_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//
80bool 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 100bool 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
117bool 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//
234CssmList 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//
247bool 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//
259uint32_t KeychainPromptAclSubject::Maker::defaultMode;
260
261KeychainPromptAclSubject *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
284KeychainPromptAclSubject *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
306KeychainPromptAclSubject::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//
323void 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
334void 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
347void 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
357uint32_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
366void KeychainPromptAclSubject::addPromptAttempt() {
367 promptsValidated++;
368}