+/*
+ * 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 <security_keychain/ACL.h>
+#include <security_keychain/SecCFTypes.h>
+#include <security_utilities/osxcode.h>
+#include <security_utilities/trackingallocator.h>
+#include <security_cdsa_utilities/walkers.h>
+#include <security_keychain/TrustedApplication.h>
+#include <Security/SecTrustedApplication.h>
+#include <security_utilities/devrandom.h>
+#include <security_cdsa_utilities/uniformrandom.h>
+#include <memory>
+
+
+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<DevRandomGenerator>().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<DevRandomGenerator>().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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(mMutex);
+ assert(subject.length() == 3);
+ mPromptSelector =
+ *subject[1].data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(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<Mutex>_(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
+ }
+}