2  * Copyright (c) 2000-2004,2007-2008 Apple 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@ 
  26 // acl_keychain - a subject type for the protected-path 
  27 //                                keychain prompt interaction model. 
  29 // Arguments in CSSM_LIST form: 
  30 //      list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure 
  31 //      list[2] = CssmData: Descriptive String (presented to user in protected dialogs) 
  32 // For legacy compatibility, we accept a single-entry form 
  33 //      list[1] = CssmData: Descriptive String 
  34 // which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value. 
  35 // This is never produced by current code, and is considered purely a legacy feature. 
  37 // On-disk (flattened) representation: 
  38 // In order to accommodate legacy formats nicely, we use the binary-versioning feature 
  39 // of the ACL machinery. Version 0 is the legacy format (storing only the description 
  40 // string), while Version 1 contains both selector and description. We are now always 
  41 // writing version-1 data, but will continue to recognize version-0 data indefinitely 
  42 // for really, really old keychain items. 
  44 #include "acl_keychain.h" 
  45 #include "agentquery.h" 
  47 #include "connection.h" 
  50 #include <security_utilities/debugging.h> 
  51 #include <security_utilities/logging.h> 
  52 #include <security_cdsa_utilities/osxverifier.h> 
  56 #define ACCEPT_LEGACY_FORM 1 
  60 // The default for the selector structure. 
  62 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR 
