X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_cdsa_utilities/lib/objectacl.cpp diff --git a/Security/libsecurity_cdsa_utilities/lib/objectacl.cpp b/Security/libsecurity_cdsa_utilities/lib/objectacl.cpp new file mode 100644 index 00000000..c89cb273 --- /dev/null +++ b/Security/libsecurity_cdsa_utilities/lib/objectacl.cpp @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2000-2004,2006,2011-2014 Apple 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. +// +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.init(this, slot.subject); + ctx.entryTag(slot.tag); + if (slot.validate(ctx)) { + IFDUMPING("acleval", Debug::dump(">PASS>>\n")); + return true; // passed + } + IFDUMPING("acleval", Debug::dump(" NO")); + } + } + IFDUMPING("acleval", Debug::dump(">FAIL>>\n")); + 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(); + + ctx.init(this, mOwner.subject); + 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 = (uint32)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); + unsigned int count = (unsigned int)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 (unsigned int)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, CSSM_ACL_HANDLE 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(%d)", *it - CSSM_ACL_AUTHORIZATION_PREAUTH_BASE); + else + Debug::dump(" %d", *it); + } + Debug::dump(")"); + } +} + +#endif //DEBUGDUMP