Index: chrome/browser/mac/keychain_reauthorize.cc |
=================================================================== |
--- chrome/browser/mac/keychain_reauthorize.cc (revision 0) |
+++ chrome/browser/mac/keychain_reauthorize.cc (revision 0) |
@@ -0,0 +1,441 @@ |
+// 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" |
+ |
+#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/stringprintf.h" |
+#include "base/sys_string_conversions.h" |
+#include "chrome/browser/mac/security_wrappers.h" |
+ |
+namespace chrome { |
+namespace browser { |
+namespace mac { |
+ |
+namespace { |
+ |
+// Returns the set of requirement strings that ought to be reauthorized. |
+std::vector<std::string> RequirementMatches(); |
+ |
+// 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::mac::ScopedCFTypeRef<SecKeychainSearchRef> search( |
+ CrSKeychainSearchCreateFromAttributes(NULL, |
+ CSSM_DL_DB_RECORD_ANY, |
+ NULL)); |
+ |
+ std::vector<std::string> requirement_matches = |
+ RequirementMatches(); |
+ |
+ base::mac::ScopedCFTypeRef<SecTrustedApplicationRef> this_application( |
+ CrSTrustedApplicationCreateFromPath(NULL)); |
+ |
+ std::vector<CrSKeychainItemAndAccess> items_and_reauthorized_accesses = |
+ KCSearchToKCItemsAndReauthorizedAccesses(search, |
+ requirement_matches, |
+ this_application); |
+ |
+ WriteKCItemsAndReauthorizedAccesses(items_and_reauthorized_accesses); |
+} |
+ |
+namespace { |
+ |
+std::vector<std::string> RequirementMatches() { |
+ // 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])); |
+ } |
+ } |
+ |
+ 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::mac::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::mac::ScopedCFTypeRef<SecAccessRef> access( |
+ CrSKeychainItemCopyAccess(item)); |
+ base::mac::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)); |
+ base::mac::ScopedCFTypeRef<SecRequirementRef> requirement( |
+ CrSTrustedApplicationCopyRequirement(application)); |
+ base::mac::ScopedCFTypeRef<CFStringRef> requirement_string_cf( |
+ CrSRequirementCopyString(requirement, kSecCSDefaultFlags)); |
+ if (!requirement_string_cf) { |
+ continue; |
+ } |
+ |
+ std::string requirement_string = |
+ base::SysCFStringRefToUTF8(requirement_string_cf); |
+ 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::mac::ScopedCFTypeRef<SecKeychainRef> keychain( |
+ CrSKeychainItemCopyKeychain(old_item)); |
+ |
+ ScopedCrSKeychainItemAttributesAndData old_attributes_and_data( |
+ CrSKeychainItemCopyAttributesAndData(keychain, old_item)); |
+ if (!old_attributes_and_data.get()) { |
+ 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::mac::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 mac |
+} // namespace browser |
+} // namespace chrome |