2  * Copyright (c) 2000-2009,2012-2013 Apple Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   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 
  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. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  26 // acls - securityd ACL implementation 
  29 #include "connection.h" 
  31 #include "agentquery.h" 
  32 #include "tokendatabase.h" 
  33 #include "acl_keychain.h" 
  34 #include "acl_partition.h" 
  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" 
  41 #include <sys/sysctl.h> 
  42 #include <security_utilities/logging.h> 
  43 #include <security_utilities/cfmunge.h> 
  46 // SecurityServerAcl is virtual 
  48 SecurityServerAcl::~SecurityServerAcl() 
  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. 
  59 void SecurityServerAcl::getOwner(AclOwnerPrototype 
&owner
) 
  61         StLock
<Mutex
> _(aclSequence
); 
  62         ObjectAcl::cssmGetOwner(owner
); 
  65 void SecurityServerAcl::getAcl(const char *tag
, uint32 
&count
, AclEntryInfo 
*&acls
) 
  67         StLock
<Mutex
> _(aclSequence
); 
  68         ObjectAcl::cssmGetAcl(tag
, count
, acls
); 
  71 void SecurityServerAcl::changeAcl(const AclEdit 
&edit
, const AccessCredentials 
*cred
, 
  74         StLock
<Mutex
> _(aclSequence
); 
  75         SecurityServerEnvironment 
env(*this, db
); 
  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 
  84             // If you passed partition validation, bypass the owner ACL check entirely. 
  85             env
.forceSuccess 
= true; 
  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
); 
  95         ObjectAcl::cssmChangeAcl(edit
, cred
, &env
, CSSM_APPLE_ACL_TAG_PARTITION_ID
); 
  99 void SecurityServerAcl::changeOwner(const AclOwnerPrototype 
&newOwner
, 
 100         const AccessCredentials 
*cred
, Database 
*db
) 
 102         StLock
<Mutex
> _(aclSequence
); 
 103         SecurityServerEnvironment 
env(*this, db
); 
 104         ObjectAcl::cssmChangeOwner(newOwner
, cred
, &env
); 
 109 // Modified validate() methods to connect all the conduits... 
 111 void SecurityServerAcl::validate(AclAuthorization auth
, const AccessCredentials 
*cred
, Database 
*db
) 
 113     SecurityServerEnvironment 
env(*this, db
); 
 115         StLock
<Mutex
> objectSequence(aclSequence
); 
 116         StLock
<Mutex
> processSequence(Server::process().aclSequence
); 
 117         ObjectAcl::validate(auth
, cred
, &env
); 
 119     // partition validation happens outside the normal acl validation flow, in addition 
 120     bool ui 
= (!!cred
) && cred
->authorizesUI(); 
 122     // we should only offer the chance to extend the partition ID list on a "read" operation, so check the AclAuthorization 
 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
); 
 136     validatePartition(env
, ui 
&& readOperation
); 
 139 void SecurityServerAcl::validate(AclAuthorization auth
, const Context 
&context
, Database 
*db
) 
 142                 context
.get
<AccessCredentials
>(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS
), db
); 
 147 // Partitioning support 
 149 void SecurityServerAcl::validatePartition(SecurityServerEnvironment
& env
, bool prompt
) 
 151     // For the Keychain Migrator, don't even check the partition list 
 152     Process 
