Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / mac / keychain_reauthorize.mm
blob24d046db710f7e682dba897e217e780cfec9225b
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.
5 #include "chrome/browser/mac/keychain_reauthorize.h"
7 #import <Foundation/Foundation.h>
8 #include <Security/Security.h>
10 #include <algorithm>
11 #include <string>
12 #include <vector>
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"
23 namespace chrome {
25 namespace {
27 // Returns the requirement string embedded within a SecTrustedApplicationRef,
28 // or an empty string on error.
29 std::string RequirementStringForApplication(
30     SecTrustedApplicationRef application);
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);
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);
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);
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);
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);
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);
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);
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);
111 }  // namespace
113 void KeychainReauthorize() {
114   ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed(FALSE);
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));
124   base::ScopedCFTypeRef<SecTrustedApplicationRef> this_application(
125       CrSTrustedApplicationCreateFromPath(NULL));
127   std::vector<std::string> requirement_matches =
128       GetRequirementMatches(this_application);
130   std::vector<CrSKeychainItemAndAccess> items_and_reauthorized_accesses =
131       KCSearchToKCItemsAndReauthorizedAccesses(search,
132                                                requirement_matches,
133                                                this_application);
135   WriteKCItemsAndReauthorizedAccesses(items_and_reauthorized_accesses);
138 void KeychainReauthorizeIfNeeded(NSString* pref_key, int max_tries) {
139   NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults];
140   int pref_value = [user_defaults integerForKey:pref_key];
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     }
153     ++pref_value;
154     [user_defaults setInteger:pref_value forKey:pref_key];
155     [user_defaults synchronize];
157     KeychainReauthorize();
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];
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   }
175 namespace {
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   }
187   std::string requirement_string =
188       base::SysCFStringRefToUTF8(requirement_string_cf);
190   return requirement_string;
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.)
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   };
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",
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",
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",
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   };
266   std::vector<std::string> requirement_matches;
267   requirement_matches.reserve(arraysize(kIdentifierMatches) *
268                               ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches));
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   }
284   if (application && base::mac::AmIBundled()) {
285     std::string application_requirement =
286         RequirementStringForApplication(application);
287     requirement_matches.push_back(application_requirement);
288   }
290   return requirement_matches;
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;
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));
306     if (item_and_access.get()) {
307       items_and_accesses.push_back(*item_and_access);
308     }
309   }
311   return items_and_accesses;
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   }
322   base::ScopedCFTypeRef<SecAccessRef> access(CrSKeychainItemCopyAccess(item));
323   base::ScopedCFTypeRef<CFArrayRef> acl_list(CrSAccessCopyACLList(access));
324   if (!acl_list) {
325     return NULL;
326   }
328   bool acl_list_modified = ReauthorizeACLList(acl_list,
329                                               requirement_matches,
330                                               this_application);
331   if (!acl_list_modified) {
332     return NULL;
333   }
335   return new CrSKeychainItemAndAccess(item, access);
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;
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     }
352     if (ReauthorizeACL(acl, requirement_matches, this_application)) {
353       acl_list_modified = true;
354     }
355   }
357   return acl_list_modified;
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   }
371   CFMutableArrayRef application_list_mutable = NULL;
372   bool added_this_application = false;
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     }
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       }
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   }
418   if (!application_list_mutable) {
419     return false;
420   }
422   if (!CrSACLSetSimpleContents(acl, *acl_simple_contents.get())) {
423     return false;
424   }
426   return true;
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   }
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));
446   ScopedCrSKeychainItemAttributesAndData old_attributes_and_data(
447       CrSKeychainItemCopyAttributesAndData(keychain, old_item));
448   if (!old_attributes_and_data.get()) {
449     return;
450   }
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   }
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;
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   }
500   base::ScopedCFTypeRef<SecKeychainItemRef> new_item(
501       CrSKeychainItemCreateFromContent(new_attributes_and_data,
502                                        keychain,
503                                        item_and_reauthorized_access.access()));
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   }
521   return new_attributes;
524 }  // namespace
526 }  // namespace chrome