Home | History | Annotate | Download | only in mac
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/mac/keychain_reauthorize.h"
      6 
      7 #import <Foundation/Foundation.h>
      8 #include <Security/Security.h>
      9 
     10 #include <algorithm>
     11 #include <string>
     12 #include <vector>
     13 
     14 #include "base/basictypes.h"
     15 #include "base/mac/foundation_util.h"
     16 #include "base/mac/scoped_cftyperef.h"
     17 #include "base/memory/scoped_ptr.h"
     18 #include "base/metrics/histogram.h"
     19 #include "base/strings/stringprintf.h"
     20 #include "base/strings/sys_string_conversions.h"
     21 #include "chrome/browser/mac/security_wrappers.h"
     22 
     23 namespace chrome {
     24 
     25 namespace {
     26 
     27 // Returns the requirement string embedded within a SecTrustedApplicationRef,
     28 // or an empty string on error.
     29 std::string RequirementStringForApplication(
     30     SecTrustedApplicationRef application);
     31 
     32 // Returns the set of requirement strings that ought to be reauthorized. In a
     33 // bundled application, the requirement string from |application| will also be
     34 // added to the hard-coded list. This allows an at-launch reauthorization to
     35 // re-reauthorize anything done by a previous at-update reauthorization.
     36 // Although items reauthorized during the at-update step will work properly in
     37 // every way, they contain a reference to the missing reauthorization stub
     38 // executable from the disk image in the Keychain, resulting in no icon and
     39 // a weird name like "com.google" (non-Canary) or "com.google.Chrome"
     40 // (Canary). Because reauthorization is controlled by a preference that limits
     41 // it to a single successful run at update and a single successful run at
     42 // launch, protection already exists against perpetually reauthorizing items.
     43 // This addition exists simply to make the Keychain Access UI match
     44 // expectations.
     45 std::vector<std::string> GetRequirementMatches(
     46     SecTrustedApplicationRef application);
     47 
     48 // Reauthorizes an ACL by examining all of the applications it names, and upon
     49 // finding any whose requirement matches any element of requirement_matches,
     50 // replaces them with this_application. At most one instance of
     51 // this_application will be added to the ACL. Subsequent applications whose
     52 // requirement matches any element of requirement_matches will be removed from
     53 // the ACL. Only the ACL is changed, nothing is written to disk. Returns true
     54 // if any reauthorization is performed and thus acl is modified, and false
     55 // otherwise.
     56 bool ReauthorizeACL(
     57     SecACLRef acl,
     58     const std::vector<std::string>& requirement_matches,
     59     SecTrustedApplicationRef this_application);
     60 
     61 // Reauthorizes a list of ACLs by calling ReauthorizeACL for each ACL in the
     62 // list. Only the ACL list is changed, nothing is written to disk. Returns
     63 // true if ReauthorizeTrue returns true for any ACL in acl_list, indicating
     64 // that at least one ACL in acl_list was modified and thus at least one child
     65 // child of acl_list was reauthorized.
     66 bool ReauthorizeACLList(
     67     CFArrayRef acl_list,
     68     const std::vector<std::string>& requirement_matches,
     69     SecTrustedApplicationRef this_application);
     70 
     71 // Reauthorizes a SecKeychainItemRef by calling ReauthorizeACLList to perform
     72 // reauthorization on all ACLs that it contains. Nothing is written to disk.
     73 // If any reauthorization was performed, returns a CrSKeychainItemAndAccess
     74 // object containing the item and its access information. Otherwise, returns
     75 // NULL.
     76 CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess(
     77     SecKeychainItemRef item,
     78     const std::vector<std::string>& requirement_matches,
     79     SecTrustedApplicationRef this_application);
     80 
     81 // Reauthorizes multiple Keychain items by calling
     82 // KCItemToKCItemAndReauthorizedAccess for each item returned by a Keychain
     83 // search. Nothing is written to disk. Reauthorized items are returned.
     84 std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses(
     85     SecKeychainSearchRef search,
     86     const std::vector<std::string>& requirement_matches,
     87     SecTrustedApplicationRef this_application);
     88 
     89 // Given a SecKeychainAttributeList, strips out any zero-length attributes and
     90 // returns a vector containing the remaining attributes.
     91 std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength(
     92     SecKeychainAttributeList* old_attribute_list);
     93 
     94 // Given a CrSKeychainItemAndAccess that has had its access field
     95 // reauthorized, places the reauthorized form into the Keychain by deleting
     96 // the old item and replacing it with a new one whose access policy matches
     97 // the reauthorized form. The new item is written to disk and becomes part of
     98 // the Keychain, replacing what had been there previously.
     99 void WriteKCItemAndReauthorizedAccess(
    100     const CrSKeychainItemAndAccess& item_and_reauthorized_access);
    101 
    102 // Given a vector of CrSKeychainItemAndAccess objects, places the reauthorized
    103 // forms of all of them into the Keychain by calling
    104 // WriteKCItemAndReauthorizedAccess for each. The new items are written to
    105 // disk and become part of the Keychain, replacing what had been there
    106 // previously.
    107 void WriteKCItemsAndReauthorizedAccesses(
    108     const std::vector<CrSKeychainItemAndAccess>&
    109         items_and_reauthorized_accesses);
    110 
    111 }  // namespace
    112 
    113 void KeychainReauthorize() {
    114   ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed(FALSE);
    115 
    116   // Apple's documentation (Keychain Services Reference, Constants/Mac OS X
    117   // Keychain Services API Constants/Keychain Item Class Constants) says to
    118   // use CSSM_DL_DB_RECORD_ALL_KEYS, but that doesn't work.
    119   // CSSM_DL_DB_RECORD_ANY (as used by SecurityTool's keychain-dump) does
    120   // work.
    121   base::ScopedCFTypeRef<SecKeychainSearchRef> search(
    122       CrSKeychainSearchCreateFromAttributes(NULL, CSSM_DL_DB_RECORD_ANY, NULL));
    123 
    124   base::ScopedCFTypeRef<SecTrustedApplicationRef> this_application(
    125       CrSTrustedApplicationCreateFromPath(NULL));
    126 
    127   std::vector<std::string> requirement_matches =
    128       GetRequirementMatches(this_application);
    129 
    130   std::vector<CrSKeychainItemAndAccess> items_and_reauthorized_accesses =
    131       KCSearchToKCItemsAndReauthorizedAccesses(search,
    132                                                requirement_matches,
    133                                                this_application);
    134 
    135   WriteKCItemsAndReauthorizedAccesses(items_and_reauthorized_accesses);
    136 }
    137 
    138 void KeychainReauthorizeIfNeeded(NSString* pref_key, int max_tries) {
    139   NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults];
    140   int pref_value = [user_defaults integerForKey:pref_key];
    141 
    142   if (pref_value < max_tries) {
    143     if (pref_value > 0) {
    144       // Logs the number of previous tries that didn't complete.
    145       if (base::mac::AmIBundled()) {
    146         UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeeded", pref_value);
    147       } else {
    148         UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdate",
    149                              pref_value);
    150       }
    151     }
    152 
    153     ++pref_value;
    154     [user_defaults setInteger:pref_value forKey:pref_key];
    155     [user_defaults synchronize];
    156 
    157     KeychainReauthorize();
    158 
    159     [user_defaults setInteger:max_tries forKey:pref_key];
    160     NSString* success_pref_key = [pref_key stringByAppendingString:@"Success"];
    161     [user_defaults setBool:YES forKey:success_pref_key];
    162     [user_defaults synchronize];
    163 
    164     // Logs the try number (1, 2) that succeeded.
    165     if (base::mac::AmIBundled()) {
    166       UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededSuccess",
    167                            pref_value);
    168     } else {
    169       UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdateSuccess",
    170                            pref_value);
    171     }
    172   }
    173 }
    174 
    175 namespace {
    176 
    177 std::string RequirementStringForApplication(
    178     SecTrustedApplicationRef application) {
    179   base::ScopedCFTypeRef<SecRequirementRef> requirement(
    180       CrSTrustedApplicationCopyRequirement(application));
    181   base::ScopedCFTypeRef<CFStringRef> requirement_string_cf(
    182       CrSRequirementCopyString(requirement, kSecCSDefaultFlags));
    183   if (!requirement_string_cf) {
    184     return std::string();
    185   }
    186 
    187   std::string requirement_string =
    188       base::SysCFStringRefToUTF8(requirement_string_cf);
    189 
    190   return requirement_string;
    191 }
    192 
    193 std::vector<std::string> GetRequirementMatches(
    194       SecTrustedApplicationRef application) {
    195   // See the designated requirement for a signed released build:
    196   // codesign -d -r- "Google Chrome.app"
    197   //
    198   // Export the certificates from a signed released build:
    199   // codesign -v --extract-certificates=/tmp/cert. "Google Chrome.app"
    200   // (The extracted leaf certificate is at /tmp/cert.0; intermediates and root
    201   // are at successive numbers.)
    202   //
    203   // Show some information about the exported certificates:
    204   // openssl x509 -inform DER -in /tmp/cert.0 -noout -text -fingerprint
    205   // (The "SHA1 Fingerprint" value printed by -fingerprint should match the
    206   // hash used in a codesign designated requirement after allowing for obvious
    207   // formatting differences.)
    208 
    209   const char* const kIdentifierMatches[] = {
    210 #if defined(GOOGLE_CHROME_BUILD)
    211     "com.google.Chrome",
    212     "com.google.Chrome.canary",
    213 #else
    214     "org.chromium.Chromium",
    215 #endif
    216   };
    217 
    218   const char* const kLeafCertificateHashMatches[] = {
    219     // Only official released builds of Google Chrome have ever been signed
    220     // (with a certificate that anyone knows about or cares about).
    221 #if defined(GOOGLE_CHROME_BUILD)
    222     // This is the new certificate that has not yet been used to sign Chrome,
    223     // but will be. Once used, the reauthorization code will become obsolete
    224     // until it's needed for some other purpose in the future.
    225     // Subject: UID=EQHXZ8M8AV, CN=Developer ID Application: Google Inc.,
    226     //     OU=EQHXZ8M8AV, O=Google Inc., C=US
    227     // Issuer: CN=Developer ID Certification Authority,
    228     //     OU=Apple Certification Authority, O=Apple Inc., C=US
    229     // Validity: 2012-04-26 14:10:10 UTC to 2017-04-27 14:10:10 UTC
    230     // "85cee8254216185620ddc8851c7a9fc4dfe120ef",
    231 
    232     // This certificate was used on 2011-12-20 and 2011-12-21, but the "since
    233     // 2010-07-19" one below was restored afterwards as an interim fix to the
    234     // Keychain authorization problem. See http://crbug.com/108238 and
    235     // http://crbug.com/62605.
    236     // Subject: C=US, ST=California, L=Mountain View, O=Google Inc,
    237     //     OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc
    238     // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network,
    239     //     OU=Terms of use at https://www.verisign.com/rpa (c)10,
    240     //     CN=VeriSign Class 3 Code Signing 2010 CA
    241     // Validity: 2011-11-14 00:00:00 UTC to 2014-11-13 23:59:59 UTC
    242     "06c92bec3bbf32068cb9208563d004169448ee21",
    243 
    244     // This certificate has been used since 2010-07-19, except for the brief
    245     // period when the certificate above was used.
    246     // Subject: C=US, ST=California, L=Mountain View, O=Google Inc,
    247     //     OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc
    248     // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network,
    249     //    OU=Terms of use at https://www.verisign.com/rpa (c)09,
    250     //    CN=VeriSign Class 3 Code Signing 2009-2 CA
    251     // Validity: 2010-02-22 00:00:00 UTC to 2012-02-22 23:59:59 UTC
    252     "9481882581d8178db8b1649c0eaa4f9eb11288f0",
    253 
    254     // This certificate was used for all public Chrome releases prior to
    255     // 2010-07-19.
    256     // Subject: C=US, ST=California, L=Mountain View, O=Google Inc,
    257     //     OU=Digital ID Class 3 - Netscape Object Signing, CN=Google Inc
    258     // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network,
    259     //     OU=Terms of use at https://www.verisign.com/rpa (c)04,
    260     //     CN=VeriSign Class 3 Code Signing 2004 CA
    261     // Validity: 2007-06-19 00:00:00 UTC to 2010-06-18 23:59:59 UTC
    262     "fe5008fe0da7a2033816752d6eafe95214f5a7e1",
    263 #endif
    264   };
    265 
    266   std::vector<std::string> requirement_matches;
    267   requirement_matches.reserve(arraysize(kIdentifierMatches) *
    268                               ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches));
    269 
    270   for (size_t identifier_index = 0;
    271        identifier_index < arraysize(kIdentifierMatches);
    272        ++identifier_index) {
    273     for (size_t leaf_certificate_hash_index = 0;
    274          leaf_certificate_hash_index <
    275              ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches);
    276          ++leaf_certificate_hash_index) {
    277       requirement_matches.push_back(base::StringPrintf(
    278           "identifier \"%s\" and certificate leaf = H\"%s\"",
    279           kIdentifierMatches[identifier_index],
    280           kLeafCertificateHashMatches[leaf_certificate_hash_index]));
    281     }
    282   }
    283 
    284   if (application && base::mac::AmIBundled()) {
    285     std::string application_requirement =
    286         RequirementStringForApplication(application);
    287     requirement_matches.push_back(application_requirement);
    288   }
    289 
    290   return requirement_matches;
    291 }
    292 
    293 std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses(
    294     SecKeychainSearchRef search,
    295     const std::vector<std::string>& requirement_matches,
    296     SecTrustedApplicationRef this_application) {
    297   std::vector<CrSKeychainItemAndAccess> items_and_accesses;
    298 
    299   base::ScopedCFTypeRef<SecKeychainItemRef> item;
    300   while (item.reset(CrSKeychainSearchCopyNext(search)), item) {
    301     scoped_ptr<CrSKeychainItemAndAccess> item_and_access(
    302         KCItemToKCItemAndReauthorizedAccess(item,
    303                                             requirement_matches,
    304                                             this_application));
    305 
    306     if (item_and_access.get()) {
    307       items_and_accesses.push_back(*item_and_access);
    308     }
    309   }
    310 
    311   return items_and_accesses;
    312 }
    313 
    314 CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess(
    315     SecKeychainItemRef item,
    316     const std::vector<std::string>& requirement_matches,
    317     SecTrustedApplicationRef this_application) {
    318   if (!CrSKeychainItemTestAccess(item)) {
    319     return NULL;
    320   }
    321 
    322   base::ScopedCFTypeRef<SecAccessRef> access(CrSKeychainItemCopyAccess(item));
    323   base::ScopedCFTypeRef<CFArrayRef> acl_list(CrSAccessCopyACLList(access));
    324   if (!acl_list) {
    325     return NULL;
    326   }
    327 
    328   bool acl_list_modified = ReauthorizeACLList(acl_list,
    329                                               requirement_matches,
    330                                               this_application);
    331   if (!acl_list_modified) {
    332     return NULL;
    333   }
    334 
    335   return new CrSKeychainItemAndAccess(item, access);
    336 }
    337 
    338 bool ReauthorizeACLList(
    339     CFArrayRef acl_list,
    340     const std::vector<std::string>& requirement_matches,
    341     SecTrustedApplicationRef this_application) {
    342   bool acl_list_modified = false;
    343 
    344   CFIndex acl_count = CFArrayGetCount(acl_list);
    345   for (CFIndex acl_index = 0; acl_index < acl_count; ++acl_index) {
    346     SecACLRef acl = base::mac::CFCast<SecACLRef>(
    347         CFArrayGetValueAtIndex(acl_list, acl_index));
    348     if (!acl) {
    349       continue;
    350     }
    351 
    352     if (ReauthorizeACL(acl, requirement_matches, this_application)) {
    353       acl_list_modified = true;
    354     }
    355   }
    356 
    357   return acl_list_modified;
    358 }
    359 
    360 bool ReauthorizeACL(
    361     SecACLRef acl,
    362     const std::vector<std::string>& requirement_matches,
    363     SecTrustedApplicationRef this_application) {
    364   scoped_ptr<CrSACLSimpleContents> acl_simple_contents(
    365       CrSACLCopySimpleContents(acl));
    366   if (!acl_simple_contents.get() ||
    367       !acl_simple_contents->application_list) {
    368     return false;
    369   }
    370 
    371   CFMutableArrayRef application_list_mutable = NULL;
    372   bool added_this_application = false;
    373 
    374   CFIndex application_count =
    375       CFArrayGetCount(acl_simple_contents->application_list);
    376   for (CFIndex application_index = 0;
    377        application_index < application_count;
    378        ++application_index) {
    379     SecTrustedApplicationRef application =
    380         base::mac::CFCast<SecTrustedApplicationRef>(
    381             CFArrayGetValueAtIndex(acl_simple_contents->application_list,
    382                                    application_index));
    383     std::string requirement_string =
    384         RequirementStringForApplication(application);
    385     if (requirement_string.empty()) {
    386       continue;
    387     }
    388 
    389     if (std::find(requirement_matches.begin(),
    390                   requirement_matches.end(),
    391                   requirement_string) != requirement_matches.end()) {
    392       if (!application_list_mutable) {
    393         application_list_mutable =
    394             CFArrayCreateMutableCopy(NULL,
    395                                      application_count,
    396                                      acl_simple_contents->application_list);
    397         acl_simple_contents->application_list.reset(
    398             application_list_mutable);
    399       }
    400 
    401       if (!added_this_application) {
    402         CFArraySetValueAtIndex(application_list_mutable,
    403                                application_index,
    404                                this_application);
    405         added_this_application = true;
    406       } else {
    407         // Even though it's more bookkeeping to walk a list in the forward
    408         // direction when there are removals, it's done here anyway to
    409         // keep this_application at the position of the first match.
    410         CFArrayRemoveValueAtIndex(application_list_mutable,
    411                                   application_index);
    412         --application_index;
    413         --application_count;
    414       }
    415     }
    416   }
    417 
    418   if (!application_list_mutable) {
    419     return false;
    420   }
    421 
    422   if (!CrSACLSetSimpleContents(acl, *acl_simple_contents.get())) {
    423     return false;
    424   }
    425 
    426   return true;
    427 }
    428 
    429 void WriteKCItemsAndReauthorizedAccesses(
    430     const std::vector<CrSKeychainItemAndAccess>&
    431         items_and_reauthorized_accesses) {
    432   for (std::vector<CrSKeychainItemAndAccess>::const_iterator iterator =
    433            items_and_reauthorized_accesses.begin();
    434        iterator != items_and_reauthorized_accesses.end();
    435        ++iterator) {
    436     WriteKCItemAndReauthorizedAccess(*iterator);
    437   }
    438 }
    439 
    440 void WriteKCItemAndReauthorizedAccess(
    441     const CrSKeychainItemAndAccess& item_and_reauthorized_access) {
    442   SecKeychainItemRef old_item = item_and_reauthorized_access.item();
    443   base::ScopedCFTypeRef<SecKeychainRef> keychain(
    444       CrSKeychainItemCopyKeychain(old_item));
    445 
    446   ScopedCrSKeychainItemAttributesAndData old_attributes_and_data(
    447       CrSKeychainItemCopyAttributesAndData(keychain, old_item));
    448   if (!old_attributes_and_data.get()) {
    449     return;
    450   }
    451 
    452   // CrSKeychainItemCreateFromContent (SecKeychainItemCreateFromContent)
    453   // returns errKCNoSuchAttr (errSecNoSuchAttr) when asked to add an item of
    454   // type kSecPrivateKeyItemClass. This would happen after the original
    455   // private key was deleted, resulting in data loss. I can't figure out how
    456   // SecKeychainItemCreateFromContent wants private keys added. Skip them,
    457   // only doing the reauthorization for Keychain item types known to work,
    458   // the item types expected to be used by most users and those that are
    459   // synced. See http://crbug.com/130738 and
    460   // http://lists.apple.com/archives/apple-cdsa/2006/Jan/msg00025.html .
    461   switch (old_attributes_and_data.item_class()) {
    462     case kSecInternetPasswordItemClass:
    463     case kSecGenericPasswordItemClass:
    464       break;
    465     default:
    466       return;
    467   }
    468 
    469   // SecKeychainItemCreateFromContent fails if any attribute is zero-length,
    470   // but old_attributes_and_data can contain zero-length attributes. Create
    471   // a new attribute list devoid of zero-length attributes.
    472   //
    473   // This is awkward: only the logic to build the
    474   // std::vector<SecKeychainAttribute> is in KCAttributesWithoutZeroLength
    475   // because the storage used for the new attribute list (the vector) needs to
    476   // persist through the lifetime of this function.
    477   // KCAttributesWithoutZeroLength doesn't return a
    478   // CrSKeychainItemAttributesAndData (which could be held here in a
    479   // ScopedCrSKeychainItemAttributesAndData) because it's more convenient to
    480   // build the attribute list using std::vector and point the data at the copy
    481   // in old_attributes_and_data, thus making nothing in new_attributes a
    482   // strongly-held reference.
    483   std::vector<SecKeychainAttribute> new_attributes =
    484       KCAttributesWithoutZeroLength(old_attributes_and_data.attribute_list());
    485   SecKeychainAttributeList new_attribute_list;
    486   new_attribute_list.count = new_attributes.size();
    487   new_attribute_list.attr =
    488       new_attribute_list.count ? &new_attributes[0] : NULL;
    489   CrSKeychainItemAttributesAndData new_attributes_and_data =
    490       *old_attributes_and_data.get();
    491   new_attributes_and_data.attribute_list = &new_attribute_list;
    492 
    493   // Delete the item last, to give everything else above a chance to bail
    494   // out early, and to ensure that the old item is still present while it
    495   // may still be used by the above code.
    496   if (!CrSKeychainItemDelete(old_item)) {
    497     return;
    498   }
    499 
    500   base::ScopedCFTypeRef<SecKeychainItemRef> new_item(
    501       CrSKeychainItemCreateFromContent(new_attributes_and_data,
    502                                        keychain,
    503                                        item_and_reauthorized_access.access()));
    504 }
    505 
    506 std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength(
    507     SecKeychainAttributeList* old_attribute_list) {
    508   UInt32 old_attribute_count = old_attribute_list->count;
    509   std::vector<SecKeychainAttribute> new_attributes;
    510   new_attributes.reserve(old_attribute_count);
    511   for (UInt32 old_attribute_index = 0;
    512        old_attribute_index < old_attribute_count;
    513        ++old_attribute_index) {
    514     SecKeychainAttribute* attribute =
    515         &old_attribute_list->attr[old_attribute_index];
    516     if (attribute->length) {
    517       new_attributes.push_back(*attribute);
    518     }
    519   }
    520 
    521   return new_attributes;
    522 }
    523 
    524 }  // namespace
    525 
    526 }  // namespace chrome
    527