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//
62CSSM_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//
71bool 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//
208CssmList 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//
221bool 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//
232uint32_t KeychainPromptAclSubject::Maker::defaultMode;
233
234KeychainPromptAclSubject *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
257KeychainPromptAclSubject *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
279KeychainPromptAclSubject::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//
296void 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
307void 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
320void 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
328