acl_keychain.cpp
1 /* 2 * Copyright (c) 2000-2004,2006-2009,2012-2013,2016 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 #include <sys/csr.h> 55 56 #include <Security/AuthorizationTagsPriv.h> 57 58 #define ACCEPT_LEGACY_FORM 1 59 60 // 61 // Initialize static memory. 62 // 63 uint32_t KeychainPromptAclSubject::promptsValidated = 0; 64 65 66 // 67 // The default for the selector structure. 68 // 69 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = { 70 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version 71 0 // flags 72 }; 73 74 75 // 76 // If we have a KeychainPromptAclSubject, we want KeychainMigrator to have 77 // access even if we don't have the "pop ui" credential. Do the code signing 78 // check first, then process this ACL as normal. 79 // 80 bool KeychainPromptAclSubject::validates(const AclValidationContext &ctx) const 81 { 82 Process &process = Server::process(); 83 if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) { 84 Syslog::info("bypassing keychain prompt for keychain migrator"); 85 secnotice("kcacl", "bypassing keychain prompt for keychain migrator"); 86 return true; // migrator client -> automatic win 87 } 88 89 // Also, mark down that we evaluated a prompt ACL. We want to record this for testing even if the client did not pass credentials for UI 90 // (so that tests can disable prompts but still detect if one would have popped) 91 promptsValidated++; 92 93 return SimpleAclSubject::validates(ctx); 94 } 95 96 97 // 98 // Validate a credential set against this subject. 99 // 100 bool KeychainPromptAclSubject::validates(const AclValidationContext &context, 101 const TypedList &sample) const 102 { 103 // Try to grab a common lock. We'll need it in queryUser, but we can't get 104 // it in validateExplicitly since other callers have it. 105 SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>(); 106 StMaybeLock<Mutex> _(env && env->database && env->database->hasCommon() ? &env->database->common() : NULL); 107 108 return validateExplicitly(context, ^{ 109 if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) { 110 Process& process = Server::process(); 111 StLock<Mutex> _(process); 112 RefPointer<AclSubject> subject = process.copyAclSubject(); 113 if (SecurityServerAcl::addToStandardACL(context, subject)) { 114 if(env->database && env->database->dbVersion() >= CommonBlob::version_partition) { 115 env->acl.addClientPartitionID(process); 116 } 117 } 118 } 119 }); 120 } 121 122 bool KeychainPromptAclSubject::validateExplicitly(const AclValidationContext &context, void (^alwaysAllow)()) const 123 { 124 if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) { 125 Process &process = Server::process(); 126 secnotice("kcacl", "Keychain query for process %d (UID %d)", process.pid(), process.uid()); 127 128 // assemble the effective validity mode mask 129 uint32_t mode = Maker::defaultMode; 130 const uint16_t &flags = selector.flags; 131 if (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT) 132 mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED); 133 if (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT) 134 mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID); 135 136 // determine signed/validity status of client, without reference to any particular Code Requirement 137 OSStatus validation = errSecCSStaticCodeNotFound; 138 { 139 StLock<Mutex> _(process); 140 Server::active().longTermActivity(); 141 142 validation = process.checkValidity(kSecCSDefaultFlags, NULL); 143 144 switch (validation) 145 { 146 case noErr: // client is signed and valid 147 { 148 secnotice("kcacl", "client is valid, proceeding"); 149 // This should almost always be handled by the check in KeychainPromptAclSubject::validate, but check again just in case 150 if (process.checkAppleSigned() && process.hasEntitlement(migrationEntitlement)) { 151 Syslog::info("bypassing keychain prompt for keychain migrator"); 152 secnotice("kcacl", "bypassing keychain prompt for keychain migrator"); 153 return true; // migrator client -> automatic win 154 } 155 } 156 break; 157 158 case errSecCSUnsigned: 159 { // client is not signed 160 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) { 161 Syslog::info("supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid()); 162 secnotice("kcacl", "supressing keychain prompt for unsigned client %s(%d)", process.getPath().c_str(), process.pid()); 163 return false; 164 } 165 } 166 break; 167 168 case errSecCSSignatureFailed: // client signed but signature is broken 169 case errSecCSGuestInvalid: // client signed but dynamically invalid 170 case errSecCSStaticCodeNotFound: // client not on disk (or unreadable) 171 { 172 if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) { 173 secnotice("kcacl", "client is invalid, suppressing prompt"); 174 Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); 175 secnotice("kcacl", "suppressing keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); 176 return false; 177 } 178 Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); 179 secnotice("kcacl", "attempting keychain prompt for invalidly signed client %s(%d)", process.getPath().c_str(), process.pid()); 180 } 181 break; 182 183 default: // something else went wrong 184 Syslog::info("suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation); 185 secnotice("kcacl", "suppressing keychain prompt %s(%d); code signing check failed rc=%d", process.getPath().c_str(), process.pid(), (int32_t) validation); 186 return false; 187 } 188 } 189 190 // At this point, we're committed to try to Pop The Question. Now, how? 191 Syslog::info("displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid()); 192 secnotice("kcacl", "displaying keychain prompt for %s(%d)", process.getPath().c_str(), process.pid()); 193 194 // does the user need to type in the passphrase? 195 const Database *db = env->database; 196 bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); 197 198 // an application (i.e. Keychain Access.app :-) can force this option 199 if (validation == noErr) { 200 StLock<Mutex> _(process); 201 CFRef<CFDictionaryRef> dict; 202 if (process.copySigningInfo(kSecCSDefaultFlags, &dict.aref()) == noErr) 203 if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList))) 204 needPassphrase |= 205 (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL); 206 } 207 208 // pop The Question 209 if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { 210 QueryKeychainAuth query; 211 query.inferHints(Server::process()); 212 // This is okay because we're in the belongsToSystem case which is true iff KeychainDbCommon which is true iff KeychainDatabase 213 const KeychainDatabase& kcdb = dynamic_cast<const KeychainDatabase&>(*db); 214 if (query.performQuery(kcdb, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason) 215 return false; 216 return true; 217 } else { 218 QueryKeychainUse query(needPassphrase, db); 219 query.inferHints(Server::process()); 220 query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation)); 221 if (query.queryUser(db ? db->dbName() : NULL, 222 description.c_str(), context.authorization()) != SecurityAgent::noReason) 223 return false; 224 225 // process an "always allow..." response 226 if (query.remember && validation != errSecCSStaticCodeNotFound) { 227 alwaysAllow(); 228 } 229 230 // finally, return the actual user response 231 return query.allow; 232 } 233 } 234 return false; // default to deny without prejudice 235 } 236 237 238 // 239 // Make a copy of this subject in CSSM_LIST form 240 // 241 CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const 242 { 243 // always issue new (non-legacy) form 244 return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, 245 new(alloc) ListElement(alloc, CssmData::wrap(selector)), 246 new(alloc) ListElement(alloc, description)); 247 } 248 249 // 250 // Has the caller recently authorized in such a way as to render unnecessary 251 // the usual QueryKeychainAuth dialog? (The right is specific to Keychain 252 // Access' way of editing a system keychain.) 253 // 254 bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const 255 { 256 // string rightString = "system.keychain.modify"; 257 // return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/); 258 return false; 259 } 260 261 262 263 // 264 // Create a KeychainPromptAclSubject 265 // 266 uint32_t KeychainPromptAclSubject::Maker::defaultMode; 267 268 KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const 269 { 270 switch (list.length()) { 271 #if ACCEPT_LEGACY_FORM 272 case 2: // legacy case: just description 273 { 274 ListElement *params[1]; 275 crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); 276 return new KeychainPromptAclSubject(*params[0], defaultSelector); 277 } 278 #endif //ACCEPT_LEGACY_FORM 279 case 3: // standard case: selector + description 280 { 281 ListElement *params[2]; 282 crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); 283 return new KeychainPromptAclSubject(*params[1], 284 *params[0]->data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); 285 } 286 default: 287 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 288 } 289 } 290 291 KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, 292 Reader &pub, Reader &) const 293 { 294 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; 295 const char *description; 296 switch (version) { 297 case pumaVersion: 298 selector = defaultSelector; 299 pub(description); 300 break; 301 case jaguarVersion: 302 pub(selector); 303 selector.version = n2h(selector.version); 304 selector.flags = n2h(selector.flags); 305 pub(description); 306 break; 307 default: 308 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 309 } 310 return new KeychainPromptAclSubject(description, selector); 311 } 312 313 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, 314 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) 315 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT), 316 selector(sel), description(descr) 317 { 318 // check selector version 319 if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) 320 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); 321 322 // always use the latest binary version 323 version(currentVersion); 324 } 325 326 327 // 328 // Export the subject to a memory blob 329 // 330 void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) 331 { 332 if (version() != 0) { 333 selector.version = h2n (selector.version); 334 selector.flags = h2n (selector.flags); 335 pub(selector); 336 } 337 338 pub.insert(description.size() + 1); 339 } 340 341 void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) 342 { 343 if (version() != 0) { 344 selector.version = h2n (selector.version); 345 selector.flags = h2n (selector.flags); 346 pub(selector); 347 } 348 pub(description.c_str()); 349 } 350 351 352 #ifdef DEBUGDUMP 353 354 void KeychainPromptAclSubject::debugDump() const 355 { 356 Debug::dump("KeychainPrompt:%s(%s)", 357 description.c_str(), 358 (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); 359 } 360 361 #endif //DEBUGDUMP 362 363 364 uint32_t KeychainPromptAclSubject::getPromptAttempts() { 365 if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) { 366 // Not an internal install; don't answer 367 return 0; 368 } else { 369 return KeychainPromptAclSubject::promptsValidated; 370 } 371 } 372 373 void KeychainPromptAclSubject::addPromptAttempt() { 374 promptsValidated++; 375 }