| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/cert/x509_util_mac.h" |
| |
| #include <CommonCrypto/CommonDigest.h> |
| |
| #include "base/logging.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "net/cert/x509_certificate.h" |
| #include "starboard/memory.h" |
| #include "starboard/types.h" |
| #include "third_party/apple_apsl/cssmapplePriv.h" |
| #include "third_party/boringssl/src/include/openssl/pool.h" |
| |
| namespace net { |
| |
| // CSSM functions are deprecated as of OSX 10.7, but have no replacement. |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1 |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| |
| namespace x509_util { |
| |
| namespace { |
| |
| // Creates a SecPolicyRef for the given OID, with optional value. |
| OSStatus CreatePolicy(const CSSM_OID* policy_oid, |
| void* option_data, |
| size_t option_length, |
| SecPolicyRef* policy) { |
| SecPolicySearchRef search; |
| OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_oid, NULL, |
| &search); |
| if (err) |
| return err; |
| err = SecPolicySearchCopyNext(search, policy); |
| CFRelease(search); |
| if (err) |
| return err; |
| |
| if (option_data) { |
| CSSM_DATA options_data = { |
| option_length, |
| reinterpret_cast<uint8_t*>(option_data) |
| }; |
| err = SecPolicySetValue(*policy, &options_data); |
| if (err) { |
| CFRelease(*policy); |
| return err; |
| } |
| } |
| return noErr; |
| } |
| |
| } // namespace |
| |
| bool IsValidSecCertificate(SecCertificateRef cert_handle) { |
| const CSSM_X509_NAME* sanity_check = NULL; |
| OSStatus status = SecCertificateGetSubject(cert_handle, &sanity_check); |
| return status == noErr && sanity_check; |
| } |
| |
| base::ScopedCFTypeRef<SecCertificateRef> CreateSecCertificateFromBytes( |
| const uint8_t* data, |
| size_t length) { |
| CSSM_DATA cert_data; |
| cert_data.Data = const_cast<uint8_t*>(data); |
| cert_data.Length = length; |
| |
| base::ScopedCFTypeRef<SecCertificateRef> cert_handle; |
| OSStatus status = SecCertificateCreateFromData(&cert_data, CSSM_CERT_X_509v3, |
| CSSM_CERT_ENCODING_DER, |
| cert_handle.InitializeInto()); |
| if (status != noErr) |
| return base::ScopedCFTypeRef<SecCertificateRef>(); |
| if (!IsValidSecCertificate(cert_handle.get())) |
| return base::ScopedCFTypeRef<SecCertificateRef>(); |
| return cert_handle; |
| } |
| |
| base::ScopedCFTypeRef<SecCertificateRef> |
| CreateSecCertificateFromX509Certificate(const X509Certificate* cert) { |
| return CreateSecCertificateFromBytes(CRYPTO_BUFFER_data(cert->cert_buffer()), |
| CRYPTO_BUFFER_len(cert->cert_buffer())); |
| } |
| |
| scoped_refptr<X509Certificate> CreateX509CertificateFromSecCertificate( |
| SecCertificateRef sec_cert, |
| const std::vector<SecCertificateRef>& sec_chain) { |
| return CreateX509CertificateFromSecCertificate(sec_cert, sec_chain, {}); |
| } |
| |
| scoped_refptr<X509Certificate> CreateX509CertificateFromSecCertificate( |
| SecCertificateRef sec_cert, |
| const std::vector<SecCertificateRef>& sec_chain, |
| X509Certificate::UnsafeCreateOptions options) { |
| CSSM_DATA der_data; |
| if (!sec_cert || SecCertificateGetData(sec_cert, &der_data) != noErr) |
| return nullptr; |
| bssl::UniquePtr<CRYPTO_BUFFER> cert_handle( |
| X509Certificate::CreateCertBufferFromBytes( |
| reinterpret_cast<const char*>(der_data.Data), der_data.Length)); |
| if (!cert_handle) |
| return nullptr; |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| for (const SecCertificateRef& sec_intermediate : sec_chain) { |
| if (!sec_intermediate || |
| SecCertificateGetData(sec_intermediate, &der_data) != noErr) { |
| return nullptr; |
| } |
| bssl::UniquePtr<CRYPTO_BUFFER> intermediate_cert_handle( |
| X509Certificate::CreateCertBufferFromBytes( |
| reinterpret_cast<const char*>(der_data.Data), der_data.Length)); |
| if (!intermediate_cert_handle) |
| return nullptr; |
| intermediates.push_back(std::move(intermediate_cert_handle)); |
| } |
| scoped_refptr<X509Certificate> result( |
| X509Certificate::CreateFromBufferUnsafeOptions( |
| std::move(cert_handle), std::move(intermediates), options)); |
| return result; |
| } |
| |
| bool IsSelfSigned(SecCertificateRef cert_handle) { |
| CSSMCachedCertificate cached_cert; |
| OSStatus status = cached_cert.Init(cert_handle); |
| if (status != noErr) |
| return false; |
| |
| CSSMFieldValue subject; |
| status = cached_cert.GetField(&CSSMOID_X509V1SubjectNameStd, &subject); |
| if (status != CSSM_OK || !subject.field()) |
| return false; |
| |
| CSSMFieldValue issuer; |
| status = cached_cert.GetField(&CSSMOID_X509V1IssuerNameStd, &issuer); |
| if (status != CSSM_OK || !issuer.field()) |
| return false; |
| |
| if (subject.field()->Length != issuer.field()->Length || |
| SbMemoryCompare(subject.field()->Data, issuer.field()->Data, |
| issuer.field()->Length) != 0) { |
| return false; |
| } |
| |
| CSSM_CL_HANDLE cl_handle = CSSM_INVALID_HANDLE; |
| status = SecCertificateGetCLHandle(cert_handle, &cl_handle); |
| if (status) |
| return false; |
| CSSM_DATA cert_data; |
| status = SecCertificateGetData(cert_handle, &cert_data); |
| if (status) |
| return false; |
| |
| if (CSSM_CL_CertVerify(cl_handle, 0, &cert_data, &cert_data, NULL, 0)) |
| return false; |
| return true; |
| } |
| |
| SHA256HashValue CalculateFingerprint256(SecCertificateRef cert) { |
| SHA256HashValue sha256; |
| SbMemorySet(sha256.data, 0, sizeof(sha256.data)); |
| |
| CSSM_DATA cert_data; |
| OSStatus status = SecCertificateGetData(cert, &cert_data); |
| if (status) |
| return sha256; |
| |
| DCHECK(cert_data.Data); |
| DCHECK_NE(cert_data.Length, 0U); |
| |
| CC_SHA256(cert_data.Data, cert_data.Length, sha256.data); |
| |
| return sha256; |
| } |
| |
| OSStatus CreateSSLClientPolicy(SecPolicyRef* policy) { |
| *policy = SecPolicyCreateSSL(false /* server */, nullptr); |
| return *policy ? noErr : errSecNoPolicyModule; |
| } |
| |
| OSStatus CreateSSLServerPolicy(const std::string& hostname, |
| SecPolicyRef* policy) { |
| base::ScopedCFTypeRef<CFStringRef> hostname_cfstring; |
| if (!hostname.empty()) { |
| hostname_cfstring.reset(base::SysUTF8ToCFStringRef(hostname)); |
| if (!hostname_cfstring) |
| return errSecNoPolicyModule; |
| } |
| |
| *policy = SecPolicyCreateSSL(true /* server */, hostname_cfstring.get()); |
| return *policy ? noErr : errSecNoPolicyModule; |
| } |
| |
| OSStatus CreateBasicX509Policy(SecPolicyRef* policy) { |
| *policy = SecPolicyCreateBasicX509(); |
| return *policy ? noErr : errSecNoPolicyModule; |
| } |
| |
| OSStatus CreateRevocationPolicies(bool enable_revocation_checking, |
| CFMutableArrayRef policies) { |
| if (__builtin_available(macos 10.12, *)) { |
| // On Sierra, it's not possible to disable network revocation checking |
| // without also breaking AIA. If revocation checking isn't explicitly |
| // enabled, just don't add a revocation policy. |
| if (!enable_revocation_checking) |
| return noErr; |
| |
| // If revocation checking is requested, enable checking and require positive |
| // results. Note that this will fail if there are certs with no |
| // CRLDistributionPoints or OCSP AIA urls, which differs from the behavior |
| // of |enable_revocation_checking| on pre-10.12. There does not appear to be |
| // a way around this, but it shouldn't matter much in practice since |
| // revocation checking is generally used with EV certs, where it is expected |
| // that all certs include revocation mechanisms. |
| SecPolicyRef revocation_policy = |
| SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod | |
| kSecRevocationRequirePositiveResponse); |
| |
| if (!revocation_policy) |
| return errSecNoPolicyModule; |
| CFArrayAppendValue(policies, revocation_policy); |
| CFRelease(revocation_policy); |
| return noErr; |
| } |
| OSStatus status = noErr; |
| |
| // In order to bypass the system revocation checking settings, the |
| // SecTrustRef must have at least one revocation policy associated with it. |
| // Since it is not known prior to verification whether the Apple TP will |
| // consider a certificate as an EV candidate, the default policy used is a |
| // CRL policy, since it does not communicate over the network. |
| // If the TP believes the leaf is an EV cert, it will explicitly add an |
| // OCSP policy to perform the online checking, and if it doesn't believe |
| // that the leaf is EV, then the default CRL policy will effectively no-op. |
| // This behaviour is used to implement EV-only revocation checking. |
| if (enable_revocation_checking) { |
| CSSM_APPLE_TP_CRL_OPTIONS tp_crl_options; |
| SbMemorySet(&tp_crl_options, 0, sizeof(tp_crl_options)); |
| tp_crl_options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; |
| // Only allow network CRL fetches if the caller explicitly requests |
| // online revocation checking. Note that, as of OS X 10.7.2, the system |
| // will set force this flag on according to system policies, so |
| // online revocation checks cannot be completely disabled. |
| // Starting with OS X 10.12, if a CRL policy is added without the |
| // FETCH_CRL_FROM_NET flag, AIA fetching is disabled. |
| tp_crl_options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET; |
| |
| SecPolicyRef crl_policy; |
| status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_CRL, &tp_crl_options, |
| sizeof(tp_crl_options), &crl_policy); |
| if (status) |
| return status; |
| CFArrayAppendValue(policies, crl_policy); |
| CFRelease(crl_policy); |
| } |
| |
| // If revocation checking is explicitly enabled, then add an OCSP policy |
| // and allow network access. If both revocation checking is |
| // disabled, then the added OCSP policy will be prevented from |
| // accessing the network. This is done because the TP will force an OCSP |
| // policy to be present when it believes the certificate is EV. |
| CSSM_APPLE_TP_OCSP_OPTIONS tp_ocsp_options; |
| SbMemorySet(&tp_ocsp_options, 0, sizeof(tp_ocsp_options)); |
| tp_ocsp_options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; |
| |
| if (enable_revocation_checking) { |
| // The default for the OCSP policy is to fetch responses via the network, |
| // unlike the CRL policy default. The policy is further modified to |
| // prefer OCSP over CRLs, if both are specified on the certificate. This |
| // is because an OCSP response is both sufficient and typically |
| // significantly smaller than the CRL counterpart. |
| tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; |
| } else { |
| // Effectively disable OCSP checking by making it impossible to get an |
| // OCSP response. Even if the Apple TP forces OCSP, no checking will |
| // be able to succeed. If this happens, the Apple TP will report an error |
| // that OCSP was unavailable, but this will be handled and suppressed in |
| // X509Certificate::Verify(). |
| tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_DISABLE_NET | |
| CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE; |
| } |
| |
| SecPolicyRef ocsp_policy; |
| status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_OCSP, &tp_ocsp_options, |
| sizeof(tp_ocsp_options), &ocsp_policy); |
| if (status) |
| return status; |
| CFArrayAppendValue(policies, ocsp_policy); |
| CFRelease(ocsp_policy); |
| |
| return status; |
| } |
| |
| CSSMFieldValue::CSSMFieldValue() |
| : cl_handle_(CSSM_INVALID_HANDLE), |
| oid_(NULL), |
| field_(NULL) { |
| } |
| CSSMFieldValue::CSSMFieldValue(CSSM_CL_HANDLE cl_handle, |
| const CSSM_OID* oid, |
| CSSM_DATA_PTR field) |
| : cl_handle_(cl_handle), |
| oid_(const_cast<CSSM_OID_PTR>(oid)), |
| field_(field) { |
| } |
| |
| CSSMFieldValue::~CSSMFieldValue() { |
| Reset(CSSM_INVALID_HANDLE, NULL, NULL); |
| } |
| |
| void CSSMFieldValue::Reset(CSSM_CL_HANDLE cl_handle, |
| CSSM_OID_PTR oid, |
| CSSM_DATA_PTR field) { |
| if (cl_handle_ && oid_ && field_) |
| CSSM_CL_FreeFieldValue(cl_handle_, oid_, field_); |
| cl_handle_ = cl_handle; |
| oid_ = oid; |
| field_ = field; |
| } |
| |
| CSSMCachedCertificate::CSSMCachedCertificate() |
| : cl_handle_(CSSM_INVALID_HANDLE), |
| cached_cert_handle_(CSSM_INVALID_HANDLE) { |
| } |
| CSSMCachedCertificate::~CSSMCachedCertificate() { |
| if (cl_handle_ && cached_cert_handle_) |
| CSSM_CL_CertAbortCache(cl_handle_, cached_cert_handle_); |
| } |
| |
| OSStatus CSSMCachedCertificate::Init(SecCertificateRef os_cert_handle) { |
| DCHECK(!cl_handle_ && !cached_cert_handle_); |
| DCHECK(os_cert_handle); |
| CSSM_DATA cert_data; |
| OSStatus status = SecCertificateGetData(os_cert_handle, &cert_data); |
| if (status) |
| return status; |
| status = SecCertificateGetCLHandle(os_cert_handle, &cl_handle_); |
| if (status) { |
| DCHECK(!cl_handle_); |
| return status; |
| } |
| |
| status = CSSM_CL_CertCache(cl_handle_, &cert_data, &cached_cert_handle_); |
| if (status) |
| DCHECK(!cached_cert_handle_); |
| return status; |
| } |
| |
| OSStatus CSSMCachedCertificate::GetField(const CSSM_OID* field_oid, |
| CSSMFieldValue* field) const { |
| DCHECK(cl_handle_); |
| DCHECK(cached_cert_handle_); |
| |
| CSSM_OID_PTR oid = const_cast<CSSM_OID_PTR>(field_oid); |
| CSSM_DATA_PTR field_ptr = NULL; |
| CSSM_HANDLE results_handle = CSSM_INVALID_HANDLE; |
| uint32_t field_value_count = 0; |
| CSSM_RETURN status = CSSM_CL_CertGetFirstCachedFieldValue( |
| cl_handle_, cached_cert_handle_, oid, &results_handle, |
| &field_value_count, &field_ptr); |
| if (status) |
| return status; |
| |
| // Note: |field_value_count| may be > 1, indicating that more than one |
| // value is present. This may happen with extensions, but for current |
| // usages, only the first value is returned. |
| CSSM_CL_CertAbortQuery(cl_handle_, results_handle); |
| field->Reset(cl_handle_, oid, field_ptr); |
| return CSSM_OK; |
| } |
| |
| } // namespace x509_util |
| |
| #pragma clang diagnostic pop // "-Wdeprecated-declarations" |
| |
| } // namespace net |