#include <Security/ACL.h>
#include <Security/SecCFTypes.h>
#include <Security/osxsigning.h>
#include <Security/osxsigner.h>
#include <Security/trackingallocator.h>
#include <Security/TrustedApplication.h>
#include <Security/SecTrustedApplication.h>
#include <Security/devrandom.h>
#include <Security/uniformrandom.h>
#include "keychainacl.h"
#include <memory>
using namespace KeychainCore;
const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = {
CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0
};
ACL::ACL(Access &acc, const AclEntryInfo &info, CssmAllocator &alloc)
: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL)
{
parse(info.proto().subject());
const AclEntryPrototype &proto = info.proto();
mAuthorizations = proto.authorization();
mDelegate = proto.delegate();
mEntryTag = proto.tag();
mCssmHandle = info.handle();
}
ACL::ACL(Access &acc, const AclOwnerPrototype &owner, CssmAllocator &alloc)
: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL)
{
parse(owner.subject());
mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_CHANGE_ACL);
mDelegate = owner.delegate();
mEntryTag[0] = '\0';
mCssmHandle = ownerHandle;
}
ACL::ACL(Access &acc, CssmAllocator &alloc)
: allocator(alloc), access(acc), mSubjectForm(NULL)
{
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,
CssmAllocator &alloc)
: allocator(alloc), access(acc), mSubjectForm(NULL)
{
mState = inserted; mForm = appListForm;
mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY); mDelegate = false;
mPromptDescription = description;
mPromptSelector = promptSelector;
UniformRandomBlobs<DevRandomGenerator>().random(mCssmHandle);
}
ACL::~ACL()
{
}
bool ACL::authorizes(AclAuthorization right) const
{
return mAuthorizations.find(right) != mAuthorizations.end()
||
mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end();
}
void ACL::addApplication(TrustedApplication *app)
{
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()
{
if (mState == unchanged) {
debug("SecAccess", "ACL %p marked modified", this);
mState = modified;
}
}
void ACL::remove()
{
mAppList.clear();
mForm = invalidForm;
mState = deleted;
}
void ACL::setAccess(AclBearer &target, bool update,
const AccessCredentials *cred)
{
State action = state();
if (!update)
action = (action == deleted) ? unchanged : inserted;
if (isOwner()) {
switch (action) {
case unchanged:
debug("SecAccess", "ACL %p owner unchanged", this);
return;
case inserted: case modified:
{
debug("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: debug("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle());
return;
case deleted: debug("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle());
target.deleteAcl(entryHandle(), cred);
return;
default:
break;
}
makeSubject();
assert(mSubjectForm);
AclEntryPrototype proto(*mSubjectForm, mDelegate);
assert(mEntryTag.size() <= CSSM_MODULE_STRING_SIZE); strcpy(proto.tag(), mEntryTag.c_str());
AutoAuthorizationGroup tags(mAuthorizations, allocator);
proto.authorization() = tags;
AclEntryInput input(proto);
switch (action) {
case inserted: debug("SecAccess", "ACL %p inserted", this);
target.addAcl(input, cred);
break;
case modified: debug("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle());
target.changeAcl(entryHandle(), input, cred);
break;
default:
assert(false);
}
}
void ACL::parse(const TypedList &subject)
{
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];
const TypedList &end = subject[count + 2]; if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT)
throw ParseError(); parsePrompt(end);
const 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(subject[n + 3]));
}
mForm = appListForm;
return;
default:
mForm = customForm;
return;
}
} catch (const ParseError &) {
debug("SecAccess", "acl compile failed; marking custom");
mForm = customForm;
mAppList.clear();
}
}
void ACL::parsePrompt(const TypedList &subject)
{
assert(subject.length() == 3);
mPromptSelector = *subject[1].data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>();
mPromptDescription = subject[2].toString();
}
void ACL::makeSubject()
{
chunkFree(mSubjectForm, allocator);
switch (form()) {
case allowAllForm:
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: {
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(false); default:
assert(false); }
}