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