KeychainPromptAclSubject::defaultSelector 
= { 
  63         CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
,       // version 
  69 // Validate a credential set against this subject. 
  71 bool KeychainPromptAclSubject::validate(const AclValidationContext 
&context
, 
  72     const TypedList 
&sample
) const 
  74     if (SecurityServerEnvironment 
*env 
= context
.environment
<SecurityServerEnvironment
>()) { 
  75                 Process 
&process 
= Server::process(); 
  76                 secdebug("kcacl", "Keychain query for process %d (UID %d)", process
.pid(), process
.uid()); 
  78                 // assemble the effective validity mode mask 
  79                 uint32_t mode 
= Maker::defaultMode
; 
  80                 const uint16_t &flags 
= selector
.flags
; 
  81                 if (flags 
& CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT
) 
  82                         mode 
= (mode 
& ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED
) | (flags 
& CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED
); 
  83                 if (flags 
& CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT
) 
  84                         mode 
= (mode 
& ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID
) | (flags 
& CSSM_ACL_KEYCHAIN_PROMPT_INVALID
); 
  86                 // determine signed/validity status of client, without reference to any particular Code Requirement 
  87                 SecCodeRef clientCode 
= process
.currentGuest(); 
  88                 Server::active().longTermActivity(); 
  89                 OSStatus validation 
= clientCode 
? SecCodeCheckValidity(clientCode
, kSecCSDefaultFlags
, NULL
) : errSecCSStaticCodeNotFound
; 
  91                 case noErr
:                                                     // client is signed and valid 
  92                         secdebug("kcacl", "client is valid, proceeding"); 
  94                 case errSecCSUnsigned
:                          // client is not signed 
  95                         if (!(mode 
& CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED
)) { 
  96                                 secdebug("kcacl", "client is unsigned, suppressing prompt"); 
 100                 case errSecCSSignatureFailed
:           // client signed but signature is broken 
 101                 case errSecCSGuestInvalid
:                      // client signed but dynamically invalid 
 102                 case errSecCSStaticCodeNotFound
:        // client not on disk (or unreadable) 
 103                         if (!(mode 
& CSSM_ACL_KEYCHAIN_PROMPT_INVALID
)) { 
 104                                 secdebug("kcacl", "client is invalid, suppressing prompt"); 
 105                                 Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", 
 106                                         process
.getPath().c_str(), process
.pid()); 
 109                         Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", 
 110                                 process
.getPath().c_str(), process
.pid()); 
 112                 default:                                                        // something else went wrong 
 113                         secdebug("kcacl", "client validation failed rc=%d, suppressing prompt", int32_t(validation
)); 
 117                 // At this point, we're committed to try to Pop The Question. Now, how? 
 119                 // does the user need to type in the passphrase? 
 120         const Database 
*db 
= env
->database
; 
 121         bool needPassphrase 
= db 
&& (selector
.flags 
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
); 
 123                 // an application (i.e. Keychain Access.app :-) can force this option 
 124                 if (clientCode 
&& validation 
== noErr
) { 
 125                         CFRef
<CFDictionaryRef
> dict
; 
 126                         if (SecCodeCopySigningInformation(clientCode
, kSecCSDefaultFlags
, &dict
.aref()) == noErr
) 
 127                                 if (CFDictionaryRef info 
= CFDictionaryRef(CFDictionaryGetValue(dict
, kSecCodeInfoPList
))) 
 129                                                 (CFDictionaryGetValue(info
, CFSTR("SecForcePassphrasePrompt")) != NULL
); 
 133                 if (db 
&& db
->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { 
 134                         QueryKeychainAuth query
; 
 135                         query
.inferHints(Server::process()); 
 136                         if (query(db 
? db
->dbName() : NULL
, description
.c_str(), context
.authorization(), NULL
) != SecurityAgent::noReason
) 
 140                         QueryKeychainUse 
query(needPassphrase
, db
); 
 141                         query
.inferHints(Server::process()); 
 142                         query
.addHint(AGENT_HINT_CLIENT_VALIDITY
, &validation
, sizeof(validation
)); 
 143                         if (query
.queryUser(db 
? db
->dbName() : NULL
,  
 144                                 description
.c_str(), context
.authorization()) != SecurityAgent::noReason
) 
 147                         // process an "always allow..." response 
 148                         if (query
.remember 
&& clientCode
) { 
 149                                 RefPointer
<OSXCode
> clientXCode 
= new OSXCodeWrap(clientCode
); 
 150                                 RefPointer
<AclSubject
> subject 
= new CodeSignatureAclSubject(OSXVerifier(clientXCode
)); 
 151                                 SecurityServerAcl::addToStandardACL(context
, subject
); 
 154                         // finally, return the actual user response 
 158         return false;        // default to deny without prejudice 
 163 // Make a copy of this subject in CSSM_LIST form 
 165 CssmList 
KeychainPromptAclSubject::toList(Allocator 
&alloc
) const 
 167         // always issue new (non-legacy) form 
 168         return TypedList(alloc
, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
, 
 169                 new(alloc
) ListElement(alloc
, CssmData::wrap(selector
)), 
 170         new(alloc
) ListElement(alloc
, description
)); 
 174 // Has the caller recently authorized in such a way as to render unnecessary 
 175 // the usual QueryKeychainAuth dialog?  (The right is specific to Keychain  
 176 // Access' way of editing a system keychain.)   
 178 bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const 
 180     string rightString 
= "system.keychain.modify"; 
 181     return Server::session().isRightAuthorized(rightString
, Server::connection(), false/*no UI*/); 
 187 // Create a KeychainPromptAclSubject 
 189 uint32_t KeychainPromptAclSubject::Maker::defaultMode
; 
 191 KeychainPromptAclSubject 
*KeychainPromptAclSubject::Maker::make(const TypedList 
&list
) const 
 193         switch (list
.length()) { 
 194 #if ACCEPT_LEGACY_FORM 
 195         case 2: // legacy case: just description 
 197                         ListElement 
*params
[1]; 
 198                         crack(list
, 1, params
, CSSM_LIST_ELEMENT_DATUM
); 
 199                         return new KeychainPromptAclSubject(*params
[0], defaultSelector
); 
 201 #endif //ACCEPT_LEGACY_FORM 
 202         case 3: // standard case: selector + description 
 204                         ListElement 
*params
[2]; 
 205                         crack(list
, 2, params
, CSSM_LIST_ELEMENT_DATUM
, CSSM_LIST_ELEMENT_DATUM
); 
 206                         return new KeychainPromptAclSubject(*params
[1], 
 207                                 *params
[0]->data().interpretedAs
<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
)); 
 210                 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 214 KeychainPromptAclSubject 
*KeychainPromptAclSubject::Maker::make(Version version
, 
 215         Reader 
&pub
, Reader 
&) const 
 217         CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector
; 
 218         const char *description
; 
 221                 selector 
= defaultSelector
; 
 226                 selector
.version 
= n2h(selector
.version
); 
 227                 selector
.flags 
= n2h(selector
.flags
); 
 231                 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 233         return new KeychainPromptAclSubject(description
, selector
); 
 236 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr
, 
 237         const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR 
&sel
) 
 238         : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
), 
 239         selector(sel
), description(descr
) 
 241         // check selector version 
 242         if (selector
.version 
!= CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
) 
 243                 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
); 
 245         // always use the latest binary version 
 246         version(currentVersion
); 
 251 // Export the subject to a memory blob 
 253 void KeychainPromptAclSubject::exportBlob(Writer::Counter 
&pub
, Writer::Counter 
&priv
) 
 255         if (version() != 0) { 
 256                 selector
.version 
= h2n (selector
.version
); 
 257                 selector
.flags 
= h2n (selector
.flags
); 
 261     pub
.insert(description
.size() + 1); 
 264 void KeychainPromptAclSubject::exportBlob(Writer 
&pub
, Writer 
&priv
) 
 266         if (version() != 0) { 
 267                 selector
.version 
= h2n (selector
.version
); 
 268                 selector
.flags 
= h2n (selector
.flags
); 
 271     pub(description
.c_str()); 
 277 void KeychainPromptAclSubject::debugDump() const 
 279         Debug::dump("KeychainPrompt:%s(%s)", 
 281                 (selector
.flags 
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
) ? "passphrase" : "standard");