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 #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 | |
OLD | NEW |