OLD | NEW |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/base/x509_certificate.h" | 5 #include "net/base/x509_certificate.h" |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 | 8 |
9 #include "base/lazy_instance.h" | 9 #include "base/lazy_instance.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
(...skipping 23 matching lines...) Expand all Loading... |
34 const X509Certificate::Format kFormatDecodePriority[] = { | 34 const X509Certificate::Format kFormatDecodePriority[] = { |
35 X509Certificate::FORMAT_SINGLE_CERTIFICATE, | 35 X509Certificate::FORMAT_SINGLE_CERTIFICATE, |
36 X509Certificate::FORMAT_PKCS7 | 36 X509Certificate::FORMAT_PKCS7 |
37 }; | 37 }; |
38 | 38 |
39 // The PEM block header used for DER certificates | 39 // The PEM block header used for DER certificates |
40 const char kCertificateHeader[] = "CERTIFICATE"; | 40 const char kCertificateHeader[] = "CERTIFICATE"; |
41 // The PEM block header used for PKCS#7 data | 41 // The PEM block header used for PKCS#7 data |
42 const char kPKCS7Header[] = "PKCS7"; | 42 const char kPKCS7Header[] = "PKCS7"; |
43 | 43 |
44 // A thread-safe cache for X509Certificate objects. | 44 // A thread-safe cache for OS certificate handles. |
45 // | 45 // |
46 // The cache does not hold a reference to the certificate objects. The objects | 46 // Within each of the supported underlying crypto libraries, a certificate |
47 // must |Remove| themselves from the cache upon destruction (or else the cache | 47 // handle is represented as a ref-counted object that contains the parsed |
48 // will be holding dead pointers to the objects). | 48 // data for the certificate. In addition, the underlying OS handle may also |
49 // TODO(rsleevi): There exists a chance of a use-after-free, due to a race | 49 // contain a copy of the original ASN.1 DER used to constructed the handle. |
50 // between Find() and Remove(). See http://crbug.com/49377 | 50 // |
| 51 // In order to reduce the memory usage when multiple SSL connections exist, |
| 52 // with each connection storing the server's identity certificate plus any |
| 53 // intermediates supplied, the certificate handles are cached. Any two |
| 54 // X509Certificates that were created from the same ASN.1 DER data, |
| 55 // regardless of where that data came from, will share the same underlying |
| 56 // OS certificate handle. |
51 class X509CertificateCache { | 57 class X509CertificateCache { |
52 public: | 58 public: |
53 void Insert(X509Certificate* cert); | 59 // Performs a compare-and-swap like operation. If an OS certificate handle |
54 void Remove(X509Certificate* cert); | 60 // for the same certificate data as |*cert_handle| already exists in the |
55 X509Certificate* Find(const SHA1Fingerprint& fingerprint); | 61 // cache, the original |*cert_handle| will be freed, and |cert_handle| |
| 62 // will be updated to point to the existing cached certificate, after |
| 63 // adding to the reference count. If an equivalent OS certificate handle |
| 64 // is not found, |*cert_handle| will be added to the cache and its |
| 65 // reference count increased, while otherwise remaining unmodified and |
| 66 // pointing to the caller's original certificate. |
| 67 void InsertOrUpdate(X509Certificate::OSCertHandle* cert_handle); |
| 68 |
| 69 // Decrements the reference count for |cert_handle|, and if it is the last |
| 70 // reference held in the cache, frees the underlying OS certificate |
| 71 // handle. InsertOrUpdate() must have been called prior to Remove() for |
| 72 // the |cert_handle|. |
| 73 void Remove(X509Certificate::OSCertHandle cert_handle); |
56 | 74 |
57 private: | 75 private: |
58 typedef std::map<SHA1Fingerprint, X509Certificate*, SHA1FingerprintLessThan> | 76 // A single entry in the cache. Certificates will be keyed by their SHA1 |
59 CertMap; | 77 // fingerprints, but will not be considered equivalent unless the entire |
| 78 // certificate data matches. |
| 79 struct Entry { |
| 80 Entry() : cert_handle(NULL), ref_count(0) {} |
| 81 X509Certificate::OSCertHandle cert_handle; |
| 82 |
| 83 // Increased by each call to InsertOrUpdate(), and balanced by each call |
| 84 // to Remove(). When it equals 0, all references have been released, so |
| 85 // the cache entry will be removed and the OS certificate released. |
| 86 size_t ref_count; |
| 87 }; |
| 88 typedef std::map<SHA1Fingerprint, Entry, SHA1FingerprintLessThan> CertMap; |
60 | 89 |
61 // Obtain an instance of X509CertificateCache via a LazyInstance. | 90 // Obtain an instance of X509CertificateCache via a LazyInstance. |
62 X509CertificateCache() {} | 91 X509CertificateCache() {} |
63 ~X509CertificateCache() {} | 92 ~X509CertificateCache() {} |
64 friend struct base::DefaultLazyInstanceTraits<X509CertificateCache>; | 93 friend struct base::DefaultLazyInstanceTraits<X509CertificateCache>; |
65 | 94 |
66 // You must acquire this lock before using any private data of this object. | 95 // You must acquire this lock before using any private data of this |
67 // You must not block while holding this lock. | 96 // object. You must not block while holding this lock. |
68 Lock lock_; | 97 Lock lock_; |
69 | 98 |
70 // The certificate cache. You must acquire |lock_| before using |cache_|. | 99 // The certificate cache. You must acquire |lock_| before using |cache_|. |
71 CertMap cache_; | 100 CertMap cache_; |
72 | 101 |
73 DISALLOW_COPY_AND_ASSIGN(X509CertificateCache); | 102 DISALLOW_COPY_AND_ASSIGN(X509CertificateCache); |
74 }; | 103 }; |
75 | 104 |
76 base::LazyInstance<X509CertificateCache, | 105 base::LazyInstance<X509CertificateCache, |
77 base::LeakyLazyInstanceTraits<X509CertificateCache> > | 106 base::LeakyLazyInstanceTraits<X509CertificateCache> > |
78 g_x509_certificate_cache(base::LINKER_INITIALIZED); | 107 g_x509_certificate_cache(base::LINKER_INITIALIZED); |
79 | 108 |
80 // Insert |cert| into the cache. The cache does NOT AddRef |cert|. | 109 void X509CertificateCache::InsertOrUpdate( |
81 // Any existing certificate with the same fingerprint will be replaced. | 110 X509Certificate::OSCertHandle* cert_handle) { |
82 void X509CertificateCache::Insert(X509Certificate* cert) { | 111 DCHECK(cert_handle); |
| 112 SHA1Fingerprint fingerprint = |
| 113 X509Certificate::CalculateFingerprint(*cert_handle); |
| 114 DCHECK(!IsNullFingerprint(fingerprint)) << |
| 115 "Only insert certs with real fingerprints."; |
| 116 |
| 117 AutoLock lock(lock_); |
| 118 CertMap::iterator pos(cache_.find(fingerprint)); |
| 119 if (pos == cache_.end()) { |
| 120 // Cached entry not found. Initialize a new entry. |*cert_handle| is |
| 121 // guaranteed to have at least one OS reference (the caller's), which |
| 122 // will be incremented, along with cache_entry.ref_count, below. |
| 123 Entry cache_entry; |
| 124 cache_entry.cert_handle = *cert_handle; |
| 125 cache_entry.ref_count = 0; |
| 126 CertMap::value_type cache_value(fingerprint, cache_entry); |
| 127 pos = cache_.insert(cache_value).first; |
| 128 } else { |
| 129 bool is_same_cert = |
| 130 X509Certificate::IsSameOSCert(*cert_handle, pos->second.cert_handle); |
| 131 if (!is_same_cert) { |
| 132 // Two certificates don't match, likely due to a SHA1 hash collision. |
| 133 // Given the low probability, the simplest solution is to not cache |
| 134 // the certificate, which should not affect performance too |
| 135 // negatively. |
| 136 return; |
| 137 } |
| 138 // Release the caller's reference to |*cert_handle|, as it will be |
| 139 // replaced by the cached handle just found. |
| 140 X509Certificate::FreeOSCertHandle(*cert_handle); |
| 141 DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1); |
| 142 } |
| 143 |
| 144 ++pos->second.ref_count; |
| 145 *cert_handle = X509Certificate::DupOSCertHandle(pos->second.cert_handle); |
| 146 } |
| 147 |
| 148 void X509CertificateCache::Remove(X509Certificate::OSCertHandle cert_handle) { |
| 149 SHA1Fingerprint fingerprint = |
| 150 X509Certificate::CalculateFingerprint(cert_handle); |
83 AutoLock lock(lock_); | 151 AutoLock lock(lock_); |
84 | 152 |
85 DCHECK(!IsNullFingerprint(cert->fingerprint())) << | 153 CertMap::iterator pos(cache_.find(fingerprint)); |
86 "Only insert certs with real fingerprints."; | 154 if (pos == cache_.end()) |
87 cache_[cert->fingerprint()] = cert; | 155 return; // A cache collision where the winning cert was already freed. |
88 }; | |
89 | 156 |
90 // Remove |cert| from the cache. The cache does not assume that |cert| is | 157 bool is_same_cert = X509Certificate::IsSameOSCert(cert_handle, |
91 // already in the cache. | 158 pos->second.cert_handle); |
92 void X509CertificateCache::Remove(X509Certificate* cert) { | 159 DCHECK(is_same_cert) << "Hash collision between different certificates"; |
93 AutoLock lock(lock_); | 160 if (!is_same_cert) |
| 161 return; // A cache collision where the winning cert is still around. |
94 | 162 |
95 CertMap::iterator pos(cache_.find(cert->fingerprint())); | 163 if (--pos->second.ref_count == 0) { |
96 if (pos == cache_.end()) | 164 // The last reference to |cert_handle| has been removed, so release the |
97 return; // It is not an error to remove a cert that is not in the cache. | 165 // underlying OS handle and remove from the cache. The caller still |
98 cache_.erase(pos); | 166 // holds a reference to the underlying OS handle and still bears |
99 }; | 167 // responsibility for freeing it. |
100 | 168 X509Certificate::FreeOSCertHandle(pos->second.cert_handle); |
101 // Find a certificate in the cache with the given fingerprint. If one does | 169 cache_.erase(pos); |
102 // not exist, this method returns NULL. | 170 } |
103 X509Certificate* X509CertificateCache::Find( | 171 } |
104 const SHA1Fingerprint& fingerprint) { | |
105 AutoLock lock(lock_); | |
106 | |
107 CertMap::iterator pos(cache_.find(fingerprint)); | |
108 if (pos == cache_.end()) | |
109 return NULL; | |
110 | |
111 return pos->second; | |
112 }; | |
113 | 172 |
114 } // namespace | 173 } // namespace |
115 | 174 |
116 bool X509Certificate::LessThan::operator()(X509Certificate* lhs, | 175 bool X509Certificate::LessThan::operator()(X509Certificate* lhs, |
117 X509Certificate* rhs) const { | 176 X509Certificate* rhs) const { |
118 if (lhs == rhs) | 177 if (lhs == rhs) |
119 return false; | 178 return false; |
120 | 179 |
121 SHA1FingerprintLessThan fingerprint_functor; | 180 SHA1FingerprintLessThan fingerprint_functor; |
122 return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_); | 181 return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_); |
123 } | 182 } |
124 | 183 |
125 // static | 184 // static |
126 X509Certificate* X509Certificate::CreateFromHandle( | 185 X509Certificate* X509Certificate::CreateFromHandle( |
127 OSCertHandle cert_handle, | 186 OSCertHandle cert_handle, |
128 Source source, | |
129 const OSCertHandles& intermediates) { | 187 const OSCertHandles& intermediates) { |
130 DCHECK(cert_handle); | 188 DCHECK(cert_handle); |
131 DCHECK(source != SOURCE_UNUSED); | 189 X509Certificate* cert = new X509Certificate(cert_handle, intermediates); |
132 | |
133 // Check if we already have this certificate in memory. | |
134 X509CertificateCache* cache = g_x509_certificate_cache.Pointer(); | |
135 X509Certificate* cached_cert = | |
136 cache->Find(CalculateFingerprint(cert_handle)); | |
137 if (cached_cert) { | |
138 DCHECK(cached_cert->source_ != SOURCE_UNUSED); | |
139 if (cached_cert->source_ > source || | |
140 (cached_cert->source_ == source && | |
141 cached_cert->HasIntermediateCertificates(intermediates))) { | |
142 // Return the certificate with the same fingerprint from our cache. | |
143 DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1); | |
144 return cached_cert; | |
145 } | |
146 // Else the new cert is better and will replace the old one in the cache. | |
147 } | |
148 | |
149 // Otherwise, allocate and cache a new object. | |
150 X509Certificate* cert = new X509Certificate(cert_handle, source, | |
151 intermediates); | |
152 cache->Insert(cert); | |
153 return cert; | 190 return cert; |
154 } | 191 } |
155 | 192 |
156 #if defined(OS_WIN) | 193 #if defined(OS_WIN) |
157 static X509Certificate::OSCertHandle CreateOSCert(base::StringPiece der_cert) { | 194 static X509Certificate::OSCertHandle CreateOSCert(base::StringPiece der_cert) { |
158 X509Certificate::OSCertHandle cert_handle = NULL; | 195 X509Certificate::OSCertHandle cert_handle = NULL; |
159 BOOL ok = CertAddEncodedCertificateToStore( | 196 BOOL ok = CertAddEncodedCertificateToStore( |
160 X509Certificate::cert_store(), X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, | 197 X509Certificate::cert_store(), X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, |
161 reinterpret_cast<const BYTE*>(der_cert.data()), der_cert.size(), | 198 reinterpret_cast<const BYTE*>(der_cert.data()), der_cert.size(), |
162 CERT_STORE_ADD_USE_EXISTING, &cert_handle); | 199 CERT_STORE_ADD_USE_EXISTING, &cert_handle); |
(...skipping 14 matching lines...) Expand all Loading... |
177 | 214 |
178 X509Certificate::OSCertHandles intermediate_ca_certs; | 215 X509Certificate::OSCertHandles intermediate_ca_certs; |
179 for (size_t i = 1; i < der_certs.size(); i++) { | 216 for (size_t i = 1; i < der_certs.size(); i++) { |
180 OSCertHandle handle = CreateOSCert(der_certs[i]); | 217 OSCertHandle handle = CreateOSCert(der_certs[i]); |
181 DCHECK(handle); | 218 DCHECK(handle); |
182 intermediate_ca_certs.push_back(handle); | 219 intermediate_ca_certs.push_back(handle); |
183 } | 220 } |
184 | 221 |
185 OSCertHandle handle = CreateOSCert(der_certs[0]); | 222 OSCertHandle handle = CreateOSCert(der_certs[0]); |
186 DCHECK(handle); | 223 DCHECK(handle); |
187 X509Certificate* cert = | 224 X509Certificate* cert = CreateFromHandle(handle, intermediate_ca_certs); |
188 CreateFromHandle(handle, SOURCE_FROM_NETWORK, intermediate_ca_certs); | |
189 FreeOSCertHandle(handle); | 225 FreeOSCertHandle(handle); |
190 for (size_t i = 0; i < intermediate_ca_certs.size(); i++) | 226 for (size_t i = 0; i < intermediate_ca_certs.size(); i++) |
191 FreeOSCertHandle(intermediate_ca_certs[i]); | 227 FreeOSCertHandle(intermediate_ca_certs[i]); |
192 | 228 |
193 return cert; | 229 return cert; |
194 } | 230 } |
195 | 231 |
196 // static | 232 // static |
197 X509Certificate* X509Certificate::CreateFromBytes(const char* data, | 233 X509Certificate* X509Certificate::CreateFromBytes(const char* data, |
198 int length) { | 234 int length) { |
199 OSCertHandle cert_handle = CreateOSCertHandleFromBytes(data, length); | 235 OSCertHandle cert_handle = CreateOSCertHandleFromBytes(data, length); |
200 if (!cert_handle) | 236 if (!cert_handle) |
201 return NULL; | 237 return NULL; |
202 | 238 |
203 X509Certificate* cert = CreateFromHandle(cert_handle, | 239 X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles()); |
204 SOURCE_LONE_CERT_IMPORT, | |
205 OSCertHandles()); | |
206 FreeOSCertHandle(cert_handle); | 240 FreeOSCertHandle(cert_handle); |
207 return cert; | 241 return cert; |
208 } | 242 } |
209 | 243 |
210 // static | 244 // static |
211 X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle, | 245 X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle, |
212 void** pickle_iter, | 246 void** pickle_iter, |
213 PickleType type) { | 247 PickleType type) { |
214 OSCertHandle cert_handle = ReadCertHandleFromPickle(pickle, pickle_iter); | 248 OSCertHandle cert_handle = ReadCertHandleFromPickle(pickle, pickle_iter); |
215 OSCertHandles intermediates; | 249 OSCertHandles intermediates; |
(...skipping 24 matching lines...) Expand all Loading... |
240 if (!ok) { | 274 if (!ok) { |
241 FreeOSCertHandle(cert_handle); | 275 FreeOSCertHandle(cert_handle); |
242 for (size_t i = 0; i < intermediates.size(); ++i) | 276 for (size_t i = 0; i < intermediates.size(); ++i) |
243 FreeOSCertHandle(intermediates[i]); | 277 FreeOSCertHandle(intermediates[i]); |
244 return NULL; | 278 return NULL; |
245 } | 279 } |
246 } | 280 } |
247 | 281 |
248 if (!cert_handle) | 282 if (!cert_handle) |
249 return NULL; | 283 return NULL; |
250 X509Certificate* cert = CreateFromHandle(cert_handle, SOURCE_FROM_CACHE, | 284 X509Certificate* cert = CreateFromHandle(cert_handle, intermediates); |
251 intermediates); | |
252 FreeOSCertHandle(cert_handle); | 285 FreeOSCertHandle(cert_handle); |
253 for (size_t i = 0; i < intermediates.size(); ++i) | 286 for (size_t i = 0; i < intermediates.size(); ++i) |
254 FreeOSCertHandle(intermediates[i]); | 287 FreeOSCertHandle(intermediates[i]); |
255 | 288 |
256 return cert; | 289 return cert; |
257 } | 290 } |
258 | 291 |
259 // static | 292 // static |
260 CertificateList X509Certificate::CreateCertificateListFromBytes( | 293 CertificateList X509Certificate::CreateCertificateListFromBytes( |
261 const char* data, int length, int format) { | 294 const char* data, int length, int format) { |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
318 kFormatDecodePriority[i]); | 351 kFormatDecodePriority[i]); |
319 } | 352 } |
320 | 353 |
321 CertificateList results; | 354 CertificateList results; |
322 // No certificates parsed. | 355 // No certificates parsed. |
323 if (certificates.empty()) | 356 if (certificates.empty()) |
324 return results; | 357 return results; |
325 | 358 |
326 for (OSCertHandles::iterator it = certificates.begin(); | 359 for (OSCertHandles::iterator it = certificates.begin(); |
327 it != certificates.end(); ++it) { | 360 it != certificates.end(); ++it) { |
328 X509Certificate* result = CreateFromHandle(*it, SOURCE_LONE_CERT_IMPORT, | 361 X509Certificate* result = CreateFromHandle(*it, OSCertHandles()); |
329 OSCertHandles()); | |
330 results.push_back(scoped_refptr<X509Certificate>(result)); | 362 results.push_back(scoped_refptr<X509Certificate>(result)); |
331 FreeOSCertHandle(*it); | 363 FreeOSCertHandle(*it); |
332 } | 364 } |
333 | 365 |
334 return results; | 366 return results; |
335 } | 367 } |
336 | 368 |
337 X509Certificate::X509Certificate(OSCertHandle cert_handle, | 369 X509Certificate::X509Certificate(OSCertHandle cert_handle, |
338 Source source, | |
339 const OSCertHandles& intermediates) | 370 const OSCertHandles& intermediates) |
340 : cert_handle_(DupOSCertHandle(cert_handle)), | 371 : cert_handle_(DupOSCertHandle(cert_handle)) { |
341 source_(source) { | 372 X509CertificateCache* cache = g_x509_certificate_cache.Pointer(); |
342 // Copy/retain the intermediate cert handles. | 373 cache->InsertOrUpdate(&cert_handle_); |
343 for (size_t i = 0; i < intermediates.size(); ++i) | 374 for (size_t i = 0; i < intermediates.size(); ++i) { |
344 intermediate_ca_certs_.push_back(DupOSCertHandle(intermediates[i])); | 375 // Duplicate the incoming certificate, as the caller retains ownership |
| 376 // of |intermediates|. |
| 377 OSCertHandle intermediate = DupOSCertHandle(intermediates[i]); |
| 378 // Update the cache, which will assume ownership of the duplicated |
| 379 // handle and return a suitable equivalent, potentially from the cache. |
| 380 cache->InsertOrUpdate(&intermediate); |
| 381 intermediate_ca_certs_.push_back(intermediate); |
| 382 } |
345 // Platform-specific initialization. | 383 // Platform-specific initialization. |
346 Initialize(); | 384 Initialize(); |
347 } | 385 } |
348 | 386 |
349 X509Certificate::X509Certificate(const std::string& subject, | 387 X509Certificate::X509Certificate(const std::string& subject, |
350 const std::string& issuer, | 388 const std::string& issuer, |
351 base::Time start_date, | 389 base::Time start_date, |
352 base::Time expiration_date) | 390 base::Time expiration_date) |
353 : subject_(subject), | 391 : subject_(subject), |
354 issuer_(issuer), | 392 issuer_(issuer), |
355 valid_start_(start_date), | 393 valid_start_(start_date), |
356 valid_expiry_(expiration_date), | 394 valid_expiry_(expiration_date), |
357 cert_handle_(NULL), | 395 cert_handle_(NULL) { |
358 source_(SOURCE_UNUSED) { | |
359 memset(fingerprint_.data, 0, sizeof(fingerprint_.data)); | 396 memset(fingerprint_.data, 0, sizeof(fingerprint_.data)); |
360 } | 397 } |
361 | 398 |
362 X509Certificate::~X509Certificate() { | 399 X509Certificate::~X509Certificate() { |
363 // We might not be in the cache, but it is safe to remove ourselves anyway. | 400 X509CertificateCache* cache = g_x509_certificate_cache.Pointer(); |
364 g_x509_certificate_cache.Get().Remove(this); | 401 if (cert_handle_) { |
365 if (cert_handle_) | 402 cache->Remove(cert_handle_); |
366 FreeOSCertHandle(cert_handle_); | 403 FreeOSCertHandle(cert_handle_); |
367 for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) | 404 } |
| 405 for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { |
| 406 cache->Remove(intermediate_ca_certs_[i]); |
368 FreeOSCertHandle(intermediate_ca_certs_[i]); | 407 FreeOSCertHandle(intermediate_ca_certs_[i]); |
| 408 } |
369 } | 409 } |
370 | 410 |
371 void X509Certificate::Persist(Pickle* pickle) { | 411 void X509Certificate::Persist(Pickle* pickle) { |
372 DCHECK(cert_handle_); | 412 DCHECK(cert_handle_); |
373 if (!WriteCertHandleToPickle(cert_handle_, pickle)) { | 413 if (!WriteCertHandleToPickle(cert_handle_, pickle)) { |
374 NOTREACHED(); | 414 NOTREACHED(); |
375 return; | 415 return; |
376 } | 416 } |
377 | 417 |
378 if (!pickle->WriteSize(intermediate_ca_certs_.size())) { | 418 if (!pickle->WriteSize(intermediate_ca_certs_.size())) { |
(...skipping 27 matching lines...) Expand all Loading... |
406 | 446 |
407 bool X509Certificate::HasIntermediateCertificates(const OSCertHandles& certs) { | 447 bool X509Certificate::HasIntermediateCertificates(const OSCertHandles& certs) { |
408 for (size_t i = 0; i < certs.size(); ++i) { | 448 for (size_t i = 0; i < certs.size(); ++i) { |
409 if (!HasIntermediateCertificate(certs[i])) | 449 if (!HasIntermediateCertificate(certs[i])) |
410 return false; | 450 return false; |
411 } | 451 } |
412 return true; | 452 return true; |
413 } | 453 } |
414 | 454 |
415 } // namespace net | 455 } // namespace net |
OLD | NEW |