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