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