1/* 2 * Copyright (c) 2000-2004,2006-2009,2012-2013 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// 26// acl_keychain - a subject type for the protected-path 27// keychain prompt interaction model. 28// 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. 36// 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. 43// 44#include "acl_keychain.h" 45#include "agentquery.h" 46#include "acls.h" 47#include "connection.h" 48#include "database.h" 49#include "server.h" 50#include <security_utilities/debugging.h> 51#include <security_utilities/logging.h> 52#include <security_cdsa_utilities/osxverifier.h> 53#include <algorithm> 54 55 56#define ACCEPT_LEGACY_FORM 1 57 58 59// 60// The default for the selector structure. 61// 62CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = { 63 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version 64 0 // flags 65}; 66 67 68// 69// Validate a credential set against this subject. 70// 71bool KeychainPromptAclSubject::validate(const AclValidationContext &context, 72 const TypedList &sample) const 73{ 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()); 77 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); 85 86 // determine signed/validity status of client, without reference to any particular Code Requirement 87 SecCodeRef clientCode = NULL; 88 OSStatus validation = errSecCSStaticCodeNotFound; 89 { 90 StLock<Mutex> _(process); 91 Server::active().longTermActivity(); 92 clientCode = process.currentGuest(); 93 if (clientCode) { 94 validation = SecCodeCheckValidity(clientCode, kSecCSDefaultFlags, NULL); 95 } 96 97 switch (validation) 98 { 99 case noErr: // client is signed and valid 100 { 101 bool forceAllow = false; 102 secdebug("kcacl", "client is valid, proceeding"); 103 CFDictionaryRef codeDictionary = NULL; 104 if (errSecSuccess == SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &codeDictionary)) { 105 CFTypeRef entitlementsDictionary = NULL; 106 entitlementsDictionary = CFDictionaryGetValue(codeDictionary, kSecCodeInfoEntitlementsDict); 107 if (NULL != entitlementsDictionary) { 108 if (CFGetTypeID(entitlementsDictionary) == CFDictionaryGetTypeID()) { 109 CFTypeRef migrationEntitlement = CFDictionaryGetValue((CFDictionaryRef)entitlementsDictionary, CFSTR("com.apple.private.security.allow-migration")); 110 if (NULL != migrationEntitlement) { 111 if (CFGetTypeID(migrationEntitlement) == CFBooleanGetTypeID()) { 112 if (migrationEntitlement == kCFBooleanTrue) { 113 secdebug("kcacl", "client has migration entitlement, allowing"); 114 forceAllow = true; 115 } 116 } 117 } 118 } 119 } 120 CFRelease(codeDictionary); 121 } 122 if (forceAllow) { 123 return true; 124 } 125 } 126 break; 127 128 case errSecCSUnsigned: 129 { // client is not signed 130 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) { 131 secdebug("kcacl", "client is unsigned, suppressing prompt"); 132 return false; 133 } 134 } 135 break; 136 137 case errSecCSSignatureFailed: // client signed but signature is broken 138 case errSecCSGuestInvalid: // client signed but dynamically invalid 139 case errSecCSStaticCodeNotFound: // client not on disk (or unreadable) 140 { 141 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) { 142 secdebug("kcacl", "client is invalid, suppressing prompt"); 143 Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", 144 process.getPath().c_str(), process.pid()); 145 return false; 146 } 147 Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", 148 process.getPath().c_str(), process.pid()); 149 } 150 break; 151 152 default: // something else went wrong 153 secdebug("kcacl", "client validation failed rc=%d, suppressing prompt", int32_t(validation)); 154 return false; 155 } 156 } 157 158 // At this point, we're committed to try to Pop The Question. Now, how? 159 160 // does the user need to type in the passphrase? 161 const Database *db = env->database; 162 bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); 163 164 // an application (i.e. Keychain Access.app :-) can force this option 165 if (clientCode && validation == noErr) { 166 StLock<Mutex> _(process); 167 CFRef<CFDictionaryRef> dict; 168 if (SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &dict.aref()) == noErr) 169 if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList))) 170 needPassphrase |= 171 (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL); 172 } 173 174 // pop The Question 175 if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { 176 QueryKeychainAuth query; 177 query.inferHints(Server::process()); 178 if (query(db ? db->dbName() : NULL, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason) 179 return false; 180 return true; 181 } else { 182 QueryKeychainUse query(needPassphrase, db); 183 query.inferHints(Server::process()); 184 query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation)); 185 if (query.queryUser(db ? db->dbName() : NULL, 186 description.c_str(), context.authorization()) != SecurityAgent::noReason) 187 return false; 188 189 // process an "always allow..." response 190 if (query.remember && clientCode) { 191 StLock<Mutex> _(process); 192 RefPointer<OSXCode> clientXCode = new OSXCodeWrap(clientCode); 193 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(clientXCode)); 194 SecurityServerAcl::addToStandardACL(context, subject); 195 } 196 197 // finally, return the actual user response 198 return query.allow; 199 } 200 } 201 return false; // default to deny without prejudice 202} 203 204 205// 206// Make a copy of this subject in CSSM_LIST form 207// 208CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const 209{ 210 // always issue new (non-legacy) form 211 return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, 212 new(alloc) ListElement(alloc, CssmData::wrap(selector)), 213 new(alloc) ListElement(alloc, description)); 214} 215 216// 217// Has the caller recently authorized in such a way as to render unnecessary 218// the usual QueryKeychainAuth dialog? (The right is specific to Keychain 219// Access' way of editing a system keychain.) 220// 221bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const 222{ 223 string rightString = "system.keychain.modify"; 224 return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/); 225} 226 227 228 229// 230// Create a KeychainPromptAclSubject 231// 232uint32_t KeychainPromptAclSubject::Maker::defaultMode; 233 234KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const 235{ 236 switch (list.length()) { 237#if ACCEPT_LEGACY_FORM 238 case 2: // legacy case: just description 239 { 240 ListElement *params[1]; 241 crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); 242 return new KeychainPromptAclSubject(*params[0], defaultSelector); 243 } 244#endif //ACCEPT_LEGACY_FORM 245 case 3: // standard case: selector + description 246 { 247 ListElement *params[2]; 248 crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); 249 return new KeychainPromptAclSubject(*params[1], 250 *params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); 251 } 252 default: 253 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 254 } 255} 256 257KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, 258 Reader &pub, Reader &) const 259{ 260 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; 261 const char *description; 262 switch (version) { 263 case pumaVersion: 264 selector = defaultSelector; 265 pub(description); 266 break; 267 case jaguarVersion: 268 pub(selector); 269 selector.version = n2h(selector.version); 270 selector.flags = n2h(selector.flags); 271 pub(description); 272 break; 273 default: 274 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 275 } 276 return new KeychainPromptAclSubject(description, selector); 277} 278 279KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, 280 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) 281 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT), 282 selector(sel), description(descr) 283{ 284 // check selector version 285 if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) 286 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 287 288 // always use the latest binary version 289 version(currentVersion); 290} 291 292 293// 294// Export the subject to a memory blob 295// 296void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) 297{ 298 if (version() != 0) { 299 selector.version = h2n (selector.version); 300 selector.flags = h2n (selector.flags); 301 pub(selector); 302 } 303 304 pub.insert(description.size() + 1); 305} 306 307void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) 308{ 309 if (version() != 0) { 310 selector.version = h2n (selector.version); 311 selector.flags = h2n (selector.flags); 312 pub(selector); 313 } 314 pub(description.c_str()); 315} 316 317 318#ifdef DEBUGDUMP 319 320void KeychainPromptAclSubject::debugDump() const 321{ 322 Debug::dump("KeychainPrompt:%s(%s)", 323 description.c_str(), 324 (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); 325} 326 327#endif //DEBUGDUMP 328