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