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