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>
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"
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
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
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(
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
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
107 void WriteKCItemsAndReauthorizedAccesses(
108 const std::vector<CrSKeychainItemAndAccess>&
109 items_and_reauthorized_accesses);
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
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,
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);
148 UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdate",
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",
169 UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdateSuccess",
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();
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"
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.)
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)
212 "com.google.Chrome.canary",
214 "org.chromium.Chromium",
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
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",
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]));
284 if (application && base::mac::AmIBundled()) {
285 std::string application_requirement =
286 RequirementStringForApplication(application);
287 requirement_matches.push_back(application_requirement);
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,
306 if (item_and_access.get()) {
307 items_and_accesses.push_back(*item_and_access);
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)) {
322 base::ScopedCFTypeRef<SecAccessRef> access(CrSKeychainItemCopyAccess(item));
323 base::ScopedCFTypeRef<CFArrayRef> acl_list(CrSAccessCopyACLList(access));
328 bool acl_list_modified = ReauthorizeACLList(acl_list,
331 if (!acl_list_modified) {
335 return new CrSKeychainItemAndAccess(item, access);
338 bool ReauthorizeACLList(
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));
352 if (ReauthorizeACL(acl, requirement_matches, this_application)) {
353 acl_list_modified = true;
357 return acl_list_modified;
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) {
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,
383 std::string requirement_string =
384 RequirementStringForApplication(application);
385 if (requirement_string.empty()) {
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,
396 acl_simple_contents->application_list);
397 acl_simple_contents->application_list.reset(
398 application_list_mutable);
401 if (!added_this_application) {
402 CFArraySetValueAtIndex(application_list_mutable,
405 added_this_application = true;
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,
418 if (!application_list_mutable) {
422 if (!CrSACLSetSimpleContents(acl, *acl_simple_contents.get())) {
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();
436 WriteKCItemAndReauthorizedAccess(*iterator);
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()) {
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:
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.
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)) {
500 base::ScopedCFTypeRef<SecKeychainItemRef> new_item(
501 CrSKeychainItemCreateFromContent(new_attributes_and_data,
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);
521 return new_attributes;
526 } // namespace chrome