#include "agentquery.h"
#include "authority.h"
#include "ccaudit_extensions.h"
#include <Security/AuthorizationTags.h>
#include <Security/AuthorizationTagsPriv.h>
#include <Security/checkpw.h>
#include <Security/Security.h>
#include <System/sys/fileport.h>
#include <bsm/audit.h>
#include <bsm/audit_uevents.h> // AUE_ssauthint
#include <membership.h>
#include <membershipPriv.h>
#include <security_utilities/logging.h>
#include <security_utilities/mach++.h>
#include <stdlib.h>
#include <xpc/xpc.h>
#include <xpc/private.h>
#include "securityd_service/securityd_service/securityd_service_client.h"
#define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agent"
#define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE "com.apple.security.agent.login"
#define AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE "com.apple.security.authhost"
#define AUTH_XPC_ITEM_NAME "_item_name"
#define AUTH_XPC_ITEM_FLAGS "_item_flags"
#define AUTH_XPC_ITEM_VALUE "_item_value"
#define AUTH_XPC_ITEM_TYPE "_item_type"
#define AUTH_XPC_REQUEST_METHOD_KEY "_agent_request_key"
#define AUTH_XPC_REQUEST_METHOD_CREATE "_agent_request_create"
#define AUTH_XPC_REQUEST_METHOD_INVOKE "_agent_request_invoke"
#define AUTH_XPC_REQUEST_METHOD_DEACTIVATE "_agent_request_deactivate"
#define AUTH_XPC_REQUEST_METHOD_DESTROY "_agent_request_destroy"
#define AUTH_XPC_REPLY_METHOD_KEY "_agent_reply_key"
#define AUTH_XPC_REPLY_METHOD_RESULT "_agent_reply_result"
#define AUTH_XPC_REPLY_METHOD_INTERRUPT "_agent_reply_interrupt"
#define AUTH_XPC_REPLY_METHOD_CREATE "_agent_reply_create"
#define AUTH_XPC_REPLY_METHOD_DEACTIVATE "_agent_reply_deactivate"
#define AUTH_XPC_PLUGIN_NAME "_agent_plugin"
#define AUTH_XPC_MECHANISM_NAME "_agent_mechanism"
#define AUTH_XPC_HINTS_NAME "_agent_hints"
#define AUTH_XPC_CONTEXT_NAME "_agent_context"
#define AUTH_XPC_IMMUTABLE_HINTS_NAME "_agent_immutable_hints"
#define AUTH_XPC_REQUEST_INSTANCE "_agent_instance"
#define AUTH_XPC_REPLY_RESULT_VALUE "_agent_reply_result_value"
#define AUTH_XPC_AUDIT_SESSION_PORT "_agent_audit_session_port"
#define AUTH_XPC_BOOTSTRAP_PORT "_agent_bootstrap_port"
#define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
{ 0,0,0,0, 0,0,0,0, 0,0,0,0, (unsigned char)((0xff000000 & (sessionid))>>24), (unsigned char)((0x00ff0000 & (sessionid))>>16), (unsigned char)((0x0000ff00 & (sessionid))>>8), (unsigned char)((0x000000ff & (sessionid))) }
#if defined(NOSA)
#include <cstdarg>
static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
memset(buffer, 0, bufferSize);
const char *nosa = getenv("NOSA");
if (!strcmp(nosa, "-")) {
if (fgets(buffer, bufferSize-1, stdin) == NULL)
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
buffer[strlen(buffer)-1] = '\0'; if (!isatty(fileno(stdin)))
printf("%s\n", buffer); } else {
strncpy(buffer, nosa, bufferSize-1);
printf("%s\n", buffer);
}
if (buffer[0] == '\0') CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
}
#endif // NOSA
SecurityAgentXPCConnection::SecurityAgentXPCConnection(const AuthHostType type, Session &session)
: mAuthHostType(type),
mHostInstance(session.authhost(mAuthHostType)),
mSession(session),
mConnection(&Server::connection()),
mAuditToken(Server::connection().auditToken())
{
Server::active().longTermActivity();
secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
mXPCConnection = NULL;
mNobodyUID = -2;
struct passwd *pw = getpwnam("nobody");
if (NULL != pw) {
mNobodyUID = pw->pw_uid;
}
}
SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
{
secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
mConnection->useAgent(NULL);
if (NULL != mXPCConnection) {
xpc_connection_cancel(mXPCConnection);
xpc_release(mXPCConnection);
mXPCConnection = NULL;
}
}
bool SecurityAgentXPCConnection::inDarkWake()
{
return mSession.server().inDarkWake();
}
void
SecurityAgentXPCConnection::activate(bool ignoreUid)
{
secdebug("SecurityAgentConnection", "activate(%p)", this);
mConnection->useAgent(this);
if (mXPCConnection != NULL) {
return;
}
try {
uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId());
if (mAuthHostType == securityAgent) {
if (!(mSession.attributes() & sessionHasGraphicAccess))
CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
if (inDarkWake())
CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
uid_t targetUid = mHostInstance->session().originatorUid();
secdebug("SecurityAgentXPCConnection","Retrieved UID %d for this session", targetUid);
if (!ignoreUid && targetUid != 0 && targetUid != mNobodyUID) {
mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL, 0);
xpc_connection_set_target_uid(mXPCConnection, targetUid);
secdebug("SecurityAgentXPCConnection", "Creating a standard security agent");
} else {
mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE, NULL, 0);
xpc_connection_set_instance(mXPCConnection, sessionUUID);
secdebug("SecurityAgentXPCConnection", "Creating a loginwindow security agent");
}
} else {
mXPCConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0);
xpc_connection_set_instance(mXPCConnection, sessionUUID);
secdebug("SecurityAgentXPCConnection", "Creating a standard authhost");
}
xpc_connection_set_event_handler(mXPCConnection, ^(xpc_object_t object) {
if (xpc_get_type(object) == XPC_TYPE_ERROR) {
secdebug("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
}
});
xpc_connection_resume(mXPCConnection);
secdebug("SecurityAgentXPCConnection", "%p activated", this);
}
catch (MacOSError &err) {
mConnection->useAgent(NULL); Syslog::error("SecurityAgentConnection: error activating %s instance %p",
mAuthHostType == privilegedAuthHost
? "authorizationhost"
: "SecurityAgent", this);
throw;
}
secdebug("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
}
void
SecurityAgentXPCConnection::reconnect()
{
}
void
SecurityAgentXPCConnection::terminate()
{
activate(false);
mConnection->useAgent(NULL);
}
using SecurityAgent::Reason;
using namespace Authorization;
ModuleNexus<RecursiveMutex> gAllXPCClientsMutex;
ModuleNexus<set<SecurityAgentXPCQuery*> > allXPCClients;
void
SecurityAgentXPCQuery::killAllXPCClients()
{
StLock<Mutex> _(gAllXPCClientsMutex());
set<SecurityAgentXPCQuery*>::iterator clientIterator = allXPCClients().begin();
while (clientIterator != allXPCClients().end())
{
set<SecurityAgentXPCQuery*>::iterator thisClient = clientIterator++;
if ((*thisClient)->getTerminateOnSleep())
{
(*thisClient)->terminate();
}
}
}
SecurityAgentXPCQuery::SecurityAgentXPCQuery(const AuthHostType type, Session &session)
: SecurityAgentXPCConnection(type, session), mAgentConnected(false), mTerminateOnSleep(false)
{
secdebug("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
}
SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
{
secdebug("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
if (mAgentConnected) {
this->disconnect();
}
}
void
SecurityAgentXPCQuery::inferHints(Process &thisProcess)
{
string guestPath;
if (SecCodeRef clientCode = thisProcess.currentGuest())
guestPath = codePath(clientCode);
AuthItemSet clientHints;
SecurityAgent::RequestorType type = SecurityAgent::bundle;
pid_t clientPid = thisProcess.pid();
uid_t clientUid = thisProcess.uid();
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(guestPath)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
mClientHints.insert(clientHints.begin(), clientHints.end());
bool validSignature = thisProcess.checkAppleSigned();
AuthItemSet clientImmutableHints;
clientImmutableHints.insert(AuthItemRef(AGENT_HINT_PROCESS_SIGNED, AuthValueOverlay(sizeof(validSignature), &validSignature)));
mImmutableHints.insert(clientImmutableHints.begin(), clientImmutableHints.end());
}
void SecurityAgentXPCQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
{
AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
mClientHints.insert(AuthItemRef(item));
}
void
SecurityAgentXPCQuery::readChoice()
{
allow = false;
remember = false;
AuthItem *allowAction = mOutContext.find(AGENT_CONTEXT_ALLOW);
if (allowAction)
{
string allowString;
if (allowAction->getString(allowString)
&& (allowString == "YES"))
allow = true;
}
AuthItem *rememberAction = mOutContext.find(AGENT_CONTEXT_REMEMBER_ACTION);
if (rememberAction)
{
string rememberString;
if (rememberAction->getString(rememberString)
&& (rememberString == "YES"))
remember = true;
}
}
void
SecurityAgentXPCQuery::disconnect()
{
if (NULL != mXPCConnection) {
xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
xpc_connection_send_message(mXPCConnection, requestObject);
xpc_release(requestObject);
}
StLock<Mutex> _(gAllXPCClientsMutex());
allXPCClients().erase(this);
}
void
SecurityAgentXPCQuery::terminate()
{
this->disconnect();
}
static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {
setToBuild->clear();
xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {
const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);
size_t length;
const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);
void *dataCopy = malloc(length);
memcpy(dataCopy, data, length);
uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);
AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);
setToBuild->insert(nextItem);
memset(dataCopy, 0, length); free(dataCopy);
return true;
});
}
void
SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId)
{
bool ignoreUid = false;
do {
activate(ignoreUid);
mAgentConnected = false;
xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, pluginId);
xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanismId);
uid_t targetUid = Server::process().uid();
bool doSwitchAudit = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
bool doSwitchBootstrap = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
if (doSwitchAudit) {
mach_port_name_t jobPort;
if (0 == audit_session_port(mSession.sessionId(), &jobPort)) {
secdebug("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid);
xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
if (mach_port_mod_refs(mach_task_self(), jobPort, MACH_PORT_RIGHT_SEND, -1) != KERN_SUCCESS) {
secdebug("SecurityAgentXPCQuery", "unable to release send right for audit session, leaking");
}
}
}
if (doSwitchBootstrap) {
secdebug("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid);
MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, processBootstrap);
}
xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
uint64_t status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
if (status == kAuthorizationResultAllow) {
mAgentConnected = true;
} else {
secdebug("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
MacOSError::throwMe(errAuthorizationInternal);
}
}
} else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
if (XPC_ERROR_CONNECTION_INVALID == object) {
if (ignoreUid) {
secdebug("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
xpc_release(object);
MacOSError::throwMe(errAuthorizationInternal);
} else {
secdebug("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
ignoreUid = true;
xpc_release(mXPCConnection);
mXPCConnection = NULL;
}
} else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
}
}
xpc_release(object);
xpc_release(requestObject);
} while (!mAgentConnected);
StLock<Mutex> _(gAllXPCClientsMutex());
allXPCClients().insert(this);
}
static xpc_object_t authItemSetToXPCArray(AuthItemSet input) {
xpc_object_t outputArray = xpc_array_create(NULL, 0);
for (AuthItemSet::iterator i = input.begin(); i != input.end(); i++) {
AuthItemRef item = *i;
xpc_object_t xpc_data = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(xpc_data, AUTH_XPC_ITEM_NAME, item->name());
AuthorizationValue value = item->value();
if (value.data != NULL) {
xpc_dictionary_set_data(xpc_data, AUTH_XPC_ITEM_VALUE, value.data, value.length);
}
xpc_dictionary_set_uint64(xpc_data, AUTH_XPC_ITEM_FLAGS, item->flags());
xpc_array_append_value(outputArray, xpc_data);
xpc_release(xpc_data);
}
return outputArray;
}
OSStatus
SecurityAgentXPCQuery::invoke() {
__block OSStatus status = kAuthorizationResultUndefined;
xpc_object_t hintsArray = authItemSetToXPCArray(mInHints);
xpc_object_t contextArray = authItemSetToXPCArray(mInContext);
xpc_object_t immutableHintsArray = authItemSetToXPCArray(mImmutableHints);
xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
AuthItemSet tempHints, tempContext;
xpcArrayToAuthItemSet(&tempHints, xpcHints);
xpcArrayToAuthItemSet(&tempContext, xpcContext);
mOutHints = tempHints;
mOutContext = tempContext;
mLastResult = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
}
} else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
if (XPC_ERROR_CONNECTION_INVALID == object) {
} else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
}
}
xpc_release(object);
xpc_release(hintsArray);
xpc_release(contextArray);
xpc_release(immutableHintsArray);
xpc_release(requestObject);
return status;
}
void SecurityAgentXPCQuery::checkResult()
{
switch (mLastResult) {
case kAuthorizationResultAllow: return;
case kAuthorizationResultDeny:
case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
default: MacOSError::throwMe(errAuthorizationInternal);
}
}
QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
: mPassphraseCheck(NULL)
{
if (needPass)
mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
setTerminateOnSleep(true);
}
Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
{
Reason reason = SecurityAgent::noReason;
int retryCount = 0;
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
#if defined(NOSA)
if (getenv("NOSA")) {
char answer[maxPassphraseLength+10];
string applicationPath;
AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
if (applicationPathItem)
applicationPathItem->getString(applicationPath);
getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
(database ? database : "[NULL database]"),
mPassphraseCheck ? ":passphrase" : "");
if (mPassphraseCheck && !strchr(answer, ':')) {
memmove(answer+2, answer, strlen(answer)+1);
memcpy(answer, "y:", 2);
}
allow = answer[0] == 'y';
remember = answer[1] == 'g';
return SecurityAgent::noReason;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
if (mPassphraseCheck)
{
create("builtin", "confirm-access-password");
CssmAutoData data(Allocator::standard(Allocator::sensitive));
do
{
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
hints.erase(triesHint); hints.insert(triesHint);
if (retryCount++ > kMaximumAuthorizationTries)
{
reason = SecurityAgent::tooManyTries;
}
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
if (retryCount > kMaximumAuthorizationTries)
{
return reason;
}
checkResult();
AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
if (!passwordItem)
continue;
passwordItem->getCssmData(data);
}
while ((reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase)));
}
else
{
create("builtin", "confirm-access");
setInput(hints, context);
invoke();
}
readChoice();
return reason;
}
bool QueryCodeCheck::operator () (const char *aclPath)
{
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
#if defined(NOSA)
if (getenv("NOSA")) {
char answer[10];
string applicationPath;
AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
if (applicationPathItem)
applicationPathItem->getString(applicationPath);
getNoSA(answer, sizeof(answer),
"Allow %s to match an ACL for %s [yn][g]? ",
applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
allow = answer[0] == 'y';
remember = answer[1] == 'g';
return;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay((uint32_t)strlen(aclPath), const_cast<char*>(aclPath))));
create("builtin", "code-identity");
setInput(hints, context);
status = invoke();
checkResult();
return kAuthorizationResultAllow == mLastResult;
}
Reason QueryOld::query()
{
Reason reason = SecurityAgent::noReason;
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
int retryCount = 0;
#if defined(NOSA)
if (getenv("NOSA")) {
char passphrase_[maxPassphraseLength];
getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
passphrase.copy(passphrase_, strlen(passphrase_));
return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
}
#endif
const char *keychainPath = database.dbName();
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
hints.insert(mClientHints.begin(), mClientHints.end());
create("builtin", "unlock-keychain");
do
{
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
hints.erase(triesHint); hints.insert(triesHint);
++retryCount;
if (retryCount > maxTries)
{
reason = SecurityAgent::tooManyTries;
}
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
if (retryCount > maxTries)
{
return reason;
}
checkResult();
AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
if (!passwordItem)
continue;
passwordItem->getCssmData(passphrase);
}
while ((reason = accept(passphrase)));
return SecurityAgent::noReason;
}
Reason QueryOld::operator () ()
{
return query();
}
Reason QueryUnlock::accept(CssmManagedData &passphrase)
{
if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
return SecurityAgent::noReason;
else
return SecurityAgent::invalidPassphrase;
}
Reason QueryUnlock::retrievePassword(CssmOwnedData &passphrase) {
CssmAutoData pass(Allocator::standard(Allocator::sensitive));
AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
if (!passwordItem)
return SecurityAgent::invalidPassphrase;
passwordItem->getCssmData(pass);
passphrase = pass;
return SecurityAgent::noReason;
}
QueryKeybagPassphrase::QueryKeybagPassphrase(Session & session, int32_t tries) : mSession(session), mContext(), mRetries(tries)
{
setTerminateOnSleep(true);
mContext = mSession.get_current_service_context();
}
Reason QueryKeybagPassphrase::query()
{
Reason reason = SecurityAgent::noReason;
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
int retryCount = 0;
const char *keychainPath = "iCloud";
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
hints.insert(mClientHints.begin(), mClientHints.end());
create("builtin", "unlock-keychain");
int currentTry = 0;
do
{
currentTry = retryCount;
if (retryCount > mRetries)
{
return SecurityAgent::tooManyTries;
}
retryCount++;
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), ¤tTry));
hints.erase(triesHint); hints.insert(triesHint);
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
checkResult();
AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
if (!passwordItem)
continue;
passwordItem->getCssmData(passphrase);
}
while ((reason = accept(passphrase)));
return SecurityAgent::noReason;
}
Reason QueryKeybagPassphrase::accept(Security::CssmManagedData & password)
{
if (service_client_kb_unlock(&mContext, password.data(), (int)password.length()) == 0) {
mSession.keybagSetState(session_keybag_unlocked);
return SecurityAgent::noReason;
} else
return SecurityAgent::invalidPassphrase;
}
QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session & session) : QueryKeybagPassphrase(session) {}
Reason QueryKeybagNewPassphrase::query(CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
{
CssmAutoData pass(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
Reason reason = SecurityAgent::noReason;
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
int retryCount = 0;
const char *keychainPath = "iCloud";
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
const char *showResetString = "YES";
hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast<char*>(showResetString))));
hints.insert(mClientHints.begin(), mClientHints.end());
create("builtin", "change-passphrase");
int currentTry = 0;
AuthItem *resetPassword = NULL;
do
{
currentTry = retryCount;
if (retryCount > mRetries)
{
return SecurityAgent::tooManyTries;
}
retryCount++;
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), ¤tTry));
hints.erase(triesHint); hints.insert(triesHint);
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
checkResult();
resetPassword = mOutContext.find(AGENT_CONTEXT_RESET_PASSWORD);
if (resetPassword != NULL) {
return SecurityAgent::resettingPassword;
}
AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
if (!oldPasswordItem)
continue;
oldPasswordItem->getCssmData(oldPass);
}
while ((reason = accept(oldPass)));
if (reason == SecurityAgent::noReason) {
AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
if (!passwordItem)
return SecurityAgent::invalidPassphrase;
passwordItem->getCssmData(pass);
oldPassphrase = oldPass;
passphrase = pass;
}
return SecurityAgent::noReason;
}
QueryPIN::QueryPIN(Database &db)
: QueryOld(db), mPin(Allocator::standard())
{
this->inferHints(Server::process());
}
Reason QueryPIN::accept(CssmManagedData &pin)
{
mPin = pin;
return SecurityAgent::noReason;
}
Reason QueryNewPassphrase::query()
{
Reason reason = initialReason;
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
OSStatus status;
AuthValueVector arguments;
AuthItemSet hints, context;
int retryCount = 0;
#if defined(NOSA)
if (getenv("NOSA")) {
char passphrase_[maxPassphraseLength];
getNoSA(passphrase_, maxPassphraseLength,
"New passphrase for %s (reason %d) [<CR> to cancel]: ",
database.dbName(), reason);
return SecurityAgent::noReason;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
switch (initialReason)
{
case SecurityAgent::newDatabase:
create("builtin", "new-passphrase");
break;
case SecurityAgent::changePassphrase:
create("builtin", "change-passphrase");
break;
default:
assert(false);
}
do
{
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
hints.erase(triesHint); hints.insert(triesHint);
if (++retryCount > maxTries)
{
reason = SecurityAgent::tooManyTries;
}
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
if (retryCount > maxTries)
{
return reason;
}
checkResult();
if (SecurityAgent::changePassphrase == initialReason)
{
AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
if (!oldPasswordItem)
continue;
oldPasswordItem->getCssmData(oldPassphrase);
}
AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
if (!passwordItem)
continue;
passwordItem->getCssmData(passphrase);
}
while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)));
return SecurityAgent::noReason;
}
Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
{
if (Reason result = query())
return result; passphrase = mPassphrase;
oldPassphrase = mOldPassphrase;
return SecurityAgent::noReason; }
Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
{
if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
return SecurityAgent::oldPassphraseWrong;
if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
mPassphrase = passphrase;
if (oldPassphrase) mOldPassphrase = *oldPassphrase;
mPassphraseValid = true;
if (mPassphrase.length() == 0)
return SecurityAgent::passphraseIsNull;
if (mPassphrase.length() < 6)
return SecurityAgent::passphraseTooSimple;
}
return SecurityAgent::noReason;
}
Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
string &passphrase)
{
return query(prompt, verify, passphrase);
}
Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
string &passphrase)
{
Reason reason = SecurityAgent::noReason;
OSStatus status; AuthValueVector arguments;
AuthItemSet hints, context;
#if defined(NOSA)
if (getenv("NOSA")) {
return SecurityAgent::noReason;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
if (false == verify) { create("builtin", "generic-unlock");
} else { create("builtin", "generic-new-passphrase");
}
AuthItem *passwordItem;
do {
setInput(hints, context);
status = invoke();
checkResult();
passwordItem = mOutContext.find(AGENT_PASSWORD);
} while (!passwordItem);
passwordItem->getString(passphrase);
return reason;
}
Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
{
return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
}
Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
{
Reason reason = SecurityAgent::noReason;
CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
OSStatus status; AuthValueVector arguments;
AuthItemSet hints, context;
#if defined(NOSA)
if (getenv("NOSA")) {
return SecurityAgent::noReason;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
create("builtin", "generic-unlock-kcblob");
AuthItem *secretItem;
int retryCount = 0;
do {
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
hints.erase(triesHint); hints.insert(triesHint);
if (++retryCount > maxTries)
{
reason = SecurityAgent::tooManyTries;
}
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
status = invoke();
checkResult();
secretItem = mOutContext.find(AGENT_PASSWORD);
if (!secretItem)
continue;
secretItem->getCssmData(passphrase);
} while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated)));
return reason;
}
Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
{
DbHandle *currHdl = dbHandlesToAuthenticate;
short index;
Boolean authenticated = false;
for (index=0; index < dbHandleCount && !authenticated; index++)
{
try
{
RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
dbToUnlock->unlockDb(passphrase);
authenticated = true;
*dbHandleAuthenticated = *currHdl; }
catch (const CommonError &err)
{
currHdl++; }
}
if ( !authenticated )
return SecurityAgent::invalidPassphrase;
return SecurityAgent::noReason;
}
Reason
QueryKeychainAuth::operator () (const char *database, const char *description, AclAuthorization action, const char *prompt)
{
Reason reason = SecurityAgent::noReason;
AuthItemSet hints, context;
AuthValueVector arguments;
int retryCount = 0;
string username;
string password;
using CommonCriteria::Securityd::KeychainAuthLogger;
KeychainAuthLogger logger(mAuditToken, AUE_ssauthint, database, description);
#if defined(NOSA)
if (getenv("NOSA")) {
char answer[maxPassphraseLength+10];
string applicationPath;
AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
if (applicationPathItem)
applicationPathItem->getString(applicationPath);
getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
(database ? database : "[NULL database]"),
mPassphraseCheck ? ":passphrase" : "");
if (mPassphraseCheck && !strchr(answer, ':')) {
memmove(answer+2, answer, strlen(answer)+1);
memcpy(answer, "y:", 2);
}
allow = answer[0] == 'y';
remember = answer[1] == 'g';
return SecurityAgent::noReason;
}
#endif
hints.insert(mClientHints.begin(), mClientHints.end());
hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast<char*>(prompt))));
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
create("builtin", "confirm-access-user-password");
AuthItem *usernameItem;
AuthItem *passwordItem;
do {
AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
hints.erase(triesHint); hints.insert(triesHint);
if (++retryCount > maxTries)
reason = SecurityAgent::tooManyTries;
if (SecurityAgent::noReason != reason)
{
if (SecurityAgent::tooManyTries == reason)
logger.logFailure(NULL, CommonCriteria::errTooManyTries);
else
logger.logFailure();
}
AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
hints.erase(retryHint); hints.insert(retryHint);
setInput(hints, context);
try
{
invoke();
checkResult();
}
catch (...) {
logger.logFailure();
throw;
}
usernameItem = mOutContext.find(AGENT_USERNAME);
passwordItem = mOutContext.find(AGENT_PASSWORD);
if (!usernameItem || !passwordItem)
continue;
usernameItem->getString(username);
passwordItem->getString(password);
} while ((reason = accept(username, password)));
if (SecurityAgent::noReason == reason)
logger.logSuccess();
return reason;
}
Reason
QueryKeychainAuth::accept(string &username, string &passphrase)
{
const char *user = username.c_str();
const char *passwd = passphrase.c_str();
int checkpw_status = checkpw(user, passwd);
if (checkpw_status != CHECKPW_SUCCESS) {
return SecurityAgent::invalidPassphrase;
}
const char *group = "admin";
if (group) {
int rc, ismember;
uuid_t group_uuid, user_uuid;
rc = mbr_group_name_to_uuid(group, group_uuid);
if (rc) { return SecurityAgent::userNotInGroup; }
rc = mbr_user_name_to_uuid(user, user_uuid);
if (rc) { return SecurityAgent::userNotInGroup; }
rc = mbr_check_membership(user_uuid, group_uuid, &ismember);
if (rc || !ismember) { return SecurityAgent::userNotInGroup; }
}
return SecurityAgent::noReason;
}