]> git.saurik.com Git - apple/securityd.git/blob - src/agentquery.cpp
securityd-32661.tar.gz
[apple/securityd.git] / src / agentquery.cpp
1 /*
2 * Copyright (c) 2000-2004 Apple Computer, 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
30 #include <Security/AuthorizationTags.h>
31 #include <Security/AuthorizationTagsPriv.h>
32
33 //
34 // NOSA support functions. This is a test mode where the SecurityAgent
35 // is simulated via stdio in the client. Good for running automated tests
36 // of client programs. Only available if -DNOSA when compiling.
37 //
38 #if defined(NOSA)
39
40 #include <cstdarg>
41
42 static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...)
43 {
44 // write prompt
45 va_list args;
46 va_start(args, fmt);
47 vfprintf(stdout, fmt, args);
48 va_end(args);
49
50 // read reply
51 memset(buffer, 0, bufferSize);
52 const char *nosa = getenv("NOSA");
53 if (!strcmp(nosa, "-")) {
54 if (fgets(buffer, bufferSize-1, stdin) == NULL)
55 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
56 buffer[strlen(buffer)-1] = '\0'; // remove trailing newline
57 if (!isatty(fileno(stdin)))
58 printf("%s\n", buffer); // echo to output if input not terminal
59 } else {
60 strncpy(buffer, nosa, bufferSize-1);
61 printf("%s\n", buffer);
62 }
63 if (buffer[0] == '\0') // empty input -> cancellation
64 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
65 }
66
67 #endif //NOSA
68
69
70 using SecurityAgent::Reason;
71 using namespace Authorization;
72
73 SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type, Session &session) : mAuthHostType(type), mHostInstance(session.authhost(mAuthHostType)), mConnection(&Server::connection())
74 {
75 // this may take a while
76 Server::active().longTermActivity();
77 secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
78 }
79
80 SecurityAgentQuery::~SecurityAgentQuery()
81 {
82 secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
83 mConnection->useAgent(NULL);
84
85 #if defined(NOSA)
86 if (getenv("NOSA")) {
87 printf(" [query done]\n");
88 return;
89 }
90 #endif
91
92 if (SecurityAgent::Client::state() != SecurityAgent::Client::dead)
93 destroy();
94 }
95
96 void
97 SecurityAgentQuery::activate()
98 {
99 mConnection->useAgent(this);
100
101 try {
102 SecurityAgent::Client::activate(mHostInstance->activate());
103 } catch (...) {
104 mConnection->useAgent(NULL); // guess not
105 throw;
106 }
107 }
108
109 void
110 SecurityAgentQuery::inferHints(Process &thisProcess)
111 {
112 string guestPath;
113 if (SecCodeRef clientCode = thisProcess.currentGuest())
114 guestPath = codePath(clientCode);
115 AuthItemSet processHints = clientHints(SecurityAgent::bundle, guestPath,
116 thisProcess.pid(), thisProcess.uid());
117 mClientHints.insert(processHints.begin(), processHints.end());
118 }
119
120 void SecurityAgentQuery::addHint(const char *name, const void *value, UInt32 valueLen, UInt32 flags)
121 {
122 AuthorizationItem item = { name, valueLen, const_cast<void *>(value), flags };
123 mClientHints.insert(AuthItemRef(item));
124 }
125
126
127 void
128 SecurityAgentQuery::readChoice()
129 {
130 allow = false;
131 remember = false;
132
133 AuthItem *allowAction = outContext().find(AGENT_CONTEXT_ALLOW);
134 if (allowAction)
135 {
136 string allowString;
137 if (allowAction->getString(allowString)
138 && (allowString == "YES"))
139 allow = true;
140 }
141
142 AuthItem *rememberAction = outContext().find(AGENT_CONTEXT_REMEMBER_ACTION);
143 if (rememberAction)
144 {
145 string rememberString;
146 if (rememberAction->getString(rememberString)
147 && (rememberString == "YES"))
148 remember = true;
149 }
150 }
151
152 void
153 SecurityAgentQuery::terminate()
154 {
155 activate();
156
157 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
158 mConnection->useAgent(NULL);
159
160 SecurityAgent::Client::terminate();
161 }
162
163 void
164 SecurityAgentQuery::create(const char *pluginId, const char *mechanismId, const SessionId inSessionId)
165 {
166 activate();
167 OSStatus status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
168 if (status)
169 {
170 secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
171 Session &session = mHostInstance->session();
172 mHostInstance = session.authhost(mAuthHostType, true);
173 activate();
174 status = SecurityAgent::Client::create(pluginId, mechanismId, inSessionId);
175 }
176 if (status) MacOSError::throwMe(status);
177 }
178
179 //
180 // Perform the "rogue app" access query dialog
181 //
182 QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db)
183 : mPassphraseCheck(NULL)
184 {
185 // if passphrase checking requested, save KeychainDatabase reference
186 // (will quietly disable check if db isn't a keychain)
187 if (needPass)
188 mPassphraseCheck = dynamic_cast<const KeychainDatabase *>(db);
189 }
190
191 Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action)
192 {
193 Reason reason = SecurityAgent::noReason;
194 int retryCount = 0;
195 OSStatus status;
196 AuthValueVector arguments;
197 AuthItemSet hints, context;
198
199 #if defined(NOSA)
200 if (getenv("NOSA")) {
201 char answer[maxPassphraseLength+10];
202
203 string applicationPath;
204 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
205 if (applicationPathItem)
206 applicationPathItem->getString(applicationPath);
207
208 getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ",
209 applicationPath.c_str(), int(action), (description ? description : "[NULL item]"),
210 (database ? database : "[NULL database]"),
211 mPassphraseCheck ? ":passphrase" : "");
212 // turn passphrase (no ':') into y:passphrase
213 if (mPassphraseCheck && !strchr(answer, ':')) {
214 memmove(answer+2, answer, strlen(answer)+1);
215 memcpy(answer, "y:", 2);
216 }
217
218 allow = answer[0] == 'y';
219 remember = answer[1] == 'g';
220 return SecurityAgent::noReason;
221 }
222 #endif
223
224 // prepopulate with client hints
225 hints.insert(mClientHints.begin(), mClientHints.end());
226
227 // put action/operation (sint32) into hints
228 hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast<sint32*>(&action))));
229
230 // item name into hints
231
232 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast<char*>(description))));
233
234 // keychain name into hints
235 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast<char*>(database))));
236
237
238 if (mPassphraseCheck)
239 {
240 create("builtin", "confirm-access-password", noSecuritySession);
241
242 CssmAutoData data(Allocator::standard(Allocator::sensitive));
243
244 do
245 {
246
247 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
248 hints.erase(triesHint); hints.insert(triesHint); // replace
249
250 if (retryCount++ > kMaximumAuthorizationTries)
251 {
252 reason = SecurityAgent::tooManyTries;
253 }
254
255 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
256 hints.erase(retryHint); hints.insert(retryHint); // replace
257
258 setInput(hints, context);
259 status = invoke();
260
261 if (retryCount > kMaximumAuthorizationTries)
262 {
263 return reason;
264 }
265
266 checkResult();
267
268 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
269 if (!passwordItem)
270 continue;
271
272 passwordItem->getCssmData(data);
273 }
274 while (reason = (const_cast<KeychainDatabase*>(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase));
275 }
276 else
277 {
278 create("builtin", "confirm-access", noSecuritySession);
279 setInput(hints, context);
280 invoke();
281 }
282
283 readChoice();
284
285 return reason;
286 }
287
288 //
289 // Perform code signature ACL access adjustment dialogs
290 //
291 bool QueryCodeCheck::operator () (const char *aclPath)
292 {
293 OSStatus status;
294 AuthValueVector arguments;
295 AuthItemSet hints, context;
296
297 #if defined(NOSA)
298 if (getenv("NOSA")) {
299 char answer[10];
300
301 string applicationPath;
302 AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH);
303 if (applicationPathItem)
304 applicationPathItem->getString(applicationPath);
305
306 getNoSA(answer, sizeof(answer),
307 "Allow %s to match an ACL for %s [yn][g]? ",
308 applicationPath.c_str(), aclPath ? aclPath : "(unknown)");
309 allow = answer[0] == 'y';
310 remember = answer[1] == 'g';
311 return;
312 }
313 #endif
314
315 // prepopulate with client hints
316 hints.insert(mClientHints.begin(), mClientHints.end());
317
318 hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay(strlen(aclPath), const_cast<char*>(aclPath))));
319
320 create("builtin", "code-identity", noSecuritySession);
321
322 setInput(hints, context);
323 status = invoke();
324
325 checkResult();
326
327 // MacOSError::check(status);
328
329 return kAuthorizationResultAllow == result();
330 }
331
332
333 //
334 // Obtain passphrases and submit them to the accept() method until it is accepted
335 // or we can't get another passphrase. Accept() should consume the passphrase
336 // if it is accepted. If no passphrase is acceptable, throw out of here.
337 //
338 Reason QueryOld::query()
339 {
340 Reason reason = SecurityAgent::noReason;
341 OSStatus status;
342 AuthValueVector arguments;
343 AuthItemSet hints, context;
344 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
345 int retryCount = 0;
346
347 #if defined(NOSA)
348 // return the passphrase
349 if (getenv("NOSA")) {
350 char passphrase_[maxPassphraseLength];
351 getNoSA(passphrase, maxPassphraseLength, "Unlock %s [<CR> to cancel]: ", database.dbName());
352 passphrase.copy(passphrase_, strlen(passphrase_));
353 return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase;
354 }
355 #endif
356
357 // prepopulate with client hints
358
359 const char *keychainPath = database.dbName();
360 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(strlen(keychainPath), const_cast<char*>(keychainPath))));
361
362 hints.insert(mClientHints.begin(), mClientHints.end());
363
364 create("builtin", "unlock-keychain", noSecuritySession);
365
366 do
367 {
368 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
369 hints.erase(triesHint); hints.insert(triesHint); // replace
370
371 ++retryCount;
372
373 if (retryCount > maxTries)
374 {
375 reason = SecurityAgent::tooManyTries;
376 }
377
378 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
379 hints.erase(retryHint); hints.insert(retryHint); // replace
380
381 setInput(hints, context);
382 status = invoke();
383
384 if (retryCount > maxTries)
385 {
386 return reason;
387 }
388
389 checkResult();
390
391 AuthItem *passwordItem = outContext().find(kAuthorizationEnvironmentPassword);
392 if (!passwordItem)
393 continue;
394
395 passwordItem->getCssmData(passphrase);
396
397 }
398 while (reason = accept(passphrase));
399
400 return SecurityAgent::noReason;
401 }
402
403
404 //
405 // Get existing passphrase (unlock) Query
406 //
407 Reason QueryOld::operator () ()
408 {
409 return query();
410 }
411
412
413 //
414 // End-classes for old secrets
415 //
416 Reason QueryUnlock::accept(CssmManagedData &passphrase)
417 {
418 if (safer_cast<KeychainDatabase &>(database).decode(passphrase))
419 return SecurityAgent::noReason;
420 else
421 return SecurityAgent::invalidPassphrase;
422 }
423
424
425 QueryPIN::QueryPIN(Database &db)
426 : QueryOld(db), mPin(Allocator::standard())
427 {
428 this->inferHints(Server::process());
429 }
430
431
432 Reason QueryPIN::accept(CssmManagedData &pin)
433 {
434 // no retries for now
435 mPin = pin;
436 return SecurityAgent::noReason;
437 }
438
439
440 //
441 // Obtain passphrases and submit them to the accept() method until it is accepted
442 // or we can't get another passphrase. Accept() should consume the passphrase
443 // if it is accepted. If no passphrase is acceptable, throw out of here.
444 //
445 Reason QueryNewPassphrase::query()
446 {
447 Reason reason = initialReason;
448 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
449 CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive));
450
451 OSStatus status;
452 AuthValueVector arguments;
453 AuthItemSet hints, context;
454
455 int retryCount = 0;
456
457 #if defined(NOSA)
458 if (getenv("NOSA")) {
459 char passphrase_[maxPassphraseLength];
460 getNoSA(passphrase_, maxPassphraseLength,
461 "New passphrase for %s (reason %d) [<CR> to cancel]: ",
462 database.dbName(), reason);
463 return SecurityAgent::noReason;
464 }
465 #endif
466
467 // prepopulate with client hints
468 hints.insert(mClientHints.begin(), mClientHints.end());
469
470 // keychain name into hints
471 hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName())));
472
473 switch (initialReason)
474 {
475 case SecurityAgent::newDatabase:
476 create("builtin", "new-passphrase", noSecuritySession);
477 break;
478 case SecurityAgent::changePassphrase:
479 create("builtin", "change-passphrase", noSecuritySession);
480 break;
481 default:
482 assert(false);
483 }
484
485 do
486 {
487 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
488 hints.erase(triesHint); hints.insert(triesHint); // replace
489
490 if (++retryCount > maxTries)
491 {
492 reason = SecurityAgent::tooManyTries;
493 }
494
495 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
496 hints.erase(retryHint); hints.insert(retryHint); // replace
497
498 setInput(hints, context);
499 status = invoke();
500
501 if (retryCount > maxTries)
502 {
503 return reason;
504 }
505
506 checkResult();
507
508 if (SecurityAgent::changePassphrase == initialReason)
509 {
510 AuthItem *oldPasswordItem = outContext().find(AGENT_PASSWORD);
511 if (!oldPasswordItem)
512 continue;
513
514 oldPasswordItem->getCssmData(oldPassphrase);
515 }
516
517 AuthItem *passwordItem = outContext().find(AGENT_CONTEXT_NEW_PASSWORD);
518 if (!passwordItem)
519 continue;
520
521 passwordItem->getCssmData(passphrase);
522
523 }
524 while (reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL));
525
526 return SecurityAgent::noReason;
527 }
528
529
530 //
531 // Get new passphrase Query
532 //
533 Reason QueryNewPassphrase::operator () (CssmOwnedData &passphrase)
534 {
535 if (Reason result = query())
536 return result; // failed
537 passphrase = mPassphrase;
538 return SecurityAgent::noReason; // success
539 }
540
541 Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
542 {
543 //@@@ acceptance criteria are currently hardwired here
544 //@@@ This validation presumes ASCII - UTF8 might be more lenient
545
546 // if we have an old passphrase, check it
547 if (oldPassphrase && !safer_cast<KeychainDatabase&>(database).validatePassphrase(*oldPassphrase))
548 return SecurityAgent::oldPassphraseWrong;
549
550 // sanity check the new passphrase (but allow user override)
551 if (!(mPassphraseValid && passphrase.get() == mPassphrase)) {
552 mPassphrase = passphrase;
553 mPassphraseValid = true;
554 if (mPassphrase.length() == 0)
555 return SecurityAgent::passphraseIsNull;
556 if (mPassphrase.length() < 6)
557 return SecurityAgent::passphraseTooSimple;
558 }
559
560 // accept this
561 return SecurityAgent::noReason;
562 }
563
564 //
565 // Get a passphrase for unspecified use
566 //
567 Reason QueryGenericPassphrase::operator () (const char *prompt, bool verify,
568 string &passphrase)
569 {
570 return query(prompt, verify, passphrase);
571 }
572
573 Reason QueryGenericPassphrase::query(const char *prompt, bool verify,
574 string &passphrase)
575 {
576 Reason reason = SecurityAgent::noReason;
577 OSStatus status; // not really used; remove?
578 AuthValueVector arguments;
579 AuthItemSet hints, context;
580
581 #if defined(NOSA)
582 if (getenv("NOSA")) {
583 // FIXME 3690984
584 return SecurityAgent::noReason;
585 }
586 #endif
587
588 hints.insert(mClientHints.begin(), mClientHints.end());
589 hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? strlen(prompt) : 0, const_cast<char*>(prompt))));
590 // XXX/gh defined by dmitch but no analogous hint in
591 // AuthorizationTagsPriv.h:
592 // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title)
593
594 if (false == verify) { // import
595 create("builtin", "generic-unlock", noSecuritySession);
596 } else { // verify passphrase (export)
597 // new-passphrase-generic works with the pre-4 June 2004 agent;
598 // generic-new-passphrase is required for the new agent
599 create("builtin", "generic-new-passphrase", noSecuritySession);
600 }
601
602 AuthItem *passwordItem;
603
604 do {
605 setInput(hints, context);
606 status = invoke();
607 checkResult();
608 passwordItem = outContext().find(AGENT_PASSWORD);
609
610 } while (!passwordItem);
611
612 passwordItem->getString(passphrase);
613
614 return reason;
615 }
616
617
618 //
619 // Get a DB blob's passphrase--keychain synchronization
620 //
621 Reason QueryDBBlobSecret::operator () (DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob)
622 {
623 return query(dbCore, secretsBlob);
624 }
625
626 Reason QueryDBBlobSecret::query(DatabaseCryptoCore &dbCore, const DbBlob *secretsBlob)
627 {
628 Reason reason = SecurityAgent::noReason;
629 CssmAutoData passphrase(Allocator::standard(Allocator::sensitive));
630 OSStatus status; // not really used; remove?
631 AuthValueVector arguments;
632 AuthItemSet hints/*NUKEME*/, context;
633
634 #if defined(NOSA)
635 if (getenv("NOSA")) {
636 // FIXME akin to 3690984
637 return SecurityAgent::noReason;
638 }
639 #endif
640
641 hints.insert(mClientHints.begin(), mClientHints.end());
642
643 create("builtin", "generic-unlock-kcblob", noSecuritySession);
644
645 AuthItem *secretItem;
646
647 int retryCount = 0;
648
649 do {
650 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount));
651 hints.erase(triesHint); hints.insert(triesHint); // replace
652
653 if (++retryCount > maxTries)
654 {
655 reason = SecurityAgent::tooManyTries;
656 }
657
658 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
659 hints.erase(retryHint); hints.insert(retryHint); // replace
660
661 setInput(hints, context);
662 status = invoke();
663 checkResult();
664 secretItem = outContext().find(AGENT_PASSWORD);
665 if (!secretItem)
666 continue;
667 secretItem->getCssmData(passphrase);
668
669 } while (reason = accept(passphrase, dbCore, secretsBlob));
670
671 return reason;
672 }
673
674 Reason QueryDBBlobSecret::accept(CssmManagedData &passphrase,
675 DatabaseCryptoCore &dbCore,
676 const DbBlob *secretsBlob)
677 {
678 try {
679 dbCore.setup(secretsBlob, passphrase);
680 dbCore.decodeCore(secretsBlob, NULL);
681 } catch (const CommonError &err) {
682 // XXX/gh Are there errors other than this?
683 return SecurityAgent::invalidPassphrase;
684 }
685 return SecurityAgent::noReason;
686 }
687
688 QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type, Session &session) :
689 SecurityAgentQuery(type, session) { }
690
691 void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const AuthValueVector &inArguments, const SessionId inSessionId)
692 {
693 if (SecurityAgent::Client::init == SecurityAgent::Client::state())
694 {
695 create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId);
696 mArguments = inArguments;
697 }
698 }
699
700 // XXX/cs should return AuthorizationResult
701 void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult)
702 {
703 // prepopulate with client hints
704 inHints.insert(mClientHints.begin(), mClientHints.end());
705
706 setArguments(inArguments);
707 setInput(inHints, inContext);
708 MacOSError::check(invoke());
709
710 if (outResult) *outResult = result();
711
712 inHints = outHints();
713 inContext = outContext();
714 }
715
716 void QueryInvokeMechanism::terminateAgent()
717 {
718 terminate();
719 }