OLD | NEW |
(Empty) | |
| 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 #include <Security/Security.h> |
| 8 |
| 9 #include <algorithm> |
| 10 #include <string> |
| 11 #include <vector> |
| 12 |
| 13 #include "base/basictypes.h" |
| 14 #include "base/mac/foundation_util.h" |
| 15 #include "base/mac/scoped_cftyperef.h" |
| 16 #include "base/memory/scoped_ptr.h" |
| 17 #include "base/stringprintf.h" |
| 18 #include "base/sys_string_conversions.h" |
| 19 #include "chrome/browser/mac/security_wrappers.h" |
| 20 |
| 21 namespace chrome { |
| 22 namespace browser { |
| 23 namespace mac { |
| 24 |
| 25 namespace { |
| 26 |
| 27 // Returns the set of requirement strings that ought to be reauthorized. |
| 28 std::vector<std::string> RequirementMatches(); |
| 29 |
| 30 // Reauthorizes an ACL by examining all of the applications it names, and upon |
| 31 // finding any whose requirement matches any element of requirement_matches, |
| 32 // replaces them with this_application. At most one instance of |
| 33 // this_application will be added to the ACL. Subsequent applications whose |
| 34 // requirement matches any element of requirement_matches will be removed from |
| 35 // the ACL. Only the ACL is changed, nothing is written to disk. Returns true |
| 36 // if any reauthorization is performed and thus acl is modified, and false |
| 37 // otherwise. |
| 38 bool ReauthorizeACL( |
| 39 SecACLRef acl, |
| 40 const std::vector<std::string>& requirement_matches, |
| 41 SecTrustedApplicationRef this_application); |
| 42 |
| 43 // Reauthorizes a list of ACLs by calling ReauthorizeACL for each ACL in the |
| 44 // list. Only the ACL list is changed, nothing is written to disk. Returns |
| 45 // true if ReauthorizeTrue returns true for any ACL in acl_list, indicating |
| 46 // that at least one ACL in acl_list was modified and thus at least one child |
| 47 // child of acl_list was reauthorized. |
| 48 bool ReauthorizeACLList( |
| 49 CFArrayRef acl_list, |
| 50 const std::vector<std::string>& requirement_matches, |
| 51 SecTrustedApplicationRef this_application); |
| 52 |
| 53 // Reauthorizes a SecKeychainItemRef by calling ReauthorizeACLList to perform |
| 54 // reauthorization on all ACLs that it contains. Nothing is written to disk. |
| 55 // If any reauthorization was performed, returns a CrSKeychainItemAndAccess |
| 56 // object containing the item and its access information. Otherwise, returns |
| 57 // NULL. |
| 58 CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess( |
| 59 SecKeychainItemRef item, |
| 60 const std::vector<std::string>& requirement_matches, |
| 61 SecTrustedApplicationRef this_application); |
| 62 |
| 63 // Reauthorizes multiple Keychain items by calling |
| 64 // KCItemToKCItemAndReauthorizedAccess for each item returned by a Keychain |
| 65 // search. Nothing is written to disk. Reauthorized items are returned. |
| 66 std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses( |
| 67 SecKeychainSearchRef search, |
| 68 const std::vector<std::string>& requirement_matches, |
| 69 SecTrustedApplicationRef this_application); |
| 70 |
| 71 // Given a SecKeychainAttributeList, strips out any zero-length attributes and |
| 72 // returns a vector containing the remaining attributes. |
| 73 std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength( |
| 74 SecKeychainAttributeList* old_attribute_list); |
| 75 |
| 76 // Given a CrSKeychainItemAndAccess that has had its access field |
| 77 // reauthorized, places the reauthorized form into the Keychain by deleting |
| 78 // the old item and replacing it with a new one whose access policy matches |
| 79 // the reauthorized form. The new item is written to disk and becomes part of |
| 80 // the Keychain, replacing what had been there previously. |
| 81 void WriteKCItemAndReauthorizedAccess( |
| 82 const CrSKeychainItemAndAccess& item_and_reauthorized_access); |
| 83 |
| 84 // Given a vector of CrSKeychainItemAndAccess objects, places the reauthorized |
| 85 // forms of all of them into the Keychain by calling |
| 86 // WriteKCItemAndReauthorizedAccess for each. The new items are written to |
| 87 // disk and become part of the Keychain, replacing what had been there |
| 88 // previously. |
| 89 void WriteKCItemsAndReauthorizedAccesses( |
| 90 const std::vector<CrSKeychainItemAndAccess>& |
| 91 items_and_reauthorized_accesses); |
| 92 |
| 93 } // namespace |
| 94 |
| 95 void KeychainReauthorize() { |
| 96 ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed(FALSE); |
| 97 |
| 98 // Apple's documentation (Keychain Services Reference, Constants/Mac OS X |
| 99 // Keychain Services API Constants/Keychain Item Class Constants) says to |
| 100 // use CSSM_DL_DB_RECORD_ALL_KEYS, but that doesn't work. |
| 101 // CSSM_DL_DB_RECORD_ANY (as used by SecurityTool's keychain-dump) does |
| 102 // work. |
| 103 base::mac::ScopedCFTypeRef<SecKeychainSearchRef> search( |
| 104 CrSKeychainSearchCreateFromAttributes(NULL, |
| 105 CSSM_DL_DB_RECORD_ANY, |
| 106 NULL)); |
| 107 |
| 108 std::vector<std::string> requirement_matches = |
| 109 RequirementMatches(); |
| 110 |
| 111 base::mac::ScopedCFTypeRef<SecTrustedApplicationRef> this_application( |
| 112 CrSTrustedApplicationCreateFromPath(NULL)); |
| 113 |
| 114 std::vector<CrSKeychainItemAndAccess> items_and_reauthorized_accesses = |
| 115 KCSearchToKCItemsAndReauthorizedAccesses(search, |
| 116 requirement_matches, |
| 117 this_application); |
| 118 |
| 119 WriteKCItemsAndReauthorizedAccesses(items_and_reauthorized_accesses); |
| 120 } |
| 121 |
| 122 namespace { |
| 123 |
| 124 std::vector<std::string> RequirementMatches() { |
| 125 // See the designated requirement for a signed released build: |
| 126 // codesign -d -r- "Google Chrome.app" |
| 127 // |
| 128 // Export the certificates from a signed released build: |
| 129 // codesign -v --extract-certificates=/tmp/cert. "Google Chrome.app" |
| 130 // (The extracted leaf certificate is at /tmp/cert.0; intermediates and root |
| 131 // are at successive numbers.) |
| 132 // |
| 133 // Show some information about the exported certificates: |
| 134 // openssl x509 -inform DER -in /tmp/cert.0 -noout -text -fingerprint |
| 135 // (The "SHA1 Fingerprint" value printed by -fingerprint should match the |
| 136 // hash used in a codesign designated requirement after allowing for obvious |
| 137 // formatting differences.) |
| 138 |
| 139 const char* const kIdentifierMatches[] = { |
| 140 #if defined(GOOGLE_CHROME_BUILD) |
| 141 "com.google.Chrome", |
| 142 "com.google.Chrome.canary", |
| 143 #else |
| 144 "org.chromium.Chromium", |
| 145 #endif |
| 146 }; |
| 147 |
| 148 const char* const kLeafCertificateHashMatches[] = { |
| 149 // Only official released builds of Google Chrome have ever been signed |
| 150 // (with a certificate that anyone knows about or cares about). |
| 151 #if defined(GOOGLE_CHROME_BUILD) |
| 152 // This is the new certificate that has not yet been used to sign Chrome, |
| 153 // but will be. Once used, the reauthorization code will become obsolete |
| 154 // until it's needed for some other purpose in the future. |
| 155 // Subject: UID=EQHXZ8M8AV, CN=Developer ID Application: Google Inc., |
| 156 // OU=EQHXZ8M8AV, O=Google Inc., C=US |
| 157 // Issuer: CN=Developer ID Certification Authority, |
| 158 // OU=Apple Certification Authority, O=Apple Inc., C=US |
| 159 // Validity: 2012-04-26 14:10:10 UTC to 2017-04-27 14:10:10 UTC |
| 160 // "85cee8254216185620ddc8851c7a9fc4dfe120ef", |
| 161 |
| 162 // This certificate was used on 2011-12-20 and 2011-12-21, but the "since |
| 163 // 2010-07-19" one below was restored afterwards as an interim fix to the |
| 164 // Keychain authorization problem. See http://crbug.com/108238 and |
| 165 // http://crbug.com/62605. |
| 166 // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, |
| 167 // OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc |
| 168 // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, |
| 169 // OU=Terms of use at https://www.verisign.com/rpa (c)10, |
| 170 // CN=VeriSign Class 3 Code Signing 2010 CA |
| 171 // Validity: 2011-11-14 00:00:00 UTC to 2014-11-13 23:59:59 UTC |
| 172 "06c92bec3bbf32068cb9208563d004169448ee21", |
| 173 |
| 174 // This certificate has been used since 2010-07-19, except for the brief |
| 175 // period when the certificate above was used. |
| 176 // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, |
| 177 // OU=Digital ID Class 3 - Java Object Signing, CN=Google Inc |
| 178 // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, |
| 179 // OU=Terms of use at https://www.verisign.com/rpa (c)09, |
| 180 // CN=VeriSign Class 3 Code Signing 2009-2 CA |
| 181 // Validity: 2010-02-22 00:00:00 UTC to 2012-02-22 23:59:59 UTC |
| 182 "9481882581d8178db8b1649c0eaa4f9eb11288f0", |
| 183 |
| 184 // This certificate was used for all public Chrome releases prior to |
| 185 // 2010-07-19. |
| 186 // Subject: C=US, ST=California, L=Mountain View, O=Google Inc, |
| 187 // OU=Digital ID Class 3 - Netscape Object Signing, CN=Google Inc |
| 188 // Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, |
| 189 // OU=Terms of use at https://www.verisign.com/rpa (c)04, |
| 190 // CN=VeriSign Class 3 Code Signing 2004 CA |
| 191 // Validity: 2007-06-19 00:00:00 UTC to 2010-06-18 23:59:59 UTC |
| 192 "fe5008fe0da7a2033816752d6eafe95214f5a7e1", |
| 193 #endif |
| 194 }; |
| 195 |
| 196 std::vector<std::string> requirement_matches; |
| 197 requirement_matches.reserve(arraysize(kIdentifierMatches) * |
| 198 ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches)); |
| 199 |
| 200 for (size_t identifier_index = 0; |
| 201 identifier_index < arraysize(kIdentifierMatches); |
| 202 ++identifier_index) { |
| 203 for (size_t leaf_certificate_hash_index = 0; |
| 204 leaf_certificate_hash_index < |
| 205 ARRAYSIZE_UNSAFE(kLeafCertificateHashMatches); |
| 206 ++leaf_certificate_hash_index) { |
| 207 requirement_matches.push_back(base::StringPrintf( |
| 208 "identifier \"%s\" and certificate leaf = H\"%s\"", |
| 209 kIdentifierMatches[identifier_index], |
| 210 kLeafCertificateHashMatches[leaf_certificate_hash_index])); |
| 211 } |
| 212 } |
| 213 |
| 214 return requirement_matches; |
| 215 } |
| 216 |
| 217 std::vector<CrSKeychainItemAndAccess> KCSearchToKCItemsAndReauthorizedAccesses( |
| 218 SecKeychainSearchRef search, |
| 219 const std::vector<std::string>& requirement_matches, |
| 220 SecTrustedApplicationRef this_application) { |
| 221 std::vector<CrSKeychainItemAndAccess> items_and_accesses; |
| 222 |
| 223 base::mac::ScopedCFTypeRef<SecKeychainItemRef> item; |
| 224 while (item.reset(CrSKeychainSearchCopyNext(search)), item) { |
| 225 scoped_ptr<CrSKeychainItemAndAccess> item_and_access( |
| 226 KCItemToKCItemAndReauthorizedAccess(item, |
| 227 requirement_matches, |
| 228 this_application)); |
| 229 |
| 230 if (item_and_access.get()) { |
| 231 items_and_accesses.push_back(*item_and_access); |
| 232 } |
| 233 } |
| 234 |
| 235 return items_and_accesses; |
| 236 } |
| 237 |
| 238 CrSKeychainItemAndAccess* KCItemToKCItemAndReauthorizedAccess( |
| 239 SecKeychainItemRef item, |
| 240 const std::vector<std::string>& requirement_matches, |
| 241 SecTrustedApplicationRef this_application) { |
| 242 if (!CrSKeychainItemTestAccess(item)) { |
| 243 return NULL; |
| 244 } |
| 245 |
| 246 base::mac::ScopedCFTypeRef<SecAccessRef> access( |
| 247 CrSKeychainItemCopyAccess(item)); |
| 248 base::mac::ScopedCFTypeRef<CFArrayRef> acl_list( |
| 249 CrSAccessCopyACLList(access)); |
| 250 if (!acl_list) { |
| 251 return NULL; |
| 252 } |
| 253 |
| 254 bool acl_list_modified = ReauthorizeACLList(acl_list, |
| 255 requirement_matches, |
| 256 this_application); |
| 257 if (!acl_list_modified) { |
| 258 return NULL; |
| 259 } |
| 260 |
| 261 return new CrSKeychainItemAndAccess(item, access); |
| 262 } |
| 263 |
| 264 bool ReauthorizeACLList( |
| 265 CFArrayRef acl_list, |
| 266 const std::vector<std::string>& requirement_matches, |
| 267 SecTrustedApplicationRef this_application) { |
| 268 bool acl_list_modified = false; |
| 269 |
| 270 CFIndex acl_count = CFArrayGetCount(acl_list); |
| 271 for (CFIndex acl_index = 0; acl_index < acl_count; ++acl_index) { |
| 272 SecACLRef acl = base::mac::CFCast<SecACLRef>( |
| 273 CFArrayGetValueAtIndex(acl_list, acl_index)); |
| 274 if (!acl) { |
| 275 continue; |
| 276 } |
| 277 |
| 278 if (ReauthorizeACL(acl, requirement_matches, this_application)) { |
| 279 acl_list_modified = true; |
| 280 } |
| 281 } |
| 282 |
| 283 return acl_list_modified; |
| 284 } |
| 285 |
| 286 bool ReauthorizeACL( |
| 287 SecACLRef acl, |
| 288 const std::vector<std::string>& requirement_matches, |
| 289 SecTrustedApplicationRef this_application) { |
| 290 scoped_ptr<CrSACLSimpleContents> acl_simple_contents( |
| 291 CrSACLCopySimpleContents(acl)); |
| 292 if (!acl_simple_contents.get() || |
| 293 !acl_simple_contents->application_list) { |
| 294 return false; |
| 295 } |
| 296 |
| 297 CFMutableArrayRef application_list_mutable = NULL; |
| 298 bool added_this_application = false; |
| 299 |
| 300 CFIndex application_count = |
| 301 CFArrayGetCount(acl_simple_contents->application_list); |
| 302 for (CFIndex application_index = 0; |
| 303 application_index < application_count; |
| 304 ++application_index) { |
| 305 SecTrustedApplicationRef application = |
| 306 base::mac::CFCast<SecTrustedApplicationRef>( |
| 307 CFArrayGetValueAtIndex(acl_simple_contents->application_list, |
| 308 application_index)); |
| 309 base::mac::ScopedCFTypeRef<SecRequirementRef> requirement( |
| 310 CrSTrustedApplicationCopyRequirement(application)); |
| 311 base::mac::ScopedCFTypeRef<CFStringRef> requirement_string_cf( |
| 312 CrSRequirementCopyString(requirement, kSecCSDefaultFlags)); |
| 313 if (!requirement_string_cf) { |
| 314 continue; |
| 315 } |
| 316 |
| 317 std::string requirement_string = |
| 318 base::SysCFStringRefToUTF8(requirement_string_cf); |
| 319 if (std::find(requirement_matches.begin(), |
| 320 requirement_matches.end(), |
| 321 requirement_string) != requirement_matches.end()) { |
| 322 if (!application_list_mutable) { |
| 323 application_list_mutable = |
| 324 CFArrayCreateMutableCopy(NULL, |
| 325 application_count, |
| 326 acl_simple_contents->application_list); |
| 327 acl_simple_contents->application_list.reset( |
| 328 application_list_mutable); |
| 329 } |
| 330 |
| 331 if (!added_this_application) { |
| 332 CFArraySetValueAtIndex(application_list_mutable, |
| 333 application_index, |
| 334 this_application); |
| 335 added_this_application = true; |
| 336 } else { |
| 337 // Even though it's more bookkeeping to walk a list in the forward |
| 338 // direction when there are removals, it's done here anyway to |
| 339 // keep this_application at the position of the first match. |
| 340 CFArrayRemoveValueAtIndex(application_list_mutable, |
| 341 application_index); |
| 342 --application_index; |
| 343 --application_count; |
| 344 } |
| 345 } |
| 346 } |
| 347 |
| 348 if (!application_list_mutable) { |
| 349 return false; |
| 350 } |
| 351 |
| 352 if (!CrSACLSetSimpleContents(acl, *acl_simple_contents.get())) { |
| 353 return false; |
| 354 } |
| 355 |
| 356 return true; |
| 357 } |
| 358 |
| 359 void WriteKCItemsAndReauthorizedAccesses( |
| 360 const std::vector<CrSKeychainItemAndAccess>& |
| 361 items_and_reauthorized_accesses) { |
| 362 for (std::vector<CrSKeychainItemAndAccess>::const_iterator iterator = |
| 363 items_and_reauthorized_accesses.begin(); |
| 364 iterator != items_and_reauthorized_accesses.end(); |
| 365 ++iterator) { |
| 366 WriteKCItemAndReauthorizedAccess(*iterator); |
| 367 } |
| 368 } |
| 369 |
| 370 void WriteKCItemAndReauthorizedAccess( |
| 371 const CrSKeychainItemAndAccess& item_and_reauthorized_access) { |
| 372 SecKeychainItemRef old_item = item_and_reauthorized_access.item(); |
| 373 base::mac::ScopedCFTypeRef<SecKeychainRef> keychain( |
| 374 CrSKeychainItemCopyKeychain(old_item)); |
| 375 |
| 376 ScopedCrSKeychainItemAttributesAndData old_attributes_and_data( |
| 377 CrSKeychainItemCopyAttributesAndData(keychain, old_item)); |
| 378 if (!old_attributes_and_data.get()) { |
| 379 return; |
| 380 } |
| 381 |
| 382 // SecKeychainItemCreateFromContent fails if any attribute is zero-length, |
| 383 // but old_attributes_and_data can contain zero-length attributes. Create |
| 384 // a new attribute list devoid of zero-length attributes. |
| 385 // |
| 386 // This is awkward: only the logic to build the |
| 387 // std::vector<SecKeychainAttribute> is in KCAttributesWithoutZeroLength |
| 388 // because the storage used for the new attribute list (the vector) needs to |
| 389 // persist through the lifetime of this function. |
| 390 // KCAttributesWithoutZeroLength doesn't return a |
| 391 // CrSKeychainItemAttributesAndData (which could be held here in a |
| 392 // ScopedCrSKeychainItemAttributesAndData) because it's more convenient to |
| 393 // build the attribute list using std::vector and point the data at the copy |
| 394 // in old_attributes_and_data, thus making nothing in new_attributes a |
| 395 // strongly-held reference. |
| 396 std::vector<SecKeychainAttribute> new_attributes = |
| 397 KCAttributesWithoutZeroLength(old_attributes_and_data.attribute_list()); |
| 398 SecKeychainAttributeList new_attribute_list; |
| 399 new_attribute_list.count = new_attributes.size(); |
| 400 new_attribute_list.attr = |
| 401 new_attribute_list.count ? &new_attributes[0] : NULL; |
| 402 CrSKeychainItemAttributesAndData new_attributes_and_data = |
| 403 *old_attributes_and_data.get(); |
| 404 new_attributes_and_data.attribute_list = &new_attribute_list; |
| 405 |
| 406 // Delete the item last, to give everything else above a chance to bail |
| 407 // out early, and to ensure that the old item is still present while it |
| 408 // may still be used by the above code. |
| 409 if (!CrSKeychainItemDelete(old_item)) { |
| 410 return; |
| 411 } |
| 412 |
| 413 base::mac::ScopedCFTypeRef<SecKeychainItemRef> new_item( |
| 414 CrSKeychainItemCreateFromContent(new_attributes_and_data, |
| 415 keychain, |
| 416 item_and_reauthorized_access.access())); |
| 417 } |
| 418 |
| 419 std::vector<SecKeychainAttribute> KCAttributesWithoutZeroLength( |
| 420 SecKeychainAttributeList* old_attribute_list) { |
| 421 UInt32 old_attribute_count = old_attribute_list->count; |
| 422 std::vector<SecKeychainAttribute> new_attributes; |
| 423 new_attributes.reserve(old_attribute_count); |
| 424 for (UInt32 old_attribute_index = 0; |
| 425 old_attribute_index < old_attribute_count; |
| 426 ++old_attribute_index) { |
| 427 SecKeychainAttribute* attribute = |
| 428 &old_attribute_list->attr[old_attribute_index]; |
| 429 if (attribute->length) { |
| 430 new_attributes.push_back(*attribute); |
| 431 } |
| 432 } |
| 433 |
| 434 return new_attributes; |
| 435 } |
| 436 |
| 437 } // namespace |
| 438 |
| 439 } // namespace mac |
| 440 } // namespace browser |
| 441 } // namespace chrome |
OLD | NEW |