2 * Copyright (c) 2000-2015 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 // passphrases - canonical code to obtain passphrases
27 #define __STDC_WANT_LIB_EXT1__ 1
30 #include "agentquery.h"
31 #include "ccaudit_extensions.h"
33 #include <Security/AuthorizationTags.h>
34 #include <Security/AuthorizationTagsPriv.h>
35 #include <Security/checkpw.h>
36 #include <Security/Security.h>
37 #include <System/sys/fileport.h>
38 #include <bsm/audit.h>
39 #include <bsm/audit_uevents.h> // AUE_ssauthint
40 #include <membership.h>
41 #include <membershipPriv.h>
42 #include <security_utilities/logging.h>
43 #include <security_utilities/mach++.h>
46 #include <xpc/private.h>
47 #include "securityd_service/securityd_service/securityd_service_client.h"
49 // Includes for <rdar://problem/34677969> Always require the user's password on keychain approval dialogs
52 #define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agent"
53 #define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE "com.apple.security.agent.login"
55 #define AUTH_XPC_ITEM_NAME "_item_name"
56 #define AUTH_XPC_ITEM_FLAGS "_item_flags"
57 #define AUTH_XPC_ITEM_VALUE "_item_value"
58 #define AUTH_XPC_ITEM_TYPE "_item_type"
59 #define AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH "_item_sensitive_value_length"
61 #define AUTH_XPC_REQUEST_METHOD_KEY "_agent_request_key"
62 #define AUTH_XPC_REQUEST_METHOD_CREATE "_agent_request_create"
63 #define AUTH_XPC_REQUEST_METHOD_INVOKE "_agent_request_invoke"
64 #define AUTH_XPC_REQUEST_METHOD_DEACTIVATE "_agent_request_deactivate"
65 #define AUTH_XPC_REQUEST_METHOD_DESTROY "_agent_request_destroy"
66 #define AUTH_XPC_REPLY_METHOD_KEY "_agent_reply_key"
67 #define AUTH_XPC_REPLY_METHOD_RESULT "_agent_reply_result"
68 #define AUTH_XPC_REPLY_METHOD_INTERRUPT "_agent_reply_interrupt"
69 #define AUTH_XPC_REPLY_METHOD_CREATE "_agent_reply_create"
70 #define AUTH_XPC_REPLY_METHOD_DEACTIVATE "_agent_reply_deactivate"
71 #define AUTH_XPC_PLUGIN_NAME "_agent_plugin"
72 #define AUTH_XPC_MECHANISM_NAME "_agent_mechanism"
73 #define AUTH_XPC_HINTS_NAME "_agent_hints"
74 #define AUTH_XPC_CONTEXT_NAME "_agent_context"
75 #define AUTH_XPC_IMMUTABLE_HINTS_NAME "_agent_immutable_hints"
76 #define AUTH_XPC_REQUEST_INSTANCE "_agent_instance"
77 #define AUTH_XPC_REPLY_RESULT_VALUE "_agent_reply_result_value"
78 #define AUTH_XPC_AUDIT_SESSION_PORT "_agent_audit_session_port"
79 #define AUTH_XPC_BOOTSTRAP_PORT "_agent_bootstrap_port"
81 #define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
82 { 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))) }
85 // SecurityAgentXPCConnection
87 SecurityAgentXPCConnection::SecurityAgentXPCConnection(Session
&session
)
88 : mHostInstance(session
.authhost()),
90 mConnection(&Server::connection()),
91 mAuditToken(Server::connection().auditToken())
93 // this may take a while
94 Server::active().longTermActivity();
95 secnotice("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
96 mXPCConnection
= NULL
;
98 struct passwd
*pw
= getpwnam("nobody");
100 mNobodyUID
= pw
->pw_uid
;
104 SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
106 secnotice("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
107 mConnection
->useAgent(NULL
);
109 // If a connection has been established, we need to tear it down.
110 if (NULL
!= mXPCConnection
) {
111 // Tearing this down is a multi-step process. First, request a cancellation.
112 // This is safe even if the connection is already in the canceled state.
113 xpc_connection_cancel(mXPCConnection
);
115 // Then release the XPC connection
116 xpc_release(mXPCConnection
);
117 mXPCConnection
= NULL
;
121 bool SecurityAgentXPCConnection::inDarkWake()
123 return mSession
.server().inDarkWake();
127 SecurityAgentXPCConnection::activate(bool ignoreUid
)
129 secnotice("SecurityAgentConnection", "activate(%p)", this);
131 mConnection
->useAgent(this);
132 if (mXPCConnection
!= NULL
) {
133 // If we already have an XPC connection, there's nothing to do.
138 uuid_t sessionUUID
= UUID_INITIALIZER_FROM_SESSIONID(mSession
.sessionId());
140 // Yes, these need to be throws, as we're still in securityd, and thus still have to do flow control with exceptions.
141 if (!(mSession
.attributes() & sessionHasGraphicAccess
))
142 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION
);
144 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE
);
145 uid_t targetUid
= mHostInstance
->session().originatorUid();
147 secnotice("SecurityAgentXPCConnection","Retrieved UID %d for this session", targetUid
);
148 if (!ignoreUid
&& targetUid
!= 0 && targetUid
!= mNobodyUID
) {
149 mXPCConnection
= xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE
, NULL
, 0);
150 xpc_connection_set_target_uid(mXPCConnection
, targetUid
);
151 secnotice("SecurityAgentXPCConnection", "Creating a standard security agent");
153 mXPCConnection
= xpc_connection_create_mach_service(SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE
, NULL
,
154 XPC_CONNECTION_MACH_SERVICE_PRIVILEGED
);
155 xpc_connection_set_instance(mXPCConnection
, sessionUUID
);
156 secnotice("SecurityAgentXPCConnection", "Creating a loginwindow security agent");
159 xpc_connection_set_event_handler(mXPCConnection
, ^(xpc_object_t object
) {
160 if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
161 secnotice("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object
, XPC_ERROR_KEY_DESCRIPTION
));
164 xpc_connection_resume(mXPCConnection
);
165 secnotice("SecurityAgentXPCConnection", "%p activated", this);
167 catch (MacOSError
&err
) {
168 mConnection
->useAgent(NULL
); // guess not
169 Syslog::error("SecurityAgentConnection: error activating SecurityAgent instance %p", this);
173 secnotice("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
177 SecurityAgentXPCConnection::terminate()
181 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
182 mConnection
->useAgent(NULL
);
186 using SecurityAgent::Reason
;
187 using namespace Authorization
;
189 ModuleNexus
<RecursiveMutex
> gAllXPCClientsMutex
;
190 ModuleNexus
<set
<SecurityAgentXPCQuery
*> > allXPCClients
;
193 SecurityAgentXPCQuery::killAllXPCClients()
195 // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
196 StLock
<Mutex
> _(gAllXPCClientsMutex());
198 set
<SecurityAgentXPCQuery
*>::iterator clientIterator
= allXPCClients().begin();
199 while (clientIterator
!= allXPCClients().end())
201 set
<SecurityAgentXPCQuery
*>::iterator thisClient
= clientIterator
++;
202 if ((*thisClient
)->getTerminateOnSleep())
204 (*thisClient
)->terminate();
210 SecurityAgentXPCQuery::SecurityAgentXPCQuery(Session
&session
)
211 : SecurityAgentXPCConnection(session
), mAgentConnected(false), mTerminateOnSleep(false)
213 secnotice("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
216 SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
218 secnotice("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
219 if (mAgentConnected
) {
225 SecurityAgentXPCQuery::inferHints(Process
&thisProcess
)
227 AuthItemSet clientHints
;
228 SecurityAgent::RequestorType type
= SecurityAgent::bundle
;
229 pid_t clientPid
= thisProcess
.pid();
230 uid_t clientUid
= thisProcess
.uid();
231 string guestPath
= thisProcess
.getPath();
232 Boolean ignoreSession
= TRUE
;
234 clientHints
.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE
, AuthValueOverlay(sizeof(type
), &type
)));
235 clientHints
.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH
, AuthValueOverlay(guestPath
)));
236 clientHints
.insert(AuthItemRef(AGENT_HINT_CLIENT_PID
, AuthValueOverlay(sizeof(clientPid
), &clientPid
)));
237 clientHints
.insert(AuthItemRef(AGENT_HINT_CLIENT_UID
, AuthValueOverlay(sizeof(clientUid
), &clientUid
)));
240 * If its loginwindow that's asking, override the loginwindow shield detection
241 * up front so that it can trigger SecurityAgent dialogs (like password change)
242 * for when the OD password and keychain password is out of sync.
245 if (guestPath
== "/System/Library/CoreServices/loginwindow.app") {
246 clientHints
.insert(AuthItemRef(AGENT_HINT_IGNORE_SESSION
, AuthValueOverlay(sizeof(ignoreSession
), &ignoreSession
)));
249 mClientHints
.insert(clientHints
.begin(), clientHints
.end());
251 bool validSignature
= thisProcess
.checkAppleSigned();
252 AuthItemSet clientImmutableHints
;
254 clientImmutableHints
.insert(AuthItemRef(AGENT_HINT_CLIENT_SIGNED
, AuthValueOverlay(sizeof(validSignature
), &validSignature
)));
256 mImmutableHints
.insert(clientImmutableHints
.begin(), clientImmutableHints
.end());
259 void SecurityAgentXPCQuery::addHint(const char *name
, const void *value
, UInt32 valueLen
, UInt32 flags
)
261 AuthorizationItem item
= { name
, valueLen
, const_cast<void *>(value
), flags
};
262 mClientHints
.insert(AuthItemRef(item
));
267 SecurityAgentXPCQuery::readChoice()
272 AuthItem
*allowAction
= mOutContext
.find(AGENT_CONTEXT_ALLOW
);
276 if (allowAction
->getString(allowString
)
277 && (allowString
== "YES"))
281 AuthItem
*rememberAction
= mOutContext
.find(AGENT_CONTEXT_REMEMBER_ACTION
);
284 string rememberString
;
285 if (rememberAction
->getString(rememberString
)
286 && (rememberString
== "YES"))
292 SecurityAgentXPCQuery::disconnect()
294 if (NULL
!= mXPCConnection
) {
295 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
296 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_DESTROY
);
297 xpc_connection_send_message(mXPCConnection
, requestObject
);
298 xpc_release(requestObject
);
301 StLock
<Mutex
> _(gAllXPCClientsMutex());
302 allXPCClients().erase(this);
306 SecurityAgentXPCQuery::terminate()
311 static void xpcArrayToAuthItemSet(AuthItemSet
*setToBuild
, xpc_object_t input
) {
314 xpc_array_apply(input
, ^bool(size_t index
, xpc_object_t item
) {
315 const char *name
= xpc_dictionary_get_string(item
, AUTH_XPC_ITEM_NAME
);
318 const void *data
= xpc_dictionary_get_data(item
, AUTH_XPC_ITEM_VALUE
, &length
);
321 // <rdar://problem/13033889> authd is holding on to multiple copies of my password in the clear
322 bool sensitive
= xpc_dictionary_get_value(item
, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH
);
324 size_t sensitiveLength
= (size_t)xpc_dictionary_get_uint64(item
, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH
);
325 if (sensitiveLength
> length
) {
326 secnotice("SecurityAgentXPCQuery", "Sensitive data len %zu is not valid", sensitiveLength
);
329 dataCopy
= malloc(sensitiveLength
);
330 memcpy(dataCopy
, data
, sensitiveLength
);
331 memset_s((void *)data
, length
, 0, sensitiveLength
); // clear the sensitive data, memset_s is never optimized away
332 length
= sensitiveLength
;
334 dataCopy
= malloc(length
);
335 memcpy(dataCopy
, data
, length
);
338 uint64_t flags
= xpc_dictionary_get_uint64(item
, AUTH_XPC_ITEM_FLAGS
);
339 AuthItemRef
nextItem(name
, AuthValueOverlay((uint32_t)length
, dataCopy
), (uint32_t)flags
);
340 setToBuild
->insert(nextItem
);
341 memset(dataCopy
, 0, length
); // The authorization items contain things like passwords, so wiping clean is important.
348 SecurityAgentXPCQuery::create(const char *pluginId
, const char *mechanismId
)
350 bool ignoreUid
= false;
355 mAgentConnected
= false;
357 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
358 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_CREATE
);
359 xpc_dictionary_set_string(requestObject
, AUTH_XPC_PLUGIN_NAME
, pluginId
);
360 xpc_dictionary_set_string(requestObject
, AUTH_XPC_MECHANISM_NAME
, mechanismId
);
362 uid_t targetUid
= Server::process().uid();
363 bool doSwitchAudit
= (ignoreUid
|| targetUid
== 0 || targetUid
== mNobodyUID
);
364 bool doSwitchBootstrap
= (ignoreUid
|| targetUid
== 0 || targetUid
== mNobodyUID
);
367 mach_port_name_t jobPort
;
368 if (0 == audit_session_port(mSession
.sessionId(), &jobPort
)) {
369 secnotice("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid
);
370 xpc_dictionary_set_mach_send(requestObject
, AUTH_XPC_AUDIT_SESSION_PORT
, jobPort
);
371 if (mach_port_mod_refs(mach_task_self(), jobPort
, MACH_PORT_RIGHT_SEND
, -1) != KERN_SUCCESS
) {
372 secnotice("SecurityAgentXPCQuery", "unable to release send right for audit session, leaking");
377 if (doSwitchBootstrap
) {
378 secnotice("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid
);
379 MachPlusPlus::Bootstrap processBootstrap
= Server::process().taskPort().bootstrap();
380 xpc_dictionary_set_mach_send(requestObject
, AUTH_XPC_BOOTSTRAP_PORT
, processBootstrap
);
383 xpc_object_t object
= xpc_connection_send_message_with_reply_sync(mXPCConnection
, requestObject
);
384 if (xpc_get_type(object
) == XPC_TYPE_DICTIONARY
) {
385 const char *replyType
= xpc_dictionary_get_string(object
, AUTH_XPC_REPLY_METHOD_KEY
);
386 if (0 == strcmp(replyType
, AUTH_XPC_REPLY_METHOD_CREATE
)) {
387 uint64_t status
= xpc_dictionary_get_uint64(object
, AUTH_XPC_REPLY_RESULT_VALUE
);
388 if (status
== kAuthorizationResultAllow
) {
389 mAgentConnected
= true;
391 secnotice("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
392 MacOSError::throwMe(errAuthorizationInternal
);
395 } else if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
396 if (XPC_ERROR_CONNECTION_INVALID
== object
) {
397 // If we get an error before getting the create response, try again without the UID
399 secnotice("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
401 MacOSError::throwMe(errAuthorizationInternal
);
403 secnotice("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
405 xpc_release(mXPCConnection
);
406 mXPCConnection
= NULL
;
408 } else if (XPC_ERROR_CONNECTION_INTERRUPTED
== object
) {
409 // If we get an error before getting the create response, try again
413 xpc_release(requestObject
);
414 } while (!mAgentConnected
);
416 StLock
<Mutex
> _(gAllXPCClientsMutex());
417 allXPCClients().insert(this);
420 static xpc_object_t
authItemSetToXPCArray(AuthItemSet input
) {
421 xpc_object_t outputArray
= xpc_array_create(NULL
, 0);
422 for (AuthItemSet::iterator i
= input
.begin(); i
!= input
.end(); i
++) {
423 AuthItemRef item
= *i
;
425 xpc_object_t xpc_data
= xpc_dictionary_create(NULL
, NULL
, 0);
426 xpc_dictionary_set_string(xpc_data
, AUTH_XPC_ITEM_NAME
, item
->name());
427 AuthorizationValue value
= item
->value();
428 if (value
.data
!= NULL
) {
429 xpc_dictionary_set_data(xpc_data
, AUTH_XPC_ITEM_VALUE
, value
.data
, value
.length
);
431 xpc_dictionary_set_uint64(xpc_data
, AUTH_XPC_ITEM_FLAGS
, item
->flags());
432 xpc_array_append_value(outputArray
, xpc_data
);
433 xpc_release(xpc_data
);
439 SecurityAgentXPCQuery::invoke() {
440 xpc_object_t hintsArray
= authItemSetToXPCArray(mInHints
);
441 xpc_object_t contextArray
= authItemSetToXPCArray(mInContext
);
442 xpc_object_t immutableHintsArray
= authItemSetToXPCArray(mImmutableHints
);
444 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
445 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_INVOKE
);
446 xpc_dictionary_set_value(requestObject
, AUTH_XPC_HINTS_NAME
, hintsArray
);
447 xpc_dictionary_set_value(requestObject
, AUTH_XPC_CONTEXT_NAME
, contextArray
);
448 xpc_dictionary_set_value(requestObject
, AUTH_XPC_IMMUTABLE_HINTS_NAME
, immutableHintsArray
);
450 xpc_object_t object
= xpc_connection_send_message_with_reply_sync(mXPCConnection
, requestObject
);
451 if (xpc_get_type(object
) == XPC_TYPE_DICTIONARY
) {
452 const char *replyType
= xpc_dictionary_get_string(object
, AUTH_XPC_REPLY_METHOD_KEY
);
453 if (0 == strcmp(replyType
, AUTH_XPC_REPLY_METHOD_RESULT
)) {
454 xpc_object_t xpcHints
= xpc_dictionary_get_value(object
, AUTH_XPC_HINTS_NAME
);
455 xpc_object_t xpcContext
= xpc_dictionary_get_value(object
, AUTH_XPC_CONTEXT_NAME
);
456 AuthItemSet tempHints
, tempContext
;
457 xpcArrayToAuthItemSet(&tempHints
, xpcHints
);
458 xpcArrayToAuthItemSet(&tempContext
, xpcContext
);
459 mOutHints
= tempHints
;
460 mOutContext
= tempContext
;
461 mLastResult
= xpc_dictionary_get_uint64(object
, AUTH_XPC_REPLY_RESULT_VALUE
);
463 } else if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
464 if (XPC_ERROR_CONNECTION_INVALID
== object
) {
465 // If the connection drops, return an "auth undefined" result, because we cannot continue
466 } else if (XPC_ERROR_CONNECTION_INTERRUPTED
== object
) {
467 // If the agent dies, return an "auth undefined" result, because we cannot continue
472 xpc_release(hintsArray
);
473 xpc_release(contextArray
);
474 xpc_release(immutableHintsArray
);
475 xpc_release(requestObject
);
478 void SecurityAgentXPCQuery::checkResult()
480 // now check the OSStatus return from the server side
481 switch (mLastResult
) {
482 case kAuthorizationResultAllow
: return;
483 case kAuthorizationResultDeny
:
484 case kAuthorizationResultUserCanceled
: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED
);
485 default: MacOSError::throwMe(errAuthorizationInternal
);
490 // Perform the "rogue app" access query dialog
492 QueryKeychainUse::QueryKeychainUse(bool needPass
, const Database
*db
)
493 : mPassphraseCheck(NULL
)
495 // if passphrase checking requested, save KeychainDatabase reference
496 // (will quietly disable check if db isn't a keychain)
498 // Always require password due to <rdar://problem/34677969>
499 mPassphraseCheck
= dynamic_cast<const KeychainDatabase
*>(db
);
501 setTerminateOnSleep(true);
504 // Callers to this function must hold the common lock
505 Reason
QueryKeychainUse::queryUser (const char *database
, const char *description
, AclAuthorization action
)
507 Reason reason
= SecurityAgent::noReason
;
508 uint32_t retryCount
= 0;
509 AuthItemSet hints
, context
;
511 // prepopulate with client hints
512 hints
.insert(mClientHints
.begin(), mClientHints
.end());
514 // put action/operation (sint32) into hints
515 hints
.insert(AuthItemRef(AGENT_HINT_ACL_TAG
, AuthValueOverlay(sizeof(action
), static_cast<sint32
*>(&action
))));
517 // item name into hints
519 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME
, AuthValueOverlay(description
? (uint32_t)strlen(description
) : 0, const_cast<char*>(description
))));
521 // keychain name into hints
522 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(database
? (uint32_t)strlen(database
) : 0, const_cast<char*>(database
))));
524 if (mPassphraseCheck
)
526 create("builtin", "confirm-access-password");
528 CssmAutoData
data(Allocator::standard(Allocator::sensitive
));
533 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
534 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
536 if (retryCount
++ > kMaximumAuthorizationTries
)
538 reason
= SecurityAgent::tooManyTries
;
541 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
542 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
544 setInput(hints
, context
);
547 // Must drop the common lock while showing UI.
548 StSyncLock
<Mutex
, Mutex
> syncLock(const_cast<KeychainDatabase
*>(mPassphraseCheck
)->common().uiLock(), const_cast<KeychainDatabase
*>(mPassphraseCheck
)->common());
552 if (retryCount
> kMaximumAuthorizationTries
)
559 AuthItem
*passwordItem
= mOutContext
.find(kAuthorizationEnvironmentPassword
);
563 passwordItem
->getCssmData(data
);
565 // decode() replaces the master key, so do this only if we know the passphrase is correct.
566 // I suspect decode() is redundant but something might rely on its side effects so let's keep it.
567 if (const_cast<KeychainDatabase
*>(mPassphraseCheck
)->validatePassphrase(data
) && const_cast<KeychainDatabase
*>(mPassphraseCheck
)->decode(data
)) {
568 reason
= SecurityAgent::noReason
;
570 reason
= SecurityAgent::invalidPassphrase
;
573 while (reason
!= SecurityAgent::noReason
);
579 // create("builtin", "confirm-access");
580 // setInput(hints, context);
583 // This is a hack to support <rdar://problem/34677969>, we can never simply prompt for confirmation
584 secerror("ACL validation fallback case! Must ask user for account password because we have no database");
585 Session
&session
= Server::session();
587 session
.verifyKeyStorePassphrase(1, true, description
);
589 return SecurityAgent::invalidPassphrase
;
591 SecurityAgentXPCQuery::allow
= true;
598 // Obtain passphrases and submit them to the accept() method until it is accepted
599 // or we can't get another passphrase. Accept() should consume the passphrase
600 // if it is accepted. If no passphrase is acceptable, throw out of here.
602 Reason
QueryOld::query()
604 Reason reason
= SecurityAgent::noReason
;
605 AuthItemSet hints
, context
;
606 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
609 // prepopulate with client hints
611 const char *keychainPath
= database
.dbName();
612 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay((uint32_t)strlen(keychainPath
), const_cast<char*>(keychainPath
))));
614 hints
.insert(mClientHints
.begin(), mClientHints
.end());
616 create("builtin", "unlock-keychain");
620 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
621 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
625 if (retryCount
> maxTries
)
627 reason
= SecurityAgent::tooManyTries
;
630 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
631 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
633 setInput(hints
, context
);
636 if (retryCount
> maxTries
)
643 AuthItem
*passwordItem
= mOutContext
.find(kAuthorizationEnvironmentPassword
);
647 passwordItem
->getCssmData(passphrase
);
650 while ((reason
= accept(passphrase
)));
652 return SecurityAgent::noReason
;
657 // Get existing passphrase (unlock) Query
659 Reason
QueryOld::operator () ()
666 // End-classes for old secrets
668 Reason
QueryUnlock::accept(CssmManagedData
&passphrase
)
670 // Must hold the 'common' lock to call decode; otherwise there's a data corruption issue
671 StLock
<Mutex
> _(safer_cast
<KeychainDatabase
&>(database
).common());
673 // Calling validatePassphrase here throws when trying to constitute a key.
674 // Unsure why but since this is for the KC unlock path and not a validation path the wrong password won't make things worse.
675 if (safer_cast
<KeychainDatabase
&>(database
).decode(passphrase
))
676 return SecurityAgent::noReason
;
678 return SecurityAgent::invalidPassphrase
;
681 Reason
QueryUnlock::retrievePassword(CssmOwnedData
&passphrase
) {
682 CssmAutoData
pass(Allocator::standard(Allocator::sensitive
));
684 AuthItem
*passwordItem
= mOutContext
.find(kAuthorizationEnvironmentPassword
);
686 return SecurityAgent::invalidPassphrase
;
688 passwordItem
->getCssmData(pass
);
692 return SecurityAgent::noReason
;
695 QueryKeybagPassphrase::QueryKeybagPassphrase(Session
& session
, int32_t tries
) : mSession(session
), mContext(), mRetries(tries
)
697 setTerminateOnSleep(true);
698 mContext
= mSession
.get_current_service_context();
701 Reason
QueryKeybagPassphrase::query()
703 Reason reason
= SecurityAgent::noReason
;
704 AuthItemSet hints
, context
;
705 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
708 // prepopulate with client hints
710 const char *keychainPath
= "iCloud";
711 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay((uint32_t)strlen(keychainPath
), const_cast<char*>(keychainPath
))));
713 hints
.insert(mClientHints
.begin(), mClientHints
.end());
715 create("builtin", "unlock-keychain");
720 currentTry
= retryCount
;
721 if (retryCount
> mRetries
)
723 return SecurityAgent::tooManyTries
;
727 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(currentTry
), ¤tTry
));
728 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
730 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
731 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
733 setInput(hints
, context
);
738 AuthItem
*passwordItem
= mOutContext
.find(kAuthorizationEnvironmentPassword
);
742 passwordItem
->getCssmData(passphrase
);
744 while ((reason
= accept(passphrase
)));
746 return SecurityAgent::noReason
;
749 Reason
QueryKeybagPassphrase::accept(Security::CssmManagedData
& password
)
751 if (service_client_kb_unlock(&mContext
, password
.data(), (int)password
.length()) == 0) {
752 mSession
.keybagSetState(session_keybag_unlocked
);
753 return SecurityAgent::noReason
;
755 return SecurityAgent::invalidPassphrase
;
758 QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session
& session
) : QueryKeybagPassphrase(session
) {}
760 Reason
QueryKeybagNewPassphrase::query(CssmOwnedData
&oldPassphrase
, CssmOwnedData
&passphrase
)
762 CssmAutoData
pass(Allocator::standard(Allocator::sensitive
));
763 CssmAutoData
oldPass(Allocator::standard(Allocator::sensitive
));
764 Reason reason
= SecurityAgent::noReason
;
765 AuthItemSet hints
, context
;
768 // prepopulate with client hints
770 const char *keychainPath
= "iCloud";
771 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay((uint32_t)strlen(keychainPath
), const_cast<char*>(keychainPath
))));
773 const char *showResetString
= "YES";
774 hints
.insert(AuthItemRef(AGENT_HINT_SHOW_RESET
, AuthValueOverlay((uint32_t)strlen(showResetString
), const_cast<char*>(showResetString
))));
776 hints
.insert(mClientHints
.begin(), mClientHints
.end());
778 create("builtin", "change-passphrase");
781 AuthItem
*resetPassword
= NULL
;
784 currentTry
= retryCount
;
785 if (retryCount
> mRetries
)
787 return SecurityAgent::tooManyTries
;
791 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(currentTry
), ¤tTry
));
792 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
794 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
795 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
797 setInput(hints
, context
);
802 resetPassword
= mOutContext
.find(AGENT_CONTEXT_RESET_PASSWORD
);
803 if (resetPassword
!= NULL
) {
804 return SecurityAgent::resettingPassword
;
807 AuthItem
*oldPasswordItem
= mOutContext
.find(AGENT_PASSWORD
);
808 if (!oldPasswordItem
)
811 oldPasswordItem
->getCssmData(oldPass
);
813 while ((reason
= accept(oldPass
)));
815 if (reason
== SecurityAgent::noReason
) {
816 AuthItem
*passwordItem
= mOutContext
.find(AGENT_CONTEXT_NEW_PASSWORD
);
818 return SecurityAgent::invalidPassphrase
;
820 passwordItem
->getCssmData(pass
);
822 oldPassphrase
= oldPass
;
826 return SecurityAgent::noReason
;
829 QueryPIN::QueryPIN(Database
&db
)
830 : QueryOld(db
), mPin(Allocator::standard())
832 this->inferHints(Server::process());
836 Reason
QueryPIN::accept(CssmManagedData
&pin
)
838 // no retries for now
840 return SecurityAgent::noReason
;
845 // Obtain passphrases and submit them to the accept() method until it is accepted
846 // or we can't get another passphrase. Accept() should consume the passphrase
847 // if it is accepted. If no passphrase is acceptable, throw out of here.
849 Reason
QueryNewPassphrase::query()
851 Reason reason
= initialReason
;
852 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
853 CssmAutoData
oldPassphrase(Allocator::standard(Allocator::sensitive
));
855 AuthItemSet hints
, context
;
859 // prepopulate with client hints
860 hints
.insert(mClientHints
.begin(), mClientHints
.end());
862 // keychain name into hints
863 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(database
.dbName())));
865 switch (initialReason
)
867 case SecurityAgent::newDatabase
:
868 create("builtin", "new-passphrase");
870 case SecurityAgent::changePassphrase
:
871 create("builtin", "change-passphrase");
879 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
880 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
882 if (++retryCount
> maxTries
)
884 reason
= SecurityAgent::tooManyTries
;
887 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
888 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
890 setInput(hints
, context
);
893 if (retryCount
> maxTries
)
900 if (SecurityAgent::changePassphrase
== initialReason
)
902 AuthItem
*oldPasswordItem
= mOutContext
.find(AGENT_PASSWORD
);
903 if (!oldPasswordItem
)
906 oldPasswordItem
->getCssmData(oldPassphrase
);
909 AuthItem
*passwordItem
= mOutContext
.find(AGENT_CONTEXT_NEW_PASSWORD
);
913 passwordItem
->getCssmData(passphrase
);
916 while ((reason
= accept(passphrase
, (initialReason
== SecurityAgent::changePassphrase
) ? &oldPassphrase
.get() : NULL
)));
918 return SecurityAgent::noReason
;
923 // Get new passphrase Query
925 Reason
QueryNewPassphrase::operator () (CssmOwnedData
&oldPassphrase
, CssmOwnedData
&passphrase
)
927 if (Reason result
= query())
928 return result
; // failed
929 passphrase
= mPassphrase
;
930 oldPassphrase
= mOldPassphrase
;
931 return SecurityAgent::noReason
; // success
934 Reason
QueryNewPassphrase::accept(CssmManagedData
&passphrase
, CssmData
*oldPassphrase
)
936 //@@@ acceptance criteria are currently hardwired here
937 //@@@ This validation presumes ASCII - UTF8 might be more lenient
939 // if we have an old passphrase, check it
940 if (oldPassphrase
&& !safer_cast
<KeychainDatabase
&>(database
).validatePassphrase(*oldPassphrase
))
941 return SecurityAgent::oldPassphraseWrong
;
943 // sanity check the new passphrase (but allow user override)
944 if (!(mPassphraseValid
&& passphrase
.get() == mPassphrase
)) {
945 mPassphrase
= passphrase
;
946 if (oldPassphrase
) mOldPassphrase
= *oldPassphrase
;
947 mPassphraseValid
= true;
948 if (mPassphrase
.length() == 0)
949 return SecurityAgent::passphraseIsNull
;
950 if (mPassphrase
.length() < 6)
951 return SecurityAgent::passphraseTooSimple
;
955 return SecurityAgent::noReason
;
959 // Get a passphrase for unspecified use
961 Reason
QueryGenericPassphrase::operator () (const CssmData
*prompt
, bool verify
,
964 return query(prompt
, verify
, passphrase
);
967 Reason
QueryGenericPassphrase::query(const CssmData
*prompt
, bool verify
,
970 Reason reason
= SecurityAgent::noReason
;
971 AuthItemSet hints
, context
;
973 hints
.insert(mClientHints
.begin(), mClientHints
.end());
974 hints
.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT
, AuthValueOverlay(prompt
? (UInt32
)prompt
->length() : 0, prompt
? prompt
->data() : NULL
)));
975 // XXX/gh defined by dmitch but no analogous hint in
976 // AuthorizationTagsPriv.h:
977 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
979 if (false == verify
) { // import
980 create("builtin", "generic-unlock");
981 } else { // verify passphrase (export)
982 create("builtin", "generic-new-passphrase");
985 AuthItem
*passwordItem
;
988 setInput(hints
, context
);
991 passwordItem
= mOutContext
.find(AGENT_PASSWORD
);
993 } while (!passwordItem
);
995 passwordItem
->getString(passphrase
);
1002 // Get a DB blob's passphrase--keychain synchronization
1004 Reason
QueryDBBlobSecret::operator () (DbHandle
*dbHandleArray
, uint8 dbHandleArrayCount
, DbHandle
*dbHandleAuthenticated
)
1006 return query(dbHandleArray
, dbHandleArrayCount
, dbHandleAuthenticated
);
1009 Reason
QueryDBBlobSecret::query(DbHandle
*dbHandleArray
, uint8 dbHandleArrayCount
, DbHandle
*dbHandleAuthenticated
)
1011 Reason reason
= SecurityAgent::noReason
;
1012 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
1013 AuthItemSet hints
/*NUKEME*/, context
;
1015 hints
.insert(mClientHints
.begin(), mClientHints
.end());
1016 create("builtin", "generic-unlock-kcblob");
1018 AuthItem
*secretItem
;
1023 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
1024 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
1026 if (++retryCount
> maxTries
)
1028 reason
= SecurityAgent::tooManyTries
;
1031 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
1032 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
1034 setInput(hints
, context
);
1037 secretItem
= mOutContext
.find(AGENT_PASSWORD
);
1040 secretItem
->getCssmData(passphrase
);
1042 } while ((reason
= accept(passphrase
, dbHandleArray
, dbHandleArrayCount
, dbHandleAuthenticated
)));
1047 Reason
QueryDBBlobSecret::accept(CssmManagedData
&passphrase
,
1048 DbHandle
*dbHandlesToAuthenticate
, uint8 dbHandleCount
, DbHandle
*dbHandleAuthenticated
)
1050 DbHandle
*currHdl
= dbHandlesToAuthenticate
;
1052 Boolean authenticated
= false;
1053 for (index
=0; index
< dbHandleCount
&& !authenticated
; index
++)
1057 RefPointer
<KeychainDatabase
> dbToUnlock
= Server::keychain(*currHdl
);
1058 dbToUnlock
->unlockDb(passphrase
, false);
1059 authenticated
= true;
1060 *dbHandleAuthenticated
= *currHdl
; // return the DbHandle that 'passphrase' authenticated with.
1062 catch (const CommonError
&err
)
1064 currHdl
++; // we failed to authenticate with this one, onto the next one.
1067 if ( !authenticated
)
1068 return SecurityAgent::invalidPassphrase
;
1070 return SecurityAgent::noReason
;
1073 // @@@ no pluggable authentication possible!
1075 QueryKeychainAuth::performQuery(const KeychainDatabase
& db
, const char *description
, AclAuthorization action
, const char *prompt
)
1077 Reason reason
= SecurityAgent::noReason
;
1078 AuthItemSet hints
, context
;
1083 using CommonCriteria::Securityd::KeychainAuthLogger
;
1084 KeychainAuthLogger
logger(mAuditToken
, (short)AUE_ssauthint
, db
.dbName(), description
);
1086 hints
.insert(mClientHints
.begin(), mClientHints
.end());
1088 // put action/operation (sint32) into hints
1089 hints
.insert(AuthItemRef(AGENT_HINT_ACL_TAG
, AuthValueOverlay(sizeof(action
), static_cast<sint32
*>(&action
))));
1091 hints
.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT
, AuthValueOverlay(prompt
? (uint32_t)strlen(prompt
) : 0, const_cast<char*>(prompt
))));
1093 // item name into hints
1094 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME
, AuthValueOverlay(description
? (uint32_t)strlen(description
) : 0, const_cast<char*>(description
))));
1096 // keychain name into hints
1097 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(db
.dbName() ? (uint32_t)strlen(db
.dbName()) : 0, const_cast<char*>(db
.dbName()))));
1099 create("builtin", "confirm-access-user-password");
1101 AuthItem
*usernameItem
;
1102 AuthItem
*passwordItem
;
1104 // This entire do..while requires the UI lock because we do accept() in the condition, which in some cases is reentrant
1105 StSyncLock
<Mutex
, Mutex
> syncLock(db
.common().uiLock(), db
.common());
1107 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
1108 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
1110 if (++retryCount
> maxTries
)
1111 reason
= SecurityAgent::tooManyTries
;
1113 if (SecurityAgent::noReason
!= reason
)
1115 if (SecurityAgent::tooManyTries
== reason
)
1116 logger
.logFailure(NULL
, CommonCriteria::errTooManyTries
);
1118 logger
.logFailure();
1121 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
1122 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
1124 setInput(hints
, context
);
1130 catch (...) // user probably clicked "deny"
1132 logger
.logFailure();
1135 usernameItem
= mOutContext
.find(AGENT_USERNAME
);
1136 passwordItem
= mOutContext
.find(AGENT_PASSWORD
);
1137 if (!usernameItem
|| !passwordItem
)
1139 usernameItem
->getString(username
);
1140 passwordItem
->getString(password
);
1141 } while ((reason
= accept(username
, password
)));
1144 if (SecurityAgent::noReason
== reason
)
1145 logger
.logSuccess();
1146 // else we logged the denial in the loop
1152 QueryKeychainAuth::accept(string
&username
, string
&passphrase
)
1154 // Note: QueryKeychainAuth currently requires that the
1155 // specified user be in the admin group. If this requirement
1156 // ever needs to change, the group name should be passed as
1157 // a separate argument to this method.
1159 const char *user
= username
.c_str();
1160 const char *passwd
= passphrase
.c_str();
1161 int checkpw_status
= checkpw(user
, passwd
);
1163 if (checkpw_status
!= CHECKPW_SUCCESS
) {
1164 return SecurityAgent::invalidPassphrase
;
1167 const char *group
= "admin";
1170 uuid_t group_uuid
, user_uuid
;
1171 rc
= mbr_group_name_to_uuid(group
, group_uuid
);
1172 if (rc
) { return SecurityAgent::userNotInGroup
; }
1174 rc
= mbr_user_name_to_uuid(user
, user_uuid
);
1175 if (rc
) { return SecurityAgent::userNotInGroup
; }
1177 rc
= mbr_check_membership(user_uuid
, group_uuid
, &ismember
);
1178 if (rc
|| !ismember
) { return SecurityAgent::userNotInGroup
; }
1181 return SecurityAgent::noReason
;