]> git.saurik.com Git - apple/securityd.git/blob - src/agentquery.cpp
securityd-55199.3.tar.gz
[apple/securityd.git] / src / agentquery.cpp
1 /*
2 * Copyright (c) 2000-2004,2008-2009 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 "authority.h"
29 #include "ccaudit_extensions.h"
30
31 #include <Security/AuthorizationTags.h>
32 #include <Security/AuthorizationTagsPriv.h>
33 #include <Security/checkpw.h>
34 #include <Security/Security.h>
35 #include <System/sys/fileport.h>
36 #include <bsm/audit.h>
37 #include <bsm/audit_uevents.h> // AUE_ssauthint
38 #include <security_utilities/logging.h>
39 #include <security_utilities/mach++.h>
40 #include <stdlib.h>
41 #include <xpc/xpc.h>
42 #include <xpc/private.h>
43 #include "securityd_service/securityd_service/securityd_service_client.h"
44
45 #define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agentMain"
46 #define SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE "com.apple.security.agentStub"
47 #define AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE "com.apple.security.authhost"
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 #define AUTH_XPC_SESSION_UUID "_agent_session_uuid"
74 #define AUTH_XPC_SESSION_PREFS "_agent_session_prefs"
75 #define AUTH_XPC_SESSION_INPUT_METHOD "_agent_session_inputMethod"
76
77 #define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \
78 { 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))) }
79
80 //
81 // NOSA support functions. This is a test mode where the SecurityAgent
82 // is simulated via stdio in the client. Good for running automated tests
83 // of client programs. Only available if -DNOSA when compiling.
84 //
85 #if defined(NOSA)
86
87 #include <cstdarg>
88
89 static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
90 {
91 // write prompt
92 va_list args;
93 va_start(args, fmt);
94 vfprintf(stdout, fmt, args);
95 va_end(args);
96
97 // read reply
98 memset(buffer, 0, bufferSize);
99 const char *nosa = getenv("NOSA");
100 if (!strcmp(nosa, "-")) {
101 if (fgets(buffer, bufferSize-1, stdin) == NULL)
102 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
103 buffer[strlen(buffer)-1] = '\0'; // remove trailing newline
104 if (!isatty(fileno(stdin)))
105 printf("%s\n", buffer); // echo to output if input not terminal
106 } else {
107 strncpy(buffer, nosa, bufferSize-1);
108 printf("%s\n", buffer);
109 }
110 if (buffer[0] == '\0') // empty input -> cancellation
111 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
112 }
113
114 #endif //NOSA
115
116
117 // SecurityAgentConnection
118
119 SecurityAgentConnection::SecurityAgentConnection(const AuthHostType type, Session &session)
120 : mAuthHostType(type),
121 mHostInstance(session.authhost(mAuthHostType)),
122 mConnection(&Server::connection()),
123 mAuditToken(Server::connection().auditToken())
124 {
125 // this may take a while
126 Server::active().longTermActivity();
127 secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
128 }
129
130 SecurityAgentConnection::~SecurityAgentConnection()
131 {
132 secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
133 mConnection->useAgent(NULL);
134 }
135
136 void
137 SecurityAgentConnection::activate()
138 {
139 secdebug("SecurityAgentConnection", "activate(%p)", this);
140
141 Session &session = mHostInstance->session();
142 SessionId targetSessionId = session.sessionId();
143 MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
144 fileport_t userPrefsFP = MACH_PORT_NULL;
145
146 // send the the userPrefs to SecurityAgent
147 if (mAuthHostType == securityAgent || mAuthHostType == userAuthHost) {
148 CFRef<CFDataRef> userPrefs(mHostInstance->session().copyUserPrefs());
149 if (0 != userPrefs)
150 {
151 FILE *mbox = NULL;
152 int fd = 0;
153 mbox = tmpfile();
154 if (NULL != mbox)
155 {
156 fd = dup(fileno(mbox));
157 fclose(mbox);
158 if (fd != -1)
159 {
160 CFIndex length = CFDataGetLength(userPrefs);
161 if (write(fd, CFDataGetBytePtr(userPrefs), length) != length)
162 Syslog::error("could not write userPrefs");
163 else
164 {
165 if (0 == fileport_makeport(fd, &userPrefsFP))
166 secdebug("SecurityAgentConnection", "stashed the userPrefs file descriptor");
167 else
168 Syslog::error("failed to stash the userPrefs file descriptor");
169 }
170 close(fd);
171 }
172 }
173 }
174 if (MACH_PORT_NULL == userPrefsFP)
175 {
176 secdebug("SecurityAgentConnection", "could not read userPrefs");
177 }
178 }
179
180 mConnection->useAgent(this);
181 try
182 {
183 StLock<Mutex> _(*mHostInstance);
184
185 mach_port_t lookupPort = mHostInstance->lookup(targetSessionId);
186 if (MACH_PORT_NULL == lookupPort)
187 {
188 Syslog::error("could not find real service, bailing");
189 MacOSError::throwMe(CSSM_ERRCODE_SERVICE_NOT_AVAILABLE);
190 }
191 // reset Client contact info
192 mPort = lookupPort;
193 SecurityAgent::Client::activate(mPort);
194
195 secdebug("SecurityAgentConnection", "%p activated", this);
196 }
197 catch (MacOSError &err)
198 {
199 mConnection->useAgent(NULL); // guess not
200 Syslog::error("SecurityAgentConnection: error activating %s instance %p",
201 mAuthHostType == privilegedAuthHost
202 ? "authorizationhost"
203 : "SecurityAgent", this);
204 throw;
205 }
206
207 secdebug("SecurityAgentConnection", "contacting service (%p)", this);
208 mach_port_name_t jobPort;
209 if (0 > audit_session_port(session.sessionId(), &jobPort))
210 Syslog::error("audit_session_port failed: %m");
211 MacOSError::check(SecurityAgent::Client::contact(jobPort, processBootstrap, userPrefsFP));
212 secdebug("SecurityAgentConnection", "contact didn't throw (%p)", this);
213
214 if (userPrefsFP != MACH_PORT_NULL)
215 mach_port_deallocate(mach_task_self(), userPrefsFP);
216 }
217
218 void
219 SecurityAgentConnection::reconnect()
220 {
221 // if !mHostInstance throw()?
222 if (mHostInstance)
223 {
224 activate();
225 }
226 }
227
228 void
229 SecurityAgentConnection::terminate()
230 {
231 activate();
232
233 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
234 mConnection->useAgent(NULL);
235 }
236
237
238 // SecurityAgentConnection
239
240 SecurityAgentXPCConnection::SecurityAgentXPCConnection(const AuthHostType type, Session &session)
241 : mAuthHostType(type),
242 mHostInstance(session.authhost(mAuthHostType)),
243 mSession(session),
244 mConnection(&Server::connection()),
245 mAuditToken(Server::connection().auditToken())
246 {
247 // this may take a while
248 Server::active().longTermActivity();
249 secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
250 mXPCConnection = NULL;
251 mNobodyUID = -2;
252 struct passwd *pw = getpwnam("nobody");
253 if (NULL != pw) {
254 mNobodyUID = pw->pw_uid;
255 }
256 }
257
258 SecurityAgentXPCConnection::~SecurityAgentXPCConnection()
259 {
260 secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
261 mConnection->useAgent(NULL);
262
263 // If a connection has been established, we need to tear it down.
264 if (NULL != mXPCConnection) {
265 // Tearing this down is a multi-step process. First, request a cancellation.
266 // This is safe even if the connection is already in the cancelled state.
267 xpc_connection_cancel(mXPCConnection);
268
269 // Then release the XPC connection
270 xpc_release(mXPCConnection);
271 mXPCConnection = NULL;
272
273 if (NULL != mXPCStubConnection) {
274 // We may or may not have one of these
275 xpc_release(mXPCStubConnection);
276 mXPCStubConnection = NULL;
277 }
278 }
279 }
280
281 bool SecurityAgentXPCConnection::inDarkWake()
282 {
283 return mSession.server().inDarkWake();
284 }
285
286 void
287 SecurityAgentXPCConnection::activate(bool ignoreUid)
288 {
289 secdebug("SecurityAgentConnection", "activate(%p)", this);
290
291 mConnection->useAgent(this);
292 if (mXPCConnection != NULL) {
293 // If we already have an XPC connection, there's nothing to do.
294 return;
295 }
296 try
297 {
298 if (mAuthHostType == securityAgent) {
299 uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID(mSession.sessionId());
300 // Yes, these need to be throws, as we're still in securityd, and thus still have to do flow control with exceptions.
301 if (!(mSession.attributes() & sessionHasGraphicAccess))
302 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
303 if (inDarkWake())
304 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
305 uid_t targetUid = mHostInstance->session().originatorUid();
306 secdebug("SecurityAgentXPCConnection","Retrieved UID %d for this session", targetUid);
307 if ((int32_t)targetUid != -1) {
308 mXPCStubConnection = xpc_connection_create_mach_service(SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE, NULL, 0);
309 xpc_connection_set_target_uid(mXPCStubConnection, targetUid);
310 secdebug("SecurityAgentXPCConnection", "Creating a security agent stub");
311 xpc_connection_set_event_handler(mXPCStubConnection, ^(xpc_object_t object){}); // Yes, this is a dummy handler, we never ever care about any responses from the stub. It can die in a fire for all I care.
312 xpc_connection_resume(mXPCStubConnection);
313
314 xpc_object_t wakeupMessage = xpc_dictionary_create(NULL, NULL, 0);
315 xpc_dictionary_set_data(wakeupMessage, AUTH_XPC_SESSION_UUID, sessionUUID, sizeof(uuid_t));
316 xpc_object_t responseMessage = xpc_connection_send_message_with_reply_sync(mXPCStubConnection, wakeupMessage);
317 if (xpc_get_type(responseMessage) == XPC_TYPE_DICTIONARY) {
318 secdebug("SecurityAgentXPCConnection", "Valid response received from stub");
319 } else {
320 secdebug("SecurityAgentXPCConnection", "Error response received from stub");
321 }
322 xpc_release(wakeupMessage);
323 xpc_release(responseMessage);
324 }
325
326 mXPCConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL,0);
327 xpc_connection_set_instance(mXPCConnection, sessionUUID);
328 secdebug("SecurityAgentXPCConnection", "Creating a security agent");
329 } else {
330 mXPCConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0);
331 secdebug("SecurityAgentXPCConnection", "Creating a standard authhost");
332 }
333
334 xpc_connection_set_event_handler(mXPCConnection, ^(xpc_object_t object) {
335 if (xpc_get_type(object) == XPC_TYPE_ERROR) {
336 secdebug("SecurityAgentXPCConnection", "error during xpc: %s", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
337 }
338 });
339
340 xpc_connection_resume(mXPCConnection);
341
342 secdebug("SecurityAgentXPCConnection", "%p activated", this);
343 }
344 catch (MacOSError &err)
345 {
346 mConnection->useAgent(NULL); // guess not
347 Syslog::error("SecurityAgentConnection: error activating %s instance %p",
348 mAuthHostType == privilegedAuthHost
349 ? "authorizationhost"
350 : "SecurityAgent", this);
351 throw;
352 }
353
354 secdebug("SecurityAgentXPCConnection", "contact didn't throw (%p)", this);
355 }
356
357 void
358 SecurityAgentXPCConnection::reconnect()
359 {
360 }
361
362 void
363 SecurityAgentXPCConnection::terminate()
364 {
365 activate(false);
366
367 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
368 mConnection->useAgent(NULL);
369 }
370
371
372 using SecurityAgent::Reason;
373 using namespace Authorization;
374
375 SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session)
376 : SecurityAgentConnection(type, session)
377 {
378 secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
379 }
380
381 SecurityAgentQuery::~SecurityAgentQuery()
382 {
383 secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
384
385 #if defined(NOSA)
386 if (getenv("NOSA")) {
387 printf(" [query done]\n");
388 return;
389 }
390 #endif
391
392 if (SecurityAgent::Client::state() != SecurityAgent::Client::dead)
393 destroy();
394 }
395
396 void
397 SecurityAgentQuery::inferHints(Process &thisProcess)
398 {
399 string guestPath;
400 {
401 StLock<Mutex> _(thisProcess);
402 if (SecCodeRef clientCode = thisProcess.currentGuest())
403 guestPath = codePath(clientCode);
404 }
405 AuthItemSet processHints = clientHints(SecurityAgent::bundle, guestPath,
406 thisProcess.pid(), thisProcess.uid());
407 mClientHints.insert(processHints.begin(), processHints.end());
408 }
409
410 void SecurityAgentQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
411 {
412 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
413 mClientHints.insert(AuthItemRef(item));
414 }
415
416
417 void
418 SecurityAgentQuery::readChoice()
419 {
420 allow = false;
421 remember = false;
422
423 AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW);
424 if (allowAction)
425 {
426 string allowString;
427 if (allowAction->getString(allowString)
428 && (allowString == "YES"))
429 allow = true;
430 }
431
432 AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION);
433 if (rememberAction)
434 {
435 string rememberString;
436 if (rememberAction->getString(rememberString)
437 && (rememberString == "YES"))
438 remember = true;
439 }
440 }
441
442 void
443 SecurityAgentQuery::disconnect()
444 {
445 SecurityAgent::Client::destroy();
446 }
447
448 void
449 SecurityAgentQuery::terminate()
450 {
451 // you might think these are called in the wrong order, but you'd be wrong
452 SecurityAgentConnection::terminate();
453 SecurityAgent::Client::terminate();
454 }
455
456 void
457 SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
458 {
459 activate();
460 OSStatus status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
461 if (status)
462 {
463 secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
464 reconnect();
465 status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
466 }
467 if (status) MacOSError::throwMe(status);
468 }
469
470 ModuleNexus<RecursiveMutex> gAllXPCClientsMutex;
471 ModuleNexus<set<SecurityAgentXPCQuery*> > allXPCClients;
472
473 void
474 SecurityAgentXPCQuery::killAllXPCClients()
475 {
476 // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
477 StLock<Mutex> _(gAllXPCClientsMutex());
478
479 set<SecurityAgentXPCQuery*>::iterator clientIterator = allXPCClients().begin();
480 while (clientIterator != allXPCClients().end())
481 {
482 set<SecurityAgentXPCQuery*>::iterator thisClient = clientIterator++;
483 if ((*thisClient)->getTerminateOnSleep())
484 {
485 (*thisClient)->terminate();
486 }
487 }
488 }
489
490
491 SecurityAgentXPCQuery::SecurityAgentXPCQuery(const AuthHostType type, Session &session)
492 : SecurityAgentXPCConnection(type, session), mAgentConnected(false), mTerminateOnSleep(false)
493 {
494 secdebug("SecurityAgentXPCQuery", "new SecurityAgentXPCQuery(%p)", this);
495 }
496
497 SecurityAgentXPCQuery::~SecurityAgentXPCQuery()
498 {
499 secdebug("SecurityAgentXPCQuery", "SecurityAgentXPCQuery(%p) dying", this);
500 if (mAgentConnected) {
501 this->disconnect();
502 }
503 }
504
505 void
506 SecurityAgentXPCQuery::inferHints(Process &thisProcess)
507 {
508 string guestPath;
509 if (SecCodeRef clientCode = thisProcess.currentGuest())
510 guestPath = codePath(clientCode);
511
512 AuthItemSet clientHints;
513 SecurityAgent::RequestorType type = SecurityAgent::bundle;
514 pid_t clientPid = thisProcess.pid();
515 uid_t clientUid = thisProcess.uid();
516
517 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
518 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(guestPath)));
519 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
520 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
521
522
523 mClientHints.insert(clientHints.begin(), clientHints.end());
524
525 bool validSignature = thisProcess.checkAppleSigned();
526 AuthItemSet clientImmutableHints;
527
528 clientImmutableHints.insert(AuthItemRef(AGENT_HINT_PROCESS_SIGNED, AuthValueOverlay(sizeof(validSignature), &validSignature)));
529
530 mImmutableHints.insert(clientImmutableHints.begin(), clientImmutableHints.end());
531 }
532
533 void SecurityAgentXPCQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
534 {
535 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
536 mClientHints.insert(AuthItemRef(item));
537 }
538
539
540 void
541 SecurityAgentXPCQuery::readChoice()
542 {
543 allow = false;
544 remember = false;
545
546 AuthItem *allowAction = mOutContext.find(AGENT_CONTEXT_ALLOW);
547 if (allowAction)
548 {
549 string allowString;
550 if (allowAction->getString(allowString)
551 && (allowString == "YES"))
552 allow = true;
553 }
554
555 AuthItem *rememberAction = mOutContext.find(AGENT_CONTEXT_REMEMBER_ACTION);
556 if (rememberAction)
557 {
558 string rememberString;
559 if (rememberAction->getString(rememberString)
560 && (rememberString == "YES"))
561 remember = true;
562 }
563 }
564
565 void
566 SecurityAgentXPCQuery::disconnect()
567 {
568 if (NULL != mXPCConnection) {
569 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
570 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY);
571 xpc_connection_send_message(mXPCConnection, requestObject);
572 xpc_release(requestObject);
573 }
574
575 StLock<Mutex> _(gAllXPCClientsMutex());
576 allXPCClients().erase(this);
577 }
578
579 void
580 SecurityAgentXPCQuery::terminate()
581 {
582 this->disconnect();
583 }
584
585 static void xpcArrayToAuthItemSet(AuthItemSet *setToBuild, xpc_object_t input) {
586 setToBuild->clear();
587
588 xpc_array_apply(input, ^bool(size_t index, xpc_object_t item) {
589 const char *name = xpc_dictionary_get_string(item, AUTH_XPC_ITEM_NAME);
590
591 size_t length;
592 const void *data = xpc_dictionary_get_data(item, AUTH_XPC_ITEM_VALUE, &length);
593 void *dataCopy = malloc(length);
594 memcpy(dataCopy, data, length);
595
596 uint64_t flags = xpc_dictionary_get_uint64(item, AUTH_XPC_ITEM_FLAGS);
597 AuthItemRef nextItem(name, AuthValueOverlay((uint32_t)length, dataCopy), (uint32_t)flags);
598 setToBuild->insert(nextItem);
599 memset(dataCopy, 0, length); // The authorization items contain things like passwords, so wiping clean is important.
600 free(dataCopy);
601 return true;
602 });
603 }
604
605 void
606 SecurityAgentXPCQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
607 {
608 bool ignoreUid = false;
609
610 do {
611 activate(ignoreUid);
612
613 mAgentConnected = false;
614
615 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
616 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE);
617 xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, pluginId);
618 xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanismId);
619
620 uid_t targetUid = Server::process().uid();
621 bool doSwitchAudit = true; // (ignoreUid) || ((targetUid == 0) || (targetUid == mNobodyUID));
622 bool doSwitchBootstrap = true; // (ignoreUid) || ((targetUid == 0) || (targetUid == mNobodyUID));
623
624 if (doSwitchAudit) {
625 mach_port_name_t jobPort;
626 if (0 == audit_session_port(mSession.sessionId(), &jobPort)) {
627 secdebug("SecurityAgentXPCQuery", "attaching an audit session port because the uid was %d", targetUid);
628 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort);
629 }
630 }
631
632 if (doSwitchBootstrap) {
633 secdebug("SecurityAgentXPCQuery", "attaching a bootstrap port because the uid was %d", targetUid);
634 MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
635 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, processBootstrap);
636 }
637
638 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
639 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
640 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
641 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) {
642 uint64_t status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
643 if (status == kAuthorizationResultAllow) {
644 mAgentConnected = true;
645 } else {
646 secdebug("SecurityAgentXPCQuery", "plugin create failed in SecurityAgent");
647 MacOSError::throwMe(errAuthorizationInternal);
648 }
649 }
650 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
651 if (XPC_ERROR_CONNECTION_INVALID == object) {
652 // If we get an error before getting the create response, try again without the UID
653 if (ignoreUid) {
654 secdebug("SecurityAgentXPCQuery", "failed to establish connection, no retries left");
655 xpc_release(object);
656 MacOSError::throwMe(errAuthorizationInternal);
657 } else {
658 secdebug("SecurityAgentXPCQuery", "failed to establish connection, retrying with no UID");
659 ignoreUid = true;
660 xpc_release(mXPCConnection);
661 mXPCConnection = NULL;
662 }
663 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
664 // If we get an error before getting the create response, try again
665 }
666 }
667 xpc_release(object);
668 xpc_release(requestObject);
669 } while (!mAgentConnected);
670
671 StLock<Mutex> _(gAllXPCClientsMutex());
672 allXPCClients().insert(this);
673 }
674
675 static xpc_object_t authItemSetToXPCArray(AuthItemSet input) {
676 xpc_object_t outputArray = xpc_array_create(NULL, 0);
677 for (AuthItemSet::iterator i = input.begin(); i != input.end(); i++) {
678 AuthItemRef item = *i;
679
680 xpc_object_t xpc_data = xpc_dictionary_create(NULL, NULL, 0);
681 xpc_dictionary_set_string(xpc_data, AUTH_XPC_ITEM_NAME, item->name());
682 AuthorizationValue value = item->value();
683 if (value.data != NULL) {
684 xpc_dictionary_set_data(xpc_data, AUTH_XPC_ITEM_VALUE, value.data, value.length);
685 }
686 xpc_dictionary_set_uint64(xpc_data, AUTH_XPC_ITEM_FLAGS, item->flags());
687 xpc_array_append_value(outputArray, xpc_data);
688 xpc_release(xpc_data);
689 }
690 return outputArray;
691 }
692
693 OSStatus
694 SecurityAgentXPCQuery::invoke() {
695 __block OSStatus status = kAuthorizationResultUndefined;
696
697 xpc_object_t hintsArray = authItemSetToXPCArray(mInHints);
698 xpc_object_t contextArray = authItemSetToXPCArray(mInContext);
699 xpc_object_t immutableHintsArray = authItemSetToXPCArray(mImmutableHints);
700
701 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0);
702 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE);
703 xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray);
704 xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray);
705 xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray);
706
707 xpc_object_t object = xpc_connection_send_message_with_reply_sync(mXPCConnection, requestObject);
708 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) {
709 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY);
710 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) {
711 xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME);
712 xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME);
713 AuthItemSet tempHints, tempContext;
714 xpcArrayToAuthItemSet(&tempHints, xpcHints);
715 xpcArrayToAuthItemSet(&tempContext, xpcContext);
716 mOutHints = tempHints;
717 mOutContext = tempContext;
718 mLastResult = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE);
719 }
720 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) {
721 if (XPC_ERROR_CONNECTION_INVALID == object) {
722 // If the connection drops, return an "auth undefined" result, because we cannot continue
723 } else if (XPC_ERROR_CONNECTION_INTERRUPTED == object) {
724 // If the agent dies, return an "auth undefined" result, because we cannot continue
725 }
726 }
727 xpc_release(object);
728
729 xpc_release(hintsArray);
730 xpc_release(contextArray);
731 xpc_release(immutableHintsArray);
732 xpc_release(requestObject);
733
734 return status;
735 }
736
737 void SecurityAgentXPCQuery::checkResult()
738 {
739 // now check the OSStatus return from the server side
740 switch (mLastResult) {
741 case kAuthorizationResultAllow: return;
742 case kAuthorizationResultDeny:
743 case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
744 default: MacOSError::throwMe(errAuthorizationInternal);
745 }
746 }
747
748 //
749 // Perform the "rogue app" access query dialog
750 //
751 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
752 : mPassphraseCheck(NULL)
753 {
754 // if passphrase checking requested, save KeychainDatabase reference
755 // (will quietly disable check if db isn't a keychain)
756 if (needPass)
757 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
758
759 setTerminateOnSleep(true);
760 }
761
762 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
763 {
764 Reason reason = SecurityAgent::noReason;
765 int retryCount = 0;
766 OSStatus status;
767 AuthValueVector arguments;
768 AuthItemSet hints, context;
769
770 #if defined(NOSA)
771 if (getenv("NOSA")) {
772 char answer[maxPassphraseLength+10];
773
774 string applicationPath;
775 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
776 if (applicationPathItem)
777 applicationPathItem->getString(applicationPath);
778
779 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
780 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
781 (database ? database : "[NULL database]"),
782 mPassphraseCheck ? ":passphrase" : "");
783 // turn passphrase (no ':') into y:passphrase
784 if (mPassphraseCheck && !strchr(answer, ':')) {
785 memmove(answer+2, answer, strlen(answer)+1);
786 memcpy(answer, "y:", 2);
787 }
788
789 allow = answer[0] == 'y';
790 remember = answer[1] == 'g';
791 return SecurityAgent::noReason;
792 }
793 #endif
794
795 // prepopulate with client hints
796 hints.insert(mClientHints.begin(), mClientHints.end());
797
798 // put action/operation (sint32) into hints
799 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
800
801 // item name into hints
802
803 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
804
805 // keychain name into hints
806 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
807
808 if (mPassphraseCheck)
809 {
810 create("builtin", "confirm-access-password", noSecuritySession);
811
812 CssmAutoData data(Allocator::standard(Allocator::sensitive));
813
814 do
815 {
816
817 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
818 hints.erase(triesHint); hints.insert(triesHint); // replace
819
820 if (retryCount++ > kMaximumAuthorizationTries)
821 {
822 reason = SecurityAgent::tooManyTries;
823 }
824
825 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
826 hints.erase(retryHint); hints.insert(retryHint); // replace
827
828 setInput(hints, context);
829 status = invoke();
830
831 if (retryCount > kMaximumAuthorizationTries)
832 {
833 return reason;
834 }
835
836 checkResult();
837
838 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
839 if (!passwordItem)
840 continue;
841
842 passwordItem->getCssmData(data);
843 }
844 while ((reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase)));
845 }
846 else
847 {
848 create("builtin", "confirm-access", noSecuritySession);
849 setInput(hints, context);
850 invoke();
851 }
852
853 readChoice();
854
855 return reason;
856 }
857
858 //
859 // Perform code signature ACL access adjustment dialogs
860 //
861 bool QueryCodeCheck::operator () (const char *aclPath)
862 {
863 OSStatus status;
864 AuthValueVector arguments;
865 AuthItemSet hints, context;
866
867 #if defined(NOSA)
868 if (getenv("NOSA")) {
869 char answer[10];
870
871 string applicationPath;
872 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
873 if (applicationPathItem)
874 applicationPathItem->getString(applicationPath);
875
876 getNoSA(answer, sizeof(answer),
877 "Allow %s to match an ACL for %s [yn][g]? ",
878 applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
879 allow = answer[0] == 'y';
880 remember = answer[1] == 'g';
881 return;
882 }
883 #endif
884
885 // prepopulate with client hints
886 hints.insert(mClientHints.begin(), mClientHints.end());
887
888 hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay((uint32_t)strlen(aclPath), const_cast<char*>(aclPath))));
889
890 create("builtin", "code-identity", noSecuritySession);
891
892 setInput(hints, context);
893 status = invoke();
894
895 checkResult();
896
897 // MacOSError::check(status);
898
899 return kAuthorizationResultAllow == mLastResult;
900 }
901
902
903 //
904 // Obtain passphrases and submit them to the accept() method until it is accepted
905 // or we can't get another passphrase. Accept() should consume the passphrase
906 // if it is accepted. If no passphrase is acceptable, throw out of here.
907 //
908 Reason QueryOld::query()
909 {
910 Reason reason = SecurityAgent::noReason;
911 OSStatus status;
912 AuthValueVector arguments;
913 AuthItemSet hints, context;
914 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
915 int retryCount = 0;
916
917 #if defined(NOSA)
918 // return the passphrase
919 if (getenv("NOSA")) {
920 char passphrase_[maxPassphraseLength];
921 getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
922 passphrase.copy(passphrase_, strlen(passphrase_));
923 return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
924 }
925 #endif
926
927 // prepopulate with client hints
928
929 const char *keychainPath = database.dbName();
930 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
931
932 hints.insert(mClientHints.begin(), mClientHints.end());
933
934 create("builtin", "unlock-keychain", noSecuritySession);
935
936 do
937 {
938 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
939 hints.erase(triesHint); hints.insert(triesHint); // replace
940
941 ++retryCount;
942
943 if (retryCount > maxTries)
944 {
945 reason = SecurityAgent::tooManyTries;
946 }
947
948 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
949 hints.erase(retryHint); hints.insert(retryHint); // replace
950
951 setInput(hints, context);
952 status = invoke();
953
954 if (retryCount > maxTries)
955 {
956 return reason;
957 }
958
959 checkResult();
960
961 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
962 if (!passwordItem)
963 continue;
964
965 passwordItem->getCssmData(passphrase);
966
967 }
968 while ((reason = accept(passphrase)));
969
970 return SecurityAgent::noReason;
971 }
972
973
974 //
975 // Get existing passphrase (unlock) Query
976 //
977 Reason QueryOld::operator () ()
978 {
979 return query();
980 }
981
982
983 //
984 // End-classes for old secrets
985 //
986 Reason QueryUnlock::accept(CssmManagedData &passphrase)
987 {
988 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
989 return SecurityAgent::noReason;
990 else
991 return SecurityAgent::invalidPassphrase;
992 }
993
994 Reason QueryUnlock::retrievePassword(CssmOwnedData &passphrase) {
995 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
996
997 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
998 if (!passwordItem)
999 return SecurityAgent::invalidPassphrase;
1000
1001 passwordItem->getCssmData(pass);
1002
1003 passphrase = pass;
1004
1005 return SecurityAgent::noReason;
1006 }
1007
1008 QueryKeybagPassphrase::QueryKeybagPassphrase(Session & session, int32_t tries) : mSession(session), mContext(), mRetries(tries)
1009 {
1010 setTerminateOnSleep(true);
1011 mContext = mSession.get_current_service_context();
1012 }
1013
1014 Reason QueryKeybagPassphrase::query()
1015 {
1016 Reason reason = SecurityAgent::noReason;
1017 OSStatus status;
1018 AuthValueVector arguments;
1019 AuthItemSet hints, context;
1020 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1021 int retryCount = 0;
1022
1023 // prepopulate with client hints
1024
1025 const char *keychainPath = "iCloud";
1026 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
1027
1028 hints.insert(mClientHints.begin(), mClientHints.end());
1029
1030 create("builtin", "unlock-keychain", noSecuritySession);
1031
1032 do
1033 {
1034 if (retryCount > mRetries)
1035 {
1036 return SecurityAgent::tooManyTries;
1037 }
1038
1039 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1040 hints.erase(triesHint); hints.insert(triesHint); // replace
1041
1042 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1043 hints.erase(retryHint); hints.insert(retryHint); // replace
1044
1045 setInput(hints, context);
1046 status = invoke();
1047
1048 checkResult();
1049
1050 AuthItem *passwordItem = mOutContext.find(kAuthorizationEnvironmentPassword);
1051 if (!passwordItem)
1052 continue;
1053
1054 passwordItem->getCssmData(passphrase);
1055
1056 ++retryCount;
1057 }
1058 while ((reason = accept(passphrase)));
1059
1060 return SecurityAgent::noReason;
1061 }
1062
1063 Reason QueryKeybagPassphrase::accept(Security::CssmManagedData & password)
1064 {
1065 if (service_client_kb_unlock(&mContext, password.data(), (int)password.length()) == 0) {
1066 mSession.keybagSetState(session_keybag_unlocked);
1067 return SecurityAgent::noReason;
1068 } else
1069 return SecurityAgent::invalidPassphrase;
1070 }
1071
1072 QueryKeybagNewPassphrase::QueryKeybagNewPassphrase(Session & session) : QueryKeybagPassphrase(session) {}
1073
1074 Reason QueryKeybagNewPassphrase::query(CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
1075 {
1076 CssmAutoData pass(Allocator::standard(Allocator::sensitive));
1077 CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
1078 Reason reason = SecurityAgent::noReason;
1079 OSStatus status;
1080 AuthValueVector arguments;
1081 AuthItemSet hints, context;
1082 int retryCount = 0;
1083
1084 // prepopulate with client hints
1085
1086 const char *keychainPath = "iCloud";
1087 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay((uint32_t)strlen(keychainPath), const_cast<char*>(keychainPath))));
1088
1089 const char *showResetString = "YES";
1090 hints.insert(AuthItemRef(AGENT_HINT_SHOW_RESET, AuthValueOverlay((uint32_t)strlen(showResetString), const_cast<char*>(showResetString))));
1091
1092 hints.insert(mClientHints.begin(), mClientHints.end());
1093
1094 create("builtin", "change-passphrase", noSecuritySession);
1095
1096 AuthItem *resetPassword = NULL;
1097 do
1098 {
1099 if (retryCount > mRetries)
1100 {
1101 return SecurityAgent::tooManyTries;
1102 }
1103
1104 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1105 hints.erase(triesHint); hints.insert(triesHint); // replace
1106
1107 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1108 hints.erase(retryHint); hints.insert(retryHint); // replace
1109
1110 setInput(hints, context);
1111 status = invoke();
1112
1113 checkResult();
1114
1115 resetPassword = mOutContext.find(AGENT_CONTEXT_RESET_PASSWORD);
1116 if (resetPassword != NULL) {
1117 return SecurityAgent::resettingPassword;
1118 }
1119
1120 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
1121 if (!oldPasswordItem)
1122 continue;
1123
1124 oldPasswordItem->getCssmData(oldPass);
1125
1126 ++retryCount;
1127 }
1128 while ((reason = accept(oldPass)));
1129
1130 if (reason == SecurityAgent::noReason) {
1131 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
1132 if (!passwordItem)
1133 return SecurityAgent::invalidPassphrase;
1134
1135 passwordItem->getCssmData(pass);
1136
1137 oldPassphrase = oldPass;
1138 passphrase = pass;
1139 }
1140
1141 return SecurityAgent::noReason;
1142 }
1143
1144 QueryPIN::QueryPIN(Database &db)
1145 : QueryOld(db), mPin(Allocator::standard())
1146 {
1147 this->inferHints(Server::process());
1148 }
1149
1150
1151 Reason QueryPIN::accept(CssmManagedData &pin)
1152 {
1153 // no retries for now
1154 mPin = pin;
1155 return SecurityAgent::noReason;
1156 }
1157
1158
1159 //
1160 // Obtain passphrases and submit them to the accept() method until it is accepted
1161 // or we can't get another passphrase. Accept() should consume the passphrase
1162 // if it is accepted. If no passphrase is acceptable, throw out of here.
1163 //
1164 Reason QueryNewPassphrase::query()
1165 {
1166 Reason reason = initialReason;
1167 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1168 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
1169
1170 OSStatus status;
1171 AuthValueVector arguments;
1172 AuthItemSet hints, context;
1173
1174 int retryCount = 0;
1175
1176 #if defined(NOSA)
1177 if (getenv("NOSA")) {
1178 char passphrase_[maxPassphraseLength];
1179 getNoSA(passphrase_, maxPassphraseLength,
1180 "New passphrase for %s (reason %d) [<CR> to cancel]: ",
1181 database.dbName(), reason);
1182 return SecurityAgent::noReason;
1183 }
1184 #endif
1185
1186 // prepopulate with client hints
1187 hints.insert(mClientHints.begin(), mClientHints.end());
1188
1189 // keychain name into hints
1190 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
1191
1192 switch (initialReason)
1193 {
1194 case SecurityAgent::newDatabase:
1195 create("builtin", "new-passphrase", noSecuritySession);
1196 break;
1197 case SecurityAgent::changePassphrase:
1198 create("builtin", "change-passphrase", noSecuritySession);
1199 break;
1200 default:
1201 assert(false);
1202 }
1203
1204 do
1205 {
1206 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1207 hints.erase(triesHint); hints.insert(triesHint); // replace
1208
1209 if (++retryCount > maxTries)
1210 {
1211 reason = SecurityAgent::tooManyTries;
1212 }
1213
1214 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1215 hints.erase(retryHint); hints.insert(retryHint); // replace
1216
1217 setInput(hints, context);
1218 status = invoke();
1219
1220 if (retryCount > maxTries)
1221 {
1222 return reason;
1223 }
1224
1225 checkResult();
1226
1227 if (SecurityAgent::changePassphrase == initialReason)
1228 {
1229 AuthItem *oldPasswordItem = mOutContext.find(AGENT_PASSWORD);
1230 if (!oldPasswordItem)
1231 continue;
1232
1233 oldPasswordItem->getCssmData(oldPassphrase);
1234 }
1235
1236 AuthItem *passwordItem = mOutContext.find(AGENT_CONTEXT_NEW_PASSWORD);
1237 if (!passwordItem)
1238 continue;
1239
1240 passwordItem->getCssmData(passphrase);
1241
1242 }
1243 while ((reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)));
1244
1245 return SecurityAgent::noReason;
1246 }
1247
1248
1249 //
1250 // Get new passphrase Query
1251 //
1252 Reason QueryNewPassphrase::operator () (CssmOwnedData &oldPassphrase, CssmOwnedData &passphrase)
1253 {
1254 if (Reason result = query())
1255 return result; // failed
1256 passphrase = mPassphrase;
1257 oldPassphrase = mOldPassphrase;
1258 return SecurityAgent::noReason; // success
1259 }
1260
1261 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
1262 {
1263 //@@@ acceptance criteria are currently hardwired here
1264 //@@@ This validation presumes ASCII - UTF8 might be more lenient
1265
1266 // if we have an old passphrase, check it
1267 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
1268 return SecurityAgent::oldPassphraseWrong;
1269
1270 // sanity check the new passphrase (but allow user override)
1271 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
1272 mPassphrase = passphrase;
1273 if (oldPassphrase) mOldPassphrase = *oldPassphrase;
1274 mPassphraseValid = true;
1275 if (mPassphrase.length() == 0)
1276 return SecurityAgent::passphraseIsNull;
1277 if (mPassphrase.length() < 6)
1278 return SecurityAgent::passphraseTooSimple;
1279 }
1280
1281 // accept this
1282 return SecurityAgent::noReason;
1283 }
1284
1285 //
1286 // Get a passphrase for unspecified use
1287 //
1288 Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
1289 string &passphrase)
1290 {
1291 return query(prompt, verify, passphrase);
1292 }
1293
1294 Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
1295 string &passphrase)
1296 {
1297 Reason reason = SecurityAgent::noReason;
1298 OSStatus status; // not really used; remove?
1299 AuthValueVector arguments;
1300 AuthItemSet hints, context;
1301
1302 #if defined(NOSA)
1303 if (getenv("NOSA")) {
1304 // FIXME 3690984
1305 return SecurityAgent::noReason;
1306 }
1307 #endif
1308
1309 hints.insert(mClientHints.begin(), mClientHints.end());
1310 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
1311 // XXX/gh defined by dmitch but no analogous hint in
1312 // AuthorizationTagsPriv.h:
1313 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
1314
1315 if (false == verify) { // import
1316 create("builtin", "generic-unlock", noSecuritySession);
1317 } else { // verify passphrase (export)
1318 // new-passphrase-generic works with the pre-4 June 2004 agent;
1319 // generic-new-passphrase is required for the new agent
1320 create("builtin", "generic-new-passphrase", noSecuritySession);
1321 }
1322
1323 AuthItem *passwordItem;
1324
1325 do {
1326 setInput(hints, context);
1327 status = invoke();
1328 checkResult();
1329 passwordItem = mOutContext.find(AGENT_PASSWORD);
1330
1331 } while (!passwordItem);
1332
1333 passwordItem->getString(passphrase);
1334
1335 return reason;
1336 }
1337
1338
1339 //
1340 // Get a DB blob's passphrase--keychain synchronization
1341 //
1342 Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1343 {
1344 return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
1345 }
1346
1347 Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
1348 {
1349 Reason reason = SecurityAgent::noReason;
1350 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
1351 OSStatus status; // not really used; remove?
1352 AuthValueVector arguments;
1353 AuthItemSet hints/*NUKEME*/, context;
1354
1355 #if defined(NOSA)
1356 if (getenv("NOSA")) {
1357 // FIXME akin to 3690984
1358 return SecurityAgent::noReason;
1359 }
1360 #endif
1361
1362 hints.insert(mClientHints.begin(), mClientHints.end());
1363 create("builtin", "generic-unlock-kcblob", noSecuritySession);
1364
1365 AuthItem *secretItem;
1366
1367 int retryCount = 0;
1368
1369 do {
1370 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1371 hints.erase(triesHint); hints.insert(triesHint); // replace
1372
1373 if (++retryCount > maxTries)
1374 {
1375 reason = SecurityAgent::tooManyTries;
1376 }
1377
1378 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1379 hints.erase(retryHint); hints.insert(retryHint); // replace
1380
1381 setInput(hints, context);
1382 status = invoke();
1383 checkResult();
1384 secretItem = mOutContext.find(AGENT_PASSWORD);
1385 if (!secretItem)
1386 continue;
1387 secretItem->getCssmData(passphrase);
1388
1389 } while ((reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated)));
1390
1391 return reason;
1392 }
1393
1394 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
1395 DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
1396 {
1397 DbHandle *currHdl = dbHandlesToAuthenticate;
1398 short index;
1399 Boolean authenticated = false;
1400 for (index=0; index < dbHandleCount && !authenticated; index++)
1401 {
1402 try
1403 {
1404 RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
1405 dbToUnlock->unlockDb(passphrase);
1406 authenticated = true;
1407 *dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with.
1408 }
1409 catch (const CommonError &err)
1410 {
1411 currHdl++; // we failed to authenticate with this one, onto the next one.
1412 }
1413 }
1414 if ( !authenticated )
1415 return SecurityAgent::invalidPassphrase;
1416
1417 return SecurityAgent::noReason;
1418 }
1419
1420 QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type, Session &session) :
1421 SecurityAgentQuery(type, session) { }
1422
1423 void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const AuthValueVector &inArguments, const SessionId inSessionId)
1424 {
1425 if (SecurityAgent::Client::init == SecurityAgent::Client::state())
1426 {
1427 create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId);
1428 mArguments = inArguments;
1429 }
1430 }
1431
1432 // XXX/cs should return AuthorizationResult
1433 void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult)
1434 {
1435 // prepopulate with client hints
1436 inHints.insert(mClientHints.begin(), mClientHints.end());
1437
1438 if (Server::active().inDarkWake())
1439 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
1440
1441 setArguments(inArguments);
1442 setInput(inHints, inContext);
1443 MacOSError::check(invoke());
1444
1445 if (outResult) *outResult = result();
1446
1447 inHints = outHints();
1448 inContext = outContext();
1449 }
1450
1451 void QueryInvokeMechanism::terminateAgent()
1452 {
1453 terminate();
1454 }
1455
1456 // @@@ no pluggable authentication possible!
1457 Reason
1458 QueryKeychainAuth::operator () (const char *database, const char *description, AclAuthorization action, const char *prompt)
1459 {
1460 Reason reason = SecurityAgent::noReason;
1461 AuthItemSet hints, context;
1462 AuthValueVector arguments;
1463 int retryCount = 0;
1464 string username;
1465 string password;
1466
1467 using CommonCriteria::Securityd::KeychainAuthLogger;
1468 KeychainAuthLogger logger(mAuditToken, AUE_ssauthint, database, description);
1469
1470 #if defined(NOSA)
1471 /* XXX/gh probably not complete; stolen verbatim from rogue-app query */
1472 if (getenv("NOSA")) {
1473 char answer[maxPassphraseLength+10];
1474
1475 string applicationPath;
1476 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
1477 if (applicationPathItem)
1478 applicationPathItem->getString(applicationPath);
1479
1480 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
1481 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
1482 (database ? database : "[NULL database]"),
1483 mPassphraseCheck ? ":passphrase" : "");
1484 // turn passphrase (no ':') into y:passphrase
1485 if (mPassphraseCheck && !strchr(answer, ':')) {
1486 memmove(answer+2, answer, strlen(answer)+1);
1487 memcpy(answer, "y:", 2);
1488 }
1489
1490 allow = answer[0] == 'y';
1491 remember = answer[1] == 'g';
1492 return SecurityAgent::noReason;
1493 }
1494 #endif
1495
1496 hints.insert(mClientHints.begin(), mClientHints.end());
1497
1498 // put action/operation (sint32) into hints
1499 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
1500
1501 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (uint32_t)strlen(prompt) : 0, const_cast<char*>(prompt))));
1502
1503 // item name into hints
1504 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? (uint32_t)strlen(description) : 0, const_cast<char*>(description))));
1505
1506 // keychain name into hints
1507 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? (uint32_t)strlen(database) : 0, const_cast<char*>(database))));
1508
1509 create("builtin", "confirm-access-user-password", noSecuritySession);
1510
1511 AuthItem *usernameItem;
1512 AuthItem *passwordItem;
1513
1514 do {
1515
1516 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
1517 hints.erase(triesHint); hints.insert(triesHint); // replace
1518
1519 if (++retryCount > maxTries)
1520 reason = SecurityAgent::tooManyTries;
1521
1522 if (SecurityAgent::noReason != reason)
1523 {
1524 if (SecurityAgent::tooManyTries == reason)
1525 logger.logFailure(NULL, CommonCriteria::errTooManyTries);
1526 else
1527 logger.logFailure();
1528 }
1529
1530 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
1531 hints.erase(retryHint); hints.insert(retryHint); // replace
1532
1533 setInput(hints, context);
1534 try
1535 {
1536 invoke();
1537 checkResult();
1538 }
1539 catch (...) // user probably clicked "deny"
1540 {
1541 logger.logFailure();
1542 throw;
1543 }
1544 usernameItem = mOutContext.find(AGENT_USERNAME);
1545 passwordItem = mOutContext.find(AGENT_PASSWORD);
1546 if (!usernameItem || !passwordItem)
1547 continue;
1548 usernameItem->getString(username);
1549 passwordItem->getString(password);
1550 } while ((reason = accept(username, password)));
1551
1552 if (SecurityAgent::noReason == reason)
1553 logger.logSuccess();
1554 // else we logged the denial in the loop
1555
1556 return reason;
1557 }
1558
1559 Reason
1560 QueryKeychainAuth::accept(string &username, string &passphrase)
1561 {
1562 const char *user = username.c_str();
1563 const char *passwd = passphrase.c_str();
1564 int checkpw_status = checkpw(user, passwd);
1565
1566 if (checkpw_status != CHECKPW_SUCCESS)
1567 return SecurityAgent::invalidPassphrase;
1568
1569 return SecurityAgent::noReason;
1570 }
1571