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