ACL.cpp
1 /* 2 * Copyright (c) 2002-2004,2011-2012,2014 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 // ACL.cpp 26 // 27 #include <security_keychain/ACL.h> 28 #include <security_keychain/SecCFTypes.h> 29 #include <security_utilities/osxcode.h> 30 #include <security_utilities/trackingallocator.h> 31 #include <security_cdsa_utilities/walkers.h> 32 #include <security_keychain/TrustedApplication.h> 33 #include <Security/SecTrustedApplication.h> 34 #include <Security/SecRandom.h> 35 #include <memory> 36 37 38 using namespace KeychainCore; 39 using namespace DataWalkers; 40 41 42 // 43 // The default form of a prompt selector 44 // 45 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = { 46 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0 47 }; 48 49 50 // 51 // ACL static constants 52 // 53 const CSSM_ACL_HANDLE ACL::ownerHandle; 54 55 56 // 57 // Create an ACL object from the result of a CSSM ACL query 58 // 59 ACL::ACL(const AclEntryInfo &info, Allocator &alloc) 60 : allocator(alloc), mState(unchanged), mSubjectForm(NULL), mIntegrity(alloc), mMutex(Mutex::recursive) 61 { 62 // parse the subject 63 parse(info.proto().subject()); 64 65 // fill in AclEntryInfo layer information 66 const AclEntryPrototype &proto = info.proto(); 67 mAuthorizations = proto.authorization(); 68 mDelegate = proto.delegate(); 69 mEntryTag = proto.s_tag(); 70 71 // take CSSM entry handle from info layer 72 mCssmHandle = info.handle(); 73 } 74 75 76 ACL::ACL(const AclOwnerPrototype &owner, Allocator &alloc) 77 : allocator(alloc), mState(unchanged), mSubjectForm(NULL), mIntegrity(alloc), mMutex(Mutex::recursive) 78 { 79 // parse subject 80 parse(owner.subject()); 81 82 // for an owner "entry", the next-layer information is fixed (and fake) 83 mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_CHANGE_ACL); 84 mDelegate = owner.delegate(); 85 mEntryTag[0] = '\0'; 86 87 // use fixed (fake) entry handle 88 mCssmHandle = ownerHandle; 89 } 90 91 92 // 93 // Create a new ACL that authorizes anyone to do anything. 94 // This constructor produces a "pure" ANY ACL, without descriptor or selector. 95 // To generate a "standard" form of ANY, use the appListForm constructor below, 96 // then change its form to allowAnyForm. 97 // 98 ACL::ACL(Allocator &alloc) 99 : allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc), mMutex(Mutex::recursive) 100 { 101 mState = inserted; // new 102 mForm = allowAllForm; // everybody 103 mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); // anything 104 mDelegate = false; 105 106 //mPromptDescription stays empty 107 mPromptSelector = defaultSelector; 108 109 // randomize the CSSM handle 110 MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), (void *)mCssmHandle)); 111 } 112 113 114 // 115 // Create a new ACL in standard form. 116 // As created, it authorizes all activities. 117 // 118 ACL::ACL(string description, const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &promptSelector, 119 Allocator &alloc) 120 : allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc), mMutex(Mutex::recursive) 121 { 122 mState = inserted; // new 123 mForm = appListForm; 124 mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); // anything 125 mDelegate = false; 126 127 mPromptDescription = description; 128 mPromptSelector = promptSelector; 129 130 // randomize the CSSM handle 131 MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), &mCssmHandle)); 132 } 133 134 135 // 136 // Create an "integrity" ACL 137 // 138 ACL::ACL(const CssmData &digest, Allocator &alloc) 139 : allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc, digest), mMutex(Mutex::recursive) 140 { 141 mState = inserted; // new 142 mForm = integrityForm; 143 mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_INTEGRITY); 144 mEntryTag = CSSM_APPLE_ACL_TAG_INTEGRITY; 145 mDelegate = false; 146 147 //mPromptDescription stays empty 148 //mPromptSelector stays empty 149 150 // randomize the CSSM handle 151 MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), &mCssmHandle)); 152 } 153 154 155 // 156 // Destroy an ACL 157 // 158 ACL::~ACL() 159 { 160 // release subject form (if any) 161 chunkFree(mSubjectForm, allocator); 162 } 163 164 165 // 166 // Does this ACL authorize a particular right? 167 // 168 bool ACL::authorizes(AclAuthorization right) 169 { 170 StLock<Mutex>_(mMutex); 171 return mAuthorizations.find(right) != mAuthorizations.end() 172 || mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end() 173 || mAuthorizations.empty(); 174 } 175 176 // 177 // Does this ACL have a specific authorization for a particular right? 178 // 179 bool ACL::authorizesSpecifically(AclAuthorization right) 180 { 181 StLock<Mutex>_(mMutex); 182 return mAuthorizations.find(right) != mAuthorizations.end(); 183 } 184 185 void ACL::setIntegrity(const CssmData& digest) { 186 if(mForm != integrityForm) { 187 secnotice("integrity", "acl has incorrect form: %d", mForm); 188 CssmError::throwMe(CSSMERR_CSP_INVALID_ACL_SUBJECT_VALUE); 189 } 190 191 mIntegrity = digest; 192 modify(); 193 } 194 195 const CssmData& ACL::integrity() { 196 return mIntegrity.get(); 197 } 198 199 // 200 // Add an application to the trusted-app list of this ACL. 201 // Will fail unless this is a standard "simple" form ACL. 202 // 203 void ACL::addApplication(TrustedApplication *app) 204 { 205 StLock<Mutex>_(mMutex); 206 switch (mForm) { 207 case appListForm: // simple... 208 mAppList.push_back(app); 209 modify(); 210 break; 211 case allowAllForm: // hmm... 212 if (!mPromptDescription.empty()) { 213 // verbose "any" form (has description, "any" override) 214 mAppList.push_back(app); 215 modify(); 216 break; 217 } 218 // pure "any" form without description. Cannot convert to appListForm 219 default: 220 MacOSError::throwMe(errSecACLNotSimple); 221 } 222 } 223 224 225 // 226 // Mark an ACL as modified. 227 // 228 void ACL::modify() 229 { 230 StLock<Mutex>_(mMutex); 231 if (mState == unchanged) { 232 secinfo("SecAccess", "ACL %p marked modified", this); 233 mState = modified; 234 } 235 } 236 237 238 // 239 // Mark an ACL as "removed" 240 // Removed ACLs have no valid contents (they are invalid on their face). 241 // When "updated" to the originating item, they will cause the corresponding 242 // ACL entry to be deleted. Otherwise, they are irrelevant. 243 // Note: Removing an ACL does not actually remove it from its Access's map. 244 // 245 void ACL::remove() 246 { 247 StLock<Mutex>_(mMutex); 248 mAppList.clear(); 249 mForm = invalidForm; 250 secinfo("SecAccess", "ACL %p marked deleted", this); 251 mState = deleted; 252 } 253 254 255 // 256 // Produce CSSM-layer form (ACL prototype) copies of our content. 257 // Note that the result is chunk-allocated, and becomes owned by the caller. 258 // 259 void ACL::copyAclEntry(AclEntryPrototype &proto, Allocator &alloc) 260 { 261 StLock<Mutex>_(mMutex); 262 proto.clearPod(); // preset 263 264 // carefully copy the subject 265 makeSubject(); 266 assert(mSubjectForm); 267 proto = AclEntryPrototype(*mSubjectForm, mDelegate); // shares subject 268 ChunkCopyWalker w(alloc); 269 walk(w, proto.subject()); // copy subject in-place 270 271 // the rest of a prototype 272 proto.tag(mEntryTag); 273 AuthorizationGroup tags(mAuthorizations, allocator); 274 proto.authorization() = tags; 275 } 276 277 void ACL::copyAclOwner(AclOwnerPrototype &proto, Allocator &alloc) 278 { 279 StLock<Mutex>_(mMutex); 280 proto.clearPod(); 281 282 makeSubject(); 283 assert(mSubjectForm); 284 proto = AclOwnerPrototype(*mSubjectForm, mDelegate); // shares subject 285 ChunkCopyWalker w(alloc); 286 walk(w, proto.subject()); // copy subject in-place 287 } 288 289 290 // 291 // (Re)place this ACL's setting into the AclBearer specified. 292 // If update, assume this is an update operation and the ACL was 293 // originally derived from this object; specifically, assume the 294 // CSSM handle is valid. If not update, assume this is a different 295 // object that has no related ACL entry (yet). 296 // 297 void ACL::setAccess(AclBearer &target, bool update, 298 const AccessCredentials *cred) 299 { 300 StLock<Mutex>_(mMutex); 301 // determine what action we need to perform 302 State action = state(); 303 if (!update) 304 action = (action == deleted) ? unchanged : inserted; 305 306 // the owner acl (pseudo) "entry" is a special case 307 if (isOwner()) { 308 switch (action) { 309 case unchanged: 310 secinfo("SecAccess", "ACL %p owner unchanged", this); 311 return; 312 case inserted: // means modify the initial owner 313 case modified: 314 { 315 secinfo("SecAccess", "ACL %p owner modified", this); 316 makeSubject(); 317 assert(mSubjectForm); 318 AclOwnerPrototype proto(*mSubjectForm, mDelegate); 319 target.changeOwner(proto, cred); 320 return; 321 } 322 default: 323 assert(false); 324 return; 325 } 326 } 327 328 // simple cases 329 switch (action) { 330 case unchanged: // ignore 331 secinfo("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle()); 332 return; 333 case deleted: // delete 334 secinfo("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle()); 335 target.deleteAcl(entryHandle(), cred); 336 return; 337 default: 338 break; 339 } 340 341 // build the byzantine data structures that CSSM loves so much 342 makeSubject(); 343 assert(mSubjectForm); 344 AclEntryPrototype proto(*mSubjectForm, mDelegate); 345 proto.tag(mEntryTag); 346 AutoAuthorizationGroup tags(mAuthorizations, allocator); 347 proto.authorization() = tags; 348 AclEntryInput input(proto); 349 switch (action) { 350 case inserted: // insert 351 secinfo("SecAccess", "ACL %p inserted", this); 352 target.addAcl(input, cred); 353 mState = unchanged; 354 break; 355 case modified: // update 356 secinfo("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle()); 357 target.changeAcl(entryHandle(), input, cred); 358 mState = unchanged; 359 break; 360 default: 361 assert(false); 362 } 363 } 364 365 366 // 367 // Parse an AclEntryPrototype (presumably from a CSSM "Get" ACL operation 368 // into internal form. 369 // 370 void ACL::parse(const TypedList &subject) 371 { 372 StLock<Mutex>_(mMutex); 373 try { 374 switch (subject.type()) { 375 case CSSM_ACL_SUBJECT_TYPE_ANY: 376 // subsume an "any" as a standard form 377 mForm = allowAllForm; 378 secinfo("SecAccess", "parsed an allowAllForm (%d) (%d)", subject.type(), mForm); 379 return; 380 case CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT: 381 // pure keychain prompt - interpret as applist form with no apps 382 parsePrompt(subject); 383 mForm = appListForm; 384 secinfo("SecAccess", "parsed a Keychain Prompt (%d) as an appListForm (%d)", subject.type(), mForm); 385 return; 386 case CSSM_ACL_SUBJECT_TYPE_THRESHOLD: 387 { 388 // app-list format: THRESHOLD(1, n): sign(1), ..., sign(n), PROMPT 389 if (subject[1] != 1) 390 throw ParseError(); 391 uint32 count = subject[2]; 392 393 // parse final (PROMPT) element 394 TypedList &end = subject[count + 2]; // last choice 395 if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT) 396 throw ParseError(); // not PROMPT at end 397 parsePrompt(end); 398 399 // check for leading ANY 400 TypedList &first = subject[3]; 401 if (first.type() == CSSM_ACL_SUBJECT_TYPE_ANY) { 402 mForm = allowAllForm; 403 secinfo("SecAccess", "parsed a Threshhold (%d) as an allowAllForm (%d)", subject.type(), mForm); 404 return; 405 } 406 407 // parse other (code signing) elements 408 for (uint32 n = 0; n < count - 1; n++) { 409 mAppList.push_back(new TrustedApplication(TypedList(subject[n + 3].list()))); 410 secinfo("SecAccess", "found an application: %s", mAppList.back()->path()); 411 } 412 } 413 mForm = appListForm; 414 secinfo("SecAccess", "parsed a Threshhold (%d) as an appListForm (%d)", subject.type(), mForm); 415 return; 416 case CSSM_ACL_SUBJECT_TYPE_PARTITION: 417 mForm = integrityForm; 418 mIntegrity.copy(subject.last()->data()); 419 secinfo("SecAccess", "parsed a Partition (%d) as an integrityForm (%d)", subject.type(), mForm); 420 return; 421 default: 422 secinfo("SecAccess", "didn't find a type for %d, marking custom (%d)", subject.type(), mForm); 423 mForm = customForm; 424 mSubjectForm = chunkCopy(&subject); 425 return; 426 } 427 } catch (const ParseError &) { 428 secinfo("SecAccess", "acl compile failed for type (%d); marking custom", subject.type()); 429 mForm = customForm; 430 mSubjectForm = chunkCopy(&subject); 431 mAppList.clear(); 432 } 433 } 434 435 void ACL::parsePrompt(const TypedList &subject) 436 { 437 StLock<Mutex>_(mMutex); 438 assert(subject.length() == 3); 439 mPromptSelector = 440 *subject[1].data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 441 mPromptDescription = subject[2].toString(); 442 } 443 444 445 // 446 // Take this ACL and produce its meaning as a CSSM ACL subject in mSubjectForm 447 // 448 void ACL::makeSubject() 449 { 450 StLock<Mutex>_(mMutex); 451 switch (form()) { 452 case allowAllForm: 453 chunkFree(mSubjectForm, allocator); // release previous 454 if (mPromptDescription.empty()) { 455 // no description -> pure ANY 456 mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY); 457 } else { 458 // have description -> threshold(1 of 2) of { ANY, PROMPT } 459 mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, 460 new(allocator) ListElement(1), 461 new(allocator) ListElement(2)); 462 *mSubjectForm += new(allocator) ListElement(TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY)); 463 TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, 464 new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)), 465 new(allocator) ListElement(allocator, mPromptDescription)); 466 *mSubjectForm += new(allocator) ListElement(prompt); 467 } 468 secinfo("SecAccess", "made an allowAllForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type()); 469 return; 470 case appListForm: { 471 // threshold(1 of n+1) of { app1, ..., appn, PROMPT } 472 chunkFree(mSubjectForm, allocator); // release previous 473 uint32 appCount = (uint32)mAppList.size(); 474 mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, 475 new(allocator) ListElement(1), 476 new(allocator) ListElement(appCount + 1)); 477 for (uint32 n = 0; n < appCount; n++) 478 *mSubjectForm += 479 new(allocator) ListElement(mAppList[n]->makeSubject(allocator)); 480 TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, 481 new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)), 482 new(allocator) ListElement(allocator, mPromptDescription)); 483 *mSubjectForm += new(allocator) ListElement(prompt); 484 } 485 secinfo("SecAccess", "made an appListForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type()); 486 return; 487 case integrityForm: 488 chunkFree(mSubjectForm, allocator); 489 mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_PARTITION, 490 new(allocator) ListElement(allocator, mIntegrity)); 491 secinfo("SecAccess", "made an integrityForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type()); 492 return; 493 case customForm: 494 assert(mSubjectForm); // already set; keep it 495 secinfo("SecAccess", "have a customForm (%d), already have a subjectForm (%d)", mForm, mSubjectForm->type()); 496 return; 497 498 default: 499 assert(false); // unexpected 500 } 501 }