]> git.saurik.com Git - apple/security.git/blob - securityd/src/acls.cpp
Security-57740.1.18.tar.gz
[apple/security.git] / securityd / src / 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 // Calling checkAppleSigned() early at boot on a clean system install
152 // will end up trying to create the system keychain and causes a hang.
153 // Avoid this by checking for the presence of the db first.
154 if((!env.database) || env.database->dbVersion() < SecurityServer::CommonBlob::version_partition) {
155 secnotice("integrity", "no db or old db version, skipping");
156 return;
157 }
158
159 // For the Keychain Migrator, don't even check the partition list
160 Process &process = Server::process();
161 if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
162 secnotice("integrity", "bypassing partition check for keychain migrator");
163 return; // migrator client -> automatic win
164 }
165
166 if (CFRef<CFDictionaryRef> partition = this->createPartitionPayload()) {
167 CFArrayRef partitionList;
168 if (cfscan(partition, "{Partitions=%AO}", &partitionList)) {
169 CFRef<CFStringRef> partitionDebug = CFCopyDescription(partitionList); // for debugging only
170 secnotice("integrity", "ACL partitionID = %s", cfString(partitionDebug).c_str());
171 if (env.database) {
172 CFRef<CFStringRef> clientPartitionID = makeCFString(env.database->process().partitionId());
173 if (CFArrayContainsValue(partitionList, CFRangeMake(0, CFArrayGetCount(partitionList)), clientPartitionID)) {
174 secnotice("integrity", "ACL partitions match: %s", cfString(clientPartitionID).c_str());
175 return;
176 } else {
177 secnotice("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID).c_str(), cfString(partitionDebug).c_str());
178 if (prompt && extendPartition(env))
179 return;
180 MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
181 }
182 }
183 }
184 secnotice("integrity", "failed to parse partition payload");
185 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
186 } else {
187 // There's no partition list. This keychain is recently upgraded.
188 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
189 if(env.database->isRecoding()) {
190 secnotice("integrity", "no partition ACL - database is recoding; skipping add");
191 // let this pass as well
192 } else {
193 secnotice("integrity", "no partition ACL - adding");
194 env.acl.instantiateAcl();
195 this->createClientPartitionID(env.database->process());
196 env.acl.changedAcl();
197 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
198 }
199 }
200 }
201
202
203 bool SecurityServerAcl::extendPartition(SecurityServerEnvironment& env)
204 {
205 // brute-force find the KeychainAclSubject in the ACL
206 KeychainPromptAclSubject *kcSubject = NULL;
207 SecurityServerAcl& acl = env.acl;
208 for (EntryMap::const_iterator it = acl.begin(); it != acl.end(); ++it) {
209 AclSubjectPointer subject = it->second.subject;
210 if (ThresholdAclSubject *threshold = dynamic_cast<ThresholdAclSubject *>(subject.get())) {
211 unsigned size = threshold->count();
212 if (KeychainPromptAclSubject* last = dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
213 // looks standard enough
214 kcSubject = last;
215 break;
216 }
217 }
218 }
219
220 if (kcSubject) {
221 BaseValidationContext ctx(NULL, CSSM_ACL_AUTHORIZATION_PARTITION_ID, &env);
222 kcSubject->addPromptAttempt();
223 return kcSubject->validateExplicitly(ctx, ^{
224 secnotice("integrity", "adding partition to list");
225 env.acl.instantiateAcl();
226 this->addClientPartitionID(env.database->process());
227 env.acl.changedAcl();
228 // trigger a special notification code on (otherwise successful) return
229 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
230 });
231 }
232 secnotice("integrity", "failure extending partition");
233 return false;
234 }
235
236
237 PartitionAclSubject* SecurityServerAcl::findPartitionSubject()
238 {
239 pair<EntryMap::const_iterator, EntryMap::const_iterator> range;
240 switch (this->getRange(CSSM_APPLE_ACL_TAG_PARTITION_ID, range, true)) {
241 case 0:
242 secnotice("integrity", "no partition tag on ACL");
243 return NULL;
244 default:
245 secnotice("integrity", "multiple partition ACL entries");
246 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
247 case 1:
248 break;
249 }
250 const AclEntry& entry = range.first->second;
251 if (!entry.authorizes(CSSM_ACL_AUTHORIZATION_PARTITION_ID)) {
252 secnotice("integrity", "partition entry does not authorize CSSM_ACL_AUTHORIZATION_PARTITION_ID");
253 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
254 }
255 if (PartitionAclSubject* partition = dynamic_cast<PartitionAclSubject*>(entry.subject.get())) {
256 return partition;
257 } else {
258 secnotice("integrity", "partition entry is not PartitionAclSubject");
259 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
260 }
261 }
262
263
264 CFDictionaryRef SecurityServerAcl::createPartitionPayload()
265 {
266 if (PartitionAclSubject* subject = this->findPartitionSubject()) {
267 if (CFDictionaryRef result = subject->createDictionaryPayload()) {
268 return result;
269 } else {
270 secnotice("integrity", "partition entry is malformed XML");
271 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
272 }
273 } else {
274 return NULL;
275 }
276 }
277
278
279 //
280 // This helper tries to add the (new) subject given to the ACL
281 // whose validation is currently proceeding through context.
282 // This will succeed if the ACL is in standard form, which means
283 // a ThresholdAclSubject.
284 // The new subject will be added at the front (so it is checked first
285 // from now on), and as a side effect we'll notify the client side to
286 // re-encode the object.
287 // Returns true if the edit could be done, or false if the ACL wasn't
288 // standard enough. May throw if the ACL is malformed or otherwise messed up.
289 //
290 // This is a self-contained helper that is here merely because it's "about"
291 // ACLs and has no better home.
292 //
293 bool SecurityServerAcl::addToStandardACL(const AclValidationContext &context, AclSubject *subject)
294 {
295 if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>())
296 if (ThresholdAclSubject *threshold = env->standardSubject(context)) {
297 unsigned size = threshold->count();
298 if (dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
299 // looks standard enough
300 secinfo("acl", "adding new subject %p to from of threshold ACL", subject);
301 threshold->add(subject, 0);
302
303 // tell the ACL it's been modified
304 context.acl()->changedAcl();
305
306 // trigger a special notification code on (otherwise successful) return
307 Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
308 return true;
309 }
310 }
311 secinfo("acl", "ACL is not standard form; cannot edit");
312 return false;
313 }
314
315
316 //
317 // Look at the ACL whose validation is currently proceeding through context.
318 // If it LOOKS like a plausible version of a legacy "dot mac item" ACL.
319 // We don't have access to the database attributes of the item up here in the
320 // securityd sky, so we have to apply a heuristic based on which applications (by path)
321 // are given access to the item.
322 // So this is strictly a heuristic. The potential downside is that we may inadvertently
323 // give access to new .Mac authorized Apple (only) applications when the user only intended
324 // a limited set of extremely popular Apple (only) applications that just happen to all be
325 // .Mac authorized today. We can live with that.
326 //
327 bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext &context)
328 {
329 static const char * const prototypicalDotMacPath[] = {
330 "/Applications/Mail.app",
331 "/Applications/Safari.app",
332 "/Applications/iSync.app",
333 "/Applications/System Preferences.app",
334 "/Applications/iCal.app",
335 "/Applications/iChat.app",
336 "/Applications/iTunes.app",
337 "/Applications/Address Book.app",
338 "/Applications/iSync.app",
339 NULL // sentinel
340 };
341
342 static const unsigned threshold = 6;
343
344 if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
345 if (ThresholdAclSubject *list = env->standardSubject(context)) {
346 unsigned count = list->count();
347 unsigned matches = 0;
348 for (unsigned n = 0; n < count; ++n) {
349 if (CodeSignatureAclSubject *app = dynamic_cast<CodeSignatureAclSubject *>(list->subject(n))) {
350 for (const char * const *p = prototypicalDotMacPath; *p; p++)
351 if (app->path() == *p)
352 matches++;
353 }
354 }
355 secinfo("codesign", "matched %d of %zd candididates (threshold=%d)",
356 matches, sizeof(prototypicalDotMacPath) / sizeof(char *) - 1, threshold);
357 return matches >= threshold;
358 }
359 }
360 return false;
361 }
362
363
364 //
365 // ACL manipulations related to keychain partitions
366 //
367 bool SecurityServerAcl::createClientPartitionID(Process& process)
368 {
369 // Make sure the ACL is ready for edits
370 instantiateAcl();
371
372 // create partition payload
373 std::string partitionID = process.partitionId();
374 CFTemp<CFDictionaryRef> payload("{Partitions=[%s]}", partitionID.c_str());
375 ObjectAcl::AclSubjectPointer subject = new PartitionAclSubject();
376 static_cast<PartitionAclSubject*>(subject.get())->setDictionaryPayload(Allocator::standard(), payload);
377 ObjectAcl::AclEntry partition(subject);
378 partition.addAuthorization(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
379 this->add(CSSM_APPLE_ACL_TAG_PARTITION_ID, partition);
380 secnotice("integrity", "added partition %s to new key", partitionID.c_str());
381 return true;
382 }
383
384
385 bool SecurityServerAcl::addClientPartitionID(Process& process)
386 {
387 if (PartitionAclSubject* subject = this->findPartitionSubject()) {
388 std::string partitionID = process.partitionId();
389 if (CFRef<CFDictionaryRef> payload = subject->createDictionaryPayload()) {
390 CFArrayRef partitionList;
391 if (cfscan(payload, "{Partitions=%AO}", &partitionList)) {
392 CFTemp<CFDictionaryRef> newPayload("{Partitions=[+%O,%s]}", partitionList, partitionID.c_str());
393 subject->setDictionaryPayload(Allocator::standard(), newPayload);
394 }
395 return true;
396 } else {
397 MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
398 }
399 } else {
400 return createClientPartitionID(process);
401 }
402 }
403
404
405 //
406 // External storage interface
407 //
408 Adornable &SecurityServerEnvironment::store(const AclSubject *subject)
409 {
410 switch (subject->type()) {
411 case CSSM_ACL_SUBJECT_TYPE_PREAUTH:
412 {
413 if (TokenDatabase *tokenDb = dynamic_cast<TokenDatabase *>(database))
414 return tokenDb->common().store();
415 }
416 break;
417 default:
418 break;
419 }
420 CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED);
421 }
422
423
424 //
425 // ProcessAclSubject personality: uid/gid/pid come from the active Process object
426 //
427 uid_t SecurityServerEnvironment::getuid() const
428 {
429 return Server::process().uid();
430 }
431
432 gid_t SecurityServerEnvironment::getgid() const
433 {
434 return Server::process().gid();
435 }
436
437 pid_t SecurityServerEnvironment::getpid() const
438 {
439 return Server::process().pid();
440 }
441
442
443 //
444 // CodeSignatureAclSubject personality: take code signature from active Process object
445 //
446 bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier &verifier,
447 const AclValidationContext &context)
448 {
449 return Server::codeSignatures().verify(Server::process(), verifier, context);
450 }
451
452
453 //
454 // PromptedAclSubject personality: Get a secret by prompting through SecurityAgent
455 //
456 bool SecurityServerEnvironment::getSecret(CssmOwnedData &secret, const CssmData &prompt) const
457 {
458 //@@@ ignoring prompt - not used right now
459 if (database) {
460 QueryPIN query(*database);
461 query.inferHints(Server::process());
462 if (!query()) { // success
463 secret = query.pin();
464 return true;
465 }
466 }
467 return false;
468 }
469
470
471 //
472 // SecretAclSubject personality: externally validate a secret (passphrase etc.)
473 // Right now, this always goes to the (Token)Database object, because that's where
474 // the PIN ACL entries are. We could direct this at the ObjectAcl (database or key)
475 // instead and rely on tokend to perform the PIN mapping, but the generic tokend
476 // wrappers do not (currently) perform any ACL validation, so every tokend would have
477 // to re-implement that. Perhaps in the next ACL revamp cycle...
478 //
479 bool SecurityServerEnvironment::validateSecret(const SecretAclSubject *me,
480 const AccessCredentials *cred)
481 {
482 return database && database->validateSecret(me, cred);
483 }
484
485
486 //
487 // PreAuthenticationAclSubject personality - refer to database (ObjectAcl)
488 //
489 ObjectAcl *SecurityServerEnvironment::preAuthSource()
490 {
491 return database ? &database->acl() : NULL;
492 }
493
494
495 //
496 // Autonomous ACL editing support
497 //
498 ThresholdAclSubject *SecurityServerEnvironment::standardSubject(const AclValidationContext &context)
499 {
500 return dynamic_cast<ThresholdAclSubject *>(context.subject());
501 }
502
503
504 //
505 // The default AclSource denies having an ACL at all
506 //
507 AclSource::~AclSource()
508 { /* virtual */ }
509
510 SecurityServerAcl &AclSource::acl()
511 {
512 CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED);
513 }
514
515 Database *AclSource::relatedDatabase()
516 {
517 return NULL;
518 }