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 <CommonCrypto/CommonDigest.h> | 7 #include <CommonCrypto/CommonDigest.h> |
8 #include <Security/Security.h> | |
8 #include <time.h> | 9 #include <time.h> |
9 | 10 |
10 #include "base/scoped_cftyperef.h" | 11 #include "base/scoped_cftyperef.h" |
11 #include "base/logging.h" | 12 #include "base/logging.h" |
12 #include "base/pickle.h" | 13 #include "base/pickle.h" |
13 #include "base/sys_string_conversions.h" | 14 #include "base/sys_string_conversions.h" |
14 #include "net/base/cert_status_flags.h" | 15 #include "net/base/cert_status_flags.h" |
15 #include "net/base/cert_verify_result.h" | 16 #include "net/base/cert_verify_result.h" |
16 #include "net/base/net_errors.h" | 17 #include "net/base/net_errors.h" |
17 | 18 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
73 | 74 |
74 void SetMacTestCertificate(X509Certificate* cert) { | 75 void SetMacTestCertificate(X509Certificate* cert) { |
75 Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert); | 76 Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert); |
76 } | 77 } |
77 | 78 |
78 namespace { | 79 namespace { |
79 | 80 |
80 typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, | 81 typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, |
81 CFDictionaryRef*); | 82 CFDictionaryRef*); |
82 | 83 |
83 inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) { | |
84 return oid1->Length == oid2->Length && | |
85 (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0); | |
86 } | |
87 | |
88 int NetErrorFromOSStatus(OSStatus status) { | 84 int NetErrorFromOSStatus(OSStatus status) { |
89 switch (status) { | 85 switch (status) { |
90 case noErr: | 86 case noErr: |
91 return OK; | 87 return OK; |
92 case errSecNotAvailable: | 88 case errSecNotAvailable: |
93 case errSecNoCertificateModule: | 89 case errSecNoCertificateModule: |
94 case errSecNoPolicyModule: | 90 case errSecNoPolicyModule: |
95 return ERR_NOT_IMPLEMENTED; | 91 return ERR_NOT_IMPLEMENTED; |
96 case errSecAuthFailed: | 92 case errSecAuthFailed: |
97 return ERR_ACCESS_DENIED; | 93 return ERR_ACCESS_DENIED; |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
166 c != hostname.end() && is_dotted_ip; ++c) | 162 c != hostname.end() && is_dotted_ip; ++c) |
167 is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.'; | 163 is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.'; |
168 if (is_dotted_ip) { | 164 if (is_dotted_ip) { |
169 for (std::vector<std::string>::const_iterator name = dns_names->begin(); | 165 for (std::vector<std::string>::const_iterator name = dns_names->begin(); |
170 name != dns_names->end() && !override_hostname_mismatch; ++name) | 166 name != dns_names->end() && !override_hostname_mismatch; ++name) |
171 override_hostname_mismatch = (*name == hostname); | 167 override_hostname_mismatch = (*name == hostname); |
172 } | 168 } |
173 return override_hostname_mismatch; | 169 return override_hostname_mismatch; |
174 } | 170 } |
175 | 171 |
176 void ParsePrincipal(const CSSM_X509_NAME* name, | |
177 X509Certificate::Principal* principal) { | |
178 std::vector<std::string> common_names, locality_names, state_names, | |
179 country_names; | |
180 | |
181 // TODO(jcampan): add business_category and serial_number. | |
182 const CSSM_OID* kOIDs[] = { &CSSMOID_CommonName, | |
183 &CSSMOID_LocalityName, | |
184 &CSSMOID_StateProvinceName, | |
185 &CSSMOID_CountryName, | |
186 &CSSMOID_StreetAddress, | |
187 &CSSMOID_OrganizationName, | |
188 &CSSMOID_OrganizationalUnitName, | |
189 &CSSMOID_DNQualifier }; // This should be "DC" | |
190 // but is undoubtedly | |
191 // wrong. TODO(avi): | |
192 // Find the right OID. | |
193 | |
194 std::vector<std::string>* values[] = { | |
195 &common_names, &locality_names, | |
196 &state_names, &country_names, | |
197 &(principal->street_addresses), | |
198 &(principal->organization_names), | |
199 &(principal->organization_unit_names), | |
200 &(principal->domain_components) }; | |
201 DCHECK(arraysize(kOIDs) == arraysize(values)); | |
202 | |
203 for (size_t rdn = 0; rdn < name->numberOfRDNs; ++rdn) { | |
204 CSSM_X509_RDN rdn_struct = name->RelativeDistinguishedName[rdn]; | |
205 for (size_t pair = 0; pair < rdn_struct.numberOfPairs; ++pair) { | |
206 CSSM_X509_TYPE_VALUE_PAIR pair_struct = | |
207 rdn_struct.AttributeTypeAndValue[pair]; | |
208 for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { | |
209 if (CSSMOIDEqual(&pair_struct.type, kOIDs[oid])) { | |
210 std::string value = | |
211 std::string(reinterpret_cast<std::string::value_type*> | |
212 (pair_struct.value.Data), | |
213 pair_struct.value.Length); | |
214 values[oid]->push_back(value); | |
215 break; | |
216 } | |
217 } | |
218 } | |
219 } | |
220 | |
221 // We don't expect to have more than one CN, L, S, and C. | |
222 std::vector<std::string>* single_value_lists[4] = { | |
223 &common_names, &locality_names, &state_names, &country_names }; | |
224 std::string* single_values[4] = { | |
225 &principal->common_name, &principal->locality_name, | |
226 &principal->state_or_province_name, &principal->country_name }; | |
227 for (size_t i = 0; i < arraysize(single_value_lists); ++i) { | |
228 DCHECK(single_value_lists[i]->size() <= 1); | |
229 if (single_value_lists[i]->size() > 0) | |
230 *(single_values[i]) = (*(single_value_lists[i]))[0]; | |
231 } | |
232 } | |
233 | |
234 struct CSSMFields { | 172 struct CSSMFields { |
235 CSSMFields() : cl_handle(NULL), num_of_fields(0), fields(NULL) {} | 173 CSSMFields() : cl_handle(NULL), num_of_fields(0), fields(NULL) {} |
236 ~CSSMFields() { | 174 ~CSSMFields() { |
237 if (cl_handle) | 175 if (cl_handle) |
238 CSSM_CL_FreeFields(cl_handle, num_of_fields, &fields); | 176 CSSM_CL_FreeFields(cl_handle, num_of_fields, &fields); |
239 } | 177 } |
240 | 178 |
241 CSSM_CL_HANDLE cl_handle; | 179 CSSM_CL_HANDLE cl_handle; |
242 uint32 num_of_fields; | 180 uint32 num_of_fields; |
243 CSSM_FIELD_PTR fields; | 181 CSSM_FIELD_PTR fields; |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
379 }; | 317 }; |
380 err = SecPolicySetValue(*policy, &options_data); | 318 err = SecPolicySetValue(*policy, &options_data); |
381 if (err) { | 319 if (err) { |
382 CFRelease(*policy); | 320 CFRelease(*policy); |
383 return err; | 321 return err; |
384 } | 322 } |
385 } | 323 } |
386 return noErr; | 324 return noErr; |
387 } | 325 } |
388 | 326 |
327 // Gets the issuer chain of intermediate and root certs for a given cert. | |
328 // This function calls SecTrust but doesn't actually pay attention to the trust | |
329 // result: it shouldn't be used to determine trust, just to traverse the chain. | |
330 // Caller is responsible for releasing the value stored into *out_trust_chain. | |
331 OSStatus CopyCertChain(SecCertificateRef cert_handle, | |
wtc
2010/03/24 23:52:05
Nit: CopyCertChain also copies the entire cert cha
| |
332 CFArrayRef* out_cert_chain) { | |
333 DCHECK(cert_handle && out_cert_chain); | |
334 // Create an SSL policy ref configured for client cert evaluation. | |
335 SecPolicyRef ssl_policy; | |
336 OSStatus result = X509Certificate::CreateSSLClientPolicy(&ssl_policy); | |
337 if (result) | |
338 return result; | |
339 scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy); | |
340 | |
341 // Create a SecTrustRef. | |
342 scoped_cftyperef<CFArrayRef> input_certs( | |
343 CFArrayCreate(NULL, (const void**)&cert_handle, 1, | |
344 &kCFTypeArrayCallBacks)); | |
345 SecTrustRef trust_ref = NULL; | |
346 result = SecTrustCreateWithCertificates(input_certs, ssl_policy, &trust_ref); | |
347 if (result) | |
348 return result; | |
349 scoped_cftyperef<SecTrustRef> trust(trust_ref); | |
350 | |
351 // Evaluate trust, which creates the cert chain. | |
352 SecTrustResultType status; | |
353 CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; | |
354 result = SecTrustEvaluate(trust, &status); | |
355 if (result) | |
356 return result; | |
357 return SecTrustGetResult(trust, &status, out_cert_chain, &status_chain); | |
358 } | |
359 | |
389 } // namespace | 360 } // namespace |
390 | 361 |
391 void X509Certificate::Initialize() { | 362 void X509Certificate::Initialize() { |
392 const CSSM_X509_NAME* name; | 363 const CSSM_X509_NAME* name; |
393 OSStatus status = SecCertificateGetSubject(cert_handle_, &name); | 364 OSStatus status = SecCertificateGetSubject(cert_handle_, &name); |
394 if (!status) { | 365 if (!status) { |
395 ParsePrincipal(name, &subject_); | 366 subject_.Parse(name); |
396 } | 367 } |
397 status = SecCertificateGetIssuer(cert_handle_, &name); | 368 status = SecCertificateGetIssuer(cert_handle_, &name); |
398 if (!status) { | 369 if (!status) { |
399 ParsePrincipal(name, &issuer_); | 370 issuer_.Parse(name); |
400 } | 371 } |
401 | 372 |
402 GetCertDateForOID(cert_handle_, CSSMOID_X509V1ValidityNotBefore, | 373 GetCertDateForOID(cert_handle_, CSSMOID_X509V1ValidityNotBefore, |
403 &valid_start_); | 374 &valid_start_); |
404 GetCertDateForOID(cert_handle_, CSSMOID_X509V1ValidityNotAfter, | 375 GetCertDateForOID(cert_handle_, CSSMOID_X509V1ValidityNotAfter, |
405 &valid_expiry_); | 376 &valid_expiry_); |
406 | 377 |
407 fingerprint_ = CalculateFingerprint(cert_handle_); | 378 fingerprint_ = CalculateFingerprint(cert_handle_); |
408 } | 379 } |
409 | 380 |
(...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
735 } else if (CSSMOIDEqual(&field.FieldOid, &CSSMOID_NetscapeCertType)) { | 706 } else if (CSSMOIDEqual(&field.FieldOid, &CSSMOID_NetscapeCertType)) { |
736 uint16_t flags = | 707 uint16_t flags = |
737 *reinterpret_cast<const uint16_t*>(ext->value.parsedValue); | 708 *reinterpret_cast<const uint16_t*>(ext->value.parsedValue); |
738 if (flags & CE_NCT_SSL_Client) | 709 if (flags & CE_NCT_SSL_Client) |
739 return true; | 710 return true; |
740 } | 711 } |
741 } | 712 } |
742 return false; | 713 return false; |
743 } | 714 } |
744 | 715 |
716 bool X509Certificate::IsIssuedBy( | |
717 const std::vector<CertPrincipal>& valid_issuers) { | |
718 // Get the cert's issuer chain. | |
719 CFArrayRef trust_chain = NULL; | |
wtc
2010/03/24 23:52:05
Nit: trust_chain => cert_chain
scoped_trust_c
| |
720 OSStatus result; | |
721 result = CopyCertChain(os_cert_handle(), &trust_chain); | |
722 if (result != noErr) | |
723 return false; | |
724 scoped_cftyperef<CFArrayRef> scoped_trust_chain(trust_chain); | |
725 | |
726 // Check all the certs in the chain for a match. | |
727 int n = CFArrayGetCount(trust_chain); | |
728 for (int i = 0; i < n; ++i) { | |
729 SecCertificateRef intermediate_handle = reinterpret_cast<SecCertificateRef>( | |
wtc
2010/03/24 23:52:05
Nit: "intermediate" is misleading, because this ca
Jens Alfke
2010/03/26 17:15:04
It is, but we have to check the issuer of that cer
wtc
2010/03/26 21:03:22
In each iteration, you are checking intermediate->
| |
730 const_cast<void*>(CFArrayGetValueAtIndex(trust_chain, i))); | |
731 CFRetain(intermediate_handle); | |
732 X509Certificate* intermediate = X509Certificate::CreateFromHandle( | |
wtc
2010/03/24 23:52:05
X509Certificate is reference counted, so always st
| |
733 intermediate_handle, | |
734 X509Certificate::SOURCE_LONE_CERT_IMPORT, | |
735 X509Certificate::OSCertHandles()); | |
736 for (unsigned j = 0; j < valid_issuers.size(); j++) | |
wtc
2010/03/24 23:52:05
Nit: use curly braces around the for loop's body,
| |
737 if (intermediate->subject().Matches(valid_issuers[j])) { | |
738 return true; | |
739 } | |
740 } | |
741 return false; | |
742 } | |
743 | |
745 // static | 744 // static |
746 OSStatus X509Certificate::CreateSSLClientPolicy(SecPolicyRef* out_policy) { | 745 OSStatus X509Certificate::CreateSSLClientPolicy(SecPolicyRef* out_policy) { |
747 CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { | 746 CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { |
748 CSSM_APPLE_TP_SSL_OPTS_VERSION, | 747 CSSM_APPLE_TP_SSL_OPTS_VERSION, |
749 0, | 748 0, |
750 NULL, | 749 NULL, |
751 CSSM_APPLE_TP_SSL_CLIENT | 750 CSSM_APPLE_TP_SSL_CLIENT |
752 }; | 751 }; |
753 return CreatePolicy(&CSSMOID_APPLE_TP_SSL, | 752 return CreatePolicy(&CSSMOID_APPLE_TP_SSL, |
754 &tp_ssl_options, | 753 &tp_ssl_options, |
755 sizeof(tp_ssl_options), | 754 sizeof(tp_ssl_options), |
756 out_policy); | 755 out_policy); |
757 } | 756 } |
758 | 757 |
759 // static | 758 // static |
760 bool X509Certificate::GetSSLClientCertificates ( | 759 bool X509Certificate::GetSSLClientCertificates ( |
761 const std::string& server_domain, | 760 const std::string& server_domain, |
761 const std::vector<Principal>& valid_issuers, | |
762 std::vector<scoped_refptr<X509Certificate> >* certs) { | 762 std::vector<scoped_refptr<X509Certificate> >* certs) { |
763 scoped_cftyperef<SecIdentityRef> preferred_identity; | 763 scoped_cftyperef<SecIdentityRef> preferred_identity; |
764 if (!server_domain.empty()) { | 764 if (!server_domain.empty()) { |
765 // See if there's an identity preference for this domain: | 765 // See if there's an identity preference for this domain: |
766 scoped_cftyperef<CFStringRef> domain_str( | 766 scoped_cftyperef<CFStringRef> domain_str( |
767 base::SysUTF8ToCFStringRef("https://" + server_domain)); | 767 base::SysUTF8ToCFStringRef("https://" + server_domain)); |
768 SecIdentityRef identity = NULL; | 768 SecIdentityRef identity = NULL; |
769 if (SecIdentityCopyPreference(domain_str, | 769 if (SecIdentityCopyPreference(domain_str, |
770 0, | 770 0, |
771 NULL, | 771 NULL, |
wtc
2010/03/24 23:52:05
IMPORTANT: we can finally pass a real validIssuers
wtc
2010/03/25 01:00:35
I looked at the source code of SecIdentityCopyPref
| |
772 &identity) == noErr) | 772 &identity) == noErr) |
773 preferred_identity.reset(identity); | 773 preferred_identity.reset(identity); |
774 } | 774 } |
775 | 775 |
776 // Now enumerate the identities in the available keychains. | |
776 SecIdentitySearchRef search = nil; | 777 SecIdentitySearchRef search = nil; |
777 OSStatus err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search); | 778 OSStatus err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search); |
778 scoped_cftyperef<SecIdentitySearchRef> scoped_search(search); | 779 scoped_cftyperef<SecIdentitySearchRef> scoped_search(search); |
779 while (!err) { | 780 while (!err) { |
780 SecIdentityRef identity = NULL; | 781 SecIdentityRef identity = NULL; |
781 err = SecIdentitySearchCopyNext(search, &identity); | 782 err = SecIdentitySearchCopyNext(search, &identity); |
782 if (err) | 783 if (err) |
783 break; | 784 break; |
784 scoped_cftyperef<SecIdentityRef> scoped_identity(identity); | 785 scoped_cftyperef<SecIdentityRef> scoped_identity(identity); |
785 | 786 |
(...skipping 12 matching lines...) Expand all Loading... | |
798 // Skip duplicates (a cert may be in multiple keychains). | 799 // Skip duplicates (a cert may be in multiple keychains). |
799 X509Certificate::Fingerprint fingerprint = cert->fingerprint(); | 800 X509Certificate::Fingerprint fingerprint = cert->fingerprint(); |
800 unsigned i; | 801 unsigned i; |
801 for (i = 0; i < certs->size(); ++i) { | 802 for (i = 0; i < certs->size(); ++i) { |
802 if ((*certs)[i]->fingerprint().Equals(fingerprint)) | 803 if ((*certs)[i]->fingerprint().Equals(fingerprint)) |
803 break; | 804 break; |
804 } | 805 } |
805 if (i < certs->size()) | 806 if (i < certs->size()) |
806 continue; | 807 continue; |
807 | 808 |
809 bool is_preferred = preferred_identity && | |
810 CFEqual(preferred_identity, identity); | |
811 | |
812 // Make sure the issuer matches valid_issuers, if given. | |
813 // But an explicit cert preference overrides this. | |
814 if (!is_preferred && | |
815 valid_issuers.size() > 0 && | |
816 !cert->IsIssuedBy(valid_issuers)) | |
817 continue; | |
818 | |
808 // The cert passes, so add it to the vector. | 819 // The cert passes, so add it to the vector. |
809 // If it's the preferred identity, add it at the start (so it'll be | 820 // If it's the preferred identity, add it at the start (so it'll be |
810 // selected by default in the UI.) | 821 // selected by default in the UI.) |
811 if (preferred_identity && CFEqual(preferred_identity, identity)) | 822 if (is_preferred) |
812 certs->insert(certs->begin(), cert); | 823 certs->insert(certs->begin(), cert); |
813 else | 824 else |
814 certs->push_back(cert); | 825 certs->push_back(cert); |
815 } | 826 } |
816 | 827 |
817 if (err != errSecItemNotFound) { | 828 if (err != errSecItemNotFound) { |
818 LOG(ERROR) << "SecIdentitySearch error " << err; | 829 LOG(ERROR) << "SecIdentitySearch error " << err; |
819 return false; | 830 return false; |
820 } | 831 } |
821 return true; | 832 return true; |
822 } | 833 } |
823 | 834 |
824 CFArrayRef X509Certificate::CreateClientCertificateChain() const { | 835 CFArrayRef X509Certificate::CreateClientCertificateChain() const { |
825 // Initialize the result array with just the IdentityRef of the receiver: | 836 // Initialize the result array with just the IdentityRef of the receiver: |
826 OSStatus result; | 837 OSStatus result; |
827 SecIdentityRef identity; | 838 SecIdentityRef identity; |
828 result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity); | 839 result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity); |
829 if (result) { | 840 if (result) { |
830 LOG(ERROR) << "SecIdentityCreateWithCertificate error " << result; | 841 LOG(ERROR) << "SecIdentityCreateWithCertificate error " << result; |
831 return NULL; | 842 return NULL; |
832 } | 843 } |
833 scoped_cftyperef<CFMutableArrayRef> chain( | 844 scoped_cftyperef<CFMutableArrayRef> chain( |
834 CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); | 845 CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); |
835 CFArrayAppendValue(chain, identity); | 846 CFArrayAppendValue(chain, identity); |
836 | 847 |
837 { | 848 CFArrayRef trust_chain = NULL; |
wtc
2010/03/24 23:52:05
Nit: trust_chain => cert_chain
| |
838 // Create an SSL policy ref configured for client cert evaluation. | 849 result = CopyCertChain(cert_handle_, &trust_chain); |
839 SecPolicyRef ssl_policy; | 850 if (result) |
840 result = CreateSSLClientPolicy(&ssl_policy); | 851 goto exit; |
841 if (result) | |
842 goto exit; | |
843 scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy); | |
844 | 852 |
845 // Use a SecTrust object to find the intermediate certs in the trust chain. | 853 // Append the intermediate certs from SecTrust to the result array: |
846 scoped_cftyperef<CFArrayRef> input_certs( | 854 if (trust_chain) { |
847 CFArrayCreate(NULL, (const void**)&cert_handle_, 1, | 855 int chain_count = CFArrayGetCount(trust_chain); |
848 &kCFTypeArrayCallBacks)); | 856 if (chain_count > 1) { |
849 SecTrustRef trust_ref = NULL; | 857 CFArrayAppendArray(chain, |
850 result = SecTrustCreateWithCertificates(input_certs, | 858 trust_chain, |
851 ssl_policy, | 859 CFRangeMake(1, chain_count - 1)); |
852 &trust_ref); | |
853 if (result) | |
854 goto exit; | |
855 scoped_cftyperef<SecTrustRef> trust(trust_ref); | |
856 | |
857 SecTrustResultType status; | |
858 CFArrayRef trust_chain = NULL; | |
859 CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; | |
860 result = SecTrustEvaluate(trust, &status); | |
861 if (result) | |
862 goto exit; | |
863 result = SecTrustGetResult(trust, &status, &trust_chain, &status_chain); | |
864 if (result) | |
865 goto exit; | |
866 | |
867 // Append the intermediate certs from SecTrust to the result array: | |
868 if (trust_chain) { | |
869 int chain_count = CFArrayGetCount(trust_chain); | |
870 if (chain_count > 1) { | |
871 CFArrayAppendArray(chain, | |
872 trust_chain, | |
873 CFRangeMake(1, chain_count - 1)); | |
874 } | |
875 CFRelease(trust_chain); | |
876 } | 860 } |
861 CFRelease(trust_chain); | |
877 } | 862 } |
878 exit: | 863 exit: |
879 if (result) | 864 if (result) |
880 LOG(ERROR) << "CreateIdentityCertificateChain error " << result; | 865 LOG(ERROR) << "CreateIdentityCertificateChain error " << result; |
881 return chain.release(); | 866 return chain.release(); |
882 } | 867 } |
883 | 868 |
884 } // namespace net | 869 } // namespace net |
OLD | NEW |