X-Git-Url: https://git.saurik.com/apple/securityd.git/blobdiff_plain/f7aa9f666a1c7ab343b4ce8f1677ea253c4e126e..4cd1cad0dea00daa03e1b54fdf2797a02373ad5b:/src/agentquery.cpp diff --git a/src/agentquery.cpp b/src/agentquery.cpp index 55a2129..c3df404 100644 --- a/src/agentquery.cpp +++ b/src/agentquery.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * Copyright (c) 2000-2004,2008-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -26,9 +26,56 @@ // #include "agentquery.h" #include "authority.h" +#include "ccaudit_extensions.h" #include #include +#include +#include +#include +#include +#include // AUE_ssauthint +#include +#include +#include +#include +#include +#include "securityd_service/securityd_service/securityd_service_client.h" + +#define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agentMain" +#define SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE "com.apple.security.agentStub" +#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 AUTH_XPC_SESSION_UUID "_agent_session_uuid" +#define AUTH_XPC_SESSION_PREFS "_agent_session_prefs" +#define AUTH_XPC_SESSION_INPUT_METHOD "_agent_session_inputMethod" + +#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))) } // // NOSA support functions. This is a test mode where the SecurityAgent @@ -67,53 +114,296 @@ static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...) #endif //NOSA -using SecurityAgent::Reason; -using namespace Authorization; +// SecurityAgentConnection -SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session) : mAuthHostType(type), mHostInstance(session.authhost(mAuthHostType)), mConnection(&Server::connection()) +SecurityAgentConnection::SecurityAgentConnection(const AuthHostType type, Session &session) +: mAuthHostType(type), +mHostInstance(session.authhost(mAuthHostType)), +mConnection(&Server::connection()), +mAuditToken(Server::connection().auditToken()) { // this may take a while Server::active().longTermActivity(); - secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this); + secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this); } -SecurityAgentQuery::~SecurityAgentQuery() +SecurityAgentConnection::~SecurityAgentConnection() { - secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this); + secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this); mConnection->useAgent(NULL); +} -#if defined(NOSA) - if (getenv("NOSA")) { - printf(" [query done]\n"); - return; +void +SecurityAgentConnection::activate() +{ + secdebug("SecurityAgentConnection", "activate(%p)", this); + + Session &session = mHostInstance->session(); + SessionId targetSessionId = session.sessionId(); + MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap(); + fileport_t userPrefsFP = MACH_PORT_NULL; + + // send the the userPrefs to SecurityAgent + if (mAuthHostType == securityAgent || mAuthHostType == userAuthHost) { + CFRef userPrefs(mHostInstance->session().copyUserPrefs()); + if (0 != userPrefs) + { + FILE *mbox = NULL; + int fd = 0; + mbox = tmpfile(); + if (NULL != mbox) + { + fd = dup(fileno(mbox)); + fclose(mbox); + if (fd != -1) + { + CFIndex length = CFDataGetLength(userPrefs); + if (write(fd, CFDataGetBytePtr(userPrefs), length) != length) + Syslog::error("could not write userPrefs"); + else + { + if (0 == fileport_makeport(fd, &userPrefsFP)) + secdebug("SecurityAgentConnection", "stashed the userPrefs file descriptor"); + else + Syslog::error("failed to stash the userPrefs file descriptor"); + } + close(fd); + } + } + } + if (MACH_PORT_NULL == userPrefsFP) + { + secdebug("SecurityAgentConnection", "could not read userPrefs"); + } + } + + mConnection->useAgent(this); + try + { + StLock _(*mHostInstance); + + mach_port_t lookupPort = mHostInstance->lookup(targetSessionId); + if (MACH_PORT_NULL == lookupPort) + { + Syslog::error("could not find real service, bailing"); + MacOSError::throwMe(CSSM_ERRCODE_SERVICE_NOT_AVAILABLE); + } + // reset Client contact info + mPort = lookupPort; + SecurityAgent::Client::activate(mPort); + + secdebug("SecurityAgentConnection", "%p activated", this); } -#endif + catch (MacOSError &err) + { + mConnection->useAgent(NULL); // guess not + Syslog::error("SecurityAgentConnection: error activating %s instance %p", + mAuthHostType == privilegedAuthHost + ? "authorizationhost" + : "SecurityAgent", this); + throw; + } + + secdebug("SecurityAgentConnection", "contacting service (%p)", this); + mach_port_name_t jobPort; + if (0 > audit_session_port(session.sessionId(), &jobPort)) + Syslog::error("audit_session_port failed: %m"); + MacOSError::check(SecurityAgent::Client::contact(jobPort, processBootstrap, userPrefsFP)); + secdebug("SecurityAgentConnection", "contact didn't throw (%p)", this); + + if (userPrefsFP != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), userPrefsFP); +} - if (SecurityAgent::Client::state() != SecurityAgent::Client::dead) - destroy(); +void +SecurityAgentConnection::reconnect() +{ + // if !mHostInstance throw()? + if (mHostInstance) + { + activate(); + } } void -SecurityAgentQuery::activate() +SecurityAgentConnection::terminate() { - mConnection->useAgent(this); + activate(); + + // @@@ This happens already in the destructor; presumably we do this to tear things down orderly + mConnection->useAgent(NULL); +} + + +// SecurityAgentConnection + +SecurityAgentXPCConnection::SecurityAgentXPCConnection(const AuthHostType type, Session &session) +: mAuthHostType(type), +mHostInstance(session.authhost(mAuthHostType)), +mSession(session), +mConnection(&Server::connection()), +mAuditToken(Server::connection().auditToken()) +{ + // this may take a while + 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; + } +} - try { - SecurityAgent::Client::activate(mHostInstance->activate()); - } catch (...) { +SecurityAgentXPCConnection::~SecurityAgentXPCConnection() +{ + secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this); + mConnection->useAgent(NULL); + + // If a connection has been established, we need to tear it down. + if (NULL != mXPCConnection) { + // Tearing this down is a multi-step process. First, request a cancellation. + // This is safe even if the connection is already in the cancelled state. + xpc_connection_cancel(mXPCConnection); + + // Then release the XPC connection + xpc_release(mXPCConnection); + mXPCConnection = NULL; + + if (NULL != mXPCStubConnection) { + // We may or may not have one of these + xpc_release(mXPCStubConnection); + mXPCStubConnection = NULL; + } + } +} + +bool SecurityAgentXPCConnection::inDarkWake() +{ + return mSession.server().inDarkWake(); +} + +void +SecurityAgentXPCConnection::activate(bool ignoreUid) +{ + secdebug("SecurityAgentConnection", "activate(%p)", this); + + mConnection->useAgent(this); + if (mXPCConnection != NULL) { + // If we already have an XPC connection, there's nothing to do. + return; + } + try + { + if (mAuthHostType == securityAgent) { + uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId()); + // Yes, these need to be throws, as we're still in securityd, and thus still have to do flow control with exceptions. + 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 ((int32_t)targetUid != -1) { + mXPCStubConnection = xpc_connection_create_mach_service(SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE, NULL, 0); + xpc_connection_set_target_uid(mXPCStubConnection, targetUid); + secdebug("SecurityAgentXPCConnection", "Creating a security agent stub"); + xpc_connection_set_event_handler(mXPCStubConnection, ^(xpc_object_t object){}); // Yes, this is a dummy handler, we never ever care about any responses from the stub. It can die in a fire for all I care. + xpc_connection_resume(mXPCStubConnection); + + xpc_object_t wakeupMessage = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_data(wakeupMessage, AUTH_XPC_SESSION_UUID, sessionUUID, sizeof(uuid_t)); + xpc_object_t responseMessage = xpc_connection_send_message_with_reply_sync(mXPCStubConnection, wakeupMessage); + if (xpc_get_type(responseMessage) == XPC_TYPE_DICTIONARY) { + secdebug("SecurityAgentXPCConnection", "Valid response received from stub"); + } else { + secdebug("SecurityAgentXPCConnection", "Error response received from stub"); + } + xpc_release(wakeupMessage); + xpc_release(responseMessage); + } + + mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL,0); + xpc_connection_set_instance(mXPCConnection, sessionUUID); + secdebug("SecurityAgentXPCConnection", "Creating a security agent"); + } else { + mXPCConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0); + 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); // guess not + 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); + + // @@@ This happens already in the destructor; presumably we do this to tear things down orderly + mConnection->useAgent(NULL); +} + + +using SecurityAgent::Reason; +using namespace Authorization; + +SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session) +: SecurityAgentConnection(type, session) +{ + secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this); +} + +SecurityAgentQuery::~SecurityAgentQuery() +{ + secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this); + +#if defined(NOSA) + if (getenv("NOSA")) { + printf(" [query done]\n"); + return; + } +#endif + + if (SecurityAgent::Client::state() != SecurityAgent::Client::dead) + destroy(); } void SecurityAgentQuery::inferHints(Process &thisProcess) { string guestPath; - if (SecCodeRef clientCode = thisProcess.currentGuest()) - guestPath = codePath(clientCode); + { + StLock _(thisProcess); + if (SecCodeRef clientCode = thisProcess.currentGuest()) + guestPath = codePath(clientCode); + } AuthItemSet processHints = clientHints(SecurityAgent::bundle, guestPath, - thisProcess.pid(), thisProcess.uid()); + thisProcess.pid(), thisProcess.uid()); mClientHints.insert(processHints.begin(), processHints.end()); } @@ -133,30 +423,33 @@ SecurityAgentQuery::readChoice() AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW); if (allowAction) { - string allowString; - if (allowAction->getString(allowString) - && (allowString == "YES")) - allow = true; + string allowString; + if (allowAction->getString(allowString) + && (allowString == "YES")) + allow = true; } AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION); if (rememberAction) { - string rememberString; - if (rememberAction->getString(rememberString) - && (rememberString == "YES")) - remember = true; + string rememberString; + if (rememberAction->getString(rememberString) + && (rememberString == "YES")) + remember = true; } -} +} void -SecurityAgentQuery::terminate() +SecurityAgentQuery::disconnect() { - activate(); + SecurityAgent::Client::destroy(); +} - // @@@ This happens already in the destructor; presumably we do this to tear things down orderly - mConnection->useAgent(NULL); - +void +SecurityAgentQuery::terminate() +{ + // you might think these are called in the wrong order, but you'd be wrong + SecurityAgentConnection::terminate(); SecurityAgent::Client::terminate(); } @@ -168,14 +461,290 @@ SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const if (status) { secdebug("SecurityAgentQuery", "agent went walkabout, restarting"); - Session &session = mHostInstance->session(); - mHostInstance = session.authhost(mAuthHostType, true); - activate(); + reconnect(); status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId); } if (status) MacOSError::throwMe(status); } +ModuleNexus gAllXPCClientsMutex; +ModuleNexus > allXPCClients; + +void +SecurityAgentXPCQuery::killAllXPCClients() +{ + // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it. + StLock _(gAllXPCClientsMutex()); + + set::iterator clientIterator = allXPCClients().begin(); + while (clientIterator != allXPCClients().end()) + { + set::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(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 _(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); // The authorization items contain things like passwords, so wiping clean is important. + free(dataCopy); + return true; + }); +} + +void +SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId) +{ + 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 = true; // (ignoreUid) || ((targetUid == 0) || (targetUid == mNobodyUID)); + bool doSwitchBootstrap = true; // (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 (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 we get an error before getting the create response, try again without the UID + 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) { + // If we get an error before getting the create response, try again + } + } + xpc_release(object); + xpc_release(requestObject); + } while (!mAgentConnected); + + StLock _(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) { + // If the connection drops, return an "auth undefined" result, because we cannot continue + } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) { + // If the agent dies, return an "auth undefined" result, because we cannot continue + } + } + xpc_release(object); + + xpc_release(hintsArray); + xpc_release(contextArray); + xpc_release(immutableHintsArray); + xpc_release(requestObject); + + return status; +} + +void SecurityAgentXPCQuery::checkResult() +{ + // now check the OSStatus return from the server side + switch (mLastResult) { + case kAuthorizationResultAllow: return; + case kAuthorizationResultDeny: + case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); + default: MacOSError::throwMe(errAuthorizationInternal); + } +} + // // Perform the "rogue app" access query dialog // @@ -186,6 +755,8 @@ QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db) // (will quietly disable check if db isn't a keychain) if (needPass) mPassphraseCheck = dynamic_cast(db); + + setTerminateOnSleep(true); } Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action) @@ -229,11 +800,10 @@ Reason QueryKeychainUse::queryUser (const char *database, const char *descriptio // item name into hints - hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast(description)))); + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast(description)))); // keychain name into hints - hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast(database)))); - + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast(database)))); if (mPassphraseCheck) { @@ -265,13 +835,13 @@ Reason QueryKeychainUse::queryUser (const char *database, const char *descriptio checkResult(); - AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword); + AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword); if (!passwordItem) continue; passwordItem->getCssmData(data); } - while (reason = (const_cast(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase)); + while ((reason = (const_cast(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase))); } else { @@ -315,8 +885,8 @@ bool QueryCodeCheck::operator () (const char *aclPath) // prepopulate with client hints hints.insert(mClientHints.begin(), mClientHints.end()); - hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay(strlen(aclPath), const_cast(aclPath)))); - + hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay((uint32_t)strlen(aclPath), const_cast(aclPath)))); + create("builtin", "code-identity", noSecuritySession); setInput(hints, context); @@ -326,7 +896,7 @@ bool QueryCodeCheck::operator () (const char *aclPath) // MacOSError::check(status); - return kAuthorizationResultAllow == result(); + return kAuthorizationResultAllow == mLastResult; } @@ -357,10 +927,10 @@ Reason QueryOld::query() // prepopulate with client hints const char *keychainPath = database.dbName(); - hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(strlen(keychainPath), const_cast(keychainPath)))); + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast(keychainPath)))); hints.insert(mClientHints.begin(), mClientHints.end()); - + create("builtin", "unlock-keychain", noSecuritySession); do @@ -388,14 +958,14 @@ Reason QueryOld::query() checkResult(); - AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword); + AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword); if (!passwordItem) continue; passwordItem->getCssmData(passphrase); } - while (reason = accept(passphrase)); + while ((reason = accept(passphrase))); return SecurityAgent::noReason; } @@ -421,6 +991,155 @@ Reason QueryUnlock::accept(CssmManagedData &passphrase) 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; + + // prepopulate with client hints + + const char *keychainPath = "iCloud"; + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast(keychainPath)))); + + hints.insert(mClientHints.begin(), mClientHints.end()); + + create("builtin", "unlock-keychain", noSecuritySession); + + do + { + if (retryCount > mRetries) + { + return SecurityAgent::tooManyTries; + } + + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + hints.erase(retryHint); hints.insert(retryHint); // replace + + setInput(hints, context); + status = invoke(); + + checkResult(); + + AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword); + if (!passwordItem) + continue; + + passwordItem->getCssmData(passphrase); + + ++retryCount; + } + 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; + + // prepopulate with client hints + + const char *keychainPath = "iCloud"; + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast(keychainPath)))); + + const char *showResetString = "YES"; + hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast(showResetString)))); + + hints.insert(mClientHints.begin(), mClientHints.end()); + + create("builtin", "change-passphrase", noSecuritySession); + + AuthItem *resetPassword = NULL; + do + { + if (retryCount > mRetries) + { + return SecurityAgent::tooManyTries; + } + + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + hints.erase(retryHint); hints.insert(retryHint); // replace + + 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); + + ++retryCount; + } + 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()) @@ -469,7 +1188,7 @@ Reason QueryNewPassphrase::query() // keychain name into hints hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName()))); - + switch (initialReason) { case SecurityAgent::newDatabase: @@ -507,21 +1226,21 @@ Reason QueryNewPassphrase::query() if (SecurityAgent::changePassphrase == initialReason) { - AuthItem *oldPasswordItem = outContext().find(AGENT_PASSWORD); + AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD); if (!oldPasswordItem) continue; oldPasswordItem->getCssmData(oldPassphrase); } - AuthItem *passwordItem = outContext().find(AGENT_CONTEXT_NEW_PASSWORD); + AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD); if (!passwordItem) continue; passwordItem->getCssmData(passphrase); } - while (reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)); + while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL))); return SecurityAgent::noReason; } @@ -530,11 +1249,12 @@ Reason QueryNewPassphrase::query() // // Get new passphrase Query // -Reason QueryNewPassphrase::operator () (CssmOwnedData &passphrase) +Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase) { if (Reason result = query()) return result; // failed passphrase = mPassphrase; + oldPassphrase = mOldPassphrase; return SecurityAgent::noReason; // success } @@ -550,6 +1270,7 @@ Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPass // sanity check the new passphrase (but allow user override) if (!(mPassphraseValid && passphrase.get() == mPassphrase)) { mPassphrase = passphrase; + if (oldPassphrase) mOldPassphrase = *oldPassphrase; mPassphraseValid = true; if (mPassphrase.length() == 0) return SecurityAgent::passphraseIsNull; @@ -564,13 +1285,13 @@ Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPass // // Get a passphrase for unspecified use // -Reason QueryGenericPassphrase::operator () (const char *prompt, bool verify, +Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify, string &passphrase) { return query(prompt, verify, passphrase); } -Reason QueryGenericPassphrase::query(const char *prompt, bool verify, +Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify, string &passphrase) { Reason reason = SecurityAgent::noReason; @@ -586,7 +1307,7 @@ Reason QueryGenericPassphrase::query(const char *prompt, bool verify, #endif hints.insert(mClientHints.begin(), mClientHints.end()); - hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? strlen(prompt) : 0, const_cast(prompt)))); + hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL))); // XXX/gh defined by dmitch but no analogous hint in // AuthorizationTagsPriv.h: // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title) @@ -605,7 +1326,7 @@ Reason QueryGenericPassphrase::query(const char *prompt, bool verify, setInput(hints, context); status = invoke(); checkResult(); - passwordItem = outContext().find(AGENT_PASSWORD); + passwordItem = mOutContext.find(AGENT_PASSWORD); } while (!passwordItem); @@ -618,12 +1339,12 @@ Reason QueryGenericPassphrase::query(const char *prompt, bool verify, // // Get a DB blob's passphrase--keychain synchronization // -Reason QueryDBBlobSecret::operator () (DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob) +Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated) { - return query(dbCore, secretsBlob); + return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated); } -Reason QueryDBBlobSecret::query(DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob) +Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated) { Reason reason = SecurityAgent::noReason; CssmAutoData passphrase(Allocator::standard(Allocator::sensitive)); @@ -637,9 +1358,8 @@ Reason QueryDBBlobSecret::query(DatabaseCryptoCore &dbCore, const DbBlob *secret return SecurityAgent::noReason; } #endif - - hints.insert(mClientHints.begin(), mClientHints.end()); - + + hints.insert(mClientHints.begin(), mClientHints.end()); create("builtin", "generic-unlock-kcblob", noSecuritySession); AuthItem *secretItem; @@ -661,27 +1381,39 @@ Reason QueryDBBlobSecret::query(DatabaseCryptoCore &dbCore, const DbBlob *secret setInput(hints, context); status = invoke(); checkResult(); - secretItem = outContext().find(AGENT_PASSWORD); + secretItem = mOutContext.find(AGENT_PASSWORD); if (!secretItem) continue; secretItem->getCssmData(passphrase); - } while (reason = accept(passphrase, dbCore, secretsBlob)); + } while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated))); return reason; } Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase, - DatabaseCryptoCore &dbCore, - const DbBlob *secretsBlob) -{ - try { - dbCore.setup(secretsBlob, passphrase); - dbCore.decodeCore(secretsBlob, NULL); - } catch (const CommonError &err) { - // XXX/gh Are there errors other than this? - return SecurityAgent::invalidPassphrase; + DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated) +{ + DbHandle *currHdl = dbHandlesToAuthenticate; + short index; + Boolean authenticated = false; + for (index=0; index < dbHandleCount && !authenticated; index++) + { + try + { + RefPointer dbToUnlock = Server::keychain(*currHdl); + dbToUnlock->unlockDb(passphrase); + authenticated = true; + *dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with. + } + catch (const CommonError &err) + { + currHdl++; // we failed to authenticate with this one, onto the next one. + } } + if ( !authenticated ) + return SecurityAgent::invalidPassphrase; + return SecurityAgent::noReason; } @@ -703,6 +1435,9 @@ void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet & // prepopulate with client hints inHints.insert(mClientHints.begin(), mClientHints.end()); + if (Server::active().inDarkWake()) + CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE); + setArguments(inArguments); setInput(inHints, inContext); MacOSError::check(invoke()); @@ -717,3 +1452,120 @@ void QueryInvokeMechanism::terminateAgent() { terminate(); } + +// @@@ no pluggable authentication possible! +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) + /* XXX/gh probably not complete; stolen verbatim from rogue-app query */ + 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" : ""); + // turn passphrase (no ':') into y: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()); + + // put action/operation (sint32) into hints + hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast(&action)))); + + hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast(prompt)))); + + // item name into hints + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast(description)))); + + // keychain name into hints + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast(database)))); + + create("builtin", "confirm-access-user-password", noSecuritySession); + + AuthItem *usernameItem; + AuthItem *passwordItem; + + do { + + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + 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); // replace + + setInput(hints, context); + try + { + invoke(); + checkResult(); + } + catch (...) // user probably clicked "deny" + { + 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(); + // else we logged the denial in the loop + + 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; + + return SecurityAgent::noReason; +} +