#include "acls.h"
#include "connection.h"
#include "server.h"
#include "agentquery.h"
#include "tokendatabase.h"
#include "acl_keychain.h"
#include "acl_partition.h"
#include <security_cdsa_utilities/acl_any.h>
#include <security_cdsa_utilities/acl_password.h>
#include "acl_keychain.h"
#include <sys/sysctl.h>
#include <security_utilities/logging.h>
#include <security_utilities/cfmunge.h>
SecurityServerAcl::~SecurityServerAcl()
{ }
void SecurityServerAcl::getOwner(AclOwnerPrototype &owner)
{
StLock<Mutex> _(aclSequence);
ObjectAcl::cssmGetOwner(owner);
}
void SecurityServerAcl::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
{
StLock<Mutex> _(aclSequence);
ObjectAcl::cssmGetAcl(tag, count, acls);
}
void SecurityServerAcl::changeAcl(const AclEdit &edit, const AccessCredentials *cred,
Database *db)
{
StLock<Mutex> _(aclSequence);
SecurityServerEnvironment env(*this, db);
if (const AclEntryInput* input = edit.newEntry()) {
if (input->proto().authorization().containsOnly(CSSM_ACL_AUTHORIZATION_INTEGRITY)) {
bool ui = (!!cred) && cred->authorizesUI();
validatePartition(env, ui);
env.forceSuccess = true;
}
}
if(db && db->checkCredentials(cred)) {
env.forceSuccess = true;
ObjectAcl::cssmChangeAcl(edit, cred, &env, NULL);
} else {
ObjectAcl::cssmChangeAcl(edit, cred, &env, CSSM_APPLE_ACL_TAG_PARTITION_ID);
}
}
void SecurityServerAcl::changeOwner(const AclOwnerPrototype &newOwner,
const AccessCredentials *cred, Database *db)
{
StLock<Mutex> _(aclSequence);
SecurityServerEnvironment env(*this, db);
ObjectAcl::cssmChangeOwner(newOwner, cred, &env);
}
void SecurityServerAcl::validate(AclAuthorization auth, const AccessCredentials *cred, Database *db)
{
SecurityServerEnvironment env(*this, db);
StLock<Mutex> objectSequence(aclSequence);
StLock<Mutex> processSequence(Server::process().aclSequence);
ObjectAcl::validate(auth, cred, &env);
bool ui = (!!cred) && cred->authorizesUI();
bool readOperation =
(auth == CSSM_ACL_AUTHORIZATION_CHANGE_ACL) ||
(auth == CSSM_ACL_AUTHORIZATION_DECRYPT) ||
(auth == CSSM_ACL_AUTHORIZATION_GENKEY) ||
(auth == CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED) ||
(auth == CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR) ||
(auth == CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED) ||
(auth == CSSM_ACL_AUTHORIZATION_IMPORT_CLEAR) ||
(auth == CSSM_ACL_AUTHORIZATION_SIGN) ||
(auth == CSSM_ACL_AUTHORIZATION_DECRYPT) ||
(auth == CSSM_ACL_AUTHORIZATION_MAC) ||
(auth == CSSM_ACL_AUTHORIZATION_DERIVE);
validatePartition(env, ui && readOperation);
}
void SecurityServerAcl::validate(AclAuthorization auth, const Context &context, Database *db)
{
validate(auth,
context.get<AccessCredentials>(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS), db);
}
void SecurityServerAcl::validatePartition(SecurityServerEnvironment& env, bool prompt)
{
Process &process = Server::process();
if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
secdebug("integrity", "bypassing partition check for keychain migrator");
return; }
if (CFRef<CFDictionaryRef> partition = this->createPartitionPayload()) {
CFArrayRef partitionList;
if (cfscan(partition, "{Partitions=%AO}", &partitionList)) {
CFRef<CFStringRef> partitionDebug = CFCopyDescription(partitionList); secdebugfunc("integrity", "ACL partitionID = %s", cfString(partitionDebug).c_str());
if (env.database) {
CFRef<CFStringRef> clientPartitionID = makeCFString(env.database->process().partitionId());
if (CFArrayContainsValue(partitionList, CFRangeMake(0, CFArrayGetCount(partitionList)), clientPartitionID)) {
secdebugfunc("integrity", "ACL partitions match: %s", cfString(clientPartitionID).c_str());
return;
} else {
secdebugfunc("integrity", "ACL partition mismatch: client %s ACL %s", cfString(clientPartitionID).c_str(), cfString(partitionDebug).c_str());
if (prompt && extendPartition(env))
return;
MacOSError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
}
}
secdebugfunc("integrity", "failed to parse partition payload");
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
} else {
if((!env.database) || env.database->dbVersion() < SecurityServer::CommonBlob::version_partition) {
secdebugfunc("integrity", "no partition ACL - legacy case");
} else {
secdebugfunc("integrity", "no partition ACL - adding");
env.acl.instantiateAcl();
this->createClientPartitionID(env.database->process());
env.acl.changedAcl();
Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
}
}
}
bool SecurityServerAcl::extendPartition(SecurityServerEnvironment& env)
{
KeychainPromptAclSubject *kcSubject = NULL;
SecurityServerAcl& acl = env.acl;
for (EntryMap::const_iterator it = acl.begin(); it != acl.end(); ++it) {
AclSubjectPointer subject = it->second.subject;
if (ThresholdAclSubject *threshold = dynamic_cast<ThresholdAclSubject *>(subject.get())) {
unsigned size = threshold->count();
if (KeychainPromptAclSubject* last = dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
kcSubject = last;
break;
}
}
}
if (kcSubject) {
BaseValidationContext ctx(NULL, CSSM_ACL_AUTHORIZATION_PARTITION_ID, &env);
return kcSubject->validateExplicitly(ctx, ^{
secdebugfunc("integrity", "adding partition to list");
env.acl.instantiateAcl();
this->addClientPartitionID(env.database->process());
env.acl.changedAcl();
Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
});
}
secdebugfunc("integrity", "failure extending partition");
return false;
}
PartitionAclSubject* SecurityServerAcl::findPartitionSubject()
{
pair<EntryMap::const_iterator, EntryMap::const_iterator> range;
switch (this->getRange(CSSM_APPLE_ACL_TAG_PARTITION_ID, range, true)) {
case 0:
secdebugfunc("integrity", "no partition tag on ACL");
return NULL;
default:
secdebugfunc("integrity", "multiple partition ACL entries");
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
case 1:
break;
}
const AclEntry& entry = range.first->second;
if (!entry.authorizes(CSSM_ACL_AUTHORIZATION_PARTITION_ID)) {
secdebugfunc("integrity", "partition entry does not authorize CSSM_ACL_AUTHORIZATION_PARTITION_ID");
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
if (PartitionAclSubject* partition = dynamic_cast<PartitionAclSubject*>(entry.subject.get())) {
return partition;
} else {
secdebugfunc("integrity", "partition entry is not PartitionAclSubject");
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
}
CFDictionaryRef SecurityServerAcl::createPartitionPayload()
{
if (PartitionAclSubject* subject = this->findPartitionSubject()) {
if (CFDictionaryRef result = subject->createDictionaryPayload()) {
return result;
} else {
secdebugfunc("integrity", "partition entry is malformed XML");
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
} else {
return NULL;
}
}
bool SecurityServerAcl::addToStandardACL(const AclValidationContext &context, AclSubject *subject)
{
if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>())
if (ThresholdAclSubject *threshold = env->standardSubject(context)) {
unsigned size = threshold->count();
if (dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
secdebug("acl", "adding new subject %p to from of threshold ACL", subject);
threshold->add(subject, 0);
context.acl()->changedAcl();
Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
return true;
}
}
secdebug("acl", "ACL is not standard form; cannot edit");
return false;
}
bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext &context)
{
static const char * const prototypicalDotMacPath[] = {
"/Applications/Mail.app",
"/Applications/Safari.app",
"/Applications/iSync.app",
"/Applications/System Preferences.app",
"/Applications/iCal.app",
"/Applications/iChat.app",
"/Applications/iTunes.app",
"/Applications/Address Book.app",
"/Applications/iSync.app",
NULL };
static const unsigned threshold = 6;
if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
if (ThresholdAclSubject *list = env->standardSubject(context)) {
unsigned count = list->count();
unsigned matches = 0;
for (unsigned n = 0; n < count; ++n) {
if (CodeSignatureAclSubject *app = dynamic_cast<CodeSignatureAclSubject *>(list->subject(n))) {
for (const char * const *p = prototypicalDotMacPath; *p; p++)
if (app->path() == *p)
matches++;
}
}
secdebug("codesign", "matched %d of %zd candididates (threshold=%d)",
matches, sizeof(prototypicalDotMacPath) / sizeof(char *) - 1, threshold);
return matches >= threshold;
}
}
return false;
}
bool SecurityServerAcl::createClientPartitionID(Process& process)
{
instantiateAcl();
std::string partitionID = process.partitionId();
CFTemp<CFDictionaryRef> payload("{Partitions=[%s]}", partitionID.c_str());
ObjectAcl::AclSubjectPointer subject = new PartitionAclSubject();
static_cast<PartitionAclSubject*>(subject.get())->setDictionaryPayload(Allocator::standard(), payload);
ObjectAcl::AclEntry partition(subject);
partition.addAuthorization(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
this->add(CSSM_APPLE_ACL_TAG_PARTITION_ID, partition);
secdebugfunc("integrity", "added partition %s to new key", partitionID.c_str());
return true;
}
bool SecurityServerAcl::addClientPartitionID(Process& process)
{
if (PartitionAclSubject* subject = this->findPartitionSubject()) {
std::string partitionID = process.partitionId();
if (CFRef<CFDictionaryRef> payload = subject->createDictionaryPayload()) {
CFArrayRef partitionList;
if (cfscan(payload, "{Partitions=%AO}", &partitionList)) {
CFTemp<CFDictionaryRef> newPayload("{Partitions=[+%O,%s]}", partitionList, partitionID.c_str());
subject->setDictionaryPayload(Allocator::standard(), newPayload);
}
return true;
} else {
MacOSError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
} else {
return createClientPartitionID(process);
}
}
Adornable &SecurityServerEnvironment::store(const AclSubject *subject)
{
switch (subject->type()) {
case CSSM_ACL_SUBJECT_TYPE_PREAUTH:
{
if (TokenDatabase *tokenDb = dynamic_cast<TokenDatabase *>(database))
return tokenDb->common().store();
}
break;
default:
break;
}
CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED);
}
uid_t SecurityServerEnvironment::getuid() const
{
return Server::process().uid();
}
gid_t SecurityServerEnvironment::getgid() const
{
return Server::process().gid();
}
pid_t SecurityServerEnvironment::getpid() const
{
return Server::process().pid();
}
bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier &verifier,
const AclValidationContext &context)
{
return Server::codeSignatures().verify(Server::process(), verifier, context);
}
bool SecurityServerEnvironment::getSecret(CssmOwnedData &secret, const CssmData &prompt) const
{
if (database) {
QueryPIN query(*database);
query.inferHints(Server::process());
if (!query()) { secret = query.pin();
return true;
}
}
return false;
}
bool SecurityServerEnvironment::validateSecret(const SecretAclSubject *me,
const AccessCredentials *cred)
{
return database && database->validateSecret(me, cred);
}
ObjectAcl *SecurityServerEnvironment::preAuthSource()
{
return database ? &database->acl() : NULL;
}
ThresholdAclSubject *SecurityServerEnvironment::standardSubject(const AclValidationContext &context)
{
return dynamic_cast<ThresholdAclSubject *>(context.subject());
}
AclSource::~AclSource()
{ }
SecurityServerAcl &AclSource::acl()
{
CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED);
}
Database *AclSource::relatedDatabase()
{
return NULL;
}