| // 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/base/cert_verify_proc_openssl.h" |
| |
| #include <openssl/x509v3.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/sha1.h" |
| #include "crypto/openssl_util.h" |
| #include "crypto/sha2.h" |
| #include "net/base/asn1_util.h" |
| #include "net/base/cert_status_flags.h" |
| #include "net/base/cert_verifier.h" |
| #include "net/base/cert_verify_result.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/x509_certificate.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // Maps X509_STORE_CTX_get_error() return values to our cert status flags. |
| CertStatus MapCertErrorToCertStatus(int err) { |
| switch (err) { |
| case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: |
| return CERT_STATUS_COMMON_NAME_INVALID; |
| case X509_V_ERR_CERT_NOT_YET_VALID: |
| case X509_V_ERR_CERT_HAS_EXPIRED: |
| case X509_V_ERR_CRL_NOT_YET_VALID: |
| case X509_V_ERR_CRL_HAS_EXPIRED: |
| case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: |
| case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: |
| case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: |
| case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: |
| return CERT_STATUS_DATE_INVALID; |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: |
| case X509_V_ERR_UNABLE_TO_GET_CRL: |
| case X509_V_ERR_INVALID_CA: |
| case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: |
| case X509_V_ERR_INVALID_NON_CA: |
| case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: |
| case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: |
| return CERT_STATUS_AUTHORITY_INVALID; |
| #if 0 |
| // TODO(bulach): what should we map to these status? |
| return CERT_STATUS_NO_REVOCATION_MECHANISM; |
| return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; |
| #endif |
| case X509_V_ERR_CERT_REVOKED: |
| return CERT_STATUS_REVOKED; |
| // All these status are mapped to CERT_STATUS_INVALID. |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: |
| case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: |
| case X509_V_ERR_CERT_SIGNATURE_FAILURE: |
| case X509_V_ERR_CRL_SIGNATURE_FAILURE: |
| case X509_V_ERR_OUT_OF_MEM: |
| case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: |
| case X509_V_ERR_CERT_CHAIN_TOO_LONG: |
| case X509_V_ERR_PATH_LENGTH_EXCEEDED: |
| case X509_V_ERR_INVALID_PURPOSE: |
| case X509_V_ERR_CERT_UNTRUSTED: |
| case X509_V_ERR_CERT_REJECTED: |
| case X509_V_ERR_AKID_SKID_MISMATCH: |
| case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: |
| case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: |
| case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: |
| case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: |
| case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: |
| case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: |
| case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: |
| case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: |
| case X509_V_ERR_INVALID_EXTENSION: |
| case X509_V_ERR_INVALID_POLICY_EXTENSION: |
| case X509_V_ERR_NO_EXPLICIT_POLICY: |
| case X509_V_ERR_UNNESTED_RESOURCE: |
| case X509_V_ERR_APPLICATION_VERIFICATION: |
| return CERT_STATUS_INVALID; |
| default: |
| NOTREACHED() << "Invalid X509 err " << err; |
| return CERT_STATUS_INVALID; |
| } |
| } |
| |
| // sk_X509_free is a function-style macro, so can't be used as a template |
| // param directly. |
| void sk_X509_free_fn(STACK_OF(X509)* st) { |
| sk_X509_free(st); |
| } |
| |
| void GetCertChainInfo(X509_STORE_CTX* store_ctx, |
| CertVerifyResult* verify_result) { |
| STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); |
| X509* verified_cert = NULL; |
| std::vector<X509*> verified_chain; |
| for (int i = 0; i < sk_X509_num(chain); ++i) { |
| X509* cert = sk_X509_value(chain, i); |
| if (i == 0) { |
| verified_cert = cert; |
| } else { |
| verified_chain.push_back(cert); |
| } |
| |
| // Only check the algorithm status for certificates that are not in the |
| // trust store. |
| if (i < store_ctx->last_untrusted) { |
| int sig_alg = OBJ_obj2nid(cert->sig_alg->algorithm); |
| if (sig_alg == NID_md2WithRSAEncryption) { |
| verify_result->has_md2 = true; |
| if (i != 0) |
| verify_result->has_md2_ca = true; |
| } else if (sig_alg == NID_md4WithRSAEncryption) { |
| verify_result->has_md4 = true; |
| } else if (sig_alg == NID_md5WithRSAEncryption) { |
| verify_result->has_md5 = true; |
| if (i != 0) |
| verify_result->has_md5_ca = true; |
| } |
| } |
| } |
| |
| if (verified_cert) { |
| verify_result->verified_cert = |
| X509Certificate::CreateFromHandle(verified_cert, verified_chain); |
| } |
| } |
| |
| void AppendPublicKeyHashes(X509_STORE_CTX* store_ctx, |
| HashValueVector* hashes) { |
| STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); |
| for (int i = 0; i < sk_X509_num(chain); ++i) { |
| X509* cert = sk_X509_value(chain, i); |
| |
| std::string der_data; |
| if (!X509Certificate::GetDEREncoded(cert, &der_data)) |
| continue; |
| |
| base::StringPiece der_bytes(der_data); |
| base::StringPiece spki_bytes; |
| if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) |
| continue; |
| |
| HashValue sha1(HASH_VALUE_SHA1); |
| base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), |
| spki_bytes.size(), sha1.data()); |
| hashes->push_back(sha1); |
| |
| HashValue sha256(HASH_VALUE_SHA256); |
| crypto::SHA256HashString(spki_bytes, sha1.data(), crypto::kSHA256Length); |
| hashes->push_back(sha256); |
| } |
| } |
| |
| } // namespace |
| |
| CertVerifyProcOpenSSL::CertVerifyProcOpenSSL() {} |
| |
| CertVerifyProcOpenSSL::~CertVerifyProcOpenSSL() {} |
| |
| int CertVerifyProcOpenSSL::VerifyInternal(X509Certificate* cert, |
| const std::string& hostname, |
| int flags, |
| CRLSet* crl_set, |
| CertVerifyResult* verify_result) { |
| crypto::EnsureOpenSSLInit(); |
| |
| if (!cert->VerifyNameMatch(hostname)) |
| verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; |
| |
| crypto::ScopedOpenSSL<X509_STORE_CTX, X509_STORE_CTX_free> ctx( |
| X509_STORE_CTX_new()); |
| |
| crypto::ScopedOpenSSL<STACK_OF(X509), sk_X509_free_fn> intermediates( |
| sk_X509_new_null()); |
| if (!intermediates.get()) |
| return ERR_OUT_OF_MEMORY; |
| |
| const X509Certificate::OSCertHandles& os_intermediates = |
| cert->GetIntermediateCertificates(); |
| for (X509Certificate::OSCertHandles::const_iterator it = |
| os_intermediates.begin(); it != os_intermediates.end(); ++it) { |
| if (!sk_X509_push(intermediates.get(), *it)) |
| return ERR_OUT_OF_MEMORY; |
| } |
| if (X509_STORE_CTX_init(ctx.get(), X509Certificate::cert_store(), |
| cert->os_cert_handle(), intermediates.get()) != 1) { |
| NOTREACHED(); |
| return ERR_FAILED; |
| } |
| |
| if (X509_verify_cert(ctx.get()) != 1) { |
| int x509_error = X509_STORE_CTX_get_error(ctx.get()); |
| CertStatus cert_status = MapCertErrorToCertStatus(x509_error); |
| LOG(ERROR) << "X509 Verification error " |
| << X509_verify_cert_error_string(x509_error) |
| << " : " << x509_error |
| << " : " << X509_STORE_CTX_get_error_depth(ctx.get()) |
| << " : " << cert_status; |
| verify_result->cert_status |= cert_status; |
| } |
| |
| GetCertChainInfo(ctx.get(), verify_result); |
| AppendPublicKeyHashes(ctx.get(), &verify_result->public_key_hashes); |
| |
| if (IsCertStatusError(verify_result->cert_status)) |
| return MapCertStatusToNetError(verify_result->cert_status); |
| |
| // Currently we only ues OpenSSL's default root CA paths, so treat all |
| // correctly verified certs as being from a known root. |
| // TODO(joth): if the motivations described in |
| // http://src.chromium.org/viewvc/chrome?view=rev&revision=80778 become an |
| // issue on OpenSSL builds, we will need to embed a hardcoded list of well |
| // known root CAs, as per the _mac and _win versions. |
| verify_result->is_issued_by_known_root = true; |
| |
| return OK; |
| } |
| |
| } // namespace net |