&process 
= Server::process(); 
 153     if (process
.checkAppleSigned() && process
.hasEntitlement(migrationEntitlement
)) { 
 154         secdebug("integrity", "bypassing partition check for keychain migrator"); 
 155         return;   // migrator client -> automatic win 
 158         if (CFRef
<CFDictionaryRef
> partition 
= this->createPartitionPayload()) { 
 159                 CFArrayRef partitionList
; 
 160                 if (cfscan(partition
, "{Partitions=%AO}", &partitionList
)) { 
 161                         CFRef
<CFStringRef
> partitionDebug 
= CFCopyDescription(partitionList
);   // for debugging only 
 162                         secdebugfunc("integrity", "ACL partitionID = %s", cfString(partitionDebug
).c_str()); 
 164                                 CFRef
<CFStringRef
> clientPartitionID 
= makeCFString(env
.database
->process().partitionId()); 
 165                                 if (CFArrayContainsValue(partitionList
, CFRangeMake(0, CFArrayGetCount(partitionList
)), clientPartitionID
)) { 
 166                                         secdebugfunc("integrity", "ACL partitions match: %s", cfString(clientPartitionID
).c_str()); 
 169                                         secdebugfunc("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID
).c_str(), cfString(partitionDebug
).c_str()); 
 170                                         if (prompt 
&& extendPartition(env
)) 
 172                                         MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED
); 
 176                 secdebugfunc("integrity", "failed to parse partition payload"); 
 177                 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 179         // There's no partition list. This is keychain is either old or recently upgraded. 
 180         // If there's no database, let it pass. 
 181         if((!env
.database
) || env
.database
->dbVersion() < SecurityServer::CommonBlob::version_partition
) { 
 182             secdebugfunc("integrity", "no partition ACL - legacy case"); 
 183             //@@@ I guess we let this pass... 
 185             secdebugfunc("integrity", "no partition ACL - adding"); 
 186             env
.acl
.instantiateAcl(); 
 187             this->createClientPartitionID(env
.database
->process()); 
 188             env
.acl
.changedAcl(); 
 189             Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
); 
 195 bool SecurityServerAcl::extendPartition(SecurityServerEnvironment
& env
) 
 197         // brute-force find the KeychainAclSubject in the ACL 
 198         KeychainPromptAclSubject 
*kcSubject 
= NULL
; 
 199         SecurityServerAcl
& acl 
= env
.acl
; 
 200         for (EntryMap::const_iterator it 
= acl
.begin(); it 
!= acl
.end(); ++it
) { 
 201                 AclSubjectPointer subject 
= it
->second
.subject
; 
 202                 if (ThresholdAclSubject 
*threshold 
= dynamic_cast<ThresholdAclSubject 
*>(subject
.get())) { 
 203                         unsigned size 
= threshold
->count(); 
 204                         if (KeychainPromptAclSubject
* last 
= dynamic_cast<KeychainPromptAclSubject 
*>(threshold
->subject(size
-1))) { 
 205                                 // looks standard enough 
 213                 BaseValidationContext 
ctx(NULL
, CSSM_ACL_AUTHORIZATION_PARTITION_ID
, &env
); 
 214                 return kcSubject
->validateExplicitly(ctx
, ^{ 
 215             secdebugfunc("integrity", "adding partition to list"); 
 216             env
.acl
.instantiateAcl(); 
 217                         this->addClientPartitionID(env
.database
->process()); 
 218                         env
.acl
.changedAcl(); 
 219                         // trigger a special notification code on (otherwise successful) return 
 220                         Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
); 
 223     secdebugfunc("integrity", "failure extending partition"); 
 228 PartitionAclSubject
* SecurityServerAcl::findPartitionSubject() 
 230         pair
<EntryMap::const_iterator
, EntryMap::const_iterator
> range
; 
 231         switch (this->getRange(CSSM_APPLE_ACL_TAG_PARTITION_ID
, range
, true)) { 
 233                         secdebugfunc("integrity", "no partition tag on ACL"); 
 236                         secdebugfunc("integrity", "multiple partition ACL entries"); 
 237                         MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 241         const AclEntry
& entry 
= range
.first
->second
; 
 242         if (!entry
.authorizes(CSSM_ACL_AUTHORIZATION_PARTITION_ID
)) { 
 243                 secdebugfunc("integrity", "partition entry does not authorize CSSM_ACL_AUTHORIZATION_PARTITION_ID"); 
 244                 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 246         if (PartitionAclSubject
* partition 
= dynamic_cast<PartitionAclSubject
*>(entry
.subject
.get())) { 
 249                 secdebugfunc("integrity", "partition entry is not PartitionAclSubject"); 
 250                 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 255 CFDictionaryRef 
SecurityServerAcl::createPartitionPayload() 
 257         if (PartitionAclSubject
* subject 
= this->findPartitionSubject()) { 
 258                 if (CFDictionaryRef result 
= subject
->createDictionaryPayload()) { 
 261                         secdebugfunc("integrity", "partition entry is malformed XML"); 
 262                         MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 271 // This helper tries to add the (new) subject given to the ACL 
 272 // whose validation is currently proceeding through context. 
 273 // This will succeed if the ACL is in standard form, which means 
 274 // a ThresholdAclSubject. 
 275 // The new subject will be added at the front (so it is checked first 
 276 // from now on), and as a side effect we'll notify the client side to 
 277 // re-encode the object. 
 278 // Returns true if the edit could be done, or false if the ACL wasn't 
 279 // standard enough. May throw if the ACL is malformed or otherwise messed up. 
 281 // This is a self-contained helper that is here merely because it's "about" 
 282 // ACLs and has no better home. 
 284 bool SecurityServerAcl::addToStandardACL(const AclValidationContext 
&context
, AclSubject 
*subject
) 
 286         if (SecurityServerEnvironment 
*env 
= context
.environment
<SecurityServerEnvironment
>()) 
 287                 if (ThresholdAclSubject 
*threshold 
= env
->standardSubject(context
)) { 
 288                         unsigned size 
= threshold
->count(); 
 289                         if (dynamic_cast<KeychainPromptAclSubject 
*>(threshold
->subject(size
-1))) { 
 290                                 // looks standard enough 
 291                                 secdebug("acl", "adding new subject %p to from of threshold ACL", subject
); 
 292                                 threshold
->add(subject
, 0); 
 294                                 // tell the ACL it's been modified 
 295                                 context
.acl()->changedAcl(); 
 297                                 // trigger a special notification code on (otherwise successful) return 
 298                                 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
); 
 302         secdebug("acl", "ACL is not standard form; cannot edit"); 
 308 // Look at the ACL whose validation is currently proceeding through context. 
 309 // If it LOOKS like a plausible version of a legacy "dot mac item" ACL. 
 310 // We don't have access to the database attributes of the item up here in the 
 311 // securityd sky, so we have to apply a heuristic based on which applications (by path) 
 312 // are given access to the item. 
 313 // So this is strictly a heuristic. The potential downside is that we may inadvertently 
 314 // give access to new .Mac authorized Apple (only) applications when the user only intended 
 315 // a limited set of extremely popular Apple (only) applications that just happen to all be 
 316 // .Mac authorized today. We can live with that. 
 318 bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext 
&context
) 
 320         static const char * const prototypicalDotMacPath
[] = { 
 321                 "/Applications/Mail.app", 
 322                 "/Applications/Safari.app", 
 323                 "/Applications/iSync.app", 
 324                 "/Applications/System Preferences.app", 
 325                 "/Applications/iCal.app", 
 326                 "/Applications/iChat.app", 
 327                 "/Applications/iTunes.app", 
 328                 "/Applications/Address Book.app", 
 329                 "/Applications/iSync.app", 
 333         static const unsigned threshold 
= 6; 
 335         if (SecurityServerEnvironment 
*env 
= context
.environment
<SecurityServerEnvironment
>()) { 
 336                 if (ThresholdAclSubject 
*list 
= env
->standardSubject(context
)) { 
 337                         unsigned count 
= list
->count(); 
 338                         unsigned matches 
= 0; 
 339                         for (unsigned n 
= 0; n 
< count
; ++n
) { 
 340                                 if (CodeSignatureAclSubject 
*app 
= dynamic_cast<CodeSignatureAclSubject 
*>(list
->subject(n
))) { 
 341                                         for (const char * const *p 
= prototypicalDotMacPath
; *p
; p
++) 
 342                                                 if (app
->path() == *p
) 
 346                         secdebug("codesign", "matched %d of %zd candididates (threshold=%d)", 
 347                                 matches
, sizeof(prototypicalDotMacPath
) / sizeof(char *) - 1, threshold
); 
 348                         return matches 
>= threshold
; 
 356 // ACL manipulations related to keychain partitions 
 358 bool SecurityServerAcl::createClientPartitionID(Process
& process
) 
 360     // Make sure the ACL is ready for edits 
 363         // create partition payload 
 364         std::string partitionID 
= process
.partitionId(); 
 365         CFTemp
<CFDictionaryRef
> payload("{Partitions=[%s]}", partitionID
.c_str()); 
 366         ObjectAcl::AclSubjectPointer subject 
= new PartitionAclSubject(); 
 367         static_cast<PartitionAclSubject
*>(subject
.get())->setDictionaryPayload(Allocator::standard(), payload
); 
 368         ObjectAcl::AclEntry 
partition(subject
); 
 369         partition
.addAuthorization(CSSM_ACL_AUTHORIZATION_PARTITION_ID
); 
 370         this->add(CSSM_APPLE_ACL_TAG_PARTITION_ID
, partition
); 
 371         secdebugfunc("integrity", "added partition %s to new key", partitionID
.c_str()); 
 376 bool SecurityServerAcl::addClientPartitionID(Process
& process
) 
 378         if (PartitionAclSubject
* subject 
= this->findPartitionSubject()) { 
 379                 std::string partitionID 
= process
.partitionId(); 
 380                 if (CFRef
<CFDictionaryRef
> payload 
= subject
->createDictionaryPayload()) { 
 381                         CFArrayRef partitionList
; 
 382                         if (cfscan(payload
, "{Partitions=%AO}", &partitionList
)) { 
 383                                 CFTemp
<CFDictionaryRef
> newPayload("{Partitions=[+%O,%s]}", partitionList
, partitionID
.c_str()); 
 384                                 subject
->setDictionaryPayload(Allocator::standard(), newPayload
); 
 388                         MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 391                 return createClientPartitionID(process
); 
 397 // External storage interface 
 399 Adornable 
&SecurityServerEnvironment::store(const AclSubject 
*subject
) 
 401         switch (subject
->type()) { 
 402         case CSSM_ACL_SUBJECT_TYPE_PREAUTH
: 
 404                         if (TokenDatabase 
*tokenDb 
= dynamic_cast<TokenDatabase 
*>(database
)) 
 405                                 return tokenDb
->common().store(); 
 411         CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED
); 
 416 // ProcessAclSubject personality: uid/gid/pid come from the active Process object 
 418 uid_t 
SecurityServerEnvironment::getuid() const 
 420     return Server::process().uid(); 
 423 gid_t 
SecurityServerEnvironment::getgid() const 
 425     return Server::process().gid(); 
 428 pid_t 
SecurityServerEnvironment::getpid() const 
 430     return Server::process().pid(); 
 435 // CodeSignatureAclSubject personality: take code signature from active Process object 
 437 bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier 
&verifier
, 
 438         const AclValidationContext 
&context
) 
 440         return Server::codeSignatures().verify(Server::process(), verifier
, context
); 
 445 // PromptedAclSubject personality: Get a secret by prompting through SecurityAgent 
 447 bool SecurityServerEnvironment::getSecret(CssmOwnedData 
&secret
, const CssmData 
&prompt
) const 
 449         //@@@ ignoring prompt - not used right now 
 451                 QueryPIN 
query(*database
); 
 452                 query
.inferHints(Server::process()); 
 453                 if (!query()) { // success 
 454                         secret 
= query
.pin(); 
 463 // SecretAclSubject personality: externally validate a secret (passphrase etc.) 
 464 // Right now, this always goes to the (Token)Database object, because that's where 
 465 // the PIN ACL entries are. We could direct this at the ObjectAcl (database or key) 
 466 // instead and rely on tokend to perform the PIN mapping, but the generic tokend 
 467 // wrappers do not (currently) perform any ACL validation, so every tokend would have 
 468 // to re-implement that. Perhaps in the next ACL revamp cycle... 
 470 bool SecurityServerEnvironment::validateSecret(const SecretAclSubject 
*me
, 
 471         const AccessCredentials 
*cred
) 
 473         return database 
&& database
->validateSecret(me
, cred
); 
 478 // PreAuthenticationAclSubject personality - refer to database (ObjectAcl) 
 480 ObjectAcl 
*SecurityServerEnvironment::preAuthSource() 
 482         return database 
? &database
->acl() : NULL
; 
 487 // Autonomous ACL editing support 
 489 ThresholdAclSubject 
*SecurityServerEnvironment::standardSubject(const AclValidationContext 
&context
) 
 491         return dynamic_cast<ThresholdAclSubject 
*>(context
.subject()); 
 496 // The default AclSource denies having an ACL at all 
 498 AclSource::~AclSource() 
 501 SecurityServerAcl 
&AclSource::acl() 
 503         CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED
); 
 506 Database 
*AclSource::relatedDatabase()