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