/* * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // objectacl - core implementation of an ACL-bearing object // #include #include #include #include #include #include #include //@@@ impure - will be removed using namespace DataWalkers; // // The static map of available ACL subject makers. // These are the kinds of ACL subjects we can deal with. // ModuleNexus ObjectAcl::makers; // // Create an ObjectAcl // ObjectAcl::ObjectAcl(Allocator &alloc) : allocator(alloc), mNextHandle(1) { } ObjectAcl::ObjectAcl(const AclEntryPrototype &proto, Allocator &alloc) : allocator(alloc), mNextHandle(1) { cssmSetInitial(proto); } ObjectAcl::~ObjectAcl() { } // // Set an "initial ACL" from a CSSM-style initial ACL argument. // This will replace the owner, as well as replace the entire ACL // with a single-item slot, as per CSSM specification. // void ObjectAcl::cssmSetInitial(const AclEntryPrototype &proto) { mOwner = OwnerEntry(proto); add(proto.s_tag(), proto); IFDUMPING("acl", debugDump("create/proto")); } void ObjectAcl::cssmSetInitial(const AclSubjectPointer &subject) { mOwner = OwnerEntry(subject); add("", subject); IFDUMPING("acl", debugDump("create/subject")); } ObjectAcl::Entry::~Entry() { } // // ObjectAcl::validate validates an access authorization claim. // Returns normally if 'auth' is granted to the bearer of 'cred'. // Otherwise, throws a suitable (ACL-related) CssmError exception. // @@@ Should it return a reference to the Entry that granted access? // class BaseValidationContext : public AclValidationContext { public: BaseValidationContext(const AccessCredentials *cred, AclAuthorization auth, AclValidationEnvironment *env) : AclValidationContext(cred, auth, env) { } uint32 count() const { return cred() ? cred()->samples().length() : 0; } uint32 size() const { return count(); } const TypedList &sample(uint32 n) const { assert(n < count()); return cred()->samples()[n]; } void matched(const TypedList *) const { } // ignore match info }; bool ObjectAcl::validates(AclAuthorization auth, const AccessCredentials *cred, AclValidationEnvironment *env) { BaseValidationContext ctx(cred, auth, env); return validates(ctx); } bool ObjectAcl::validates(AclValidationContext &ctx) { // make sure we are ready to go instantiateAcl(); IFDUMPING("acleval", Debug::dump("< range; if (getRange(ctx.s_credTag(), range) == 0) // no such tag CssmError::throwMe(CSSM_ERRCODE_ACL_ENTRY_TAG_NOT_FOUND); // try each entry in turn for (EntryMap::const_iterator it = range.first; it != range.second; it++) { const AclEntry &slot = it->second; IFDUMPING("acleval", (Debug::dump(" EVAL["), slot.debugDump(), Debug::dump("]"))); if (slot.authorizes(ctx.authorization())) { ctx.entryTag(slot.tag); if (slot.validate(ctx)) { IFDUMPING("acleval", Debug::dump(">PASS>>")); return true; // passed } IFDUMPING("acleval", Debug::dump(" NO")); } } IFDUMPING("acleval", Debug::dump(">FAIL>>")); return false; // no joy } void ObjectAcl::validate(AclAuthorization auth, const AccessCredentials *cred, AclValidationEnvironment *env) { if (!validates(auth, cred, env)) CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); } void ObjectAcl::validate(AclValidationContext &ctx) { if (!validates(ctx)) CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); } void ObjectAcl::validateOwner(AclAuthorization authorizationHint, const AccessCredentials *cred, AclValidationEnvironment *env) { BaseValidationContext ctx(cred, authorizationHint, env); validateOwner(ctx); } void ObjectAcl::validateOwner(AclValidationContext &ctx) { instantiateAcl(); if (mOwner.validate(ctx)) return; CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); } // // Export an ObjectAcl to two memory blobs: public and private data separated. // This is a standard two-pass size+copy operation. // void ObjectAcl::exportBlob(CssmData &publicBlob, CssmData &privateBlob) { instantiateAcl(); Writer::Counter pubSize, privSize; Endian entryCount = mEntries.size(); mOwner.exportBlob(pubSize, privSize); pubSize(entryCount); for (EntryMap::iterator it = begin(); it != end(); it++) it->second.exportBlob(pubSize, privSize); publicBlob = CssmData(allocator.malloc(pubSize), pubSize); privateBlob = CssmData(allocator.malloc(privSize), privSize); Writer pubWriter(publicBlob), privWriter(privateBlob); mOwner.exportBlob(pubWriter, privWriter); pubWriter(entryCount); for (EntryMap::iterator it = begin(); it != end(); it++) it->second.exportBlob(pubWriter, privWriter); IFDUMPING("acl", debugDump("exported")); } // // Import an ObjectAcl's contents from two memory blobs representing public and // private contents, respectively. These blobs must have been generated by the // export method. // Prior contents (if any) are deleted and replaced. // void ObjectAcl::importBlob(const void *publicBlob, const void *privateBlob) { Reader pubReader(publicBlob), privReader(privateBlob); mOwner.importBlob(pubReader, privReader); Endian entryCountIn; pubReader(entryCountIn); uint32 entryCount = entryCountIn; mEntries.erase(begin(), end()); for (uint32 n = 0; n < entryCount; n++) { AclEntry newEntry; newEntry.importBlob(pubReader, privReader); add(newEntry.tag, newEntry); } IFDUMPING("acl", debugDump("imported")); } // // Import/export helpers for subjects. // This is exported to (subject implementation) callers to maintain consistency // in binary format handling. // AclSubject *ObjectAcl::importSubject(Reader &pub, Reader &priv) { Endian typeAndVersion; pub(typeAndVersion); return make(typeAndVersion, pub, priv); } // // Setup/update hooks // void ObjectAcl::instantiateAcl() { // nothing by default } void ObjectAcl::changedAcl() { // nothing by default } // // ACL utility methods // unsigned int ObjectAcl::getRange(const std::string &tag, pair &range) const { if (!tag.empty()) { // tag restriction in effect range = mEntries.equal_range(tag); uint32 count = mEntries.count(tag); if (count == 0) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_ENTRY_TAG); return count; } else { // try all tags range.first = mEntries.begin(); range.second = mEntries.end(); return mEntries.size(); } } ObjectAcl::EntryMap::iterator ObjectAcl::findEntryHandle(CSSM_ACL_HANDLE handle) { for (EntryMap::iterator it = mEntries.begin(); it != mEntries.end(); it++) if (it->second.handle == handle) return it; CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); //%%% imprecise error code } // // CSSM style ACL access and modification functions. // void ObjectAcl::cssmGetAcl(const char *tag, uint32 &count, AclEntryInfo * &acls) { instantiateAcl(); pair range; count = getRange(tag ? tag : "", range); acls = allocator.alloc(count); uint32 n = 0; for (EntryMap::const_iterator it = range.first; it != range.second; it++, n++) { acls[n].EntryHandle = it->second.handle; it->second.toEntryInfo(acls[n].EntryPublicInfo, allocator); } count = n; } void ObjectAcl::cssmChangeAcl(const AclEdit &edit, const AccessCredentials *cred, AclValidationEnvironment *env) { IFDUMPING("acl", debugDump("acl-change-from")); // make sure we're ready to go instantiateAcl(); // validate access credentials validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_ACL, cred, env); // what is Thy wish, effendi? switch (edit.EditMode) { case CSSM_ACL_EDIT_MODE_ADD: { const AclEntryInput &input = Required(edit.newEntry()); add(input.proto().s_tag(), input.proto()); } break; case CSSM_ACL_EDIT_MODE_REPLACE: { // keep the handle, and try for some modicum of atomicity EntryMap::iterator it = findEntryHandle(edit.handle()); AclEntryPrototype proto = Required(edit.newEntry()).proto(); // (bypassing callbacks) add(proto.s_tag(), proto, edit.handle()); mEntries.erase(it); } break; case CSSM_ACL_EDIT_MODE_DELETE: mEntries.erase(findEntryHandle(edit.OldEntryHandle)); break; default: CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_EDIT_MODE); } // notify change changedAcl(); IFDUMPING("acl", debugDump("acl-change-to")); } void ObjectAcl::cssmGetOwner(AclOwnerPrototype &outOwner) { instantiateAcl(); outOwner.TypedSubject = mOwner.subject->toList(allocator); outOwner.Delegate = mOwner.delegate; } void ObjectAcl::cssmChangeOwner(const AclOwnerPrototype &newOwner, const AccessCredentials *cred, AclValidationEnvironment *env) { IFDUMPING("acl", debugDump("owner-change-from")); instantiateAcl(); // only the owner entry can match validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_OWNER, cred, env); // okay, replace it mOwner = newOwner; changedAcl(); IFDUMPING("acl", debugDump("owner-change-to")); } // // Load a set of ACL entries from an AclEntryInfo array. // This completely replaces the ACL's entries. // Note that we will adopt the handles in the infos, so they better be proper // (unique, nonzero). // template void ObjectAcl::owner(const Input &input) { IFDUMPING("acl", debugDump("owner-load-old")); mOwner = OwnerEntry(input); IFDUMPING("acl", debugDump("owner-load-new")); } template void ObjectAcl::owner(const AclOwnerPrototype &); template void ObjectAcl::owner(const AclSubjectPointer &); void ObjectAcl::entries(uint32 count, const AclEntryInfo *info) { IFDUMPING("acl", debugDump("entries-load-old")); mEntries.erase(mEntries.begin(), mEntries.end()); for (uint32 n = 0; n < count; n++, info++) add(info->proto().s_tag(), info->proto()); IFDUMPING("acl", debugDump("entries-load-new")); } // // Clear out the ACL and return it to un-initialized state // void ObjectAcl::clear() { mOwner = OwnerEntry(); mEntries.erase(mEntries.begin(), mEntries.end()); secdebug("acl", "%p cleared", this); } // // Common gate to add an ACL entry // void ObjectAcl::add(const std::string &tag, const AclEntry &newEntry) { add(tag, newEntry, mNextHandle++); } void ObjectAcl::add(const std::string &tag, AclEntry newEntry, uint32 handle) { //@@@ This should use a hook-registry mechanism. But for now, we are explicit: if (!newEntry.authorizesAnything) { for (AclAuthorizationSet::const_iterator it = newEntry.authorizations.begin(); it != newEntry.authorizations.end(); it++) if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE && *it < CSSM_ACL_AUTHORIZATION_PREAUTH_END) { // preauthorization right - special handling if (newEntry.subject->type() != CSSM_ACL_SUBJECT_TYPE_PREAUTH_SOURCE) newEntry.subject = new PreAuthorizationAcls::SourceAclSubject(newEntry.subject); } } mEntries.insert(make_pair(tag, newEntry))->second.handle = handle; if (handle >= mNextHandle) mNextHandle = handle + 1; // don't reuse this handle (in this ACL) } // // Common features of ACL entries/owners // void ObjectAcl::Entry::init(const AclSubjectPointer &subject, bool delegate) { this->subject = subject; this->delegate = delegate; } void ObjectAcl::Entry::importBlob(Reader &pub, Reader &priv) { // the delegation flag is 4 bytes for historic reasons Endian del; pub(del); delegate = del; subject = importSubject(pub, priv); } // // An OwnerEntry is a restricted EntryPrototype for use as the ACL owner. // bool ObjectAcl::OwnerEntry::authorizes(AclAuthorization) const { return true; // owner can do anything } bool ObjectAcl::OwnerEntry::validate(const AclValidationContext &ctx) const { return subject->validate(ctx); // simple subject match - no strings attached } // // An AclEntry has some extra goodies // ObjectAcl::AclEntry::AclEntry(const AclEntryPrototype &proto) : Entry(proto) { tag = proto.s_tag(); if (proto.authorization().contains(CSSM_ACL_AUTHORIZATION_ANY)) authorizesAnything = true; // anything else wouldn't add anything else if (proto.authorization().empty()) authorizesAnything = true; // not in standard, but common sense else { authorizesAnything = false; authorizations = proto.authorization(); } //@@@ not setting time range // handle = not set here. Set by caller when the AclEntry is created. } ObjectAcl::AclEntry::AclEntry(const AclSubjectPointer &subject) : Entry(subject) { authorizesAnything = true; // by default, everything //@@@ not setting time range } void ObjectAcl::AclEntry::toEntryInfo(CSSM_ACL_ENTRY_PROTOTYPE &info, Allocator &alloc) const { info.TypedSubject = subject->toList(alloc); info.Delegate = delegate; info.Authorization = authorizesAnything ? AuthorizationGroup(CSSM_ACL_AUTHORIZATION_ANY, alloc) : AuthorizationGroup(authorizations, alloc); //@@@ info.TimeRange = assert(tag.length() <= CSSM_MODULE_STRING_SIZE); memcpy(info.EntryTag, tag.c_str(), tag.length() + 1); } bool ObjectAcl::AclEntry::authorizes(AclAuthorization auth) const { return authorizesAnything || authorizations.find(auth) != authorizations.end(); } bool ObjectAcl::AclEntry::validate(const AclValidationContext &ctx) const { //@@@ not checking time ranges return subject->validate(ctx); } void ObjectAcl::AclEntry::importBlob(Reader &pub, Reader &priv) { Entry::importBlob(pub, priv); const char *s; pub(s); tag = s; // authorizesAnything is on disk as a 4-byte flag Endian tmpAuthorizesAnything; pub(tmpAuthorizesAnything); authorizesAnything = tmpAuthorizesAnything; authorizations.erase(authorizations.begin(), authorizations.end()); if (!authorizesAnything) { Endian countIn; pub(countIn); uint32 count = countIn; for (uint32 n = 0; n < count; n++) { Endian auth; pub(auth); authorizations.insert(auth); } } //@@@ import time range } // // Subject factory and makers // AclSubject::Maker::Maker(CSSM_ACL_SUBJECT_TYPE type) : mType(type) { ObjectAcl::makers()[type] = this; } AclSubject *ObjectAcl::make(const TypedList &list) { if (!list.isProper()) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); return makerFor(list.type()).make(list); } AclSubject *ObjectAcl::make(uint32 typeAndVersion, Reader &pub, Reader &priv) { // this type is encoded as (version << 24) | type return makerFor(typeAndVersion & ~AclSubject::versionMask).make(typeAndVersion >> AclSubject::versionShift, pub, priv); } AclSubject::Maker &ObjectAcl::makerFor(CSSM_ACL_SUBJECT_TYPE type) { AclSubject::Maker *maker = makers()[type]; if (maker == NULL) CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED); return *maker; } // // Parsing helper for subject makers. // Note that count/array exclude the first element of list, which is the subject type wordid. // void AclSubject::Maker::crack(const CssmList &list, uint32 count, ListElement **array, ...) { if (count != list.length() - 1) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); if (count > 0) { va_list args; va_start(args, array); ListElement *elem = list.first()->next(); for (uint32 n = 0; n < count; n++, elem = elem->next()) { CSSM_LIST_ELEMENT_TYPE expectedType = va_arg(args, CSSM_LIST_ELEMENT_TYPE); if (elem->type() != expectedType) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); array[n] = elem; } va_end(args); } } CSSM_WORDID_TYPE AclSubject::Maker::getWord(const ListElement &elem, int min /*= 0*/, int max /*= INT_MAX*/) { if (elem.type() != CSSM_LIST_ELEMENT_WORDID) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); CSSM_WORDID_TYPE value = elem; if (value < min || value > max) CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); return value; } // // Debug dumping support. // Leave the ObjectAcl::debugDump method in (stubbed out) // to keep the virtual table layout stable, and to allow // proper linking in weird mix-and-match scenarios. // void ObjectAcl::debugDump(const char *what) const { #if defined(DEBUGDUMP) if (!what) what = "Dump"; Debug::dump("%p ACL %s: %d entries\n", this, what, int(mEntries.size())); Debug::dump(" OWNER ["); mOwner.debugDump(); Debug::dump("]\n"); for (EntryMap::const_iterator it = begin(); it != end(); it++) { const AclEntry &ent = it->second; Debug::dump(" (%ld:%s) [", ent.handle, ent.tag.c_str()); ent.debugDump(); Debug::dump("]\n"); } Debug::dump("%p ACL END\n", this); #endif //DEBUGDUMP } #if defined(DEBUGDUMP) void ObjectAcl::Entry::debugDump() const { if (subject) { if (AclSubject::Version v = subject->version()) Debug::dump("V=%d ", v); subject->debugDump(); } else { Debug::dump("NULL subject"); } if (delegate) Debug::dump(" DELEGATE"); } void ObjectAcl::AclEntry::debugDump() const { Entry::debugDump(); if (authorizesAnything) { Debug::dump(" auth(ALL)"); } else { Debug::dump(" auth("); for (AclAuthorizationSet::iterator it = authorizations.begin(); it != authorizations.end(); it++) { if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE && *it < CSSM_ACL_AUTHORIZATION_PREAUTH_END) Debug::dump(" PRE(%ld)", *it - CSSM_ACL_AUTHORIZATION_PREAUTH_BASE); else Debug::dump(" %ld", *it); } Debug::dump(")"); } } #endif //DEBUGDUMP