1/* 2 * Copyright (c) 2000-2004,2006,2011-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// 26// objectacl - core implementation of an ACL-bearing object 27// 28#include <security_cdsa_utilities/objectacl.h> 29#include <security_cdsa_utilities/cssmbridge.h> 30#include <security_utilities/endian.h> 31#include <security_utilities/debugging.h> 32#include <algorithm> 33#include <cstdarg> 34 35#include <security_cdsa_utilities/acl_preauth.h> //@@@ impure - will be removed 36 37using namespace DataWalkers; 38 39 40// 41// The static map of available ACL subject makers. 42// These are the kinds of ACL subjects we can deal with. 43// 44ModuleNexus<ObjectAcl::MakerMap> ObjectAcl::makers; 45 46 47// 48// Create an ObjectAcl 49// 50ObjectAcl::ObjectAcl(Allocator &alloc) : allocator(alloc), mNextHandle(1) 51{ 52} 53 54ObjectAcl::ObjectAcl(const AclEntryPrototype &proto, Allocator &alloc) 55 : allocator(alloc), mNextHandle(1) 56{ 57 cssmSetInitial(proto); 58} 59 60ObjectAcl::~ObjectAcl() 61{ } 62 63 64// 65// Set an "initial ACL" from a CSSM-style initial ACL argument. 66// This will replace the owner, as well as replace the entire ACL 67// with a single-item slot, as per CSSM specification. 68// 69void ObjectAcl::cssmSetInitial(const AclEntryPrototype &proto) 70{ 71 mOwner = OwnerEntry(proto); 72 add(proto.s_tag(), proto); 73 IFDUMPING("acl", debugDump("create/proto")); 74} 75 76void ObjectAcl::cssmSetInitial(const AclSubjectPointer &subject) 77{ 78 mOwner = OwnerEntry(subject); 79 add("", subject); 80 IFDUMPING("acl", debugDump("create/subject")); 81} 82 83ObjectAcl::Entry::~Entry() 84{ 85} 86 87 88// 89// ObjectAcl::validate validates an access authorization claim. 90// Returns normally if 'auth' is granted to the bearer of 'cred'. 91// Otherwise, throws a suitable (ACL-related) CssmError exception. 92// 93class BaseValidationContext : public AclValidationContext { 94public: 95 BaseValidationContext(const AccessCredentials *cred, 96 AclAuthorization auth, AclValidationEnvironment *env) 97 : AclValidationContext(cred, auth, env) { } 98 99 uint32 count() const { return cred() ? cred()->samples().length() : 0; } 100 uint32 size() const { return count(); } 101 const TypedList &sample(uint32 n) const 102 { assert(n < count()); return cred()->samples()[n]; } 103 104 void matched(const TypedList *) const { } // ignore match info 105}; 106 107 108bool ObjectAcl::validates(AclAuthorization auth, const AccessCredentials *cred, 109 AclValidationEnvironment *env) 110{ 111 BaseValidationContext ctx(cred, auth, env); 112 return validates(ctx); 113} 114 115bool ObjectAcl::validates(AclValidationContext &ctx) 116{ 117 // make sure we are ready to go 118 instantiateAcl(); 119 120 IFDUMPING("acleval", Debug::dump("<<WANT(%d)<", ctx.authorization())); 121 122 //@@@ should pre-screen based on requested auth, maybe? 123 124#if defined(ACL_OMNIPOTENT_OWNER) 125 // try owner (owner can do anything) 126 if (mOwner.validate(ctx)) 127 return; 128#endif //ACL_OMNIPOTENT_OWNER 129 130 // try applicable ACLs 131 pair<EntryMap::const_iterator, EntryMap::const_iterator> range; 132 if (getRange(ctx.s_credTag(), range) == 0) // no such tag 133 CssmError::throwMe(CSSM_ERRCODE_ACL_ENTRY_TAG_NOT_FOUND); 134 // try each entry in turn 135 for (EntryMap::const_iterator it = range.first; it != range.second; it++) { 136 const AclEntry &slot = it->second; 137 IFDUMPING("acleval", (Debug::dump(" EVAL["), slot.debugDump(), Debug::dump("]"))); 138 if (slot.authorizes(ctx.authorization())) { 139 ctx.init(this, slot.subject); 140 ctx.entryTag(slot.tag); 141 if (slot.validate(ctx)) { 142 IFDUMPING("acleval", Debug::dump(">PASS>>\n")); 143 return true; // passed 144 } 145 IFDUMPING("acleval", Debug::dump(" NO")); 146 } 147 } 148 IFDUMPING("acleval", Debug::dump(">FAIL>>\n")); 149 return false; // no joy 150} 151 152void ObjectAcl::validate(AclAuthorization auth, const AccessCredentials *cred, 153 AclValidationEnvironment *env) 154{ 155 if (!validates(auth, cred, env)) 156 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); 157} 158 159void ObjectAcl::validate(AclValidationContext &ctx) 160{ 161 if (!validates(ctx)) 162 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); 163} 164 165 166void ObjectAcl::validateOwner(AclAuthorization authorizationHint, 167 const AccessCredentials *cred, AclValidationEnvironment *env) 168{ 169 BaseValidationContext ctx(cred, authorizationHint, env); 170 validateOwner(ctx); 171} 172 173void ObjectAcl::validateOwner(AclValidationContext &ctx) 174{ 175 instantiateAcl(); 176 177 ctx.init(this, mOwner.subject); 178 if (mOwner.validate(ctx)) 179 return; 180 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); 181} 182 183 184// 185// Export an ObjectAcl to two memory blobs: public and private data separated. 186// This is a standard two-pass size+copy operation. 187// 188void ObjectAcl::exportBlob(CssmData &publicBlob, CssmData &privateBlob) 189{ 190 instantiateAcl(); 191 Writer::Counter pubSize, privSize; 192 Endian<uint32> entryCount = (uint32)mEntries.size(); 193 mOwner.exportBlob(pubSize, privSize); 194 pubSize(entryCount); 195 for (EntryMap::iterator it = begin(); it != end(); it++) 196 it->second.exportBlob(pubSize, privSize); 197 publicBlob = CssmData(allocator.malloc(pubSize), pubSize); 198 privateBlob = CssmData(allocator.malloc(privSize), privSize); 199 Writer pubWriter(publicBlob), privWriter(privateBlob); 200 mOwner.exportBlob(pubWriter, privWriter); 201 pubWriter(entryCount); 202 for (EntryMap::iterator it = begin(); it != end(); it++) 203 it->second.exportBlob(pubWriter, privWriter); 204 IFDUMPING("acl", debugDump("exported")); 205} 206 207 208// 209// Import an ObjectAcl's contents from two memory blobs representing public and 210// private contents, respectively. These blobs must have been generated by the 211// export method. 212// Prior contents (if any) are deleted and replaced. 213// 214void ObjectAcl::importBlob(const void *publicBlob, const void *privateBlob) 215{ 216 Reader pubReader(publicBlob), privReader(privateBlob); 217 mOwner.importBlob(pubReader, privReader); 218 Endian<uint32> entryCountIn; pubReader(entryCountIn); 219 uint32 entryCount = entryCountIn; 220 221 mEntries.erase(begin(), end()); 222 for (uint32 n = 0; n < entryCount; n++) { 223 AclEntry newEntry; 224 newEntry.importBlob(pubReader, privReader); 225 add(newEntry.tag, newEntry); 226 } 227 IFDUMPING("acl", debugDump("imported")); 228} 229 230 231// 232// Import/export helpers for subjects. 233// This is exported to (subject implementation) callers to maintain consistency 234// in binary format handling. 235// 236AclSubject *ObjectAcl::importSubject(Reader &pub, Reader &priv) 237{ 238 Endian<uint32> typeAndVersion; pub(typeAndVersion); 239 return make(typeAndVersion, pub, priv); 240} 241 242 243// 244// Setup/update hooks 245// 246void ObjectAcl::instantiateAcl() 247{ 248 // nothing by default 249} 250 251void ObjectAcl::changedAcl() 252{ 253 // nothing by default 254} 255 256 257// 258// ACL utility methods 259// 260unsigned int ObjectAcl::getRange(const std::string &tag, 261 pair<EntryMap::const_iterator, EntryMap::const_iterator> &range) const 262{ 263 if (!tag.empty()) { // tag restriction in effect 264 range = mEntries.equal_range(tag); 265 unsigned int count = (unsigned int)mEntries.count(tag); 266 if (count == 0) 267 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_ENTRY_TAG); 268 return count; 269 } else { // try all tags 270 range.first = mEntries.begin(); 271 range.second = mEntries.end(); 272 return (unsigned int)mEntries.size(); 273 } 274} 275 276ObjectAcl::EntryMap::iterator ObjectAcl::findEntryHandle(CSSM_ACL_HANDLE handle) 277{ 278 for (EntryMap::iterator it = mEntries.begin(); it != mEntries.end(); it++) 279 if (it->second.handle == handle) 280 return it; 281 CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); //%%% imprecise error code 282} 283 284 285// 286// CSSM style ACL access and modification functions. 287// 288void ObjectAcl::cssmGetAcl(const char *tag, uint32 &count, AclEntryInfo * &acls) 289{ 290 instantiateAcl(); 291 pair<EntryMap::const_iterator, EntryMap::const_iterator> range; 292 count = getRange(tag ? tag : "", range); 293 acls = allocator.alloc<AclEntryInfo>(count); 294 uint32 n = 0; 295 for (EntryMap::const_iterator it = range.first; it != range.second; it++, n++) { 296 acls[n].EntryHandle = it->second.handle; 297 it->second.toEntryInfo(acls[n].EntryPublicInfo, allocator); 298 } 299 count = n; 300} 301 302void ObjectAcl::cssmChangeAcl(const AclEdit &edit, 303 const AccessCredentials *cred, AclValidationEnvironment *env) 304{ 305 IFDUMPING("acl", debugDump("acl-change-from")); 306 307 // make sure we're ready to go 308 instantiateAcl(); 309 310 // validate access credentials 311 validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_ACL, cred, env); 312 313 // what is Thy wish, effendi? 314 switch (edit.EditMode) { 315 case CSSM_ACL_EDIT_MODE_ADD: { 316 const AclEntryInput &input = Required(edit.newEntry()); 317 add(input.proto().s_tag(), input.proto()); 318 } 319 break; 320 case CSSM_ACL_EDIT_MODE_REPLACE: { 321 // keep the handle, and try for some modicum of atomicity 322 EntryMap::iterator it = findEntryHandle(edit.handle()); 323 AclEntryPrototype proto = Required(edit.newEntry()).proto(); // (bypassing callbacks) 324 add(proto.s_tag(), proto, edit.handle()); 325 mEntries.erase(it); 326 } 327 break; 328 case CSSM_ACL_EDIT_MODE_DELETE: 329 mEntries.erase(findEntryHandle(edit.OldEntryHandle)); 330 break; 331 default: 332 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_EDIT_MODE); 333 } 334 335 // notify change 336 changedAcl(); 337 338 IFDUMPING("acl", debugDump("acl-change-to")); 339} 340 341void ObjectAcl::cssmGetOwner(AclOwnerPrototype &outOwner) 342{ 343 instantiateAcl(); 344 outOwner.TypedSubject = mOwner.subject->toList(allocator); 345 outOwner.Delegate = mOwner.delegate; 346} 347 348void ObjectAcl::cssmChangeOwner(const AclOwnerPrototype &newOwner, 349 const AccessCredentials *cred, AclValidationEnvironment *env) 350{ 351 IFDUMPING("acl", debugDump("owner-change-from")); 352 353 instantiateAcl(); 354 355 // only the owner entry can match 356 validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_OWNER, cred, env); 357 358 // okay, replace it 359 mOwner = newOwner; 360 361 changedAcl(); 362 363 IFDUMPING("acl", debugDump("owner-change-to")); 364} 365 366 367// 368// Load a set of ACL entries from an AclEntryInfo array. 369// This completely replaces the ACL's entries. 370// Note that we will adopt the handles in the infos, so they better be proper 371// (unique, nonzero). 372// 373template <class Input> 374void ObjectAcl::owner(const Input &input) 375{ 376 IFDUMPING("acl", debugDump("owner-load-old")); 377 mOwner = OwnerEntry(input); 378 IFDUMPING("acl", debugDump("owner-load-new")); 379} 380 381template void ObjectAcl::owner(const AclOwnerPrototype &); 382template void ObjectAcl::owner(const AclSubjectPointer &); 383 384 385void ObjectAcl::entries(uint32 count, const AclEntryInfo *info) 386{ 387 IFDUMPING("acl", debugDump("entries-load-old")); 388 mEntries.erase(mEntries.begin(), mEntries.end()); 389 for (uint32 n = 0; n < count; n++, info++) 390 add(info->proto().s_tag(), info->proto()); 391 IFDUMPING("acl", debugDump("entries-load-new")); 392} 393 394 395// 396// Clear out the ACL and return it to un-initialized state 397// 398void ObjectAcl::clear() 399{ 400 mOwner = OwnerEntry(); 401 mEntries.erase(mEntries.begin(), mEntries.end()); 402 secdebug("acl", "%p cleared", this); 403} 404 405 406// 407// Common gate to add an ACL entry 408// 409void ObjectAcl::add(const std::string &tag, const AclEntry &newEntry) 410{ 411 add(tag, newEntry, mNextHandle++); 412} 413 414void ObjectAcl::add(const std::string &tag, AclEntry newEntry, CSSM_ACL_HANDLE handle) 415{ 416 //@@@ This should use a hook-registry mechanism. But for now, we are explicit: 417 if (!newEntry.authorizesAnything) { 418 for (AclAuthorizationSet::const_iterator it = newEntry.authorizations.begin(); 419 it != newEntry.authorizations.end(); it++) 420 if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE && 421 *it < CSSM_ACL_AUTHORIZATION_PREAUTH_END) { 422 // preauthorization right - special handling 423 if (newEntry.subject->type() != CSSM_ACL_SUBJECT_TYPE_PREAUTH_SOURCE) 424 newEntry.subject = 425 new PreAuthorizationAcls::SourceAclSubject(newEntry.subject); 426 } 427 } 428 429 mEntries.insert(make_pair(tag, newEntry))->second.handle = handle; 430 if (handle >= mNextHandle) 431 mNextHandle = handle + 1; // don't reuse this handle (in this ACL) 432} 433 434 435// 436// Common features of ACL entries/owners 437// 438void ObjectAcl::Entry::init(const AclSubjectPointer &subject, bool delegate) 439{ 440 this->subject = subject; 441 this->delegate = delegate; 442} 443 444void ObjectAcl::Entry::importBlob(Reader &pub, Reader &priv) 445{ 446 // the delegation flag is 4 bytes for historic reasons 447 Endian<uint32> del; 448 pub(del); 449 delegate = del; 450 451 subject = importSubject(pub, priv); 452} 453 454 455// 456// An OwnerEntry is a restricted EntryPrototype for use as the ACL owner. 457// 458bool ObjectAcl::OwnerEntry::authorizes(AclAuthorization) const 459{ 460 return true; // owner can do anything 461} 462 463bool ObjectAcl::OwnerEntry::validate(const AclValidationContext &ctx) const 464{ 465 return subject->validate(ctx); // simple subject match - no strings attached 466} 467 468 469// 470// An AclEntry has some extra goodies 471// 472ObjectAcl::AclEntry::AclEntry(const AclEntryPrototype &proto) : Entry(proto) 473{ 474 tag = proto.s_tag(); 475 if (proto.authorization().contains(CSSM_ACL_AUTHORIZATION_ANY)) 476 authorizesAnything = true; // anything else wouldn't add anything 477 else if (proto.authorization().empty()) 478 authorizesAnything = true; // not in standard, but common sense 479 else { 480 authorizesAnything = false; 481 authorizations = proto.authorization(); 482 } 483 //@@@ not setting time range 484 // handle = not set here. Set by caller when the AclEntry is created. 485} 486 487ObjectAcl::AclEntry::AclEntry(const AclSubjectPointer &subject) : Entry(subject) 488{ 489 authorizesAnything = true; // by default, everything 490 //@@@ not setting time range 491} 492 493void ObjectAcl::AclEntry::toEntryInfo(CSSM_ACL_ENTRY_PROTOTYPE &info, Allocator &alloc) const 494{ 495 info.TypedSubject = subject->toList(alloc); 496 info.Delegate = delegate; 497 info.Authorization = authorizesAnything ? 498 AuthorizationGroup(CSSM_ACL_AUTHORIZATION_ANY, alloc) : 499 AuthorizationGroup(authorizations, alloc); 500 //@@@ info.TimeRange = 501 assert(tag.length() <= CSSM_MODULE_STRING_SIZE); 502 memcpy(info.EntryTag, tag.c_str(), tag.length() + 1); 503} 504 505bool ObjectAcl::AclEntry::authorizes(AclAuthorization auth) const 506{ 507 return authorizesAnything || authorizations.find(auth) != authorizations.end(); 508} 509 510bool ObjectAcl::AclEntry::validate(const AclValidationContext &ctx) const 511{ 512 //@@@ not checking time ranges 513 return subject->validate(ctx); 514} 515 516void ObjectAcl::AclEntry::importBlob(Reader &pub, Reader &priv) 517{ 518 Entry::importBlob(pub, priv); 519 const char *s; pub(s); tag = s; 520 521 // authorizesAnything is on disk as a 4-byte flag 522 Endian<uint32> tmpAuthorizesAnything; 523 pub(tmpAuthorizesAnything); 524 authorizesAnything = tmpAuthorizesAnything; 525 526 authorizations.erase(authorizations.begin(), authorizations.end()); 527 if (!authorizesAnything) { 528 Endian<uint32> countIn; pub(countIn); 529 uint32 count = countIn; 530 531 for (uint32 n = 0; n < count; n++) { 532 Endian<AclAuthorization> auth; pub(auth); 533 authorizations.insert(auth); 534 } 535 } 536 //@@@ import time range 537} 538 539 540// 541// Subject factory and makers 542// 543AclSubject::Maker::Maker(CSSM_ACL_SUBJECT_TYPE type) 544 : mType(type) 545{ 546 ObjectAcl::makers()[type] = this; 547} 548 549AclSubject *ObjectAcl::make(const TypedList &list) 550{ 551 if (!list.isProper()) 552 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 553 return makerFor(list.type()).make(list); 554} 555 556AclSubject *ObjectAcl::make(uint32 typeAndVersion, Reader &pub, Reader &priv) 557{ 558 // this type is encoded as (version << 24) | type 559 return makerFor(typeAndVersion & ~AclSubject::versionMask).make(typeAndVersion >> AclSubject::versionShift, pub, priv); 560} 561 562AclSubject::Maker &ObjectAcl::makerFor(CSSM_ACL_SUBJECT_TYPE type) 563{ 564 AclSubject::Maker *maker = makers()[type]; 565 if (maker == NULL) 566 CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED); 567 return *maker; 568} 569 570 571// 572// Parsing helper for subject makers. 573// Note that count/array exclude the first element of list, which is the subject type wordid. 574// 575void AclSubject::Maker::crack(const CssmList &list, uint32 count, ListElement **array, ...) 576{ 577 if (count != list.length() - 1) 578 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 579 if (count > 0) { 580 va_list args; 581 va_start(args, array); 582 ListElement *elem = list.first()->next(); 583 for (uint32 n = 0; n < count; n++, elem = elem->next()) { 584 CSSM_LIST_ELEMENT_TYPE expectedType = va_arg(args, CSSM_LIST_ELEMENT_TYPE); 585 if (elem->type() != expectedType) 586 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 587 array[n] = elem; 588 } 589 va_end(args); 590 } 591} 592 593CSSM_WORDID_TYPE AclSubject::Maker::getWord(const ListElement &elem, 594 int min /*= 0*/, int max /*= INT_MAX*/) 595{ 596 if (elem.type() != CSSM_LIST_ELEMENT_WORDID) 597 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 598 CSSM_WORDID_TYPE value = elem; 599 if (value < min || value > max) 600 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 601 return value; 602} 603 604 605// 606// Debug dumping support. 607// Leave the ObjectAcl::debugDump method in (stubbed out) 608// to keep the virtual table layout stable, and to allow 609// proper linking in weird mix-and-match scenarios. 610// 611void ObjectAcl::debugDump(const char *what) const 612{ 613#if defined(DEBUGDUMP) 614 if (!what) 615 what = "Dump"; 616 Debug::dump("%p ACL %s: %d entries\n", this, what, int(mEntries.size())); 617 Debug::dump(" OWNER ["); mOwner.debugDump(); Debug::dump("]\n"); 618 for (EntryMap::const_iterator it = begin(); it != end(); it++) { 619 const AclEntry &ent = it->second; 620 Debug::dump(" (%ld:%s) [", ent.handle, ent.tag.c_str()); 621 ent.debugDump(); 622 Debug::dump("]\n"); 623 } 624 Debug::dump("%p ACL END\n", this); 625#endif //DEBUGDUMP 626} 627 628#if defined(DEBUGDUMP) 629 630void ObjectAcl::Entry::debugDump() const 631{ 632 if (subject) { 633 if (AclSubject::Version v = subject->version()) 634 Debug::dump("V=%d ", v); 635 subject->debugDump(); 636 } else { 637 Debug::dump("NULL subject"); 638 } 639 if (delegate) 640 Debug::dump(" DELEGATE"); 641} 642 643void ObjectAcl::AclEntry::debugDump() const 644{ 645 Entry::debugDump(); 646 if (authorizesAnything) { 647 Debug::dump(" auth(ALL)"); 648 } else { 649 Debug::dump(" auth("); 650 for (AclAuthorizationSet::iterator it = authorizations.begin(); 651 it != authorizations.end(); it++) { 652 if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE 653 && *it < CSSM_ACL_AUTHORIZATION_PREAUTH_END) 654 Debug::dump(" PRE(%d)", *it - CSSM_ACL_AUTHORIZATION_PREAUTH_BASE); 655 else 656 Debug::dump(" %d", *it); 657 } 658 Debug::dump(")"); 659 } 660} 661 662#endif //DEBUGDUMP 663