]> git.saurik.com Git - apple/securityd.git/blob - src/agentquery.cpp
securityd-55137.1.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 <System/sys/fileport.h>
35 #include <bsm/audit.h>
36 #include <bsm/audit_uevents.h> // AUE_ssauthint
37 #include <security_utilities/logging.h>
38 #include <security_utilities/mach++.h>
39 #include <stdlib.h>
40
41 //
42 // NOSA support functions. This is a test mode where the SecurityAgent
43 // is simulated via stdio in the client. Good for running automated tests
44 // of client programs. Only available if -DNOSA when compiling.
45 //
46 #if defined(NOSA)
47
48 #include <cstdarg>
49
50 static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
51 {
52 // write prompt
53 va_list args;
54 va_start(args, fmt);
55 vfprintf(stdout, fmt, args);
56 va_end(args);
57
58 // read reply
59 memset(buffer, 0, bufferSize);
60 const char *nosa = getenv("NOSA");
61 if (!strcmp(nosa, "-")) {
62 if (fgets(buffer, bufferSize-1, stdin) == NULL)
63 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
64 buffer[strlen(buffer)-1] = '\0'; // remove trailing newline
65 if (!isatty(fileno(stdin)))
66 printf("%s\n", buffer); // echo to output if input not terminal
67 } else {
68 strncpy(buffer, nosa, bufferSize-1);
69 printf("%s\n", buffer);
70 }
71 if (buffer[0] == '\0') // empty input -> cancellation
72 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
73 }
74
75 #endif //NOSA
76
77
78 // SecurityAgentConnection
79
80 SecurityAgentConnection::SecurityAgentConnection(const AuthHostType type, Session &session)
81 : mAuthHostType(type),
82 mHostInstance(session.authhost(mAuthHostType)),
83 mConnection(&Server::connection()),
84 mAuditToken(Server::connection().auditToken())
85 {
86 // this may take a while
87 Server::active().longTermActivity();
88 secdebug("SecurityAgentConnection", "new SecurityAgentConnection(%p)", this);
89 }
90
91 SecurityAgentConnection::~SecurityAgentConnection()
92 {
93 secdebug("SecurityAgentConnection", "SecurityAgentConnection(%p) dying", this);
94 mConnection->useAgent(NULL);
95 }
96
97 void
98 SecurityAgentConnection::activate()
99 {
100 secdebug("SecurityAgentConnection", "activate(%p)", this);
101
102 Session &session = mHostInstance->session();
103 SessionId targetSessionId = session.sessionId();
104 MachPlusPlus::Bootstrap processBootstrap = Server::process().taskPort().bootstrap();
105 fileport_t userPrefsFP = MACH_PORT_NULL;
106
107 // send the the userPrefs to SecurityAgent
108 if (mAuthHostType == securityAgent || mAuthHostType == userAuthHost) {
109 CFRef<CFDataRef> userPrefs(mHostInstance->session().copyUserPrefs());
110 if (NULL != userPrefs)
111 {
112 FILE *mbox = NULL;
113 int fd = 0;
114 mbox = tmpfile();
115 if (NULL != mbox)
116 {
117 fd = dup(fileno(mbox));
118 fclose(mbox);
119 if (fd != -1)
120 {
121 CFIndex length = CFDataGetLength(userPrefs);
122 if (write(fd, CFDataGetBytePtr(userPrefs), length) != length)
123 Syslog::error("could not write userPrefs");
124 else
125 {
126 if (0 == fileport_makeport(fd, &userPrefsFP))
127 secdebug("SecurityAgentConnection", "stashed the userPrefs file descriptor");
128 else
129 Syslog::error("failed to stash the userPrefs file descriptor");
130 }
131 close(fd);
132 }
133 }
134 }
135 if (MACH_PORT_NULL == userPrefsFP)
136 {
137 secdebug("SecurityAgentConnection", "could not read userPrefs");
138 }
139 }
140
141 mConnection->useAgent(this);
142 try
143 {
144 StLock<Mutex> _(*mHostInstance);
145
146 mach_port_t lookupPort = mHostInstance->lookup(targetSessionId);
147 if (MACH_PORT_NULL == lookupPort)
148 {
149 Syslog::error("could not find real service, bailing");
150 MacOSError::throwMe(CSSM_ERRCODE_SERVICE_NOT_AVAILABLE);
151 }
152 // reset Client contact info
153 mPort = lookupPort;
154 SecurityAgent::Client::activate(mPort);
155
156 secdebug("SecurityAgentConnection", "%p activated", this);
157 }
158 catch (MacOSError &err)
159 {
160 mConnection->useAgent(NULL); // guess not
161 Syslog::error("SecurityAgentConnection: error activating %s instance %p",
162 mAuthHostType == privilegedAuthHost
163 ? "authorizationhost"
164 : "SecurityAgent", this);
165 throw;
166 }
167
168 secdebug("SecurityAgentConnection", "contacting service (%p)", this);
169 mach_port_name_t jobPort;
170 if (0 > audit_session_port(session.sessionId(), &jobPort))
171 Syslog::error("audit_session_port failed: %m");
172 MacOSError::check(SecurityAgent::Client::contact(jobPort, processBootstrap, userPrefsFP));
173 secdebug("SecurityAgentConnection", "contact didn't throw (%p)", this);
174
175 if (userPrefsFP != MACH_PORT_NULL)
176 mach_port_deallocate(mach_task_self(), userPrefsFP);
177 }
178
179 void
180 SecurityAgentConnection::reconnect()
181 {
182 // if !mHostInstance throw()?
183 if (mHostInstance)
184 {
185 activate();
186 }
187 }
188
189 void
190 SecurityAgentConnection::terminate()
191 {
192 activate();
193
194 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
195 mConnection->useAgent(NULL);
196 }
197
198
199 // SecurityAgentTransaction
200
201 SecurityAgentTransaction::SecurityAgentTransaction(const AuthHostType type, Session &session, bool startNow)
202 : SecurityAgentConnection(type, session),
203 mStarted(false)
204 {
205 secdebug("SecurityAgentTransaction", "New SecurityAgentTransaction(%p)", this);
206 activate(); // start agent now, or other SAConnections will kill and spawn new agents
207 if (startNow)
208 start();
209 }
210
211 SecurityAgentTransaction::~SecurityAgentTransaction()
212 {
213 try { end(); } catch(...) {}
214 secdebug("SecurityAgentTransaction", "Destroying %p", this);
215 }
216
217 void
218 SecurityAgentTransaction::start()
219 {
220 secdebug("SecurityAgentTransaction", "start(%p)", this);
221 MacOSError::check(SecurityAgentQuery::Client::startTransaction(mPort));
222 mStarted = true;
223 secdebug("SecurityAgentTransaction", "started(%p)", this);
224 }
225
226 void
227 SecurityAgentTransaction::end()
228 {
229 if (started())
230 {
231 MacOSError::check(SecurityAgentQuery::Client::endTransaction(mPort));
232 mStarted = false;
233 }
234 secdebug("SecurityAgentTransaction", "End SecurityAgentTransaction(%p)", this);
235 }
236
237 using SecurityAgent::Reason;
238 using namespace Authorization;
239
240 SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session)
241 : SecurityAgentConnection(type, session)
242 {
243 secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
244 }
245
246 SecurityAgentQuery::~SecurityAgentQuery()
247 {
248 secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
249
250 #if defined(NOSA)
251 if (getenv("NOSA")) {
252 printf(" [query done]\n");
253 return;
254 }
255 #endif
256
257 if (SecurityAgent::Client::state() != SecurityAgent::Client::dead)
258 destroy();
259 }
260
261 void
262 SecurityAgentQuery::inferHints(Process &thisProcess)
263 {
264 string guestPath;
265 if (SecCodeRef clientCode = thisProcess.currentGuest())
266 guestPath = codePath(clientCode);
267 AuthItemSet processHints = clientHints(SecurityAgent::bundle, guestPath,
268 thisProcess.pid(), thisProcess.uid());
269 mClientHints.insert(processHints.begin(), processHints.end());
270 }
271
272 void SecurityAgentQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
273 {
274 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
275 mClientHints.insert(AuthItemRef(item));
276 }
277
278
279 void
280 SecurityAgentQuery::readChoice()
281 {
282 allow = false;
283 remember = false;
284
285 AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW);
286 if (allowAction)
287 {
288 string allowString;
289 if (allowAction->getString(allowString)
290 && (allowString == "YES"))
291 allow = true;
292 }
293
294 AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION);
295 if (rememberAction)
296 {
297 string rememberString;
298 if (rememberAction->getString(rememberString)
299 && (rememberString == "YES"))
300 remember = true;
301 }
302 }
303
304 void
305 SecurityAgentQuery::disconnect()
306 {
307 SecurityAgent::Client::destroy();
308 }
309
310 void
311 SecurityAgentQuery::terminate()
312 {
313 // you might think these are called in the wrong order, but you'd be wrong
314 SecurityAgentConnection::terminate();
315 SecurityAgent::Client::terminate();
316 }
317
318 void
319 SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
320 {
321 activate();
322 OSStatus status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
323 if (status)
324 {
325 secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
326 reconnect();
327 status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
328 }
329 if (status) MacOSError::throwMe(status);
330 }
331
332 //
333 // Perform the "rogue app" access query dialog
334 //
335 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
336 : mPassphraseCheck(NULL)
337 {
338 // if passphrase checking requested, save KeychainDatabase reference
339 // (will quietly disable check if db isn't a keychain)
340 if (needPass)
341 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
342
343 setTerminateOnSleep(true);
344 }
345
346 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
347 {
348 Reason reason = SecurityAgent::noReason;
349 int retryCount = 0;
350 OSStatus status;
351 AuthValueVector arguments;
352 AuthItemSet hints, context;
353
354 #if defined(NOSA)
355 if (getenv("NOSA")) {
356 char answer[maxPassphraseLength+10];
357
358 string applicationPath;
359 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
360 if (applicationPathItem)
361 applicationPathItem->getString(applicationPath);
362
363 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
364 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
365 (database ? database : "[NULL database]"),
366 mPassphraseCheck ? ":passphrase" : "");
367 // turn passphrase (no ':') into y:passphrase
368 if (mPassphraseCheck && !strchr(answer, ':')) {
369 memmove(answer+2, answer, strlen(answer)+1);
370 memcpy(answer, "y:", 2);
371 }
372
373 allow = answer[0] == 'y';
374 remember = answer[1] == 'g';
375 return SecurityAgent::noReason;
376 }
377 #endif
378
379 // prepopulate with client hints
380 hints.insert(mClientHints.begin(), mClientHints.end());
381
382 // put action/operation (sint32) into hints
383 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
384
385 // item name into hints
386
387 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast<char*>(description))));
388
389 // keychain name into hints
390 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast<char*>(database))));
391
392
393 if (mPassphraseCheck)
394 {
395 create("builtin", "confirm-access-password", noSecuritySession);
396
397 CssmAutoData data(Allocator::standard(Allocator::sensitive));
398
399 do
400 {
401
402 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
403 hints.erase(triesHint); hints.insert(triesHint); // replace
404
405 if (retryCount++ > kMaximumAuthorizationTries)
406 {
407 reason = SecurityAgent::tooManyTries;
408 }
409
410 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
411 hints.erase(retryHint); hints.insert(retryHint); // replace
412
413 setInput(hints, context);
414 status = invoke();
415
416 if (retryCount > kMaximumAuthorizationTries)
417 {
418 return reason;
419 }
420
421 checkResult();
422
423 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
424 if (!passwordItem)
425 continue;
426
427 passwordItem->getCssmData(data);
428 }
429 while (reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase));
430 }
431 else
432 {
433 create("builtin", "confirm-access", noSecuritySession);
434 setInput(hints, context);
435 invoke();
436 }
437
438 readChoice();
439
440 return reason;
441 }
442
443 //
444 // Perform code signature ACL access adjustment dialogs
445 //
446 bool QueryCodeCheck::operator () (const char *aclPath)
447 {
448 OSStatus status;
449 AuthValueVector arguments;
450 AuthItemSet hints, context;
451
452 #if defined(NOSA)
453 if (getenv("NOSA")) {
454 char answer[10];
455
456 string applicationPath;
457 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
458 if (applicationPathItem)
459 applicationPathItem->getString(applicationPath);
460
461 getNoSA(answer, sizeof(answer),
462 "Allow %s to match an ACL for %s [yn][g]? ",
463 applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
464 allow = answer[0] == 'y';
465 remember = answer[1] == 'g';
466 return;
467 }
468 #endif
469
470 // prepopulate with client hints
471 hints.insert(mClientHints.begin(), mClientHints.end());
472
473 hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay(strlen(aclPath), const_cast<char*>(aclPath))));
474
475 create("builtin", "code-identity", noSecuritySession);
476
477 setInput(hints, context);
478 status = invoke();
479
480 checkResult();
481
482 // MacOSError::check(status);
483
484 return kAuthorizationResultAllow == result();
485 }
486
487
488 //
489 // Obtain passphrases and submit them to the accept() method until it is accepted
490 // or we can't get another passphrase. Accept() should consume the passphrase
491 // if it is accepted. If no passphrase is acceptable, throw out of here.
492 //
493 Reason QueryOld::query()
494 {
495 Reason reason = SecurityAgent::noReason;
496 OSStatus status;
497 AuthValueVector arguments;
498 AuthItemSet hints, context;
499 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
500 int retryCount = 0;
501
502 #if defined(NOSA)
503 // return the passphrase
504 if (getenv("NOSA")) {
505 char passphrase_[maxPassphraseLength];
506 getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
507 passphrase.copy(passphrase_, strlen(passphrase_));
508 return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
509 }
510 #endif
511
512 // prepopulate with client hints
513
514 const char *keychainPath = database.dbName();
515 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(strlen(keychainPath), const_cast<char*>(keychainPath))));
516
517 hints.insert(mClientHints.begin(), mClientHints.end());
518
519 create("builtin", "unlock-keychain", noSecuritySession);
520
521 do
522 {
523 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
524 hints.erase(triesHint); hints.insert(triesHint); // replace
525
526 ++retryCount;
527
528 if (retryCount > maxTries)
529 {
530 reason = SecurityAgent::tooManyTries;
531 }
532
533 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
534 hints.erase(retryHint); hints.insert(retryHint); // replace
535
536 setInput(hints, context);
537 status = invoke();
538
539 if (retryCount > maxTries)
540 {
541 return reason;
542 }
543
544 checkResult();
545
546 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
547 if (!passwordItem)
548 continue;
549
550 passwordItem->getCssmData(passphrase);
551
552 }
553 while (reason = accept(passphrase));
554
555 return SecurityAgent::noReason;
556 }
557
558
559 //
560 // Get existing passphrase (unlock) Query
561 //
562 Reason QueryOld::operator () ()
563 {
564 return query();
565 }
566
567
568 //
569 // End-classes for old secrets
570 //
571 Reason QueryUnlock::accept(CssmManagedData &passphrase)
572 {
573 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
574 return SecurityAgent::noReason;
575 else
576 return SecurityAgent::invalidPassphrase;
577 }
578
579
580 QueryPIN::QueryPIN(Database &db)
581 : QueryOld(db), mPin(Allocator::standard())
582 {
583 this->inferHints(Server::process());
584 }
585
586
587 Reason QueryPIN::accept(CssmManagedData &pin)
588 {
589 // no retries for now
590 mPin = pin;
591 return SecurityAgent::noReason;
592 }
593
594
595 //
596 // Obtain passphrases and submit them to the accept() method until it is accepted
597 // or we can't get another passphrase. Accept() should consume the passphrase
598 // if it is accepted. If no passphrase is acceptable, throw out of here.
599 //
600 Reason QueryNewPassphrase::query()
601 {
602 Reason reason = initialReason;
603 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
604 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
605
606 OSStatus status;
607 AuthValueVector arguments;
608 AuthItemSet hints, context;
609
610 int retryCount = 0;
611
612 #if defined(NOSA)
613 if (getenv("NOSA")) {
614 char passphrase_[maxPassphraseLength];
615 getNoSA(passphrase_, maxPassphraseLength,
616 "New passphrase for %s (reason %d) [<CR> to cancel]: ",
617 database.dbName(), reason);
618 return SecurityAgent::noReason;
619 }
620 #endif
621
622 // prepopulate with client hints
623 hints.insert(mClientHints.begin(), mClientHints.end());
624
625 // keychain name into hints
626 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
627
628 switch (initialReason)
629 {
630 case SecurityAgent::newDatabase:
631 create("builtin", "new-passphrase", noSecuritySession);
632 break;
633 case SecurityAgent::changePassphrase:
634 create("builtin", "change-passphrase", noSecuritySession);
635 break;
636 default:
637 assert(false);
638 }
639
640 do
641 {
642 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
643 hints.erase(triesHint); hints.insert(triesHint); // replace
644
645 if (++retryCount > maxTries)
646 {
647 reason = SecurityAgent::tooManyTries;
648 }
649
650 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
651 hints.erase(retryHint); hints.insert(retryHint); // replace
652
653 setInput(hints, context);
654 status = invoke();
655
656 if (retryCount > maxTries)
657 {
658 return reason;
659 }
660
661 checkResult();
662
663 if (SecurityAgent::changePassphrase == initialReason)
664 {
665 AuthItem *oldPasswordItem = outContext().find(AGENT_PASSWORD);
666 if (!oldPasswordItem)
667 continue;
668
669 oldPasswordItem->getCssmData(oldPassphrase);
670 }
671
672 AuthItem *passwordItem = outContext().find(AGENT_CONTEXT_NEW_PASSWORD);
673 if (!passwordItem)
674 continue;
675
676 passwordItem->getCssmData(passphrase);
677
678 }
679 while (reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL));
680
681 return SecurityAgent::noReason;
682 }
683
684
685 //
686 // Get new passphrase Query
687 //
688 Reason QueryNewPassphrase::operator () (CssmOwnedData &passphrase)
689 {
690 if (Reason result = query())
691 return result; // failed
692 passphrase = mPassphrase;
693 return SecurityAgent::noReason; // success
694 }
695
696 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
697 {
698 //@@@ acceptance criteria are currently hardwired here
699 //@@@ This validation presumes ASCII - UTF8 might be more lenient
700
701 // if we have an old passphrase, check it
702 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
703 return SecurityAgent::oldPassphraseWrong;
704
705 // sanity check the new passphrase (but allow user override)
706 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
707 mPassphrase = passphrase;
708 mPassphraseValid = true;
709 if (mPassphrase.length() == 0)
710 return SecurityAgent::passphraseIsNull;
711 if (mPassphrase.length() < 6)
712 return SecurityAgent::passphraseTooSimple;
713 }
714
715 // accept this
716 return SecurityAgent::noReason;
717 }
718
719 //
720 // Get a passphrase for unspecified use
721 //
722 Reason QueryGenericPassphrase::operator () (const CssmData *prompt, bool verify,
723 string &passphrase)
724 {
725 return query(prompt, verify, passphrase);
726 }
727
728 Reason QueryGenericPassphrase::query(const CssmData *prompt, bool verify,
729 string &passphrase)
730 {
731 Reason reason = SecurityAgent::noReason;
732 OSStatus status; // not really used; remove?
733 AuthValueVector arguments;
734 AuthItemSet hints, context;
735
736 #if defined(NOSA)
737 if (getenv("NOSA")) {
738 // FIXME 3690984
739 return SecurityAgent::noReason;
740 }
741 #endif
742
743 hints.insert(mClientHints.begin(), mClientHints.end());
744 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? (UInt32)prompt->length() : 0, prompt ? prompt->data() : NULL)));
745 // XXX/gh defined by dmitch but no analogous hint in
746 // AuthorizationTagsPriv.h:
747 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
748
749 if (false == verify) { // import
750 create("builtin", "generic-unlock", noSecuritySession);
751 } else { // verify passphrase (export)
752 // new-passphrase-generic works with the pre-4 June 2004 agent;
753 // generic-new-passphrase is required for the new agent
754 create("builtin", "generic-new-passphrase", noSecuritySession);
755 }
756
757 AuthItem *passwordItem;
758
759 do {
760 setInput(hints, context);
761 status = invoke();
762 checkResult();
763 passwordItem = outContext().find(AGENT_PASSWORD);
764
765 } while (!passwordItem);
766
767 passwordItem->getString(passphrase);
768
769 return reason;
770 }
771
772
773 //
774 // Get a DB blob's passphrase--keychain synchronization
775 //
776 Reason QueryDBBlobSecret::operator () (DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
777 {
778 return query(dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated);
779 }
780
781 Reason QueryDBBlobSecret::query(DbHandle *dbHandleArray, uint8 dbHandleArrayCount, DbHandle *dbHandleAuthenticated)
782 {
783 Reason reason = SecurityAgent::noReason;
784 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
785 OSStatus status; // not really used; remove?
786 AuthValueVector arguments;
787 AuthItemSet hints/*NUKEME*/, context;
788
789 #if defined(NOSA)
790 if (getenv("NOSA")) {
791 // FIXME akin to 3690984
792 return SecurityAgent::noReason;
793 }
794 #endif
795
796 hints.insert(mClientHints.begin(), mClientHints.end());
797
798 create("builtin", "generic-unlock-kcblob", noSecuritySession);
799
800 AuthItem *secretItem;
801
802 int retryCount = 0;
803
804 do {
805 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
806 hints.erase(triesHint); hints.insert(triesHint); // replace
807
808 if (++retryCount > maxTries)
809 {
810 reason = SecurityAgent::tooManyTries;
811 }
812
813 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
814 hints.erase(retryHint); hints.insert(retryHint); // replace
815
816 setInput(hints, context);
817 status = invoke();
818 checkResult();
819 secretItem = outContext().find(AGENT_PASSWORD);
820 if (!secretItem)
821 continue;
822 secretItem->getCssmData(passphrase);
823
824 } while (reason = accept(passphrase, dbHandleArray, dbHandleArrayCount, dbHandleAuthenticated));
825
826 return reason;
827 }
828
829 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
830 DbHandle *dbHandlesToAuthenticate, uint8 dbHandleCount, DbHandle *dbHandleAuthenticated)
831 {
832 DbHandle *currHdl = dbHandlesToAuthenticate;
833 short index;
834 Boolean authenticated = false;
835 for (index=0; index < dbHandleCount && !authenticated; index++)
836 {
837 try
838 {
839 RefPointer<KeychainDatabase> dbToUnlock = Server::keychain(*currHdl);
840 dbToUnlock->unlockDb(passphrase);
841 authenticated = true;
842 *dbHandleAuthenticated = *currHdl; // return the DbHandle that 'passphrase' authenticated with.
843 }
844 catch (const CommonError &err)
845 {
846 currHdl++; // we failed to authenticate with this one, onto the next one.
847 }
848 }
849 if ( !authenticated )
850 return SecurityAgent::invalidPassphrase;
851
852 return SecurityAgent::noReason;
853 }
854
855 QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type, Session &session) :
856 SecurityAgentQuery(type, session) { }
857
858 void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const AuthValueVector &inArguments, const SessionId inSessionId)
859 {
860 if (SecurityAgent::Client::init == SecurityAgent::Client::state())
861 {
862 create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId);
863 mArguments = inArguments;
864 }
865 }
866
867 // XXX/cs should return AuthorizationResult
868 void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult)
869 {
870 // prepopulate with client hints
871 inHints.insert(mClientHints.begin(), mClientHints.end());
872
873 if (Server::active().inDarkWake())
874 CssmError::throwMe(CSSM_ERRCODE_IN_DARK_WAKE);
875
876 setArguments(inArguments);
877 setInput(inHints, inContext);
878 MacOSError::check(invoke());
879
880 if (outResult) *outResult = result();
881
882 inHints = outHints();
883 inContext = outContext();
884 }
885
886 void QueryInvokeMechanism::terminateAgent()
887 {
888 terminate();
889 }
890
891 // @@@ no pluggable authentication possible!
892 Reason
893 QueryKeychainAuth::operator () (const char *database, const char *description, AclAuthorization action, const char *prompt)
894 {
895 Reason reason = SecurityAgent::noReason;
896 AuthItemSet hints, context;
897 AuthValueVector arguments;
898 int retryCount = 0;
899 string username;
900 string password;
901
902 using CommonCriteria::Securityd::KeychainAuthLogger;
903 KeychainAuthLogger logger(mAuditToken, AUE_ssauthint, database, description);
904
905 #if defined(NOSA)
906 /* XXX/gh probably not complete; stolen verbatim from rogue-app query */
907 if (getenv("NOSA")) {
908 char answer[maxPassphraseLength+10];
909
910 string applicationPath;
911 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
912 if (applicationPathItem)
913 applicationPathItem->getString(applicationPath);
914
915 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
916 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
917 (database ? database : "[NULL database]"),
918 mPassphraseCheck ? ":passphrase" : "");
919 // turn passphrase (no ':') into y:passphrase
920 if (mPassphraseCheck && !strchr(answer, ':')) {
921 memmove(answer+2, answer, strlen(answer)+1);
922 memcpy(answer, "y:", 2);
923 }
924
925 allow = answer[0] == 'y';
926 remember = answer[1] == 'g';
927 return SecurityAgent::noReason;
928 }
929 #endif
930
931 hints.insert(mClientHints.begin(), mClientHints.end());
932
933 // put action/operation (sint32) into hints
934 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
935
936 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? strlen(prompt) : 0, const_cast<char*>(prompt))));
937
938 // item name into hints
939 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast<char*>(description))));
940
941 // keychain name into hints
942 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast<char*>(database))));
943
944 create("builtin", "confirm-access-user-password", noSecuritySession);
945
946 AuthItem *usernameItem;
947 AuthItem *passwordItem;
948
949 do {
950
951 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
952 hints.erase(triesHint); hints.insert(triesHint); // replace
953
954 if (++retryCount > maxTries)
955 reason = SecurityAgent::tooManyTries;
956
957 if (SecurityAgent::noReason != reason)
958 {
959 if (SecurityAgent::tooManyTries == reason)
960 logger.logFailure(NULL, CommonCriteria::errTooManyTries);
961 else
962 logger.logFailure();
963 }
964
965 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
966 hints.erase(retryHint); hints.insert(retryHint); // replace
967
968 setInput(hints, context);
969 try
970 {
971 invoke();
972 checkResult();
973 }
974 catch (...) // user probably clicked "deny"
975 {
976 logger.logFailure();
977 throw;
978 }
979 usernameItem = outContext().find(AGENT_USERNAME);
980 passwordItem = outContext().find(AGENT_PASSWORD);
981 if (!usernameItem || !passwordItem)
982 continue;
983 usernameItem->getString(username);
984 passwordItem->getString(password);
985 } while (reason = accept(username, password));
986
987 if (SecurityAgent::noReason == reason)
988 logger.logSuccess();
989 // else we logged the denial in the loop
990
991 return reason;
992 }
993
994 Reason
995 QueryKeychainAuth::accept(string &username, string &passphrase)
996 {
997 const char *user = username.c_str();
998 const char *passwd = passphrase.c_str();
999 int checkpw_status = checkpw(user, passwd);
1000
1001 if (checkpw_status != CHECKPW_SUCCESS)
1002 return SecurityAgent::invalidPassphrase;
1003
1004 return SecurityAgent::noReason;
1005 }
1006