#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;
const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = {
CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0
};
const CSSM_ACL_HANDLE ACL::ownerHandle;
ACL::ACL(Access &acc, const AclEntryInfo &info, Allocator &alloc)
: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive)
{
parse(info.proto().subject());
const AclEntryPrototype &proto = info.proto();
mAuthorizations = proto.authorization();
mDelegate = proto.delegate();
mEntryTag = proto.s_tag();
mCssmHandle = info.handle();
}
ACL::ACL(Access &acc, const AclOwnerPrototype &owner, Allocator &alloc)
: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive)
{
parse(owner.subject());
mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_CHANGE_ACL);
mDelegate = owner.delegate();
mEntryTag[0] = '\0';
mCssmHandle = ownerHandle;
}
ACL::ACL(Access &acc, Allocator &alloc)
: allocator(alloc), access(acc), mSubjectForm(NULL), mMutex(Mutex::recursive)
{
mState = inserted; mForm = allowAllForm; mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); mDelegate = false;
mPromptSelector = defaultSelector;
UniformRandomBlobs<DevRandomGenerator>().random(mCssmHandle);
}
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; mForm = appListForm;
mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); mDelegate = false;
mPromptDescription = description;
mPromptSelector = promptSelector;
UniformRandomBlobs<DevRandomGenerator>().random(mCssmHandle);
}
ACL::~ACL()
{
chunkFree(mSubjectForm, allocator);
}
bool ACL::authorizes(AclAuthorization right)
{
StLock<Mutex>_(mMutex);
return mAuthorizations.find(right) != mAuthorizations.end()
|| mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end()
|| mAuthorizations.empty();
}
void ACL::addApplication(TrustedApplication *app)
{
StLock<Mutex>_(mMutex);
switch (mForm) {
case appListForm: mAppList.push_back(app);
modify();
break;
case allowAllForm: if (!mPromptDescription.empty()) {
mAppList.push_back(app);
modify();
break;
}
default:
MacOSError::throwMe(errSecACLNotSimple);
}
}
void ACL::modify()
{
StLock<Mutex>_(mMutex);
if (mState == unchanged) {
secdebug("SecAccess", "ACL %p marked modified", this);
mState = modified;
}
}
void ACL::remove()
{
StLock<Mutex>_(mMutex);
mAppList.clear();
mForm = invalidForm;
mState = deleted;
}
void ACL::copyAclEntry(AclEntryPrototype &proto, Allocator &alloc)
{
StLock<Mutex>_(mMutex);
proto.clearPod();
makeSubject();
assert(mSubjectForm);
proto = AclEntryPrototype(*mSubjectForm, mDelegate); ChunkCopyWalker w(alloc);
walk(w, proto.subject());
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); ChunkCopyWalker w(alloc);
walk(w, proto.subject()); }
void ACL::setAccess(AclBearer &target, bool update,
const AccessCredentials *cred)
{
StLock<Mutex>_(mMutex);
State action = state();
if (!update)
action = (action == deleted) ? unchanged : inserted;
if (isOwner()) {
switch (action) {
case unchanged:
secdebug("SecAccess", "ACL %p owner unchanged", this);
return;
case inserted: 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;
}
}
switch (action) {
case unchanged: secdebug("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle());
return;
case deleted: secdebug("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle());
target.deleteAcl(entryHandle(), cred);
return;
default:
break;
}
makeSubject();
assert(mSubjectForm);
AclEntryPrototype proto(*mSubjectForm, mDelegate);
proto.tag(mEntryTag);
AutoAuthorizationGroup tags(mAuthorizations, allocator);
proto.authorization() = tags;
AclEntryInput input(proto);
switch (action) {
case inserted: secdebug("SecAccess", "ACL %p inserted", this);
target.addAcl(input, cred);
break;
case modified: secdebug("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle());
target.changeAcl(entryHandle(), input, cred);
break;
default:
assert(false);
}
}
void ACL::parse(const TypedList &subject)
{
StLock<Mutex>_(mMutex);
try {
switch (subject.type()) {
case CSSM_ACL_SUBJECT_TYPE_ANY:
mForm = allowAllForm;
return;
case CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT:
parsePrompt(subject);
mForm = appListForm;
return;
case CSSM_ACL_SUBJECT_TYPE_THRESHOLD:
{
if (subject[1] != 1)
throw ParseError();
uint32 count = subject[2];
TypedList &end = subject[count + 2]; if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT)
throw ParseError(); parsePrompt(end);
TypedList &first = subject[3];
if (first.type() == CSSM_ACL_SUBJECT_TYPE_ANY) {
mForm = allowAllForm;
return;
}
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();
}
void ACL::makeSubject()
{
StLock<Mutex>_(mMutex);
switch (form()) {
case allowAllForm:
chunkFree(mSubjectForm, allocator); if (mPromptDescription.empty()) {
mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY);
} else {
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: {
chunkFree(mSubjectForm, allocator); uint32 appCount = 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); return;
default:
assert(false); }
}