X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_keychain/lib/ACL.cpp?ds=inline diff --git a/OSX/libsecurity_keychain/lib/ACL.cpp b/OSX/libsecurity_keychain/lib/ACL.cpp new file mode 100644 index 00000000..9b655a69 --- /dev/null +++ b/OSX/libsecurity_keychain/lib/ACL.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2002-2004,2011-2012,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@ + */ + +// +// ACL.cpp +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace KeychainCore; +using namespace DataWalkers; + + +// +// The default form of a prompt selector +// +const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = { + CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0 +}; + + +// +// ACL static constants +// +const CSSM_ACL_HANDLE ACL::ownerHandle; + + +// +// Create an ACL object from the result of a CSSM ACL query +// +ACL::ACL(Access &acc, const AclEntryInfo &info, Allocator &alloc) + : allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive) +{ + // parse the subject + parse(info.proto().subject()); + + // fill in AclEntryInfo layer information + const AclEntryPrototype &proto = info.proto(); + mAuthorizations = proto.authorization(); + mDelegate = proto.delegate(); + mEntryTag = proto.s_tag(); + + // take CSSM entry handle from info layer + mCssmHandle = info.handle(); +} + +ACL::ACL(Access &acc, const AclOwnerPrototype &owner, Allocator &alloc) + : allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive) +{ + // parse subject + parse(owner.subject()); + + // for an owner "entry", the next-layer information is fixed (and fake) + mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_CHANGE_ACL); + mDelegate = owner.delegate(); + mEntryTag[0] = '\0'; + + // use fixed (fake) entry handle + mCssmHandle = ownerHandle; +} + + +// +// Create a new ACL that authorizes anyone to do anything. +// This constructor produces a "pure" ANY ACL, without descriptor or selector. +// To generate a "standard" form of ANY, use the appListForm constructor below, +// then change its form to allowAnyForm. +// +ACL::ACL(Access &acc, Allocator &alloc) + : allocator(alloc), access(acc), mSubjectForm(NULL), mMutex(Mutex::recursive) +{ + mState = inserted; // new + mForm = allowAllForm; // everybody + mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); // anything + mDelegate = false; + + //mPromptDescription stays empty + mPromptSelector = defaultSelector; + + // randomize the CSSM handle + UniformRandomBlobs().random(mCssmHandle); +} + + +// +// Create a new ACL in standard form. +// As created, it authorizes all activities. +// +ACL::ACL(Access &acc, string description, const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &promptSelector, + Allocator &alloc) + : allocator(alloc), access(acc), mSubjectForm(NULL), mMutex(Mutex::recursive) +{ + mState = inserted; // new + mForm = appListForm; + mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); // anything + mDelegate = false; + + mPromptDescription = description; + mPromptSelector = promptSelector; + + // randomize the CSSM handle + UniformRandomBlobs().random(mCssmHandle); +} + + +// +// Destroy an ACL +// +ACL::~ACL() +{ + // release subject form (if any) + chunkFree(mSubjectForm, allocator); +} + + +// +// Does this ACL authorize a particular right? +// +bool ACL::authorizes(AclAuthorization right) +{ + StLock_(mMutex); + return mAuthorizations.find(right) != mAuthorizations.end() + || mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end() + || mAuthorizations.empty(); +} + + +// +// Add an application to the trusted-app list of this ACL. +// Will fail unless this is a standard "simple" form ACL. +// +void ACL::addApplication(TrustedApplication *app) +{ + StLock_(mMutex); + switch (mForm) { + case appListForm: // simple... + mAppList.push_back(app); + modify(); + break; + case allowAllForm: // hmm... + if (!mPromptDescription.empty()) { + // verbose "any" form (has description, "any" override) + mAppList.push_back(app); + modify(); + break; + } + // pure "any" form without description. Cannot convert to appListForm + default: + MacOSError::throwMe(errSecACLNotSimple); + } +} + + +// +// Mark an ACL as modified. +// +void ACL::modify() +{ + StLock_(mMutex); + if (mState == unchanged) { + secdebug("SecAccess", "ACL %p marked modified", this); + mState = modified; + } +} + + +// +// Mark an ACL as "removed" +// Removed ACLs have no valid contents (they are invalid on their face). +// When "updated" to the originating item, they will cause the corresponding +// ACL entry to be deleted. Otherwise, they are irrelevant. +// Note: Removing an ACL does not actually remove it from its Access's map. +// +void ACL::remove() +{ + StLock_(mMutex); + mAppList.clear(); + mForm = invalidForm; + mState = deleted; +} + + +// +// Produce CSSM-layer form (ACL prototype) copies of our content. +// Note that the result is chunk-allocated, and becomes owned by the caller. +// +void ACL::copyAclEntry(AclEntryPrototype &proto, Allocator &alloc) +{ + StLock_(mMutex); + proto.clearPod(); // preset + + // carefully copy the subject + makeSubject(); + assert(mSubjectForm); + proto = AclEntryPrototype(*mSubjectForm, mDelegate); // shares subject + ChunkCopyWalker w(alloc); + walk(w, proto.subject()); // copy subject in-place + + // the rest of a prototype + proto.tag(mEntryTag); + AuthorizationGroup tags(mAuthorizations, allocator); + proto.authorization() = tags; +} + +void ACL::copyAclOwner(AclOwnerPrototype &proto, Allocator &alloc) +{ + StLock_(mMutex); + proto.clearPod(); + + makeSubject(); + assert(mSubjectForm); + proto = AclOwnerPrototype(*mSubjectForm, mDelegate); // shares subject + ChunkCopyWalker w(alloc); + walk(w, proto.subject()); // copy subject in-place +} + + +// +// (Re)place this ACL's setting into the AclBearer specified. +// If update, assume this is an update operation and the ACL was +// originally derived from this object; specifically, assume the +// CSSM handle is valid. If not update, assume this is a different +// object that has no related ACL entry (yet). +// +void ACL::setAccess(AclBearer &target, bool update, + const AccessCredentials *cred) +{ + StLock_(mMutex); + // determine what action we need to perform + State action = state(); + if (!update) + action = (action == deleted) ? unchanged : inserted; + + // the owner acl (pseudo) "entry" is a special case + if (isOwner()) { + switch (action) { + case unchanged: + secdebug("SecAccess", "ACL %p owner unchanged", this); + return; + case inserted: // means modify the initial owner + case modified: + { + secdebug("SecAccess", "ACL %p owner modified", this); + makeSubject(); + assert(mSubjectForm); + AclOwnerPrototype proto(*mSubjectForm, mDelegate); + target.changeOwner(proto, cred); + return; + } + default: + assert(false); + return; + } + } + + // simple cases + switch (action) { + case unchanged: // ignore + secdebug("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle()); + return; + case deleted: // delete + secdebug("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle()); + target.deleteAcl(entryHandle(), cred); + return; + default: + break; + } + + // build the byzantine data structures that CSSM loves so much + makeSubject(); + assert(mSubjectForm); + AclEntryPrototype proto(*mSubjectForm, mDelegate); + proto.tag(mEntryTag); + AutoAuthorizationGroup tags(mAuthorizations, allocator); + proto.authorization() = tags; + AclEntryInput input(proto); + switch (action) { + case inserted: // insert + secdebug("SecAccess", "ACL %p inserted", this); + target.addAcl(input, cred); + break; + case modified: // update + secdebug("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle()); + target.changeAcl(entryHandle(), input, cred); + break; + default: + assert(false); + } +} + + +// +// Parse an AclEntryPrototype (presumably from a CSSM "Get" ACL operation +// into internal form. +// +void ACL::parse(const TypedList &subject) +{ + StLock_(mMutex); + try { + switch (subject.type()) { + case CSSM_ACL_SUBJECT_TYPE_ANY: + // subsume an "any" as a standard form + mForm = allowAllForm; + return; + case CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT: + // pure keychain prompt - interpret as applist form with no apps + parsePrompt(subject); + mForm = appListForm; + return; + case CSSM_ACL_SUBJECT_TYPE_THRESHOLD: + { + // app-list format: THRESHOLD(1, n): sign(1), ..., sign(n), PROMPT + if (subject[1] != 1) + throw ParseError(); + uint32 count = subject[2]; + + // parse final (PROMPT) element + TypedList &end = subject[count + 2]; // last choice + if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT) + throw ParseError(); // not PROMPT at end + parsePrompt(end); + + // check for leading ANY + TypedList &first = subject[3]; + if (first.type() == CSSM_ACL_SUBJECT_TYPE_ANY) { + mForm = allowAllForm; + return; + } + + // parse other (code signing) elements + for (uint32 n = 0; n < count - 1; n++) + mAppList.push_back(new TrustedApplication(TypedList(subject[n + 3].list()))); + } + mForm = appListForm; + return; + default: + mForm = customForm; + mSubjectForm = chunkCopy(&subject); + return; + } + } catch (const ParseError &) { + secdebug("SecAccess", "acl compile failed; marking custom"); + mForm = customForm; + mSubjectForm = chunkCopy(&subject); + mAppList.clear(); + } +} + +void ACL::parsePrompt(const TypedList &subject) +{ + StLock_(mMutex); + assert(subject.length() == 3); + mPromptSelector = + *subject[1].data().interpretedAs(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + mPromptDescription = subject[2].toString(); +} + + +// +// Take this ACL and produce its meaning as a CSSM ACL subject in mSubjectForm +// +void ACL::makeSubject() +{ + StLock_(mMutex); + switch (form()) { + case allowAllForm: + chunkFree(mSubjectForm, allocator); // release previous + if (mPromptDescription.empty()) { + // no description -> pure ANY + mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY); + } else { + // have description -> threshold(1 of 2) of { ANY, PROMPT } + mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, + new(allocator) ListElement(1), + new(allocator) ListElement(2)); + *mSubjectForm += new(allocator) ListElement(TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY)); + TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, + new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)), + new(allocator) ListElement(allocator, mPromptDescription)); + *mSubjectForm += new(allocator) ListElement(prompt); + } + return; + case appListForm: { + // threshold(1 of n+1) of { app1, ..., appn, PROMPT } + chunkFree(mSubjectForm, allocator); // release previous + uint32 appCount = (uint32)mAppList.size(); + mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, + new(allocator) ListElement(1), + new(allocator) ListElement(appCount + 1)); + for (uint32 n = 0; n < appCount; n++) + *mSubjectForm += + new(allocator) ListElement(mAppList[n]->makeSubject(allocator)); + TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, + new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)), + new(allocator) ListElement(allocator, mPromptDescription)); + *mSubjectForm += new(allocator) ListElement(prompt); + } + return; + case customForm: + assert(mSubjectForm); // already set; keep it + return; + default: + assert(false); // unexpected + } +}