2 * Copyright (c) 2000-2009,2012-2016 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
)
152 StMaybeLock
<Mutex
> lock(env
.database
&& env
.database
->hasCommon() ? &(env
.database
->common()) : NULL
);
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");
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
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());
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());
180 secnotice("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID
).c_str(), cfString(partitionDebug
).c_str());
181 if (prompt
&& extendPartition(env
))
183 MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED
);
187 secnotice("integrity", "failed to parse partition payload");
188 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
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
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
);
206 bool SecurityServerAcl::extendPartition(SecurityServerEnvironment
& env
)
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
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
);
235 secnotice("integrity", "failure extending partition");
240 PartitionAclSubject
* SecurityServerAcl::findPartitionSubject()
242 pair
<EntryMap::const_iterator
, EntryMap::const_iterator
> range
;
243 switch (this->getRange(CSSM_APPLE_ACL_TAG_PARTITION_ID
, range
, true)) {
245 secnotice("integrity", "no partition tag on ACL");
248 secnotice("integrity", "multiple partition ACL entries");
249 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
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
);
258 if (PartitionAclSubject
* partition
= dynamic_cast<PartitionAclSubject
*>(entry
.subject
.get())) {
261 secnotice("integrity", "partition entry is not PartitionAclSubject");
262 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
267 CFDictionaryRef
SecurityServerAcl::createPartitionPayload()
269 if (PartitionAclSubject
* subject
= this->findPartitionSubject()) {
270 if (CFDictionaryRef result
= subject
->createDictionaryPayload()) {
273 secnotice("integrity", "partition entry is malformed XML");
274 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
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.
293 // This is a self-contained helper that is here merely because it's "about"
294 // ACLs and has no better home.
296 bool SecurityServerAcl::addToStandardACL(const AclValidationContext
&context
, AclSubject
*subject
)
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);
306 // tell the ACL it's been modified
307 context
.acl()->changedAcl();
309 // trigger a special notification code on (otherwise successful) return
310 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
);
314 secinfo("acl", "ACL is not standard form; cannot edit");
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.
330 bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext
&context
)
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",
345 static const unsigned threshold
= 6;
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
)
358 secinfo("codesign", "matched %d of %zd candididates (threshold=%d)",
359 matches
, sizeof(prototypicalDotMacPath
) / sizeof(char *) - 1, threshold
);
360 return matches
>= threshold
;
368 // ACL manipulations related to keychain partitions
370 bool SecurityServerAcl::createClientPartitionID(Process
& process
)
372 // Make sure the ACL is ready for edits
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());
388 bool SecurityServerAcl::addClientPartitionID(Process
& process
)
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
);
400 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
403 return createClientPartitionID(process
);
409 // External storage interface
411 Adornable
&SecurityServerEnvironment::store(const AclSubject
*subject
)
413 switch (subject
->type()) {
414 case CSSM_ACL_SUBJECT_TYPE_PREAUTH
:
416 if (TokenDatabase
*tokenDb
= dynamic_cast<TokenDatabase
*>(database
))
417 return tokenDb
->common().store();
423 CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED
);
428 // ProcessAclSubject personality: uid/gid/pid come from the active Process object
430 uid_t
SecurityServerEnvironment::getuid() const
432 return Server::process().uid();
435 gid_t
SecurityServerEnvironment::getgid() const
437 return Server::process().gid();
440 pid_t
SecurityServerEnvironment::getpid() const
442 return Server::process().pid();
447 // CodeSignatureAclSubject personality: take code signature from active Process object
449 bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier
&verifier
,
450 const AclValidationContext
&context
)
452 return Server::codeSignatures().verify(Server::process(), verifier
, context
);
457 // PromptedAclSubject personality: Get a secret by prompting through SecurityAgent
459 bool SecurityServerEnvironment::getSecret(CssmOwnedData
&secret
, const CssmData
&prompt
) const
461 //@@@ ignoring prompt - not used right now
463 QueryPIN
query(*database
);
464 query
.inferHints(Server::process());
465 if (!query()) { // success
466 secret
= query
.pin();
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...
482 bool SecurityServerEnvironment::validateSecret(const SecretAclSubject
*me
,
483 const AccessCredentials
*cred
)
485 return database
&& database
->validateSecret(me
, cred
);
490 // PreAuthenticationAclSubject personality - refer to database (ObjectAcl)
492 ObjectAcl
*SecurityServerEnvironment::preAuthSource()
494 return database
? &database
->acl() : NULL
;
499 // Autonomous ACL editing support
501 ThresholdAclSubject
*SecurityServerEnvironment::standardSubject(const AclValidationContext
&context
)
503 return dynamic_cast<ThresholdAclSubject
*>(context
.subject());
508 // The default AclSource denies having an ACL at all
510 AclSource::~AclSource()
513 SecurityServerAcl
&AclSource::acl()
515 CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED
);
518 Database
*AclSource::relatedDatabase()