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 }