2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 // passphrases - canonical code to obtain passphrases
27 #include "agentquery.h"
28 #include "authority.h"
30 #include <Security/AuthorizationTags.h>
31 #include <Security/AuthorizationTagsPriv.h>
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.
42 static void getNoSA(char *buffer
, size_t bufferSize
, const char *fmt
, ...)
47 vfprintf(stdout
, fmt
, args
);
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
60 strncpy(buffer
, nosa
, bufferSize
-1);
61 printf("%s\n", buffer
);
63 if (buffer
[0] == '\0') // empty input -> cancellation
64 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED
);
70 using SecurityAgent::Reason
;
71 using namespace Authorization
;
73 SecurityAgentQuery::SecurityAgentQuery(const AuthHostType type
, Session
&session
) : mAuthHostType(type
), mHostInstance(session
.authhost(mAuthHostType
)), mConnection(&Server::connection())
75 // this may take a while
76 Server::active().longTermActivity();
77 secdebug("SecurityAgentQuery", "new SecurityAgentQuery(%p)", this);
80 SecurityAgentQuery::~SecurityAgentQuery()
82 secdebug("SecurityAgentQuery", "SecurityAgentQuery(%p) dying", this);
83 mConnection
->useAgent(NULL
);
87 printf(" [query done]\n");
92 if (SecurityAgent::Client::state() != SecurityAgent::Client::dead
)
97 SecurityAgentQuery::activate()
99 mConnection
->useAgent(this);
102 SecurityAgent::Client::activate(mHostInstance
->activate());
104 mConnection
->useAgent(NULL
); // guess not
110 SecurityAgentQuery::inferHints(Process
&thisProcess
)
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());
120 void SecurityAgentQuery::addHint(const char *name
, const void *value
, UInt32 valueLen
, UInt32 flags
)
122 AuthorizationItem item
= { name
, valueLen
, const_cast<void *>(value
), flags
};
123 mClientHints
.insert(AuthItemRef(item
));
128 SecurityAgentQuery::readChoice()
133 AuthItem
*allowAction
= outContext().find(AGENT_CONTEXT_ALLOW
);
137 if (allowAction
->getString(allowString
)
138 && (allowString
== "YES"))
142 AuthItem
*rememberAction
= outContext().find(AGENT_CONTEXT_REMEMBER_ACTION
);
145 string rememberString
;
146 if (rememberAction
->getString(rememberString
)
147 && (rememberString
== "YES"))
153 SecurityAgentQuery::terminate()
157 // @@@ This happens already in the destructor; presumably we do this to tear things down orderly
158 mConnection
->useAgent(NULL
);
160 SecurityAgent::Client::terminate();
164 SecurityAgentQuery::create(const char *pluginId
, const char *mechanismId
, const SessionId inSessionId
)
167 OSStatus status
= SecurityAgent::Client::create(pluginId
, mechanismId
, inSessionId
);
170 secdebug("SecurityAgentQuery", "agent went walkabout, restarting");
171 Session
&session
= mHostInstance
->session();
172 mHostInstance
= session
.authhost(mAuthHostType
, true);
174 status
= SecurityAgent::Client::create(pluginId
, mechanismId
, inSessionId
);
176 if (status
) MacOSError::throwMe(status
);
180 // Perform the "rogue app" access query dialog
182 QueryKeychainUse::QueryKeychainUse(bool needPass
, const Database
*db
)
183 : mPassphraseCheck(NULL
)
185 // if passphrase checking requested, save KeychainDatabase reference
186 // (will quietly disable check if db isn't a keychain)
188 mPassphraseCheck
= dynamic_cast<const KeychainDatabase
*>(db
);
191 Reason
QueryKeychainUse::queryUser (const char *database
, const char *description
, AclAuthorization action
)
193 Reason reason
= SecurityAgent::noReason
;
196 AuthValueVector arguments
;
197 AuthItemSet hints
, context
;
200 if (getenv("NOSA")) {
201 char answer
[maxPassphraseLength
+10];
203 string applicationPath
;
204 AuthItem
*applicationPathItem
= mClientHints
.find(AGENT_HINT_APPLICATION_PATH
);
205 if (applicationPathItem
)
206 applicationPathItem
->getString(applicationPath
);
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);
218 allow
= answer
[0] == 'y';
219 remember
= answer
[1] == 'g';
220 return SecurityAgent::noReason
;
224 // prepopulate with client hints
225 hints
.insert(mClientHints
.begin(), mClientHints
.end());
227 // put action/operation (sint32) into hints
228 hints
.insert(AuthItemRef(AGENT_HINT_ACL_TAG
, AuthValueOverlay(sizeof(action
), static_cast<sint32
*>(&action
))));
230 // item name into hints
232 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME
, AuthValueOverlay(description
? strlen(description
) : 0, const_cast<char*>(description
))));
234 // keychain name into hints
235 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(database
? strlen(database
) : 0, const_cast<char*>(database
))));
238 if (mPassphraseCheck
)
240 create("builtin", "confirm-access-password", noSecuritySession
);
242 CssmAutoData
data(Allocator::standard(Allocator::sensitive
));
247 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
248 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
250 if (retryCount
++ > kMaximumAuthorizationTries
)
252 reason
= SecurityAgent::tooManyTries
;
255 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
256 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
258 setInput(hints
, context
);
261 if (retryCount
> kMaximumAuthorizationTries
)
268 AuthItem
*passwordItem
= outContext().find(kAuthorizationEnvironmentPassword
);
272 passwordItem
->getCssmData(data
);
274 while (reason
= (const_cast<KeychainDatabase
*>(mPassphraseCheck
)->decode(data
) ? SecurityAgent::noReason
: SecurityAgent::invalidPassphrase
));
278 create("builtin", "confirm-access", noSecuritySession
);
279 setInput(hints
, context
);
289 // Perform code signature ACL access adjustment dialogs
291 bool QueryCodeCheck::operator () (const char *aclPath
)
294 AuthValueVector arguments
;
295 AuthItemSet hints
, context
;
298 if (getenv("NOSA")) {
301 string applicationPath
;
302 AuthItem
*applicationPathItem
= mClientHints
.find(AGENT_HINT_APPLICATION_PATH
);
303 if (applicationPathItem
)
304 applicationPathItem
->getString(applicationPath
);
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';
315 // prepopulate with client hints
316 hints
.insert(mClientHints
.begin(), mClientHints
.end());
318 hints
.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH
, AuthValueOverlay(strlen(aclPath
), const_cast<char*>(aclPath
))));
320 create("builtin", "code-identity", noSecuritySession
);
322 setInput(hints
, context
);
327 // MacOSError::check(status);
329 return kAuthorizationResultAllow
== result();
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.
338 Reason
QueryOld::query()
340 Reason reason
= SecurityAgent::noReason
;
342 AuthValueVector arguments
;
343 AuthItemSet hints
, context
;
344 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
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
;
357 // prepopulate with client hints
359 const char *keychainPath
= database
.dbName();
360 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(strlen(keychainPath
), const_cast<char*>(keychainPath
))));
362 hints
.insert(mClientHints
.begin(), mClientHints
.end());
364 create("builtin", "unlock-keychain", noSecuritySession
);
368 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
369 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
373 if (retryCount
> maxTries
)
375 reason
= SecurityAgent::tooManyTries
;
378 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
379 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
381 setInput(hints
, context
);
384 if (retryCount
> maxTries
)
391 AuthItem
*passwordItem
= outContext().find(kAuthorizationEnvironmentPassword
);
395 passwordItem
->getCssmData(passphrase
);
398 while (reason
= accept(passphrase
));
400 return SecurityAgent::noReason
;
405 // Get existing passphrase (unlock) Query
407 Reason
QueryOld::operator () ()
414 // End-classes for old secrets
416 Reason
QueryUnlock::accept(CssmManagedData
&passphrase
)
418 if (safer_cast
<KeychainDatabase
&>(database
).decode(passphrase
))
419 return SecurityAgent::noReason
;
421 return SecurityAgent::invalidPassphrase
;
425 QueryPIN::QueryPIN(Database
&db
)
426 : QueryOld(db
), mPin(Allocator::standard())
428 this->inferHints(Server::process());
432 Reason
QueryPIN::accept(CssmManagedData
&pin
)
434 // no retries for now
436 return SecurityAgent::noReason
;
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.
445 Reason
QueryNewPassphrase::query()
447 Reason reason
= initialReason
;
448 CssmAutoData
passphrase(Allocator::standard(Allocator::sensitive
));
449 CssmAutoData
oldPassphrase(Allocator::standard(Allocator::sensitive
));
452 AuthValueVector arguments
;
453 AuthItemSet hints
, context
;
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
;
467 // prepopulate with client hints
468 hints
.insert(mClientHints
.begin(), mClientHints
.end());
470 // keychain name into hints
471 hints
.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH
, AuthValueOverlay(database
.dbName())));
473 switch (initialReason
)
475 case SecurityAgent::newDatabase
:
476 create("builtin", "new-passphrase", noSecuritySession
);
478 case SecurityAgent::changePassphrase
:
479 create("builtin", "change-passphrase", noSecuritySession
);
487 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
488 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
490 if (++retryCount
> maxTries
)
492 reason
= SecurityAgent::tooManyTries
;
495 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
496 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
498 setInput(hints
, context
);
501 if (retryCount
> maxTries
)
508 if (SecurityAgent::changePassphrase
== initialReason
)
510 AuthItem
*oldPasswordItem
= outContext().find(AGENT_PASSWORD
);
511 if (!oldPasswordItem
)
514 oldPasswordItem
->getCssmData(oldPassphrase
);
517 AuthItem
*passwordItem
= outContext().find(AGENT_CONTEXT_NEW_PASSWORD
);
521 passwordItem
->getCssmData(passphrase
);
524 while (reason
= accept(passphrase
, (initialReason
== SecurityAgent::changePassphrase
) ? &oldPassphrase
.get() : NULL
));
526 return SecurityAgent::noReason
;
531 // Get new passphrase Query
533 Reason
QueryNewPassphrase::operator () (CssmOwnedData
&passphrase
)
535 if (Reason result
= query())
536 return result
; // failed
537 passphrase
= mPassphrase
;
538 return SecurityAgent::noReason
; // success
541 Reason
QueryNewPassphrase::accept(CssmManagedData
&passphrase
, CssmData
*oldPassphrase
)
543 //@@@ acceptance criteria are currently hardwired here
544 //@@@ This validation presumes ASCII - UTF8 might be more lenient
546 // if we have an old passphrase, check it
547 if (oldPassphrase
&& !safer_cast
<KeychainDatabase
&>(database
).validatePassphrase(*oldPassphrase
))
548 return SecurityAgent::oldPassphraseWrong
;
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
;
561 return SecurityAgent::noReason
;
565 // Get a passphrase for unspecified use
567 Reason
QueryGenericPassphrase::operator () (const char *prompt
, bool verify
,
570 return query(prompt
, verify
, passphrase
);
573 Reason
QueryGenericPassphrase::query(const char *prompt
, bool verify
,
576 Reason reason
= SecurityAgent::noReason
;
577 OSStatus status
; // not really used; remove?
578 AuthValueVector arguments
;
579 AuthItemSet hints
, context
;
582 if (getenv("NOSA")) {
584 return SecurityAgent::noReason
;
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)
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
);
602 AuthItem
*passwordItem
;
605 setInput(hints
, context
);
608 passwordItem
= outContext().find(AGENT_PASSWORD
);
610 } while (!passwordItem
);
612 passwordItem
->getString(passphrase
);
619 // Get a DB blob's passphrase--keychain synchronization
621 Reason
QueryDBBlobSecret::operator () (DatabaseCryptoCore
&dbCore
, const DbBlob
*secretsBlob
)
623 return query(dbCore
, secretsBlob
);
626 Reason
QueryDBBlobSecret::query(DatabaseCryptoCore
&dbCore
, const DbBlob
*secretsBlob
)
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
;
635 if (getenv("NOSA")) {
636 // FIXME akin to 3690984
637 return SecurityAgent::noReason
;
641 hints
.insert(mClientHints
.begin(), mClientHints
.end());
643 create("builtin", "generic-unlock-kcblob", noSecuritySession
);
645 AuthItem
*secretItem
;
650 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(retryCount
), &retryCount
));
651 hints
.erase(triesHint
); hints
.insert(triesHint
); // replace
653 if (++retryCount
> maxTries
)
655 reason
= SecurityAgent::tooManyTries
;
658 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
659 hints
.erase(retryHint
); hints
.insert(retryHint
); // replace
661 setInput(hints
, context
);
664 secretItem
= outContext().find(AGENT_PASSWORD
);
667 secretItem
->getCssmData(passphrase
);
669 } while (reason
= accept(passphrase
, dbCore
, secretsBlob
));
674 Reason
QueryDBBlobSecret::accept(CssmManagedData
&passphrase
,
675 DatabaseCryptoCore
&dbCore
,
676 const DbBlob
*secretsBlob
)
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
;
685 return SecurityAgent::noReason
;
688 QueryInvokeMechanism::QueryInvokeMechanism(const AuthHostType type
, Session
&session
) :
689 SecurityAgentQuery(type
, session
) { }
691 void QueryInvokeMechanism::initialize(const string
&inPluginId
, const string
&inMechanismId
, const AuthValueVector
&inArguments
, const SessionId inSessionId
)
693 if (SecurityAgent::Client::init
== SecurityAgent::Client::state())
695 create(inPluginId
.c_str(), inMechanismId
.c_str(), inSessionId
);
696 mArguments
= inArguments
;
700 // XXX/cs should return AuthorizationResult
701 void QueryInvokeMechanism::run(const AuthValueVector
&inArguments
, AuthItemSet
&inHints
, AuthItemSet
&inContext
, AuthorizationResult
*outResult
)
703 // prepopulate with client hints
704 inHints
.insert(mClientHints
.begin(), mClientHints
.end());
706 setArguments(inArguments
);
707 setInput(inHints
, inContext
);
708 MacOSError::check(invoke());
710 if (outResult
) *outResult
= result();
712 inHints
= outHints();
713 inContext
= outContext();
716 void QueryInvokeMechanism::terminateAgent()