// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/mac/keychain_reauthorize.h" #import <Foundation/Foundation.h> #include <Security/Security.h> #include <algorithm> #include <string> #include <vector> #include "base/basictypes.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "chrome/browser/mac/security_wrappers.h" namespace chrome { namespace { // Returns the requirement string embedded within a SecTrustedApplicationRef, // or an empty string on error. std::string RequirementStringForApplication( SecTrustedApplicationRef application); // Returns the set of requirement strings that ought to be reauthorized. In a // bundled application, the requirement string from |application| will also be // added to the hard-coded list. This allows an at-launch reauthorization to // re-reauthorize anything done by a previous at-update reauthorization. // Although items reauthorized during the at-update step will work properly in // every way, they contain a reference to the missing reauthorization stub // executable from the disk image in the Keychain, resulting in no icon and // a weird name like "com.google" (non-Canary) or "com.google.Chrome" // (Canary). Because reauthorization is controlled by a preference that limits // it to a single successful run at update and a single successful run at // launch, protection already exists against perpetually reauthorizing items. // This addition exists simply to make the Keychain Access UI match // expectations. std::vector<std::string> GetRequirementMatches( SecTrustedApplicationRef application); // Reauthorizes an ACL by examining all of the applications it names, and upon // finding any whose requirement matches any element of requirement_matches, // replaces them with this_application. At most one instance of // this_application will be added to the ACL. Subsequent applications whose // requirement matches any element of requirement_matches will be removed from // the ACL. Only the ACL is changed, nothing is written to disk. Returns true // if any reauthorization is performed and thus acl is modified, and false // otherwise. bool ReauthorizeACL( SecACLRef acl, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application); // Reauthorizes a list of ACLs by calling ReauthorizeACL for each ACL in the // list. Only the ACL list is changed, nothing is written to disk. Returns // true if ReauthorizeTrue returns true for any ACL in acl_list, indicating // that at least one ACL in acl_list was modified and thus at least one child // child of acl_list was reauthorized. bool ReauthorizeACLList( CFArrayRef acl_list, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application); // Reauthorizes a SecKeychainItemRef by calling ReauthorizeACLList to perform // reauthorization on all ACLs that it contains. Nothing is written to disk. // If any reauthorization was performed, returns a CrSKeychainItemAndAccess // object containing the item and its access information. Otherwise, returns // NULL. CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess( SecKeychainItemRef item, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application); // Reauthorizes multiple Keychain items by calling // KCItemToKCItemAndReauthorizedAccess for each item returned by a Keychain // search. Nothing is written to disk. Reauthorized items are returned. std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses( SecKeychainSearchRef search, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application); // Given a SecKeychainAttributeList, strips out any zero-length attributes and // returns a vector containing the remaining attributes. std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength( SecKeychainAttributeList* old_attribute_list); // Given a CrSKeychainItemAndAccess that has had its access field // reauthorized, places the reauthorized form into the Keychain by deleting // the old item and replacing it with a new one whose access policy matches // the reauthorized form. The new item is written to disk and becomes part of // the Keychain, replacing what had been there previously. void WriteKCItemAndReauthorizedAccess( const CrSKeychainItemAndAccess& item_and_reauthorized_access); // Given a vector of CrSKeychainItemAndAccess objects, places the reauthorized // forms of all of them into the Keychain by calling // WriteKCItemAndReauthorizedAccess for each. The new items are written to // disk and become part of the Keychain, replacing what had been there // previously. void WriteKCItemsAndReauthorizedAccesses( const std::vector<CrSKeychainItemAndAccess>& items_and_reauthorized_accesses); } // namespace void KeychainReauthorize() { ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed(FALSE); // Apple's documentation (Keychain Services Reference, Constants/Mac OS X // Keychain Services API Constants/Keychain Item Class Constants) says to // use CSSM_DL_DB_RECORD_ALL_KEYS, but that doesn't work. // CSSM_DL_DB_RECORD_ANY (as used by SecurityTool's keychain-dump) does // work. base::ScopedCFTypeRef<SecKeychainSearchRef> search( CrSKeychainSearchCreateFromAttributes(NULL, CSSM_DL_DB_RECORD_ANY, NULL)); base::ScopedCFTypeRef<SecTrustedApplicationRef> this_application( CrSTrustedApplicationCreateFromPath(NULL)); std::vector<std::string> requirement_matches = GetRequirementMatches(this_application); std::vector<CrSKeychainItemAndAccess> items_and_reauthorized_accesses = KCSearchToKCItemsAndReauthorizedAccesses(search, requirement_matches, this_application); WriteKCItemsAndReauthorizedAccesses(items_and_reauthorized_accesses); } void KeychainReauthorizeIfNeeded(NSString* pref_key, int max_tries) { NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; int pref_value = [user_defaults integerForKey:pref_key]; if (pref_value < max_tries) { if (pref_value > 0) { // Logs the number of previous tries that didn't complete. if (base::mac::AmIBundled()) { UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeeded", pref_value); } else { UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdate", pref_value); } } ++pref_value; [user_defaults setInteger:pref_value forKey:pref_key]; [user_defaults synchronize]; KeychainReauthorize(); [user_defaults setInteger:max_tries forKey:pref_key]; NSString* success_pref_key = [pref_key stringByAppendingString:@"Success"]; [user_defaults setBool:YES forKey:success_pref_key]; [user_defaults synchronize]; // Logs the try number (1, 2) that succeeded. if (base::mac::AmIBundled()) { UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededSuccess", pref_value); } else { UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdateSuccess", pref_value); } } } namespace { std::string RequirementStringForApplication( SecTrustedApplicationRef application) { base::ScopedCFTypeRef<SecRequirementRef> requirement( CrSTrustedApplicationCopyRequirement(application)); base::ScopedCFTypeRef<CFStringRef> requirement_string_cf( CrSRequirementCopyString(requirement, kSecCSDefaultFlags)); if (!requirement_string_cf) { return std::string(); } std::string requirement_string = base::SysCFStringRefToUTF8(requirement_string_cf); return requirement_string; } std::vector<std::string> GetRequirementMatches( SecTrustedApplicationRef application) { // See the designated requirement for a signed released build: // codesign -d -r- "Google Chrome.app" // // Export the certificates from a signed released build: // codesign -v --extract-certificates=/tmp/cert. "Google Chrome.app" // (The extracted leaf certificate is at /tmp/cert.0; intermediates and root // are at successive numbers.) // // Show some information about the exported certificates: // openssl x509 -inform DER -in /tmp/cert.0 -noout -text -fingerprint // (The "SHA1 Fingerprint" value printed by -fingerprint should match the // hash used in a codesign designated requirement after allowing for obvious // formatting differences.) const char* const kIdentifierMatches[] = { #if defined(GOOGLE_CHROME_BUILD) "com.google.Chrome", "com.google.Chrome.canary", #else "org.chromium.Chromium", #endif }; const char* const kLeafCertificateHashMatches[] = { // Only official released builds of Google Chrome have ever been signed // (with a certificate that anyone knows about or cares about). #if defined(GOOGLE_CHROME_BUILD) // This is the new certificate that has not yet been used to sign Chrome, // but will be. Once used, the reauthorization code will become obsolete // until it's needed for some other purpose in the future. // Subject: UID=EQHXZ8M8AV, CN=Developer ID Application: Google Inc., // OU=EQHXZ8M8AV, O=Google Inc., C=US // Issuer: CN=Developer ID Certification Authority, // OU=Apple Certification Authority, O=Apple Inc., C=US // Validity: 2012-04-26 14:10:10 UTC to 2017-04-27 14:10:10 UTC // "85cee8254216185620ddc8851c7a9fc4dfe120ef", // This certificate was used on 2011-12-20 and 2011-12-21, but the "since // 2010-07-19" one below was restored afterwards as an interim fix to the // Keychain authorization problem. See http://crbug.com/108238 and // http://crbug.com/62605. // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, // OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, // OU=Terms of use at https://www.verisign.com/rpa (c)10, // CN=VeriSign Class 3 Code Signing 2010 CA // Validity: 2011-11-14 00:00:00 UTC to 2014-11-13 23:59:59 UTC "06c92bec3bbf32068cb9208563d004169448ee21", // This certificate has been used since 2010-07-19, except for the brief // period when the certificate above was used. // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, // OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, // OU=Terms of use at https://www.verisign.com/rpa (c)09, // CN=VeriSign Class 3 Code Signing 2009-2 CA // Validity: 2010-02-22 00:00:00 UTC to 2012-02-22 23:59:59 UTC "9481882581d8178db8b1649c0eaa4f9eb11288f0", // This certificate was used for all public Chrome releases prior to // 2010-07-19. // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, // OU=Digital ID Class 3 - Netscape Object Signing, CN=Google Inc // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, // OU=Terms of use at https://www.verisign.com/rpa (c)04, // CN=VeriSign Class 3 Code Signing 2004 CA // Validity: 2007-06-19 00:00:00 UTC to 2010-06-18 23:59:59 UTC "fe5008fe0da7a2033816752d6eafe95214f5a7e1", #endif }; std::vector<std::string> requirement_matches; requirement_matches.reserve(arraysize(kIdentifierMatches) * ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches)); for (size_t identifier_index = 0; identifier_index < arraysize(kIdentifierMatches); ++identifier_index) { for (size_t leaf_certificate_hash_index = 0; leaf_certificate_hash_index < ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches); ++leaf_certificate_hash_index) { requirement_matches.push_back(base::StringPrintf( "identifier \"%s\" and certificate leaf = H\"%s\"", kIdentifierMatches[identifier_index], kLeafCertificateHashMatches[leaf_certificate_hash_index])); } } if (application && base::mac::AmIBundled()) { std::string application_requirement = RequirementStringForApplication(application); requirement_matches.push_back(application_requirement); } return requirement_matches; } std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses( SecKeychainSearchRef search, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application) { std::vector<CrSKeychainItemAndAccess> items_and_accesses; base::ScopedCFTypeRef<SecKeychainItemRef> item; while (item.reset(CrSKeychainSearchCopyNext(search)), item) { scoped_ptr<CrSKeychainItemAndAccess> item_and_access( KCItemToKCItemAndReauthorizedAccess(item, requirement_matches, this_application)); if (item_and_access.get()) { items_and_accesses.push_back(*item_and_access); } } return items_and_accesses; } CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess( SecKeychainItemRef item, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application) { if (!CrSKeychainItemTestAccess(item)) { return NULL; } base::ScopedCFTypeRef<SecAccessRef> access(CrSKeychainItemCopyAccess(item)); base::ScopedCFTypeRef<CFArrayRef> acl_list(CrSAccessCopyACLList(access)); if (!acl_list) { return NULL; } bool acl_list_modified = ReauthorizeACLList(acl_list, requirement_matches, this_application); if (!acl_list_modified) { return NULL; } return new CrSKeychainItemAndAccess(item, access); } bool ReauthorizeACLList( CFArrayRef acl_list, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application) { bool acl_list_modified = false; CFIndex acl_count = CFArrayGetCount(acl_list); for (CFIndex acl_index = 0; acl_index < acl_count; ++acl_index) { SecACLRef acl = base::mac::CFCast<SecACLRef>( CFArrayGetValueAtIndex(acl_list, acl_index)); if (!acl) { continue; } if (ReauthorizeACL(acl, requirement_matches, this_application)) { acl_list_modified = true; } } return acl_list_modified; } bool ReauthorizeACL( SecACLRef acl, const std::vector<std::string>& requirement_matches, SecTrustedApplicationRef this_application) { scoped_ptr<CrSACLSimpleContents> acl_simple_contents( CrSACLCopySimpleContents(acl)); if (!acl_simple_contents.get() || !acl_simple_contents->application_list) { return false; } CFMutableArrayRef application_list_mutable = NULL; bool added_this_application = false; CFIndex application_count = CFArrayGetCount(acl_simple_contents->application_list); for (CFIndex application_index = 0; application_index < application_count; ++application_index) { SecTrustedApplicationRef application = base::mac::CFCast<SecTrustedApplicationRef>( CFArrayGetValueAtIndex(acl_simple_contents->application_list, application_index)); std::string requirement_string = RequirementStringForApplication(application); if (requirement_string.empty()) { continue; } if (std::find(requirement_matches.begin(), requirement_matches.end(), requirement_string) != requirement_matches.end()) { if (!application_list_mutable) { application_list_mutable = CFArrayCreateMutableCopy(NULL, application_count, acl_simple_contents->application_list); acl_simple_contents->application_list.reset( application_list_mutable); } if (!added_this_application) { CFArraySetValueAtIndex(application_list_mutable, application_index, this_application); added_this_application = true; } else { // Even though it's more bookkeeping to walk a list in the forward // direction when there are removals, it's done here anyway to // keep this_application at the position of the first match. CFArrayRemoveValueAtIndex(application_list_mutable, application_index); --application_index; --application_count; } } } if (!application_list_mutable) { return false; } if (!CrSACLSetSimpleContents(acl, *acl_simple_contents.get())) { return false; } return true; } void WriteKCItemsAndReauthorizedAccesses( const std::vector<CrSKeychainItemAndAccess>& items_and_reauthorized_accesses) { for (std::vector<CrSKeychainItemAndAccess>::const_iterator iterator = items_and_reauthorized_accesses.begin(); iterator != items_and_reauthorized_accesses.end(); ++iterator) { WriteKCItemAndReauthorizedAccess(*iterator); } } void WriteKCItemAndReauthorizedAccess( const CrSKeychainItemAndAccess& item_and_reauthorized_access) { SecKeychainItemRef old_item = item_and_reauthorized_access.item(); base::ScopedCFTypeRef<SecKeychainRef> keychain( CrSKeychainItemCopyKeychain(old_item)); ScopedCrSKeychainItemAttributesAndData old_attributes_and_data( CrSKeychainItemCopyAttributesAndData(keychain, old_item)); if (!old_attributes_and_data.get()) { return; } // CrSKeychainItemCreateFromContent (SecKeychainItemCreateFromContent) // returns errKCNoSuchAttr (errSecNoSuchAttr) when asked to add an item of // type kSecPrivateKeyItemClass. This would happen after the original // private key was deleted, resulting in data loss. I can't figure out how // SecKeychainItemCreateFromContent wants private keys added. Skip them, // only doing the reauthorization for Keychain item types known to work, // the item types expected to be used by most users and those that are // synced. See http://crbug.com/130738 and // http://lists.apple.com/archives/apple-cdsa/2006/Jan/msg00025.html . switch (old_attributes_and_data.item_class()) { case kSecInternetPasswordItemClass: case kSecGenericPasswordItemClass: break; default: return; } // SecKeychainItemCreateFromContent fails if any attribute is zero-length, // but old_attributes_and_data can contain zero-length attributes. Create // a new attribute list devoid of zero-length attributes. // // This is awkward: only the logic to build the // std::vector<SecKeychainAttribute> is in KCAttributesWithoutZeroLength // because the storage used for the new attribute list (the vector) needs to // persist through the lifetime of this function. // KCAttributesWithoutZeroLength doesn't return a // CrSKeychainItemAttributesAndData (which could be held here in a // ScopedCrSKeychainItemAttributesAndData) because it's more convenient to // build the attribute list using std::vector and point the data at the copy // in old_attributes_and_data, thus making nothing in new_attributes a // strongly-held reference. std::vector<SecKeychainAttribute> new_attributes = KCAttributesWithoutZeroLength(old_attributes_and_data.attribute_list()); SecKeychainAttributeList new_attribute_list; new_attribute_list.count = new_attributes.size(); new_attribute_list.attr = new_attribute_list.count ? &new_attributes[0] : NULL; CrSKeychainItemAttributesAndData new_attributes_and_data = *old_attributes_and_data.get(); new_attributes_and_data.attribute_list = &new_attribute_list; // Delete the item last, to give everything else above a chance to bail // out early, and to ensure that the old item is still present while it // may still be used by the above code. if (!CrSKeychainItemDelete(old_item)) { return; } base::ScopedCFTypeRef<SecKeychainItemRef> new_item( CrSKeychainItemCreateFromContent(new_attributes_and_data, keychain, item_and_reauthorized_access.access())); } std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength( SecKeychainAttributeList* old_attribute_list) { UInt32 old_attribute_count = old_attribute_list->count; std::vector<SecKeychainAttribute> new_attributes; new_attributes.reserve(old_attribute_count); for (UInt32 old_attribute_index = 0; old_attribute_index < old_attribute_count; ++old_attribute_index) { SecKeychainAttribute* attribute = &old_attribute_list->attr[old_attribute_index]; if (attribute->length) { new_attributes.push_back(*attribute); } } return new_attributes; } } // namespace } // namespace chrome