]> git.saurik.com Git - apple/security.git/blame - securityd/src/acls.cpp
Security-59306.11.20.tar.gz
[apple/security.git] / securityd / src / acls.cpp
CommitLineData
d8f41ccd 1/*
fa7225c8
A
2 * Copyright (c) 2000-2009,2012-2016 Apple Inc. All Rights Reserved.
3 *
d8f41ccd 4 * @APPLE_LICENSE_HEADER_START@
fa7225c8 5 *
d8f41ccd
A
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.
fa7225c8 12 *
d8f41ccd
A
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.
fa7225c8 20 *
d8f41ccd
A
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"
e3d460c9 34#include "acl_partition.h"
d8f41ccd
A
35
36// ACL subjects whose Environments we implement
37#include <security_cdsa_utilities/acl_any.h>
38#include <security_cdsa_utilities/acl_password.h>
e3d460c9 39#include "acl_keychain.h"
d8f41ccd
A
40
41#include <sys/sysctl.h>
42#include <security_utilities/logging.h>
e3d460c9 43#include <security_utilities/cfmunge.h>
d8f41ccd
A
44
45//
46// SecurityServerAcl is virtual
47//
48SecurityServerAcl::~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//
59void SecurityServerAcl::getOwner(AclOwnerPrototype &owner)
60{
61 StLock<Mutex> _(aclSequence);
62 ObjectAcl::cssmGetOwner(owner);
63}
64
65void SecurityServerAcl::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
66{
67 StLock<Mutex> _(aclSequence);
68 ObjectAcl::cssmGetAcl(tag, count, acls);
69}
70
71void SecurityServerAcl::changeAcl(const AclEdit &edit, const AccessCredentials *cred,
72 Database *db)
73{
74 StLock<Mutex> _(aclSequence);
75 SecurityServerEnvironment env(*this, db);
e3d460c9
A
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 }
d8f41ccd
A
97}
98
99void 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//
111void SecurityServerAcl::validate(AclAuthorization auth, const AccessCredentials *cred, Database *db)
112{
113 SecurityServerEnvironment env(*this, db);
fa7225c8 114
d8f41ccd
A
115 StLock<Mutex> objectSequence(aclSequence);
116 StLock<Mutex> processSequence(Server::process().aclSequence);
e3d460c9
A
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);
d8f41ccd
A
137}
138
139void 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
e3d460c9
A
146//
147// Partitioning support
148//
149void SecurityServerAcl::validatePartition(SecurityServerEnvironment& env, bool prompt)
150{
79b9da22
A
151 // Avert your eyes!
152 StMaybeLock<Mutex> lock(env.database && env.database->hasCommon() ? &(env.database->common()) : NULL);
153
fa7225c8
A
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) {
b04fe171 158 secinfo("integrity", "no db or old db version, skipping");
fa7225c8
A
159 return;
160 }
161
e3d460c9
A
162 // For the Keychain Migrator, don't even check the partition list
163 Process &process = Server::process();
164 if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
fa7225c8 165 secnotice("integrity", "bypassing partition check for keychain migrator");
e3d460c9
A
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
866f8763 173 secinfo("integrity", "ACL partitionID = %s", cfString(partitionDebug).c_str());
e3d460c9
A
174 if (env.database) {
175 CFRef<CFStringRef> clientPartitionID = makeCFString(env.database->process().partitionId());
176 if (CFArrayContainsValue(partitionList, CFRangeMake(0, CFArrayGetCount(partitionList)), clientPartitionID)) {
866f8763 177 secinfo("integrity", "ACL partitions match: %s", cfString(clientPartitionID).c_str());
e3d460c9
A
178 return;
179 } else {
fa7225c8 180 secnotice("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID).c_str(), cfString(partitionDebug).c_str());
e3d460c9
A
181 if (prompt && extendPartition(env))
182 return;
183 MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
184 }
185 }
186 }
fa7225c8 187 secnotice("integrity", "failed to parse partition payload");
e3d460c9
A
188 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
189 } else {
fa7225c8
A
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
e3d460c9 195 } else {
fa7225c8 196 secnotice("integrity", "no partition ACL - adding");
e3d460c9
A
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
206bool 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 }
fa7225c8 222
e3d460c9
A
223 if (kcSubject) {
224 BaseValidationContext ctx(NULL, CSSM_ACL_AUTHORIZATION_PARTITION_ID, &env);
fa7225c8 225 kcSubject->addPromptAttempt();
e3d460c9 226 return kcSubject->validateExplicitly(ctx, ^{
fa7225c8 227 secnotice("integrity", "adding partition to list");
e3d460c9
A
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 }
fa7225c8 235 secnotice("integrity", "failure extending partition");
e3d460c9
A
236 return false;
237}
238
239
240PartitionAclSubject* 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:
fa7225c8 245 secnotice("integrity", "no partition tag on ACL");
e3d460c9
A
246 return NULL;
247 default:
fa7225c8 248 secnotice("integrity", "multiple partition ACL entries");
e3d460c9
A
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)) {
fa7225c8 255 secnotice("integrity", "partition entry does not authorize CSSM_ACL_AUTHORIZATION_PARTITION_ID");
e3d460c9
A
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 {
fa7225c8 261 secnotice("integrity", "partition entry is not PartitionAclSubject");
e3d460c9
A
262 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
263 }
264}
265
266
267CFDictionaryRef SecurityServerAcl::createPartitionPayload()
268{
269 if (PartitionAclSubject* subject = this->findPartitionSubject()) {
270 if (CFDictionaryRef result = subject->createDictionaryPayload()) {
271 return result;
272 } else {
fa7225c8 273 secnotice("integrity", "partition entry is malformed XML");
e3d460c9
A
274 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
275 }
276 } else {
277 return NULL;
278 }
279}
280
281
d8f41ccd
A
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//
296bool 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
fa7225c8 303 secinfo("acl", "adding new subject %p to from of threshold ACL", subject);
d8f41ccd 304 threshold->add(subject, 0);
fa7225c8 305
d8f41ccd
A
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 }
fa7225c8 314 secinfo("acl", "ACL is not standard form; cannot edit");
d8f41ccd
A
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//
330bool 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 };
fa7225c8 344
d8f41ccd 345 static const unsigned threshold = 6;
fa7225c8 346
d8f41ccd
A
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 }
fa7225c8 358 secinfo("codesign", "matched %d of %zd candididates (threshold=%d)",
d8f41ccd
A
359 matches, sizeof(prototypicalDotMacPath) / sizeof(char *) - 1, threshold);
360 return matches >= threshold;
361 }
362 }
363 return false;
364}
365
366
e3d460c9
A
367//
368// ACL manipulations related to keychain partitions
369//
370bool 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);
866f8763 383 secinfo("integrity", "added partition %s to new key", partitionID.c_str());
e3d460c9
A
384 return true;
385}
386
387
388bool 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
d8f41ccd
A
408//
409// External storage interface
410//
411Adornable &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//
430uid_t SecurityServerEnvironment::getuid() const
431{
432 return Server::process().uid();
433}
434
435gid_t SecurityServerEnvironment::getgid() const
436{
437 return Server::process().gid();
438}
439
440pid_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//
449bool 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//
459bool 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//
482bool 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//
492ObjectAcl *SecurityServerEnvironment::preAuthSource()
493{
494 return database ? &database->acl() : NULL;
495}
496
497
498//
499// Autonomous ACL editing support
500//
501ThresholdAclSubject *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//
510AclSource::~AclSource()
511{ /* virtual */ }
512
513SecurityServerAcl &AclSource::acl()
514{
515 CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED);
516}
517
518Database *AclSource::relatedDatabase()
519{
520 return NULL;
521}