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