X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/securityd/src/acl_keychain.cpp diff --git a/securityd/src/acl_keychain.cpp b/securityd/src/acl_keychain.cpp new file mode 100644 index 00000000..dbc4bcb7 --- /dev/null +++ b/securityd/src/acl_keychain.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2000-2004,2006-2009,2012-2013 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// acl_keychain - a subject type for the protected-path +// keychain prompt interaction model. +// +// Arguments in CSSM_LIST form: +// list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure +// list[2] = CssmData: Descriptive String (presented to user in protected dialogs) +// For legacy compatibility, we accept a single-entry form +// list[1] = CssmData: Descriptive String +// which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value. +// This is never produced by current code, and is considered purely a legacy feature. +// +// On-disk (flattened) representation: +// In order to accommodate legacy formats nicely, we use the binary-versioning feature +// of the ACL machinery. Version 0 is the legacy format (storing only the description +// string), while Version 1 contains both selector and description. We are now always +// writing version-1 data, but will continue to recognize version-0 data indefinitely +// for really, really old keychain items. +// +#include "acl_keychain.h" +#include "agentquery.h" +#include "acls.h" +#include "connection.h" +#include "database.h" +#include "server.h" +#include +#include +#include +#include + + +#define ACCEPT_LEGACY_FORM 1 + + +// +// The default for the selector structure. +// +CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = { + CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version + 0 // flags +}; + + +// +// Validate a credential set against this subject. +// +bool KeychainPromptAclSubject::validate(const AclValidationContext &context, + const TypedList &sample) const +{ + if (SecurityServerEnvironment *env = context.environment()) { + Process &process = Server::process(); + secdebug("kcacl", "Keychain query for process %d (UID %d)", process.pid(), process.uid()); + + // assemble the effective validity mode mask + uint32_t mode = Maker::defaultMode; + const uint16_t &flags = selector.flags; + if (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED_ACT) + mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED); + if (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID_ACT) + mode = (mode & ~CSSM_ACL_KEYCHAIN_PROMPT_INVALID) | (flags & CSSM_ACL_KEYCHAIN_PROMPT_INVALID); + + // determine signed/validity status of client, without reference to any particular Code Requirement + SecCodeRef clientCode = NULL; + OSStatus validation = errSecCSStaticCodeNotFound; + { + StLock _(process); + Server::active().longTermActivity(); + clientCode = process.currentGuest(); + if (clientCode) { + validation = SecCodeCheckValidity(clientCode, kSecCSDefaultFlags, NULL); + } + + switch (validation) + { + case noErr: // client is signed and valid + { + bool forceAllow = false; + secdebug("kcacl", "client is valid, proceeding"); + CFDictionaryRef codeDictionary = NULL; + if (errSecSuccess == SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &codeDictionary)) { + CFTypeRef entitlementsDictionary = NULL; + entitlementsDictionary = CFDictionaryGetValue(codeDictionary, kSecCodeInfoEntitlementsDict); + if (NULL != entitlementsDictionary) { + if (CFGetTypeID(entitlementsDictionary) == CFDictionaryGetTypeID()) { + CFTypeRef migrationEntitlement = CFDictionaryGetValue((CFDictionaryRef)entitlementsDictionary, CFSTR("com.apple.private.security.allow-migration")); + if (NULL != migrationEntitlement) { + if (CFGetTypeID(migrationEntitlement) == CFBooleanGetTypeID()) { + if (migrationEntitlement == kCFBooleanTrue) { + secdebug("kcacl", "client has migration entitlement, allowing"); + forceAllow = true; + } + } + } + } + } + CFRelease(codeDictionary); + } + if (forceAllow) { + return true; + } + } + break; + + case errSecCSUnsigned: + { // client is not signed + if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_UNSIGNED)) { + secdebug("kcacl", "client is unsigned, suppressing prompt"); + return false; + } + } + break; + + case errSecCSSignatureFailed: // client signed but signature is broken + case errSecCSGuestInvalid: // client signed but dynamically invalid + case errSecCSStaticCodeNotFound: // client not on disk (or unreadable) + { + if (!(mode & CSSM_ACL_KEYCHAIN_PROMPT_INVALID)) { + secdebug("kcacl", "client is invalid, suppressing prompt"); + Syslog::info("suppressing keychain prompt for invalidly signed client %s(%d)", + process.getPath().c_str(), process.pid()); + return false; + } + Syslog::info("attempting keychain prompt for invalidly signed client %s(%d)", + process.getPath().c_str(), process.pid()); + } + break; + + default: // something else went wrong + secdebug("kcacl", "client validation failed rc=%d, suppressing prompt", int32_t(validation)); + return false; + } + } + + // At this point, we're committed to try to Pop The Question. Now, how? + + // does the user need to type in the passphrase? + const Database *db = env->database; + bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); + + // an application (i.e. Keychain Access.app :-) can force this option + if (clientCode && validation == noErr) { + StLock _(process); + CFRef dict; + if (SecCodeCopySigningInformation(clientCode, kSecCSDefaultFlags, &dict.aref()) == noErr) + if (CFDictionaryRef info = CFDictionaryRef(CFDictionaryGetValue(dict, kSecCodeInfoPList))) + needPassphrase |= + (CFDictionaryGetValue(info, CFSTR("SecForcePassphrasePrompt")) != NULL); + } + + // pop The Question + if (db && db->belongsToSystem() && !hasAuthorizedForSystemKeychain()) { + QueryKeychainAuth query; + query.inferHints(Server::process()); + if (query(db ? db->dbName() : NULL, description.c_str(), context.authorization(), NULL) != SecurityAgent::noReason) + return false; + return true; + } else { + QueryKeychainUse query(needPassphrase, db); + query.inferHints(Server::process()); + query.addHint(AGENT_HINT_CLIENT_VALIDITY, &validation, sizeof(validation)); + if (query.queryUser(db ? db->dbName() : NULL, + description.c_str(), context.authorization()) != SecurityAgent::noReason) + return false; + + // process an "always allow..." response + if (query.remember && clientCode) { + StLock _(process); + RefPointer clientXCode = new OSXCodeWrap(clientCode); + RefPointer subject = new CodeSignatureAclSubject(OSXVerifier(clientXCode)); + SecurityServerAcl::addToStandardACL(context, subject); + } + + // finally, return the actual user response + return query.allow; + } + } + return false; // default to deny without prejudice +} + + +// +// Make a copy of this subject in CSSM_LIST form +// +CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const +{ + // always issue new (non-legacy) form + return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, + new(alloc) ListElement(alloc, CssmData::wrap(selector)), + new(alloc) ListElement(alloc, description)); +} + +// +// Has the caller recently authorized in such a way as to render unnecessary +// the usual QueryKeychainAuth dialog? (The right is specific to Keychain +// Access' way of editing a system keychain.) +// +bool KeychainPromptAclSubject::hasAuthorizedForSystemKeychain() const +{ + string rightString = "system.keychain.modify"; + return Server::session().isRightAuthorized(rightString, Server::connection(), false/*no UI*/); +} + + + +// +// Create a KeychainPromptAclSubject +// +uint32_t KeychainPromptAclSubject::Maker::defaultMode; + +KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const +{ + switch (list.length()) { +#if ACCEPT_LEGACY_FORM + case 2: // legacy case: just description + { + ListElement *params[1]; + crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); + return new KeychainPromptAclSubject(*params[0], defaultSelector); + } +#endif //ACCEPT_LEGACY_FORM + case 3: // standard case: selector + description + { + ListElement *params[2]; + crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); + return new KeychainPromptAclSubject(*params[1], + *params[0]->data().interpretedAs(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); + } + default: + CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + } +} + +KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, + Reader &pub, Reader &) const +{ + CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; + const char *description; + switch (version) { + case pumaVersion: + selector = defaultSelector; + pub(description); + break; + case jaguarVersion: + pub(selector); + selector.version = n2h(selector.version); + selector.flags = n2h(selector.flags); + pub(description); + break; + default: + CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + } + return new KeychainPromptAclSubject(description, selector); +} + +KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, + const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) + : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT), + selector(sel), description(descr) +{ + // check selector version + if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) + CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + + // always use the latest binary version + version(currentVersion); +} + + +// +// Export the subject to a memory blob +// +void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) +{ + if (version() != 0) { + selector.version = h2n (selector.version); + selector.flags = h2n (selector.flags); + pub(selector); + } + + pub.insert(description.size() + 1); +} + +void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) +{ + if (version() != 0) { + selector.version = h2n (selector.version); + selector.flags = h2n (selector.flags); + pub(selector); + } + pub(description.c_str()); +} + + +#ifdef DEBUGDUMP + +void KeychainPromptAclSubject::debugDump() const +{ + Debug::dump("KeychainPrompt:%s(%s)", + description.c_str(), + (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); +} + +#endif //DEBUGDUMP