/ securityd / src / acls.cpp
acls.cpp
  1  /*
  2   * Copyright (c) 2000-2009,2012-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  // acls - securityd ACL implementation
 27  //
 28  #include "acls.h"
 29  #include "connection.h"
 30  #include "server.h"
 31  #include "agentquery.h"
 32  #include "tokendatabase.h"
 33  #include "acl_keychain.h"
 34  #include "acl_partition.h"
 35  
 36  // ACL subjects whose Environments we implement
 37  #include <security_cdsa_utilities/acl_any.h>
 38  #include <security_cdsa_utilities/acl_password.h>
 39  #include "acl_keychain.h"
 40  
 41  #include <sys/sysctl.h>
 42  #include <security_utilities/logging.h>
 43  #include <security_utilities/cfmunge.h>
 44  
 45  //
 46  // SecurityServerAcl is virtual
 47  //
 48  SecurityServerAcl::~SecurityServerAcl()
 49  { }
 50  
 51  
 52  //
 53  // The default implementation of the ACL interface simply uses the local ObjectAcl
 54  // data. You can customize this by implementing instantiateAcl() [from ObjectAcl]
 55  // or by overriding these methods as desired.
 56  // Note: While you can completely ignore the ObjectAcl personality if you wish, it's
 57  // usually smarter to adapt it.
 58  //
 59  void SecurityServerAcl::getOwner(AclOwnerPrototype &owner)
 60  {
 61  	StLock<Mutex> _(aclSequence);
 62  	ObjectAcl::cssmGetOwner(owner);
 63  }
 64  
 65  void SecurityServerAcl::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
 66  {
 67  	StLock<Mutex> _(aclSequence);
 68  	ObjectAcl::cssmGetAcl(tag, count, acls);
 69  }
 70  
 71  void SecurityServerAcl::changeAcl(const AclEdit &edit, const AccessCredentials *cred,
 72  	Database *db)
 73  {
 74  	StLock<Mutex> _(aclSequence);
 75  	SecurityServerEnvironment env(*this, db);
 76  
 77      // if we're setting the INTEGRITY entry, check if you're in the partition list.
 78      if (const AclEntryInput* input = edit.newEntry()) {
 79          if (input->proto().authorization().containsOnly(CSSM_ACL_AUTHORIZATION_INTEGRITY)) {
 80              // Only prompt the user if these creds allow UI.
 81              bool ui = (!!cred) && cred->authorizesUI();
 82              validatePartition(env, ui); // throws if fail
 83  
 84              // If you passed partition validation, bypass the owner ACL check entirely.
 85              env.forceSuccess = true;
 86          }
 87      }
 88  
 89      // If these access credentials, by themselves, protect this database, force success and don't
 90      // restrict changing PARTITION_ID
 91      if(db && db->checkCredentials(cred)) {
 92          env.forceSuccess = true;
 93          ObjectAcl::cssmChangeAcl(edit, cred, &env, NULL);
 94      } else {
 95          ObjectAcl::cssmChangeAcl(edit, cred, &env, CSSM_APPLE_ACL_TAG_PARTITION_ID);
 96      }
 97  }
 98  
 99  void SecurityServerAcl::changeOwner(const AclOwnerPrototype &newOwner,
100  	const AccessCredentials *cred, Database *db)
101  {
102  	StLock<Mutex> _(aclSequence);
103  	SecurityServerEnvironment env(*this, db);
104  	ObjectAcl::cssmChangeOwner(newOwner, cred, &env);
105  }
106  
107  
108  //
109  // Modified validate() methods to connect all the conduits...
110  //
111  void SecurityServerAcl::validate(AclAuthorization auth, const AccessCredentials *cred, Database *db)
112  {
113      SecurityServerEnvironment env(*this, db);
114  
115  	StLock<Mutex> objectSequence(aclSequence);
116  	StLock<Mutex> processSequence(Server::process().aclSequence);
117  	ObjectAcl::validate(auth, cred, &env);
118  
119      // partition validation happens outside the normal acl validation flow, in addition
120      bool ui = (!!cred) && cred->authorizesUI();
121  
122      // we should only offer the chance to extend the partition ID list on a "read" operation, so check the AclAuthorization
123      bool readOperation =
124          (auth == CSSM_ACL_AUTHORIZATION_CHANGE_ACL)     ||
125          (auth == CSSM_ACL_AUTHORIZATION_DECRYPT)        ||
126          (auth == CSSM_ACL_AUTHORIZATION_GENKEY)         ||
127          (auth == CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED) ||
128          (auth == CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR)   ||
129          (auth == CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED) ||
130          (auth == CSSM_ACL_AUTHORIZATION_IMPORT_CLEAR)   ||
131          (auth == CSSM_ACL_AUTHORIZATION_SIGN)           ||
132          (auth == CSSM_ACL_AUTHORIZATION_DECRYPT)        ||
133          (auth == CSSM_ACL_AUTHORIZATION_MAC)            ||
134          (auth == CSSM_ACL_AUTHORIZATION_DERIVE);
135  
136      validatePartition(env, ui && readOperation);
137  }
138  
139  void SecurityServerAcl::validate(AclAuthorization auth, const Context &context, Database *db)
140  {
141  	validate(auth,
142  		context.get<AccessCredentials>(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS), db);
143  }
144  
145  
146  //
147  // Partitioning support
148  //
149  void SecurityServerAcl::validatePartition(SecurityServerEnvironment& env, bool prompt)
150  {
151      // Avert your eyes!
152      StMaybeLock<Mutex> lock(env.database && env.database->hasCommon() ? &(env.database->common()) : NULL);
153  
154      // Calling checkAppleSigned() early at boot on a clean system install
155      // will end up trying to create the system keychain and causes a hang.
156      // Avoid this by checking for the presence of the db first.
157      if((!env.database) || env.database->dbVersion() < SecurityServer::CommonBlob::version_partition) {
158          secinfo("integrity", "no db or old db version, skipping");
159          return;
160      }
161  
162      // For the Keychain Migrator, don't even check the partition list
163      Process &process = Server::process();
164      if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
165          secnotice("integrity", "bypassing partition check for keychain migrator");
166          return;   // migrator client -> automatic win
167      }
168  
169  	if (CFRef<CFDictionaryRef> partition = this->createPartitionPayload()) {
170  		CFArrayRef partitionList;
171  		if (cfscan(partition, "{Partitions=%AO}", &partitionList)) {
172  			CFRef<CFStringRef> partitionDebug = CFCopyDescription(partitionList);	// for debugging only
173  			secinfo("integrity", "ACL partitionID = %s", cfString(partitionDebug).c_str());
174  			if (env.database) {
175  				CFRef<CFStringRef> clientPartitionID = makeCFString(env.database->process().partitionId());
176  				if (CFArrayContainsValue(partitionList, CFRangeMake(0, CFArrayGetCount(partitionList)), clientPartitionID)) {
177  					secinfo("integrity", "ACL partitions match: %s", cfString(clientPartitionID).c_str());
178  					return;
179  				} else {
180  					secnotice("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID).c_str(), cfString(partitionDebug).c_str());
181  					if (prompt && extendPartition(env))
182  						return;
183  					MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
184  				}
185  			}
186  		}
187  		secnotice("integrity", "failed to parse partition payload");
188  		MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
189      } else {
190          // There's no partition list. This keychain is recently upgraded.
191          Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
192          if(env.database->isRecoding()) {
193              secnotice("integrity", "no partition ACL - database is recoding; skipping add");
194              // let this pass as well
195          } else {
196              secnotice("integrity", "no partition ACL - adding");
197              env.acl.instantiateAcl();
198              this->createClientPartitionID(env.database->process());
199              env.acl.changedAcl();
200              Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
201          }
202      }
203  }
204  
205  
206  bool SecurityServerAcl::extendPartition(SecurityServerEnvironment& env)
207  {
208  	// brute-force find the KeychainAclSubject in the ACL
209  	KeychainPromptAclSubject *kcSubject = NULL;
210  	SecurityServerAcl& acl = env.acl;
211  	for (EntryMap::const_iterator it = acl.begin(); it != acl.end(); ++it) {
212  		AclSubjectPointer subject = it->second.subject;
213  		if (ThresholdAclSubject *threshold = dynamic_cast<ThresholdAclSubject *>(subject.get())) {
214  			unsigned size = threshold->count();
215  			if (KeychainPromptAclSubject* last = dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
216  				// looks standard enough
217  				kcSubject = last;
218  				break;
219  			}
220  		}
221  	}
222  
223  	if (kcSubject) {
224  		BaseValidationContext ctx(NULL, CSSM_ACL_AUTHORIZATION_PARTITION_ID, &env);
225          kcSubject->addPromptAttempt();
226  		return kcSubject->validateExplicitly(ctx, ^{
227              secnotice("integrity", "adding partition to list");
228              env.acl.instantiateAcl();
229  			this->addClientPartitionID(env.database->process());
230  			env.acl.changedAcl();
231  			// trigger a special notification code on (otherwise successful) return
232  			Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
233  		});
234  	}
235      secnotice("integrity", "failure extending partition");
236  	return false;
237  }
238  
239  
240  PartitionAclSubject* SecurityServerAcl::findPartitionSubject()
241  {
242  	pair<EntryMap::const_iterator, EntryMap::const_iterator> range;
243  	switch (this->getRange(CSSM_APPLE_ACL_TAG_PARTITION_ID, range, true)) {
244  		case 0:
245  			secnotice("integrity", "no partition tag on ACL");
246  			return NULL;
247  		default:
248  			secnotice("integrity", "multiple partition ACL entries");
249  			MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
250  		case 1:
251  			break;
252  	}
253  	const AclEntry& entry = range.first->second;
254  	if (!entry.authorizes(CSSM_ACL_AUTHORIZATION_PARTITION_ID)) {
255  		secnotice("integrity", "partition entry does not authorize CSSM_ACL_AUTHORIZATION_PARTITION_ID");
256  		MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
257  	}
258  	if (PartitionAclSubject* partition = dynamic_cast<PartitionAclSubject*>(entry.subject.get())) {
259  		return partition;
260  	} else {
261  		secnotice("integrity", "partition entry is not PartitionAclSubject");
262  		MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
263  	}
264  }
265  
266  
267  CFDictionaryRef SecurityServerAcl::createPartitionPayload()
268  {
269  	if (PartitionAclSubject* subject = this->findPartitionSubject()) {
270  		if (CFDictionaryRef result = subject->createDictionaryPayload()) {
271  			return result;
272  		} else {
273  			secnotice("integrity", "partition entry is malformed XML");
274  			MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
275  		}
276  	} else {
277  		return NULL;
278  	}
279  }
280  
281  
282  //
283  // This helper tries to add the (new) subject given to the ACL
284  // whose validation is currently proceeding through context.
285  // This will succeed if the ACL is in standard form, which means
286  // a ThresholdAclSubject.
287  // The new subject will be added at the front (so it is checked first
288  // from now on), and as a side effect we'll notify the client side to
289  // re-encode the object.
290  // Returns true if the edit could be done, or false if the ACL wasn't
291  // standard enough. May throw if the ACL is malformed or otherwise messed up.
292  //
293  // This is a self-contained helper that is here merely because it's "about"
294  // ACLs and has no better home.
295  //
296  bool SecurityServerAcl::addToStandardACL(const AclValidationContext &context, AclSubject *subject)
297  {
298  	if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>())
299  		if (ThresholdAclSubject *threshold = env->standardSubject(context)) {
300  			unsigned size = threshold->count();
301  			if (dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
302  				// looks standard enough
303  				secinfo("acl", "adding new subject %p to from of threshold ACL", subject);
304  				threshold->add(subject, 0);
305  
306  				// tell the ACL it's been modified
307  				context.acl()->changedAcl();
308  
309  				// trigger a special notification code on (otherwise successful) return
310  				Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
311  				return true;
312  			}
313  		}
314  	secinfo("acl", "ACL is not standard form; cannot edit");
315  	return false;
316  }
317  
318  
319  //
320  // Look at the ACL whose validation is currently proceeding through context.
321  // If it LOOKS like a plausible version of a legacy "dot mac item" ACL.
322  // We don't have access to the database attributes of the item up here in the
323  // securityd sky, so we have to apply a heuristic based on which applications (by path)
324  // are given access to the item.
325  // So this is strictly a heuristic. The potential downside is that we may inadvertently
326  // give access to new .Mac authorized Apple (only) applications when the user only intended
327  // a limited set of extremely popular Apple (only) applications that just happen to all be
328  // .Mac authorized today. We can live with that.
329  //
330  bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext &context)
331  {
332  	static const char * const prototypicalDotMacPath[] = {
333  		"/Applications/Mail.app",
334  		"/Applications/Safari.app",
335  		"/Applications/iSync.app",
336  		"/Applications/System Preferences.app",
337  		"/Applications/iCal.app",
338  		"/Applications/iChat.app",
339  		"/Applications/iTunes.app",
340  		"/Applications/Address Book.app",
341  		"/Applications/iSync.app",
342  		NULL	// sentinel
343  	};
344  
345  	static const unsigned threshold = 6;
346  
347  	if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
348  		if (ThresholdAclSubject *list = env->standardSubject(context)) {
349  			unsigned count = list->count();
350  			unsigned matches = 0;
351  			for (unsigned n = 0; n < count; ++n) {
352  				if (CodeSignatureAclSubject *app = dynamic_cast<CodeSignatureAclSubject *>(list->subject(n))) {
353  					for (const char * const *p = prototypicalDotMacPath; *p; p++)
354  						if (app->path() == *p)
355  							matches++;
356  				}
357  			}
358  			secinfo("codesign", "matched %d of %zd candididates (threshold=%d)",
359  				matches, sizeof(prototypicalDotMacPath) / sizeof(char *) - 1, threshold);
360  			return matches >= threshold;
361  		}
362  	}
363  	return false;
364  }
365  
366  
367  //
368  // ACL manipulations related to keychain partitions
369  //
370  bool SecurityServerAcl::createClientPartitionID(Process& process)
371  {
372      // Make sure the ACL is ready for edits
373      instantiateAcl();
374  
375  	// create partition payload
376  	std::string partitionID = process.partitionId();
377  	CFTemp<CFDictionaryRef> payload("{Partitions=[%s]}", partitionID.c_str());
378  	ObjectAcl::AclSubjectPointer subject = new PartitionAclSubject();
379  	static_cast<PartitionAclSubject*>(subject.get())->setDictionaryPayload(Allocator::standard(), payload);
380  	ObjectAcl::AclEntry partition(subject);
381  	partition.addAuthorization(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
382  	this->add(CSSM_APPLE_ACL_TAG_PARTITION_ID, partition);
383  	secinfo("integrity", "added partition %s to new key", partitionID.c_str());
384  	return true;
385  }
386  
387  
388  bool SecurityServerAcl::addClientPartitionID(Process& process)
389  {
390  	if (PartitionAclSubject* subject = this->findPartitionSubject()) {
391  		std::string partitionID = process.partitionId();
392  		if (CFRef<CFDictionaryRef> payload = subject->createDictionaryPayload()) {
393  			CFArrayRef partitionList;
394  			if (cfscan(payload, "{Partitions=%AO}", &partitionList)) {
395  				CFTemp<CFDictionaryRef> newPayload("{Partitions=[+%O,%s]}", partitionList, partitionID.c_str());
396  				subject->setDictionaryPayload(Allocator::standard(), newPayload);
397  			}
398  			return true;
399  		} else {
400  			MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
401  		}
402  	} else {
403  		return createClientPartitionID(process);
404  	}
405  }
406  
407  
408  //
409  // External storage interface
410  //
411  Adornable &SecurityServerEnvironment::store(const AclSubject *subject)
412  {
413  	switch (subject->type()) {
414  	case CSSM_ACL_SUBJECT_TYPE_PREAUTH:
415  		{
416  			if (TokenDatabase *tokenDb = dynamic_cast<TokenDatabase *>(database))
417  				return tokenDb->common().store();
418  		}
419  		break;
420  	default:
421  		break;
422  	}
423  	CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED);
424  }
425  
426  
427  //
428  // ProcessAclSubject personality: uid/gid/pid come from the active Process object
429  //
430  uid_t SecurityServerEnvironment::getuid() const
431  {
432      return Server::process().uid();
433  }
434  
435  gid_t SecurityServerEnvironment::getgid() const
436  {
437      return Server::process().gid();
438  }
439  
440  pid_t SecurityServerEnvironment::getpid() const
441  {
442      return Server::process().pid();
443  }
444  
445  
446  //
447  // CodeSignatureAclSubject personality: take code signature from active Process object
448  //
449  bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier &verifier,
450  	const AclValidationContext &context)
451  {
452  	return Server::codeSignatures().verify(Server::process(), verifier, context);
453  }
454  
455  
456  //
457  // PromptedAclSubject personality: Get a secret by prompting through SecurityAgent
458  //
459  bool SecurityServerEnvironment::getSecret(CssmOwnedData &secret, const CssmData &prompt) const
460  {
461  	//@@@ ignoring prompt - not used right now
462  	if (database) {
463  		QueryPIN query(*database);
464  		query.inferHints(Server::process());
465  		if (!query()) {	// success
466  			secret = query.pin();
467  			return true;
468  		}
469  	}
470  	return false;
471  }
472  
473  
474  //
475  // SecretAclSubject personality: externally validate a secret (passphrase etc.)
476  // Right now, this always goes to the (Token)Database object, because that's where
477  // the PIN ACL entries are. We could direct this at the ObjectAcl (database or key)
478  // instead and rely on tokend to perform the PIN mapping, but the generic tokend
479  // wrappers do not (currently) perform any ACL validation, so every tokend would have
480  // to re-implement that. Perhaps in the next ACL revamp cycle...
481  //
482  bool SecurityServerEnvironment::validateSecret(const SecretAclSubject *me,
483  	const AccessCredentials *cred)
484  {
485  	return database && database->validateSecret(me, cred);
486  }
487  
488  
489  //
490  // PreAuthenticationAclSubject personality - refer to database (ObjectAcl)
491  //
492  ObjectAcl *SecurityServerEnvironment::preAuthSource()
493  {
494  	return database ? &database->acl() : NULL;
495  }
496  
497  
498  //
499  // Autonomous ACL editing support
500  //
501  ThresholdAclSubject *SecurityServerEnvironment::standardSubject(const AclValidationContext &context)
502  {
503  	return dynamic_cast<ThresholdAclSubject *>(context.subject());
504  }
505  
506  
507  //
508  // The default AclSource denies having an ACL at all
509  //
510  AclSource::~AclSource()
511  { /* virtual */ }
512  
513  SecurityServerAcl &AclSource::acl()
514  {
515  	CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED);
516  }
517  
518  Database *AclSource::relatedDatabase()
519  {
520  	return NULL;
521  }