]> git.saurik.com Git - apple/security.git/blob - securityd/src/agentquery.cpp
Security-58286.251.4.tar.gz
[apple/security.git] / securityd / src / agentquery.cpp
1 /*
2 * Copyright (c) 2000-2015 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 //
25 // passphrases - canonical code to obtain passphrases
26 //
27 #define __STDC_WANT_LIB_EXT1__ 1
28 #include <string.h>
29
30 #include "agentquery.h"
31 #include "ccaudit_extensions.h"
32
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>
44 #include <stdlib.h>
45 #include <xpc/xpc.h>
46 #include <xpc/private.h>
47 #include "securityd_service/securityd_service/securityd_service_client.h"
48
49 // Includes for <rdar://problem/34677969> Always require the user's password on keychain approval dialogs
50 #include "server.h"
51
52 #define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agent"
53 #define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE "com.apple.security.agent.login"
54
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"
60
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"
80
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))) }
83
84
85 // SecurityAgentXPCConnection
86
87 SecurityAgentXPCConnection::SecurityAgentXPCConnection(Session &session)
88 : mHostInstance(session.authhost()),
89 mSession(session),
90 mConnection(&Server::connection()),
91 mAuditToken(Server::connection().auditToken())
92 {
93 // this may take a while
94 Server::active().longTermActivity();
95 secnotice("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
96 mXPCConnection = NULL;
97 mNobodyUID = -2;
98 struct passwd *pw = getpwnam("nobody");
99 if (NULL != pw) {
100 mNobodyUID = pw->pw_uid;
101 }
102 }
103
104 SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
105 {
106 secnotice("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
107 mConnection->useAgent(NULL);
108
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);
114
115 // Then release the XPC connection
116 xpc_release(mXPCConnection);
117 mXPCConnection = NULL;
118 }
119 }
120
121 bool SecurityAgentXPCConnection::inDarkWake()
122 {
123 return mSession.server().inDarkWake();
124 }
125
126 void
127 SecurityAgentXPCConnection::activate(bool ignoreUid)
128 {
129 secnotice("SecurityAgentConnection", "activate(%p)", this);
130
131 mConnection->useAgent(this);
132 if (mXPCConnection != NULL) {
133 // If we already have an XPC connection, there's nothing to do.
134 return;
135 }
136
137 try {
138 uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId());
139
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);
143 if (inDarkWake())
144 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
145 uid_t targetUid = mHostInstance->session().originatorUid();
146
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");
152 } else {
153 mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE, NULL, 0);
154 xpc_connection_set_instance(mXPCConnection, sessionUUID);
155 secnotice("SecurityAgentXPCConnection", "Creating a loginwindow security agent");
156 }
157
158 xpc_connection_set_event_handler(mXPCConnection, ^(xpc_object_t object) {
159 if (xpc_get_type(object) == XPC_TYPE_ERROR) {
160 secnotice("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
161 }
162 });
163 xpc_connection_resume(mXPCConnection);
164 secnotice("SecurityAgentXPCConnection", "%p activated", this);
165 }
166 catch (MacOSError &err) {
167 mConnection->useAgent(NULL); // guess not
168 Syslog::error("SecurityAgentConnection: error activating SecurityAgent instance %p", this);
169 throw;
170 }
171
172 secnotice("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
173 }
174
175 void
176 SecurityAgentXPCConnection::terminate()
177 {
178 activate(false);
179
180 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
181 mConnection->useAgent(NULL);
182 }
183
184
185 using SecurityAgent::Reason;
186 using namespace Authorization;
187
188 ModuleNexus<RecursiveMutex> gAllXPCClientsMutex;
189 ModuleNexus<set<SecurityAgentXPCQuery*> > allXPCClients;
190
191 void
192 SecurityAgentXPCQuery::killAllXPCClients()
193 {
194 // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
195 StLock<Mutex> _(gAllXPCClientsMutex());
196
197 set<SecurityAgentXPCQuery*>::iterator clientIterator = allXPCClients().begin();
198 while (clientIterator != allXPCClients().end())
199 {
200 set<SecurityAgentXPCQuery*>::iterator thisClient = clientIterator++;
201 if ((*thisClient)->getTerminateOnSleep())
202 {
203 (*thisClient)->terminate();
204 }
205 }
206 }
207
208
209 SecurityAgentXPCQuery::SecurityAgentXPCQuery(Session &session)
210 : SecurityAgentXPCConnection(session), mAgentConnected(false), mTerminateOnSleep(false)
211 {
212 secnotice("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
213 }
214
215 SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
216 {
217 secnotice("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
218 if (mAgentConnected) {
219 this->disconnect();
220 }
221 }
222
223 void
224 SecurityAgentXPCQuery::inferHints(Process &thisProcess)
225 {
226 AuthItemSet clientHints;
227 SecurityAgent::RequestorType type = SecurityAgent::bundle;
228 pid_t clientPid = thisProcess.pid();
229 uid_t clientUid = thisProcess.uid();
230 string guestPath = thisProcess.getPath();
231 Boolean ignoreSession = TRUE;
232
233 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
234 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(guestPath)));
235 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
236 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
237
238 /*
239 * If its loginwindow that's asking, override the loginwindow shield detection
240 * up front so that it can trigger SecurityAgent dialogs (like password change)
241 * for when the OD password and keychain password is out of sync.
242 */
243
244 if (guestPath == "/System/Library/CoreServices/loginwindow.app") {
245 clientHints.insert(AuthItemRef(AGENT_HINT_IGNORE_SESSION, AuthValueOverlay(sizeof(ignoreSession), &ignoreSession)));
246 }
247
248 mClientHints.insert(clientHints.begin(), clientHints.end());
249
250 bool validSignature = thisProcess.checkAppleSigned();
251 AuthItemSet clientImmutableHints;
252
253 clientImmutableHints.insert(AuthItemRef(AGENT_HINT_CLIENT_SIGNED, AuthValueOverlay(sizeof(validSignature), &validSignature)));
254
255 mImmutableHints.insert(clientImmutableHints.begin(), clientImmutableHints.end());
256 }
257
258 void SecurityAgentXPCQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
259 {
260 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
261 mClientHints.insert(AuthItemRef(item));
262 }
263
264
265 void
266 SecurityAgentXPCQuery::readChoice()
267 {
268 allow = false;
269 remember = false;
270
271 AuthItem *allowAction = mOutContext.find(AGENT_CONTEXT_ALLOW);
272 if (allowAction)
273 {
274 string allowString;
275 if (allowAction->getString(allowString)
276 && (allowString == "YES"))
277 allow = true;
278 }
279
280 AuthItem *rememberAction = mOutContext.find(AGENT_CONTEXT_REMEMBER_ACTION);
281 if (rememberAction)
282 {
283 string rememberString;
284 if (rememberAction->getString(rememberString)
285 && (rememberString == "YES"))
286 remember = true;
287 }
288 }
289
290 void
291 SecurityAgentXPCQuery::disconnect()
292 {
293 if (NULL != mXPCConnection) {
294 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
295 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
296 xpc_connection_send_message(mXPCConnection, requestObject);
297 xpc_release(requestObject);
298 }
299
300 StLock<Mutex> _(gAllXPCClientsMutex());
301 allXPCClients().erase(this);
302 }
303
304 void
305 SecurityAgentXPCQuery::terminate()
306 {
307 this->disconnect();
308 }
309
310 static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {
311 setToBuild->clear();
312
313 xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {
314 const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);
315
316 size_t length;
317 const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);
318 void *dataCopy = 0;
319
320 // <rdar://problem/13033889> authd is holding on to multiple copies of my password in the clear
321 bool sensitive = xpc_dictionary_get_value(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);
322 if (sensitive) {
323 size_t sensitiveLength = (size_t)xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_SENSITIVE_VALUE_LENGTH);
324 if (sensitiveLength > length) {
325 secnotice("SecurityAgentXPCQuery", "Sensitive data len %zu is not valid", sensitiveLength);
326 return true;
327 }
328 dataCopy = malloc(sensitiveLength);
329 memcpy(dataCopy, data, sensitiveLength);
330 memset_s((void *)data, length, 0, sensitiveLength); // clear the sensitive data, memset_s is never optimized away
331 length = sensitiveLength;
332 } else {
333 dataCopy = malloc(length);
334 memcpy(dataCopy, data, length);
335 }
336
337 uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);
338 AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);
339 setToBuild->insert(nextItem);
340 memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.
341 free(dataCopy);
342 return true;
343 });
344 }
345
346 void
347 SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId)
348 {
349 bool ignoreUid = false;
350
351 do {
352 activate(ignoreUid);
353
354 mAgentConnected = false;
355
356 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
357 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
358 xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, pluginId);
359 xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanismId);
360
361 uid_t targetUid = Server::process().uid();
362 bool doSwitchAudit = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
363 bool doSwitchBootstrap = (ignoreUid || targetUid == 0 || targetUid == mNobodyUID);
364
365 if (doSwitchAudit) {
366 mach_port_name_t jobPort;
367 if (0 == audit_session_port(mSession.sessionId(), &jobPort)) {
368 secnotice("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid);
369 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
370 if (mach_port_mod_refs(mach_task_self(), jobPort, MACH_PORT_RIGHT_SEND, -1) != KERN_SUCCESS) {
371 secnotice("SecurityAgentXPCQuery", "unable to release send right for audit session, leaking");
372 }
373 }
374 }
375
376 if (doSwitchBootstrap) {
377 secnotice("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid);
378 MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
379 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, processBootstrap);
380 }
381
382 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
383 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
384 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
385 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
386 uint64_t status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
387 if (status == kAuthorizationResultAllow) {
388 mAgentConnected = true;
389 } else {
390 secnotice("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
391 MacOSError::throwMe(errAuthorizationInternal);
392 }
393 }
394 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
395 if (XPC_ERROR_CONNECTION_INVALID == object) {
396 // If we get an error before getting the create response, try again without the UID
397 if (ignoreUid) {
398 secnotice("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
399 xpc_release(object);
400 MacOSError::throwMe(errAuthorizationInternal);
401 } else {
402 secnotice("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
403 ignoreUid = true;
404 xpc_release(mXPCConnection);
405 mXPCConnection = NULL;
406 }
407 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
408 // If we get an error before getting the create response, try again
409 }
410 }
411 xpc_release(object);
412 xpc_release(requestObject);
413 } while (!mAgentConnected);
414
415 StLock<Mutex> _(gAllXPCClientsMutex());
416 allXPCClients().insert(this);
417 }
418
419 static xpc_object_t authItemSetToXPCArray(AuthItemSet input) {
420 xpc_object_t outputArray = xpc_array_create(NULL, 0);
421 for (AuthItemSet::iterator i = input.begin(); i != input.end(); i++) {
422 AuthItemRef item = *i;
423
424 xpc_object_t xpc_data = xpc_dictionary_create(NULL, NULL, 0);
425 xpc_dictionary_set_string(xpc_data, AUTH_XPC_ITEM_NAME, item->name());
426 AuthorizationValue value = item->value();
427 if (value.data != NULL) {
428 xpc_dictionary_set_data(xpc_data, AUTH_XPC_ITEM_VALUE, value.data, value.length);
429 }
430 xpc_dictionary_set_uint64(xpc_data, AUTH_XPC_ITEM_FLAGS, item->flags());
431 xpc_array_append_value(outputArray, xpc_data);
432 xpc_release(xpc_data);
433 }
434 return outputArray;
435 }
436
437 void
438 SecurityAgentXPCQuery::invoke() {
439 xpc_object_t hintsArray = authItemSetToXPCArray(mInHints);
440 xpc_object_t contextArray = authItemSetToXPCArray(mInContext);
441 xpc_object_t immutableHintsArray = authItemSetToXPCArray(mImmutableHints);
442
443 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
444 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
445 xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
446 xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
447 xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
448
449 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
450 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
451 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
452 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
453 xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
454 xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
455 AuthItemSet tempHints, tempContext;
456 xpcArrayToAuthItemSet(&tempHints, xpcHints);
457 xpcArrayToAuthItemSet(&tempContext, xpcContext);
458 mOutHints = tempHints;
459 mOutContext = tempContext;
460 mLastResult = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
461 }
462 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
463 if (XPC_ERROR_CONNECTION_INVALID == object) {
464 // If the connection drops, return an "auth undefined" result, because we cannot continue
465 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
466 // If the agent dies, return an "auth undefined" result, because we cannot continue
467 }
468 }
469 xpc_release(object);
470
471 xpc_release(hintsArray);
472 xpc_release(contextArray);
473 xpc_release(immutableHintsArray);
474 xpc_release(requestObject);
475 }
476
477 void SecurityAgentXPCQuery::checkResult()
478 {
479 // now check the OSStatus return from the server side
480 switch (mLastResult) {
481 case kAuthorizationResultAllow: return;
482 case kAuthorizationResultDeny:
483 case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
484 default: MacOSError::throwMe(errAuthorizationInternal);
485 }
486 }
487
488 //
489 // Perform the "rogue app" access query dialog
490 //
491 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
492 : mPassphraseCheck(NULL)
493 {
494 // if passphrase checking requested, save KeychainDatabase reference
495 // (will quietly disable check if db isn't a keychain)
496
497 // Always require password due to <rdar://problem/34677969>
498 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
499
500 setTerminateOnSleep(true);
501 }
502
503 // Callers to this function must hold the common lock
504 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
505 {
506 Reason reason = SecurityAgent::noReason;
507 uint32_t retryCount = 0;
508 AuthItemSet hints, context;
509
510 // prepopulate with client hints
511 hints.insert(mClientHints.begin(), mClientHints.end());
512
513 // put action/operation (sint32) into hints
514 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
515
516 // item name into hints
517
518 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
519
520 // keychain name into hints
521 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
522
523 if (mPassphraseCheck)
524 {
525 create("builtin", "confirm-access-password");
526
527 CssmAutoData data(Allocator::standard(Allocator::sensitive));
528
529 do
530 {
531
532 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
533 hints.erase(triesHint); hints.insert(triesHint); // replace
534
535 if (retryCount++ > kMaximumAuthorizationTries)
536 {
537 reason = SecurityAgent::tooManyTries;
538 }
539
540 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
541 hints.erase(retryHint); hints.insert(retryHint); // replace
542
543 setInput(hints, context);
544
545 {
546 // Must drop the common lock while showing UI.
547 StSyncLock<Mutex, Mutex> syncLock(const_cast<KeychainDatabase*>(mPassphraseCheck)->common().uiLock(), const_cast<KeychainDatabase*>(mPassphraseCheck)->common());
548 invoke();
549 }
550
551 if (retryCount > kMaximumAuthorizationTries)
552 {
553 return reason;
554 }
555
556 checkResult();
557
558 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
559 if (!passwordItem)
560 continue;
561
562 passwordItem->getCssmData(data);
563
564 reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase);
565 }
566 while (reason != SecurityAgent::noReason);
567
568 readChoice();
569 }
570 else
571 {
572 // create("builtin", "confirm-access");
573 // setInput(hints, context);
574 // invoke();
575
576 // This is a hack to support <rdar://problem/34677969>, we can never simply prompt for confirmation
577 secerror("ACL validation fallback case! Must ask user for account password because we have no database");
578 Session &session = Server::session();
579 try{
580 session.verifyKeyStorePassphrase(1, true, description);
581 } catch (...) {
582 return SecurityAgent::invalidPassphrase;
583 }
584 SecurityAgentXPCQuery::allow = true;
585 }
586
587 return reason;
588 }
589
590 //
591 // Obtain passphrases and submit them to the accept() method until it is accepted
592 // or we can't get another passphrase. Accept() should consume the passphrase
593 // if it is accepted. If no passphrase is acceptable, throw out of here.
594 //
595 Reason QueryOld::query()
596 {
597 Reason reason = SecurityAgent::noReason;
598 AuthItemSet hints, context;
599 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
600 int retryCount = 0;
601
602 // prepopulate with client hints
603
604 const char *keychainPath = database.dbName();
605 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
606
607 hints.insert(mClientHints.begin(), mClientHints.end());
608
609 create("builtin", "unlock-keychain");
610
611 do
612 {
613 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
614 hints.erase(triesHint); hints.insert(triesHint); // replace
615
616 ++retryCount;
617
618 if (retryCount > maxTries)
619 {
620 reason = SecurityAgent::tooManyTries;
621 }
622
623 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
624 hints.erase(retryHint); hints.insert(retryHint); // replace
625
626 setInput(hints, context);
627 invoke();
628
629 if (retryCount > maxTries)
630 {
631 return reason;
632 }
633
634 checkResult();
635
636 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
637 if (!passwordItem)
638 continue;
639
640 passwordItem->getCssmData(passphrase);
641
642 }
643 while ((reason = accept(passphrase)));
644
645 return SecurityAgent::noReason;
646 }
647
648
649 //
650 // Get existing passphrase (unlock) Query
651 //
652 Reason QueryOld::operator () ()
653 {
654 return query();
655 }
656
657
658 //
659 // End-classes for old secrets
660 //
661 Reason QueryUnlock::accept(CssmManagedData &passphrase)
662 {
663 // Must hold the 'common' lock to call decode; otherwise there's a data corruption issue
664 StLock<Mutex> _(safer_cast<KeychainDatabase &>(database).common());
665
666 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
667 return SecurityAgent::noReason;
668 else
669 return SecurityAgent::invalidPassphrase;
670 }
671
672 Reason QueryUnlock::retrievePassword(CssmOwnedData &passphrase) {
673 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
674
675 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
676 if (!passwordItem)
677 return SecurityAgent::invalidPassphrase;
678
679 passwordItem->getCssmData(pass);
680
681 passphrase = pass;
682
683 return SecurityAgent::noReason;
684 }
685
686 QueryKeybagPassphrase::QueryKeybagPassphrase(Session & session, int32_t tries) : mSession(session), mContext(), mRetries(tries)
687 {
688 setTerminateOnSleep(true);
689 mContext = mSession.get_current_service_context();
690 }
691
692 Reason QueryKeybagPassphrase::query()
693 {
694 Reason reason = SecurityAgent::noReason;
695 AuthItemSet hints, context;
696 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
697 int retryCount = 0;
698
699 // prepopulate with client hints
700
701 const char *keychainPath = "iCloud";
702 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
703
704 hints.insert(mClientHints.begin(), mClientHints.end());
705
706 create("builtin", "unlock-keychain");
707
708 int currentTry = 0;
709 do
710 {
711 currentTry = retryCount;
712 if (retryCount > mRetries)
713 {
714 return SecurityAgent::tooManyTries;
715 }
716 retryCount++;
717
718 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), &currentTry));
719 hints.erase(triesHint); hints.insert(triesHint); // replace
720
721 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
722 hints.erase(retryHint); hints.insert(retryHint); // replace
723
724 setInput(hints, context);
725 invoke();
726
727 checkResult();
728
729 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
730 if (!passwordItem)
731 continue;
732
733 passwordItem->getCssmData(passphrase);
734 }
735 while ((reason = accept(passphrase)));
736
737 return SecurityAgent::noReason;
738 }
739
740 Reason QueryKeybagPassphrase::accept(Security::CssmManagedData & password)
741 {
742 if (service_client_kb_unlock(&mContext, password.data(), (int)password.length()) == 0) {
743 mSession.keybagSetState(session_keybag_unlocked);
744 return SecurityAgent::noReason;
745 } else
746 return SecurityAgent::invalidPassphrase;
747 }
748
749 QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session & session) : QueryKeybagPassphrase(session) {}
750
751 Reason QueryKeybagNewPassphrase::query(CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
752 {
753 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
754 CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
755 Reason reason = SecurityAgent::noReason;
756 AuthItemSet hints, context;
757 int retryCount = 0;
758
759 // prepopulate with client hints
760
761 const char *keychainPath = "iCloud";
762 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
763
764 const char *showResetString = "YES";
765 hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast<char*>(showResetString))));
766
767 hints.insert(mClientHints.begin(), mClientHints.end());
768
769 create("builtin", "change-passphrase");
770
771 int currentTry = 0;
772 AuthItem *resetPassword = NULL;
773 do
774 {
775 currentTry = retryCount;
776 if (retryCount > mRetries)
777 {
778 return SecurityAgent::tooManyTries;
779 }
780 retryCount++;
781
782 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(currentTry), &currentTry));
783 hints.erase(triesHint); hints.insert(triesHint); // replace
784
785 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
786 hints.erase(retryHint); hints.insert(retryHint); // replace
787
788 setInput(hints, context);
789 invoke();
790
791 checkResult();
792
793 resetPassword = mOutContext.find(AGENT_CONTEXT_RESET_PASSWORD);
794 if (resetPassword != NULL) {
795 return SecurityAgent::resettingPassword;
796 }
797
798 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
799 if (!oldPasswordItem)
800 continue;
801
802 oldPasswordItem->getCssmData(oldPass);
803 }
804 while ((reason = accept(oldPass)));
805
806 if (reason == SecurityAgent::noReason) {
807 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
808 if (!passwordItem)
809 return SecurityAgent::invalidPassphrase;
810
811 passwordItem->getCssmData(pass);
812
813 oldPassphrase = oldPass;
814 passphrase = pass;
815 }
816
817 return SecurityAgent::noReason;
818 }
819
820 QueryPIN::QueryPIN(Database &db)
821 : QueryOld(db), mPin(Allocator::standard())
822 {
823 this->inferHints(Server::process());
824 }
825
826
827 Reason QueryPIN::accept(CssmManagedData &pin)
828 {
829 // no retries for now
830 mPin = pin;
831 return SecurityAgent::noReason;
832 }
833
834
835 //
836 // Obtain passphrases and submit them to the accept() method until it is accepted
837 // or we can't get another passphrase. Accept() should consume the passphrase
838 // if it is accepted. If no passphrase is acceptable, throw out of here.
839 //
840 Reason QueryNewPassphrase::query()
841 {
842 Reason reason = initialReason;
843 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
844 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
845
846 AuthItemSet hints, context;
847
848 int retryCount = 0;
849
850 // prepopulate with client hints
851 hints.insert(mClientHints.begin(), mClientHints.end());
852
853 // keychain name into hints
854 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
855
856 switch (initialReason)
857 {
858 case SecurityAgent::newDatabase:
859 create("builtin", "new-passphrase");
860 break;
861 case SecurityAgent::changePassphrase:
862 create("builtin", "change-passphrase");
863 break;
864 default:
865 assert(false);
866 }
867
868 do
869 {
870 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
871 hints.erase(triesHint); hints.insert(triesHint); // replace
872
873 if (++retryCount > maxTries)
874 {
875 reason = SecurityAgent::tooManyTries;
876 }
877
878 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
879 hints.erase(retryHint); hints.insert(retryHint); // replace
880
881 setInput(hints, context);
882 invoke();
883
884 if (retryCount > maxTries)
885 {
886 return reason;
887 }
888
889 checkResult();
890
891 if (SecurityAgent::changePassphrase == initialReason)
892 {
893 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
894 if (!oldPasswordItem)
895 continue;
896
897 oldPasswordItem->getCssmData(oldPassphrase);
898 }
899
900 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
901 if (!passwordItem)
902 continue;
903
904 passwordItem->getCssmData(passphrase);
905
906 }
907 while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)));
908
909 return SecurityAgent::noReason;
910 }
911
912
913 //
914 // Get new passphrase Query
915 //
916 Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
917 {
918 if (Reason result = query())
919 return result; // failed
920 passphrase = mPassphrase;
921 oldPassphrase = mOldPassphrase;
922 return SecurityAgent::noReason; // success
923 }
924
925 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
926 {
927 //@@@ acceptance criteria are currently hardwired here
928 //@@@ This validation presumes ASCII - UTF8 might be more lenient
929
930 // if we have an old passphrase, check it
931 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
932 return SecurityAgent::oldPassphraseWrong;
933
934 // sanity check the new passphrase (but allow user override)
935 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
936 mPassphrase = passphrase;
937 if (oldPassphrase) mOldPassphrase = *oldPassphrase;
938 mPassphraseValid = true;
939 if (mPassphrase.length() == 0)
940 return SecurityAgent::passphraseIsNull;
941 if (mPassphrase.length() < 6)
942 return SecurityAgent::passphraseTooSimple;
943 }
944
945 // accept this
946 return SecurityAgent::noReason;
947 }
948
949 //
950 // Get a passphrase for unspecified use
951 //
952 Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
953 string &passphrase)
954 {
955 return query(prompt, verify, passphrase);
956 }
957
958 Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
959 string &passphrase)
960 {
961 Reason reason = SecurityAgent::noReason;
962 AuthItemSet hints, context;
963
964 hints.insert(mClientHints.begin(), mClientHints.end());
965 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
966 // XXX/gh defined by dmitch but no analogous hint in
967 // AuthorizationTagsPriv.h:
968 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
969
970 if (false == verify) { // import
971 create("builtin", "generic-unlock");
972 } else { // verify passphrase (export)
973 create("builtin", "generic-new-passphrase");
974 }
975
976 AuthItem *passwordItem;
977
978 do {
979 setInput(hints, context);
980 invoke();
981 checkResult();
982 passwordItem = mOutContext.find(AGENT_PASSWORD);
983
984 } while (!passwordItem);
985
986 passwordItem->getString(passphrase);
987
988 return reason;
989 }
990
991
992 //
993 // Get a DB blob's passphrase--keychain synchronization
994 //
995 Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
996 {
997 return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
998 }
999
1000 Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1001 {
1002 Reason reason = SecurityAgent::noReason;
1003 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1004 AuthItemSet hints/*NUKEME*/, context;
1005
1006 hints.insert(mClientHints.begin(), mClientHints.end());
1007 create("builtin", "generic-unlock-kcblob");
1008
1009 AuthItem *secretItem;
1010
1011 int retryCount = 0;
1012
1013 do {
1014 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1015 hints.erase(triesHint); hints.insert(triesHint); // replace
1016
1017 if (++retryCount > maxTries)
1018 {
1019 reason = SecurityAgent::tooManyTries;
1020 }
1021
1022 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1023 hints.erase(retryHint); hints.insert(retryHint); // replace
1024
1025 setInput(hints, context);
1026 invoke();
1027 checkResult();
1028 secretItem = mOutContext.find(AGENT_PASSWORD);
1029 if (!secretItem)
1030 continue;
1031 secretItem->getCssmData(passphrase);
1032
1033 } while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated)));
1034
1035 return reason;
1036 }
1037
1038 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
1039 DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
1040 {
1041 DbHandle *currHdl = dbHandlesToAuthenticate;
1042 short index;
1043 Boolean authenticated = false;
1044 for (index=0; index < dbHandleCount && !authenticated; index++)
1045 {
1046 try
1047 {
1048 RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
1049 dbToUnlock->unlockDb(passphrase, false);
1050 authenticated = true;
1051 *dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with.
1052 }
1053 catch (const CommonError &err)
1054 {
1055 currHdl++; // we failed to authenticate with this one, onto the next one.
1056 }
1057 }
1058 if ( !authenticated )
1059 return SecurityAgent::invalidPassphrase;
1060
1061 return SecurityAgent::noReason;
1062 }
1063
1064 // @@@ no pluggable authentication possible!
1065 Reason
1066 QueryKeychainAuth::operator () (const char *database, const char *description, AclAuthorization action, const char *prompt)
1067 {
1068 Reason reason = SecurityAgent::noReason;
1069 AuthItemSet hints, context;
1070 int retryCount = 0;
1071 string username;
1072 string password;
1073
1074 using CommonCriteria::Securityd::KeychainAuthLogger;
1075 KeychainAuthLogger logger(mAuditToken, (short)AUE_ssauthint, database, description);
1076
1077 hints.insert(mClientHints.begin(), mClientHints.end());
1078
1079 // put action/operation (sint32) into hints
1080 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
1081
1082 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast<char*>(prompt))));
1083
1084 // item name into hints
1085 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
1086
1087 // keychain name into hints
1088 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
1089
1090 create("builtin", "confirm-access-user-password");
1091
1092 AuthItem *usernameItem;
1093 AuthItem *passwordItem;
1094
1095 do {
1096
1097 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1098 hints.erase(triesHint); hints.insert(triesHint); // replace
1099
1100 if (++retryCount > maxTries)
1101 reason = SecurityAgent::tooManyTries;
1102
1103 if (SecurityAgent::noReason != reason)
1104 {
1105 if (SecurityAgent::tooManyTries == reason)
1106 logger.logFailure(NULL, CommonCriteria::errTooManyTries);
1107 else
1108 logger.logFailure();
1109 }
1110
1111 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1112 hints.erase(retryHint); hints.insert(retryHint); // replace
1113
1114 setInput(hints, context);
1115 try
1116 {
1117 invoke();
1118 checkResult();
1119 }
1120 catch (...) // user probably clicked "deny"
1121 {
1122 logger.logFailure();
1123 throw;
1124 }
1125 usernameItem = mOutContext.find(AGENT_USERNAME);
1126 passwordItem = mOutContext.find(AGENT_PASSWORD);
1127 if (!usernameItem || !passwordItem)
1128 continue;
1129 usernameItem->getString(username);
1130 passwordItem->getString(password);
1131 } while ((reason = accept(username, password)));
1132
1133 if (SecurityAgent::noReason == reason)
1134 logger.logSuccess();
1135 // else we logged the denial in the loop
1136
1137 return reason;
1138 }
1139
1140 Reason
1141 QueryKeychainAuth::accept(string &username, string &passphrase)
1142 {
1143 // Note: QueryKeychainAuth currently requires that the
1144 // specified user be in the admin group. If this requirement
1145 // ever needs to change, the group name should be passed as
1146 // a separate argument to this method.
1147
1148 const char *user = username.c_str();
1149 const char *passwd = passphrase.c_str();
1150 int checkpw_status = checkpw(user, passwd);
1151
1152 if (checkpw_status != CHECKPW_SUCCESS) {
1153 return SecurityAgent::invalidPassphrase;
1154 }
1155
1156 const char *group = "admin";
1157 if (group) {
1158 int rc, ismember;
1159 uuid_t group_uuid, user_uuid;
1160 rc = mbr_group_name_to_uuid(group, group_uuid);
1161 if (rc) { return SecurityAgent::userNotInGroup; }
1162
1163 rc = mbr_user_name_to_uuid(user, user_uuid);
1164 if (rc) { return SecurityAgent::userNotInGroup; }
1165
1166 rc = mbr_check_membership(user_uuid, group_uuid, &ismember);
1167 if (rc || !ismember) { return SecurityAgent::userNotInGroup; }
1168 }
1169
1170 return SecurityAgent::noReason;
1171 }
1172