#include "acl_keychain.h"
#include "agentquery.h"
#include "acls.h"
#include "connection.h"
#include "database.h"
#include "server.h"
#include <security_utilities/debugging.h>
#include <security_utilities/logging.h>
#include <security_cdsa_utilities/osxverifier.h>
#include <algorithm>
#include <sys/csr.h>
#include <Security/AuthorizationTagsPriv.h>
#define ACCEPT_LEGACY_FORM 1
uint32_t KeychainPromptAclSubject::promptsValidated = 0;
CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = {
CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0 };
bool KeychainPromptAclSubject::validates(const AclValidationContext &ctx) const
{
Process &process = Server::process();
if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
Syslog::info("bypassing keychain prompt for keychain migrator");
secnotice("kcacl", "bypassing keychain prompt for keychain migrator");
return true; }
promptsValidated++;
return SimpleAclSubject::validates(ctx);
}
bool KeychainPromptAclSubject::validates(const AclValidationContext &context,
const TypedList &sample) const
{
return validateExplicitly(context, ^{
if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
Process& process = Server::process();
StLock<Mutex> _(process);
RefPointer<AclSubject> subject = process.copyAclSubject();
if (SecurityServerAcl::addToStandardACL(context, subject)) {
if(env->database && env->database->dbVersion() >= CommonBlob::version_partition) {
env->acl.addClientPartitionID(process);
}
}
}
});
}
bool KeychainPromptAclSubject::validateExplicitly(const AclValidationContext &context, void (^alwaysAllow)()) const
{
if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
Process &process = Server::process();
secnotice("kcacl", "Keychain query for process %d (UID %d)", process.pid(), process.uid());
uint32_t mode = Maker::defaultMode;
const uint16_t &flags = selector.flags;
if (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT)
mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED);
if (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT)
mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID);
OSStatus validation = errSecCSStaticCodeNotFound;
{
StLock<Mutex> _(process);
Server::active().longTermActivity();
validation = process.checkValidity(kSecCSDefaultFlags, NULL);
switch (validation)
{
case noErr: {
secnotice("kcacl", "client is valid, proceeding");
if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) {
Syslog::info("bypassing keychain prompt for keychain migrator");
secnotice("kcacl", "bypassing keychain prompt for keychain migrator");
return true; }
}
break;
case errSecCSUnsigned:
{ if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) {
Syslog::info("supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid());
secnotice("kcacl", "supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid());
return false;
}
}
break;
case errSecCSSignatureFailed: case errSecCSGuestInvalid: case errSecCSStaticCodeNotFound: {
if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) {
secnotice("kcacl", "client is invalid, suppressing prompt");
Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid());
secnotice("kcacl", "suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid());
return false;
}
Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid());
secnotice("kcacl", "attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid());
}
break;
default: Syslog::info("suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation);
secnotice("kcacl", "suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation);
return false;
}
}
Syslog::info("displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid());
secnotice("kcacl", "displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid());
const Database *db = env->database;
bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE);
if (validation == noErr) {
StLock<Mutex> _(process);
CFRef<CFDictionaryRef> dict;
if (process.copySigningInfo(kSecCSDefaultFlags, &dict.aref()) == noErr)
if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList)))
needPassphrase |=
(CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL);
}
if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) {
QueryKeychainAuth query;
query.inferHints(Server::process());
if (query(db ? db->dbName() : NULL, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason)
return false;
return true;
} else {
QueryKeychainUse query(needPassphrase, db);
query.inferHints(Server::process());
query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation));
if (query.queryUser(db ? db->dbName() : NULL,
description.c_str(), context.authorization()) != SecurityAgent::noReason)
return false;
if (query.remember && validation != errSecCSStaticCodeNotFound) {
alwaysAllow();
}
return query.allow;
}
}
return false; }
CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const
{
return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
new(alloc) ListElement(alloc, CssmData::wrap(selector)),
new(alloc) ListElement(alloc, description));
}
bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const
{
return false;
}
uint32_t KeychainPromptAclSubject::Maker::defaultMode;
KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const
{
switch (list.length()) {
#if ACCEPT_LEGACY_FORM
case 2: {
ListElement *params[1];
crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM);
return new KeychainPromptAclSubject(*params[0], defaultSelector);
}
#endif //ACCEPT_LEGACY_FORM
case 3: {
ListElement *params[2];
crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM);
return new KeychainPromptAclSubject(*params[1],
*params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE));
}
default:
CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
}
KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version,
Reader &pub, Reader &) const
{
CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector;
const char *description;
switch (version) {
case pumaVersion:
selector = defaultSelector;
pub(description);
break;
case jaguarVersion:
pub(selector);
selector.version = n2h(selector.version);
selector.flags = n2h(selector.flags);
pub(description);
break;
default:
CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}
return new KeychainPromptAclSubject(description, selector);
}
KeychainPromptAclSubject::KeychainPromptAclSubject(string descr,
const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel)
: SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT),
selector(sel), description(descr)
{
if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION)
CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
version(currentVersion);
}
void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv)
{
if (version() != 0) {
selector.version = h2n (selector.version);
selector.flags = h2n (selector.flags);
pub(selector);
}
pub.insert(description.size() + 1);
}
void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv)
{
if (version() != 0) {
selector.version = h2n (selector.version);
selector.flags = h2n (selector.flags);
pub(selector);
}
pub(description.c_str());
}
#ifdef DEBUGDUMP
void KeychainPromptAclSubject::debugDump() const
{
Debug::dump("KeychainPrompt:%s(%s)",
description.c_str(),
(selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard");
}
#endif //DEBUGDUMP
uint32_t KeychainPromptAclSubject::getPromptAttempts() {
if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) {
return 0;
} else {
return KeychainPromptAclSubject::promptsValidated;
}
}
void KeychainPromptAclSubject::addPromptAttempt() {
promptsValidated++;
}