6139 lines
238 KiB
C++
6139 lines
238 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/cert/cert_verify_proc.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "base/files/file_path.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/message_loop/message_pump_type.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/ranges/algorithm.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "base/test/task_environment.h"
|
|
#include "base/threading/thread.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "crypto/sha2.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/cert/asn1_util.h"
|
|
#include "net/cert/cert_net_fetcher.h"
|
|
#include "net/cert/cert_status_flags.h"
|
|
#include "net/cert/cert_verifier.h"
|
|
#include "net/cert/cert_verify_proc_builtin.h"
|
|
#include "net/cert/cert_verify_result.h"
|
|
#include "net/cert/crl_set.h"
|
|
#include "net/cert/ev_root_ca_metadata.h"
|
|
#include "net/cert/internal/system_trust_store.h"
|
|
#include "net/cert/ocsp_revocation_status.h"
|
|
#include "net/cert/pem.h"
|
|
#include "net/cert/pki/extended_key_usage.h"
|
|
#include "net/cert/pki/parse_certificate.h"
|
|
#include "net/cert/pki/signature_algorithm.h"
|
|
#include "net/cert/pki/trust_store.h"
|
|
#include "net/cert/test_root_certs.h"
|
|
#include "net/cert/x509_certificate.h"
|
|
#include "net/cert/x509_util.h"
|
|
#include "net/cert_net/cert_net_fetcher_url_request.h"
|
|
#include "net/der/input.h"
|
|
#include "net/der/parser.h"
|
|
#include "net/log/test_net_log.h"
|
|
#include "net/proxy_resolution/proxy_config.h"
|
|
#include "net/proxy_resolution/proxy_config_service_fixed.h"
|
|
#include "net/test/cert_builder.h"
|
|
#include "net/test/cert_test_util.h"
|
|
#include "net/test/embedded_test_server/embedded_test_server.h"
|
|
#include "net/test/embedded_test_server/http_request.h"
|
|
#include "net/test/embedded_test_server/http_response.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/revocation_builder.h"
|
|
#include "net/test/test_certificate_data.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "net/url_request/url_request_context.h"
|
|
#include "net/url_request/url_request_context_builder.h"
|
|
#include "net/url_request/url_request_context_getter.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/boringssl/src/include/openssl/bytestring.h"
|
|
#include "third_party/boringssl/src/include/openssl/mem.h"
|
|
#include "third_party/boringssl/src/include/openssl/pool.h"
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#include "net/cert/cert_verify_proc_android.h"
|
|
#elif BUILDFLAG(IS_IOS)
|
|
#include "base/ios/ios_util.h"
|
|
#include "net/cert/cert_verify_proc_ios.h"
|
|
#elif BUILDFLAG(IS_MAC)
|
|
#include "base/mac/mac_util.h"
|
|
#include "net/cert/internal/trust_store_mac.h"
|
|
#endif
|
|
|
|
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
|
|
#include "net/cert/internal/trust_store_chrome.h"
|
|
#endif
|
|
|
|
// TODO(crbug.com/649017): Add tests that only certificates with
|
|
// serverAuth are accepted.
|
|
|
|
using net::test::IsError;
|
|
using net::test::IsOk;
|
|
|
|
using base::HexEncode;
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
const char kTrustAnchorVerifyHistogram[] = "Net.Certificate.TrustAnchor.Verify";
|
|
const char kTrustAnchorVerifyOutOfDateHistogram[] =
|
|
"Net.Certificate.TrustAnchor.VerifyOutOfDate";
|
|
|
|
// Returns a TLV to use as an unknown signature algorithm when building a cert.
|
|
// The specific contents are as follows (the OID is from
|
|
// https://davidben.net/oid):
|
|
//
|
|
// SEQUENCE {
|
|
// OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.0 }
|
|
// NULL {}
|
|
// }
|
|
std::string TestOid0SignatureAlgorithmTLV() {
|
|
constexpr uint8_t kTestOid0SigAlgTLV[] = {0x30, 0x10, 0x06, 0x0c, 0x2a, 0x86,
|
|
0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
|
|
0x84, 0xb7, 0x09, 0x00, 0x05, 0x00};
|
|
return std::string(std::begin(kTestOid0SigAlgTLV),
|
|
std::end(kTestOid0SigAlgTLV));
|
|
}
|
|
|
|
// An OID for use in tests, from https://davidben.net/oid
|
|
// OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.0 }
|
|
der::Input TestOid0() {
|
|
static uint8_t kTestOid0[] = {0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
|
0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x00};
|
|
return der::Input(kTestOid0);
|
|
}
|
|
|
|
// Mock CertVerifyProc that sets the CertVerifyResult to a given value for
|
|
// all certificates that are Verify()'d
|
|
class MockCertVerifyProc : public CertVerifyProc {
|
|
public:
|
|
explicit MockCertVerifyProc(const CertVerifyResult& result)
|
|
: CertVerifyProc(CRLSet::BuiltinCRLSet()), result_(result) {}
|
|
MockCertVerifyProc(const CertVerifyResult& result, int error)
|
|
: CertVerifyProc(CRLSet::BuiltinCRLSet()),
|
|
result_(result),
|
|
error_(error) {}
|
|
|
|
MockCertVerifyProc(const MockCertVerifyProc&) = delete;
|
|
MockCertVerifyProc& operator=(const MockCertVerifyProc&) = delete;
|
|
|
|
// CertVerifyProc implementation:
|
|
bool SupportsAdditionalTrustAnchors() const override { return false; }
|
|
|
|
protected:
|
|
~MockCertVerifyProc() override = default;
|
|
|
|
private:
|
|
int VerifyInternal(X509Certificate* cert,
|
|
const std::string& hostname,
|
|
const std::string& ocsp_response,
|
|
const std::string& sct_list,
|
|
int flags,
|
|
const CertificateList& additional_trust_anchors,
|
|
CertVerifyResult* verify_result,
|
|
const NetLogWithSource& net_log) override;
|
|
|
|
const CertVerifyResult result_;
|
|
const int error_ = OK;
|
|
};
|
|
|
|
int MockCertVerifyProc::VerifyInternal(
|
|
X509Certificate* cert,
|
|
const std::string& hostname,
|
|
const std::string& ocsp_response,
|
|
const std::string& sct_list,
|
|
int flags,
|
|
const CertificateList& additional_trust_anchors,
|
|
CertVerifyResult* verify_result,
|
|
const NetLogWithSource& net_log) {
|
|
*verify_result = result_;
|
|
verify_result->verified_cert = cert;
|
|
return error_;
|
|
}
|
|
|
|
// This enum identifies a concrete implemenation of CertVerifyProc.
|
|
//
|
|
// The type is erased by CreateCertVerifyProc(), however needs to be known for
|
|
// some of the test expectations.
|
|
enum CertVerifyProcType {
|
|
CERT_VERIFY_PROC_ANDROID,
|
|
CERT_VERIFY_PROC_IOS,
|
|
CERT_VERIFY_PROC_BUILTIN,
|
|
CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS,
|
|
};
|
|
|
|
// Returns a textual description of the CertVerifyProc implementation
|
|
// that is being tested, used to give better names to parameterized
|
|
// tests.
|
|
std::string VerifyProcTypeToName(
|
|
const testing::TestParamInfo<CertVerifyProcType>& params) {
|
|
switch (params.param) {
|
|
case CERT_VERIFY_PROC_ANDROID:
|
|
return "CertVerifyProcAndroid";
|
|
case CERT_VERIFY_PROC_IOS:
|
|
return "CertVerifyProcIOS";
|
|
case CERT_VERIFY_PROC_BUILTIN:
|
|
return "CertVerifyProcBuiltin";
|
|
case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS:
|
|
return "CertVerifyProcBuiltinChromeRoots";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
scoped_refptr<CertVerifyProc> CreateCertVerifyProc(
|
|
CertVerifyProcType type,
|
|
scoped_refptr<CertNetFetcher> cert_net_fetcher,
|
|
scoped_refptr<CRLSet> crl_set) {
|
|
switch (type) {
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
case CERT_VERIFY_PROC_ANDROID:
|
|
return base::MakeRefCounted<CertVerifyProcAndroid>(
|
|
std::move(cert_net_fetcher), std::move(crl_set));
|
|
#elif BUILDFLAG(IS_IOS)
|
|
case CERT_VERIFY_PROC_IOS:
|
|
return base::MakeRefCounted<CertVerifyProcIOS>(std::move(crl_set));
|
|
#endif
|
|
#if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
case CERT_VERIFY_PROC_BUILTIN:
|
|
return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher),
|
|
std::move(crl_set),
|
|
CreateSslSystemTrustStore());
|
|
#endif
|
|
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
|
|
case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS:
|
|
return CreateCertVerifyProcBuiltin(
|
|
std::move(cert_net_fetcher), std::move(crl_set),
|
|
CreateSslSystemTrustStoreChromeRoot(
|
|
std::make_unique<net::TrustStoreChrome>()));
|
|
#endif
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// The set of all CertVerifyProcTypes that tests should be parameterized on.
|
|
// This needs to be kept in sync with CertVerifyProc::CreateSystemVerifyProc()
|
|
// and the platforms where CreateSslSystemTrustStore() is not a dummy store.
|
|
// TODO(crbug.com/649017): Enable CERT_VERIFY_PROC_BUILTIN everywhere. Right
|
|
// now this is gated on having CertVerifyProcBuiltin understand the roots added
|
|
// via TestRootCerts.
|
|
constexpr CertVerifyProcType kAllCertVerifiers[] = {
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
CERT_VERIFY_PROC_ANDROID,
|
|
#elif BUILDFLAG(IS_IOS)
|
|
CERT_VERIFY_PROC_IOS,
|
|
#elif BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
CERT_VERIFY_PROC_BUILTIN,
|
|
#endif
|
|
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
|
|
CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS,
|
|
#endif
|
|
};
|
|
static_assert(std::size(kAllCertVerifiers) != 0, "Unsupported platform");
|
|
|
|
// Returns true if a test root added through ScopedTestRoot can verify
|
|
// successfully as a target certificate with chain of length 1 on the given
|
|
// CertVerifyProcType.
|
|
bool ScopedTestRootCanTrustTargetCert(CertVerifyProcType verify_proc_type) {
|
|
return verify_proc_type == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type == CERT_VERIFY_PROC_ANDROID;
|
|
}
|
|
|
|
// Returns true if a non-self-signed CA certificate added through
|
|
// ScopedTestRoot can verify successfully as the root of a chain by the given
|
|
// CertVerifyProcType.
|
|
bool ScopedTestRootCanTrustIntermediateCert(
|
|
CertVerifyProcType verify_proc_type) {
|
|
return verify_proc_type == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type == CERT_VERIFY_PROC_BUILTIN ||
|
|
verify_proc_type == CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS ||
|
|
verify_proc_type == CERT_VERIFY_PROC_ANDROID;
|
|
}
|
|
|
|
std::string MakeRandomHexString(size_t num_bytes) {
|
|
std::vector<char> rand_bytes;
|
|
rand_bytes.resize(num_bytes);
|
|
|
|
base::RandBytes(rand_bytes.data(), rand_bytes.size());
|
|
return base::HexEncode(rand_bytes.data(), rand_bytes.size());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// This fixture is for tests that apply to concrete implementations of
|
|
// CertVerifyProc. It will be run for all of the concrete CertVerifyProc types.
|
|
//
|
|
// It is called "Internal" as it tests the internal methods like
|
|
// "VerifyInternal()".
|
|
class CertVerifyProcInternalTest
|
|
: public testing::TestWithParam<CertVerifyProcType> {
|
|
protected:
|
|
void SetUp() override { SetUpCertVerifyProc(CRLSet::BuiltinCRLSet()); }
|
|
|
|
// CertNetFetcher may be initialized by subclasses that want to use net
|
|
// fetching by calling SetUpWithCertNetFetcher instead of SetUp.
|
|
void SetUpWithCertNetFetcher(scoped_refptr<CertNetFetcher> cert_net_fetcher,
|
|
scoped_refptr<CRLSet> crl_set) {
|
|
CertVerifyProcType type = verify_proc_type();
|
|
verify_proc_ = CreateCertVerifyProc(type, std::move(cert_net_fetcher),
|
|
std::move(crl_set));
|
|
ASSERT_TRUE(verify_proc_);
|
|
}
|
|
|
|
virtual void SetUpCertVerifyProc(scoped_refptr<CRLSet> crl_set) {
|
|
SetUpWithCertNetFetcher(nullptr, std::move(crl_set));
|
|
}
|
|
|
|
int Verify(X509Certificate* cert,
|
|
const std::string& hostname,
|
|
int flags,
|
|
const CertificateList& additional_trust_anchors,
|
|
CertVerifyResult* verify_result,
|
|
const NetLogWithSource& net_log) {
|
|
return verify_proc_->Verify(cert, hostname, /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags,
|
|
additional_trust_anchors, verify_result,
|
|
net_log);
|
|
}
|
|
|
|
int Verify(X509Certificate* cert,
|
|
const std::string& hostname,
|
|
int flags,
|
|
const CertificateList& additional_trust_anchors,
|
|
CertVerifyResult* verify_result) {
|
|
return Verify(cert, hostname, flags, additional_trust_anchors,
|
|
verify_result, NetLogWithSource());
|
|
}
|
|
|
|
int Verify(X509Certificate* cert, const std::string& hostname) {
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
return Verify(cert, hostname, flags, CertificateList(), &verify_result);
|
|
}
|
|
|
|
CertVerifyProcType verify_proc_type() const { return GetParam(); }
|
|
|
|
bool SupportsAdditionalTrustAnchors() const {
|
|
return verify_proc_->SupportsAdditionalTrustAnchors();
|
|
}
|
|
|
|
// Returns true if the RSA/DSA keysize will be considered weak on the current
|
|
// platform. IsInvalidRsaDsaKeySize should be checked prior, since some very
|
|
// weak keys may be considered invalid.
|
|
bool IsWeakRsaDsaKeySize(int size) const {
|
|
#if BUILDFLAG(IS_IOS)
|
|
// Beginning with iOS 13, the minimum key size for RSA/DSA algorithms is
|
|
// 2048 bits. See https://support.apple.com/en-us/HT210176
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_IOS &&
|
|
base::ios::IsRunningOnIOS13OrLater()) {
|
|
return size < 2048;
|
|
}
|
|
#endif
|
|
|
|
return size < 1024;
|
|
}
|
|
|
|
// Returns true if the RSA/DSA keysize will be considered invalid on the
|
|
// current platform.
|
|
bool IsInvalidRsaDsaKeySize(int size) const {
|
|
#if BUILDFLAG(IS_IOS)
|
|
if (base::ios::IsRunningOnIOS12OrLater()) {
|
|
// On iOS using SecTrustEvaluateWithError it is not possible to
|
|
// distinguish between weak and invalid key sizes.
|
|
return IsWeakRsaDsaKeySize(size);
|
|
}
|
|
#endif
|
|
|
|
// This platform does not mark certificates with weak keys as invalid.
|
|
return false;
|
|
}
|
|
|
|
static bool ParseKeyType(const std::string& key_type,
|
|
std::string* type,
|
|
int* size) {
|
|
size_t pos = key_type.find("-");
|
|
std::string size_str = key_type.substr(0, pos);
|
|
*type = key_type.substr(pos + 1);
|
|
return base::StringToInt(size_str, size);
|
|
}
|
|
|
|
// Some platforms may reject certificates with very weak keys as invalid.
|
|
bool IsInvalidKeyType(const std::string& key_type) const {
|
|
std::string type;
|
|
int size = 0;
|
|
if (!ParseKeyType(key_type, &type, &size))
|
|
return false;
|
|
|
|
if (type == "rsa" || type == "dsa")
|
|
return IsInvalidRsaDsaKeySize(size);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Currently, only RSA and DSA keys are checked for weakness, and our example
|
|
// weak size is 768. These could change in the future.
|
|
//
|
|
// Note that this means there may be false negatives: keys for other
|
|
// algorithms and which are weak will pass this test.
|
|
//
|
|
// Also, IsInvalidKeyType should be checked prior, since some weak keys may be
|
|
// considered invalid.
|
|
bool IsWeakKeyType(const std::string& key_type) const {
|
|
std::string type;
|
|
int size = 0;
|
|
if (!ParseKeyType(key_type, &type, &size))
|
|
return false;
|
|
|
|
if (type == "rsa" || type == "dsa")
|
|
return IsWeakRsaDsaKeySize(size);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SupportsCRLSet() const { return VerifyProcTypeIsBuiltin(); }
|
|
|
|
bool SupportsCRLSetsInPathBuilding() const {
|
|
return VerifyProcTypeIsBuiltin();
|
|
}
|
|
|
|
bool SupportsEV() const {
|
|
// Android and iOS do not support EV. See https://crbug.com/117478#7
|
|
#if defined(PLATFORM_USES_CHROMIUM_EV_METADATA)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool SupportsSoftFailRevChecking() const { return VerifyProcTypeIsBuiltin(); }
|
|
|
|
bool SupportsRevCheckingRequiredLocalAnchors() const {
|
|
return VerifyProcTypeIsBuiltin();
|
|
}
|
|
|
|
bool VerifyProcTypeIsBuiltin() const {
|
|
return verify_proc_type() == CERT_VERIFY_PROC_BUILTIN ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS;
|
|
}
|
|
|
|
bool VerifyProcTypeIsIOSAtMostOS14() const {
|
|
#if BUILDFLAG(IS_IOS)
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_IOS &&
|
|
!base::ios::IsRunningOnIOS15OrLater()) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool VerifyProcTypeIsIOSAtMostOS15() const {
|
|
#if BUILDFLAG(IS_IOS)
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_IOS &&
|
|
!base::ios::IsRunningOnIOS16OrLater()) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
CertVerifyProc* verify_proc() const { return verify_proc_.get(); }
|
|
|
|
private:
|
|
scoped_refptr<CertVerifyProc> verify_proc_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcInternalTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
TEST_P(CertVerifyProcInternalTest, DistrustedIntermediate) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
constexpr char kHostname[] = "www.example.com";
|
|
|
|
// Chain should not verify without any ScopedTestRoots.
|
|
EXPECT_THAT(Verify(chain.get(), kHostname),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
|
|
// Trusting root should cause chain to verify successfully.
|
|
ScopedTestRoot trust_root(root->GetX509Certificate().get(),
|
|
CertificateTrust::ForTrustAnchor());
|
|
EXPECT_THAT(Verify(chain.get(), kHostname), IsOk());
|
|
|
|
ScopedTestRoot distrust_intermediate(intermediate->GetX509Certificate().get(),
|
|
CertificateTrust::ForDistrusted());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Distrusting intermediate should cause chain to not verify again.
|
|
EXPECT_THAT(Verify(chain.get(), kHostname),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
// Specifying trust types for the platform verifiers through ScopedTestRoot
|
|
// is not supported, so this should still verify successfully.
|
|
EXPECT_THAT(Verify(chain.get(), kHostname), IsOk());
|
|
}
|
|
}
|
|
|
|
// Tests that a certificate is recognized as EV, when the valid EV policy OID
|
|
// for the trust anchor is the second candidate EV oid in the target
|
|
// certificate. This is a regression test for crbug.com/705285.
|
|
TEST_P(CertVerifyProcInternalTest, EVVerificationMultipleOID) {
|
|
if (!SupportsEV()) {
|
|
LOG(INFO) << "Skipping test as EV verification is not yet supported";
|
|
return;
|
|
}
|
|
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
|
|
// The policies that target certificate asserts.
|
|
static const char kOtherTestCertPolicy[] = "2.23.140.1.1";
|
|
static const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
// Specify the extraneous policy first, then the actual policy.
|
|
leaf->SetCertificatePolicies({kOtherTestCertPolicy, kEVTestCertPolicy});
|
|
|
|
scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate();
|
|
ScopedTestRoot test_root(root->GetX509Certificate().get());
|
|
|
|
// Build a CRLSet that covers the target certificate.
|
|
//
|
|
// This way CRLSet coverage will be sufficient for EV revocation checking,
|
|
// so this test does not depend on online revocation checking.
|
|
base::StringPiece spki;
|
|
ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(
|
|
x509_util::CryptoBufferAsStringPiece(root->GetCertBuffer()), &spki));
|
|
SHA256HashValue spki_sha256;
|
|
crypto::SHA256HashString(spki, spki_sha256.data, sizeof(spki_sha256.data));
|
|
SetUpCertVerifyProc(CRLSet::ForTesting(false, &spki_sha256, "", "", {}));
|
|
|
|
// Consider the root of the test chain a valid EV root for the test policy.
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(root->GetCertBuffer()),
|
|
kEVTestCertPolicy);
|
|
ScopedTestEVPolicy scoped_test_other_policy(
|
|
EVRootCAMetadata::GetInstance(), SHA256HashValue(), kOtherTestCertPolicy);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
}
|
|
|
|
// Target cert has an EV policy, and verifies successfully, but has a chain of
|
|
// length 1 because the target cert was directly trusted in the trust store.
|
|
// Should verify OK but not with STATUS_IS_EV.
|
|
TEST_P(CertVerifyProcInternalTest, TrustedTargetCertWithEVPolicy) {
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
|
|
static const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
leaf->SetCertificatePolicies({kEVTestCertPolicy});
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(), SHA256HashValue(), kEVTestCertPolicy);
|
|
|
|
scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate();
|
|
ScopedTestRoot scoped_test_root(cert.get());
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
if (ScopedTestRootCanTrustTargetCert(verify_proc_type())) {
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
EXPECT_TRUE(verify_result.verified_cert->intermediate_buffers().empty());
|
|
} else {
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
}
|
|
|
|
// Target cert has an EV policy, and verifies successfully with a chain of
|
|
// length 1, and its fingerprint matches the cert fingerprint for that ev
|
|
// policy. This should never happen in reality, but just test that things don't
|
|
// explode if it does.
|
|
TEST_P(CertVerifyProcInternalTest,
|
|
TrustedTargetCertWithEVPolicyAndEVFingerprint) {
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
|
|
static const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
leaf->SetCertificatePolicies({kEVTestCertPolicy});
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(leaf->GetCertBuffer()),
|
|
kEVTestCertPolicy);
|
|
scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate();
|
|
ScopedTestRoot scoped_test_root(cert.get());
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
if (ScopedTestRootCanTrustTargetCert(verify_proc_type())) {
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
EXPECT_TRUE(verify_result.verified_cert->intermediate_buffers().empty());
|
|
} else {
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
// An EV Root certificate should never be used as an end-entity certificate.
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
}
|
|
|
|
// Target cert has an EV policy, and has a valid path to the EV root, but the
|
|
// intermediate has been trusted directly. Should stop building the path at the
|
|
// intermediate and verify OK but not with STATUS_IS_EV.
|
|
// See https://crbug.com/979801
|
|
TEST_P(CertVerifyProcInternalTest, TrustedIntermediateCertWithEVPolicy) {
|
|
if (!SupportsEV()) {
|
|
LOG(INFO) << "Skipping test as EV verification is not yet supported";
|
|
return;
|
|
}
|
|
if (!ScopedTestRootCanTrustIntermediateCert(verify_proc_type())) {
|
|
LOG(INFO) << "Skipping test as intermediate cert cannot be trusted";
|
|
return;
|
|
}
|
|
|
|
for (bool trust_the_intermediate : {false, true}) {
|
|
SCOPED_TRACE(trust_the_intermediate);
|
|
|
|
// Need to build unique certs for each try otherwise caching can break
|
|
// things.
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
static const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
leaf->SetCertificatePolicies({kEVTestCertPolicy});
|
|
intermediate->SetCertificatePolicies({kEVTestCertPolicy});
|
|
// Consider the root of the test chain a valid EV root for the test policy.
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(root->GetCertBuffer()),
|
|
kEVTestCertPolicy);
|
|
|
|
scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(cert.get());
|
|
|
|
scoped_refptr<X509Certificate> intermediate_cert =
|
|
intermediate->GetX509Certificate();
|
|
ASSERT_TRUE(intermediate_cert.get());
|
|
|
|
scoped_refptr<X509Certificate> root_cert = root->GetX509Certificate();
|
|
ASSERT_TRUE(root_cert.get());
|
|
|
|
if (!trust_the_intermediate) {
|
|
// First trust just the root. This verifies that the test setup is
|
|
// actually correct.
|
|
ScopedTestRoot scoped_test_root({root_cert});
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert.get(), "www.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
// Verified chain should include the intermediate and the root.
|
|
EXPECT_EQ(2U, verify_result.verified_cert->intermediate_buffers().size());
|
|
// Should be EV.
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
} else {
|
|
// Now try with trusting both the intermediate and the root.
|
|
ScopedTestRoot scoped_test_root({intermediate_cert, root_cert});
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert.get(), "www.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
// Verified chain should only go to the trusted intermediate, not the
|
|
// root.
|
|
EXPECT_EQ(1U, verify_result.verified_cert->intermediate_buffers().size());
|
|
// Should not be EV.
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, CertWithNullInCommonNameAndNoSAN) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
leaf->EraseExtension(der::Input(kSubjectAltNameOid));
|
|
|
|
std::string common_name;
|
|
common_name += "www.fake.com";
|
|
common_name += '\0';
|
|
common_name += "a" + MakeRandomHexString(12) + ".example.com";
|
|
leaf->SetSubjectCommonName(common_name);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
// This actually fails because Chrome only looks for hostnames in
|
|
// SubjectAltNames now and no SubjectAltName is present.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, CertWithNullInCommonNameAndValidSAN) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
leaf->SetSubjectAltName("www.fake.com");
|
|
|
|
std::string common_name;
|
|
common_name += "www.fake.com";
|
|
common_name += '\0';
|
|
common_name += "a" + MakeRandomHexString(12) + ".example.com";
|
|
leaf->SetSubjectCommonName(common_name);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
// SubjectAltName is valid and Chrome does not use the common name.
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, CertWithNullInSAN) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
std::string hostname;
|
|
hostname += "www.fake.com";
|
|
hostname += '\0';
|
|
hostname += "a" + MakeRandomHexString(12) + ".example.com";
|
|
leaf->SetSubjectAltName(hostname);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
// SubjectAltName is invalid.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID));
|
|
}
|
|
|
|
// Tests the case where the target certificate is accepted by
|
|
// X509CertificateBytes, but has errors that should cause verification to fail.
|
|
TEST_P(CertVerifyProcInternalTest, InvalidTarget) {
|
|
base::FilePath certs_dir =
|
|
GetTestNetDataDirectory().AppendASCII("parse_certificate_unittest");
|
|
scoped_refptr<X509Certificate> bad_cert;
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Builtin verifier doesn't distinguish between invalid signature algorithm
|
|
// and unknown signature algorithm, so use a different test file that will
|
|
// fail in ParsedCertificate::Create. The other verifiers use a different
|
|
// test file since the platform verifiers don't all consider empty
|
|
// extensions sequence invalid.
|
|
bad_cert = ImportCertFromFile(certs_dir, "extensions_empty_sequence.pem");
|
|
} else {
|
|
bad_cert = ImportCertFromFile(certs_dir, "signature_algorithm_null.pem");
|
|
}
|
|
ASSERT_TRUE(bad_cert);
|
|
|
|
scoped_refptr<X509Certificate> ok_cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(ok_cert);
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(ok_cert->cert_buffer()));
|
|
scoped_refptr<X509Certificate> cert_with_bad_target(
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(bad_cert->cert_buffer()),
|
|
std::move(intermediates)));
|
|
ASSERT_TRUE(cert_with_bad_target);
|
|
EXPECT_EQ(1U, cert_with_bad_target->intermediate_buffers().size());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert_with_bad_target.get(), "127.0.0.1", flags,
|
|
CertificateList(), &verify_result);
|
|
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Tests the case where an intermediate certificate is accepted by
|
|
// X509CertificateBytes, but has errors that should prevent using it during
|
|
// verification. The verification should succeed, since the intermediate
|
|
// wasn't necessary.
|
|
TEST_P(CertVerifyProcInternalTest, UnnecessaryInvalidIntermediate) {
|
|
ScopedTestRoot test_root(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem").get());
|
|
|
|
base::FilePath certs_dir =
|
|
GetTestNetDataDirectory().AppendASCII("parse_certificate_unittest");
|
|
bssl::UniquePtr<CRYPTO_BUFFER> bad_cert =
|
|
x509_util::CreateCryptoBuffer(base::StringPiece("invalid"));
|
|
ASSERT_TRUE(bad_cert);
|
|
|
|
scoped_refptr<X509Certificate> ok_cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(ok_cert);
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(std::move(bad_cert));
|
|
scoped_refptr<X509Certificate> cert_with_bad_intermediate(
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(ok_cert->cert_buffer()),
|
|
std::move(intermediates)));
|
|
ASSERT_TRUE(cert_with_bad_intermediate);
|
|
EXPECT_EQ(1U, cert_with_bad_intermediate->intermediate_buffers().size());
|
|
|
|
RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
|
|
NetLogWithSource net_log(NetLogWithSource::Make(
|
|
net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK));
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert_with_bad_intermediate.get(), "127.0.0.1", flags,
|
|
CertificateList(), &verify_result, net_log);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0u, verify_result.cert_status);
|
|
|
|
auto events = net_log_observer.GetEntriesForSource(net_log.source());
|
|
EXPECT_FALSE(events.empty());
|
|
|
|
auto event = base::ranges::find(events, NetLogEventType::CERT_VERIFY_PROC,
|
|
&NetLogEntry::type);
|
|
ASSERT_NE(event, events.end());
|
|
EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);
|
|
const std::string* host = event->params.FindString("host");
|
|
ASSERT_TRUE(host);
|
|
EXPECT_EQ("127.0.0.1", *host);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
event =
|
|
base::ranges::find(events, NetLogEventType::CERT_VERIFY_PROC_INPUT_CERT,
|
|
&NetLogEntry::type);
|
|
ASSERT_NE(event, events.end());
|
|
EXPECT_EQ(net::NetLogEventPhase::NONE, event->phase);
|
|
const std::string* errors = event->params.FindString("errors");
|
|
ASSERT_TRUE(errors);
|
|
EXPECT_EQ(
|
|
"ERROR: Failed parsing Certificate SEQUENCE\nERROR: Failed parsing "
|
|
"Certificate\n",
|
|
*errors);
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, RejectExpiredCert) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
|
|
// Load root_ca_cert.pem into the test root store.
|
|
ScopedTestRoot test_root(
|
|
ImportCertFromFile(certs_dir, "root_ca_cert.pem").get());
|
|
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
certs_dir, "expired_cert.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
ASSERT_EQ(0U, cert->intermediate_buffers().size());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, RejectWeakKeys) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
typedef std::vector<std::string> Strings;
|
|
Strings key_types;
|
|
|
|
// generate-weak-test-chains.sh currently has:
|
|
// key_types="768-rsa 1024-rsa 2048-rsa prime256v1-ecdsa"
|
|
// We must use the same key types here. The filenames generated look like:
|
|
// 2048-rsa-ee-by-768-rsa-intermediate.pem
|
|
key_types.push_back("768-rsa");
|
|
key_types.push_back("1024-rsa");
|
|
key_types.push_back("2048-rsa");
|
|
key_types.push_back("prime256v1-ecdsa");
|
|
|
|
// Add the root that signed the intermediates for this test.
|
|
scoped_refptr<X509Certificate> root_cert =
|
|
ImportCertFromFile(certs_dir, "2048-rsa-root.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), root_cert.get());
|
|
ScopedTestRoot scoped_root(root_cert.get());
|
|
|
|
// Now test each chain.
|
|
for (Strings::const_iterator ee_type = key_types.begin();
|
|
ee_type != key_types.end(); ++ee_type) {
|
|
for (Strings::const_iterator signer_type = key_types.begin();
|
|
signer_type != key_types.end(); ++signer_type) {
|
|
std::string basename =
|
|
*ee_type + "-ee-by-" + *signer_type + "-intermediate.pem";
|
|
SCOPED_TRACE(basename);
|
|
scoped_refptr<X509Certificate> ee_cert =
|
|
ImportCertFromFile(certs_dir, basename);
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), ee_cert.get());
|
|
|
|
basename = *signer_type + "-intermediate.pem";
|
|
scoped_refptr<X509Certificate> intermediate =
|
|
ImportCertFromFile(certs_dir, basename);
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), intermediate.get());
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(intermediate->cert_buffer()));
|
|
scoped_refptr<X509Certificate> cert_chain =
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(ee_cert->cert_buffer()),
|
|
std::move(intermediates));
|
|
ASSERT_TRUE(cert_chain);
|
|
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert_chain.get(), "127.0.0.1", 0, CertificateList(),
|
|
&verify_result);
|
|
|
|
if (IsInvalidKeyType(*ee_type) || IsInvalidKeyType(*signer_type)) {
|
|
EXPECT_NE(OK, error);
|
|
EXPECT_EQ(CERT_STATUS_INVALID,
|
|
verify_result.cert_status & CERT_STATUS_INVALID);
|
|
} else if (IsWeakKeyType(*ee_type) || IsWeakKeyType(*signer_type)) {
|
|
EXPECT_NE(OK, error);
|
|
EXPECT_EQ(CERT_STATUS_WEAK_KEY,
|
|
verify_result.cert_status & CERT_STATUS_WEAK_KEY);
|
|
EXPECT_EQ(0u, verify_result.cert_status & CERT_STATUS_INVALID);
|
|
} else {
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status & CERT_STATUS_WEAK_KEY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regression test for http://crbug.com/108514.
|
|
TEST_P(CertVerifyProcInternalTest, ExtraneousMD5RootCert) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
|
|
scoped_refptr<X509Certificate> server_cert =
|
|
ImportCertFromFile(certs_dir, "cross-signed-leaf.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), server_cert.get());
|
|
|
|
scoped_refptr<X509Certificate> extra_cert =
|
|
ImportCertFromFile(certs_dir, "cross-signed-root-md5.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), extra_cert.get());
|
|
|
|
scoped_refptr<X509Certificate> root_cert =
|
|
ImportCertFromFile(certs_dir, "cross-signed-root-sha256.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), root_cert.get());
|
|
|
|
ScopedTestRoot scoped_root(root_cert.get());
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(extra_cert->cert_buffer()));
|
|
scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(server_cert->cert_buffer()), std::move(intermediates));
|
|
ASSERT_TRUE(cert_chain);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(cert_chain.get(), "127.0.0.1", flags, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
|
|
// The extra MD5 root should be discarded
|
|
ASSERT_TRUE(verify_result.verified_cert.get());
|
|
ASSERT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size());
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(
|
|
verify_result.verified_cert->intermediate_buffers().front().get(),
|
|
root_cert->cert_buffer()));
|
|
}
|
|
|
|
// Test for bug 94673.
|
|
TEST_P(CertVerifyProcInternalTest, GoogleDigiNotarTest) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
|
|
scoped_refptr<X509Certificate> server_cert =
|
|
ImportCertFromFile(certs_dir, "google_diginotar.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), server_cert.get());
|
|
|
|
scoped_refptr<X509Certificate> intermediate_cert =
|
|
ImportCertFromFile(certs_dir, "diginotar_public_ca_2025.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), intermediate_cert.get());
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(intermediate_cert->cert_buffer()));
|
|
scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(server_cert->cert_buffer()), std::move(intermediates));
|
|
ASSERT_TRUE(cert_chain);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
int error = Verify(cert_chain.get(), "mail.google.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_NE(OK, error);
|
|
|
|
// Now turn off revocation checking. Certificate verification should still
|
|
// fail.
|
|
flags = 0;
|
|
error = Verify(cert_chain.get(), "mail.google.com", flags, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_NE(OK, error);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, NameConstraintsOk) {
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
|
|
// Use the private key matching the public_key_hash of the kDomainsTest
|
|
// constraint in CertVerifyProc::HasNameConstraintsViolation.
|
|
ASSERT_TRUE(leaf->UseKeyFromFile(
|
|
GetTestCertsDirectory().AppendASCII("name_constrained_key.pem")));
|
|
// example.com is allowed by kDomainsTest, and notarealtld is not a known
|
|
// TLD, so that's allowed too.
|
|
leaf->SetSubjectAltNames({"test.ExAmPlE.CoM", "example.notarealtld",
|
|
"*.test2.ExAmPlE.CoM", "*.example2.notarealtld"},
|
|
{});
|
|
|
|
ScopedTestRoot test_root(root->GetX509Certificate().get());
|
|
|
|
scoped_refptr<X509Certificate> leaf_cert = leaf->GetX509Certificate();
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(leaf_cert.get(), "test.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
|
|
error = Verify(leaf_cert.get(), "foo.test2.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, NameConstraintsFailure) {
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
|
|
// Use the private key matching the public_key_hash of the kDomainsTest
|
|
// constraint in CertVerifyProc::HasNameConstraintsViolation.
|
|
ASSERT_TRUE(leaf->UseKeyFromFile(
|
|
GetTestCertsDirectory().AppendASCII("name_constrained_key.pem")));
|
|
// example.com is allowed by kDomainsTest, but example.org is not.
|
|
leaf->SetSubjectAltNames({"test.ExAmPlE.CoM", "test.ExAmPlE.OrG"}, {});
|
|
|
|
ScopedTestRoot test_root(root->GetX509Certificate().get());
|
|
|
|
scoped_refptr<X509Certificate> leaf_cert = leaf->GetX509Certificate();
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(leaf_cert.get(), "test.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_NAME_CONSTRAINT_VIOLATION));
|
|
EXPECT_EQ(CERT_STATUS_NAME_CONSTRAINT_VIOLATION,
|
|
verify_result.cert_status & CERT_STATUS_NAME_CONSTRAINT_VIOLATION);
|
|
}
|
|
|
|
// This fixture is for testing the verification of a certificate chain which
|
|
// has some sort of mismatched signature algorithm (i.e.
|
|
// Certificate.signatureAlgorithm and TBSCertificate.algorithm are different).
|
|
class CertVerifyProcInspectSignatureAlgorithmsTest : public ::testing::Test {
|
|
protected:
|
|
// In the test setup, SHA384 is given special treatment as an unknown
|
|
// algorithm.
|
|
static constexpr DigestAlgorithm kUnknownDigestAlgorithm =
|
|
DigestAlgorithm::Sha384;
|
|
|
|
struct CertParams {
|
|
// Certificate.signatureAlgorithm
|
|
DigestAlgorithm cert_algorithm;
|
|
|
|
// TBSCertificate.algorithm
|
|
DigestAlgorithm tbs_algorithm;
|
|
};
|
|
|
|
// Shorthand for VerifyChain() where only the leaf's parameters need
|
|
// to be specified.
|
|
[[nodiscard]] int VerifyLeaf(const CertParams& leaf_params) {
|
|
return VerifyChain({// Target
|
|
leaf_params,
|
|
// Root
|
|
{DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}});
|
|
}
|
|
|
|
// Shorthand for VerifyChain() where only the intermediate's parameters need
|
|
// to be specified.
|
|
[[nodiscard]] int VerifyIntermediate(const CertParams& intermediate_params) {
|
|
return VerifyChain({// Target
|
|
{DigestAlgorithm::Sha256, DigestAlgorithm::Sha256},
|
|
// Intermediate
|
|
intermediate_params,
|
|
// Root
|
|
{DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}});
|
|
}
|
|
|
|
// Shorthand for VerifyChain() where only the root's parameters need to be
|
|
// specified.
|
|
[[nodiscard]] int VerifyRoot(const CertParams& root_params) {
|
|
return VerifyChain({// Target
|
|
{DigestAlgorithm::Sha256, DigestAlgorithm::Sha256},
|
|
// Intermediate
|
|
{DigestAlgorithm::Sha256, DigestAlgorithm::Sha256},
|
|
// Root
|
|
root_params});
|
|
}
|
|
|
|
// Manufactures a certificate chain where each certificate has the indicated
|
|
// signature algorithms, and then returns the result of verifying this chain.
|
|
//
|
|
// TODO(mattm): Replace the custom cert mangling code in this test with
|
|
// CertBuilder.
|
|
[[nodiscard]] int VerifyChain(const std::vector<CertParams>& chain_params) {
|
|
auto chain = CreateChain(chain_params);
|
|
if (!chain) {
|
|
ADD_FAILURE() << "Failed creating certificate chain";
|
|
return ERR_UNEXPECTED;
|
|
}
|
|
|
|
int flags = 0;
|
|
CertVerifyResult dummy_result;
|
|
CertVerifyResult verify_result;
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result);
|
|
|
|
return verify_proc->Verify(
|
|
chain.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
}
|
|
|
|
private:
|
|
// Overwrites the AlgorithmIdentifier pointed to by |algorithm_sequence| with
|
|
// |algorithm|. Note this violates the constness of StringPiece.
|
|
[[nodiscard]] static bool SetAlgorithmSequence(
|
|
DigestAlgorithm algorithm,
|
|
base::StringPiece* algorithm_sequence) {
|
|
// This string of bytes is the full SEQUENCE for an AlgorithmIdentifier.
|
|
std::vector<uint8_t> replacement_sequence;
|
|
switch (algorithm) {
|
|
case DigestAlgorithm::Sha1:
|
|
// sha1WithRSAEncryption
|
|
replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
|
|
0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00};
|
|
break;
|
|
case DigestAlgorithm::Sha256:
|
|
// sha256WithRSAEncryption
|
|
replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
|
|
0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00};
|
|
break;
|
|
case kUnknownDigestAlgorithm:
|
|
// This shouldn't be anything meaningful (modified numbers at random).
|
|
replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x8a, 0x87, 0x18, 0x46,
|
|
0xd7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00};
|
|
break;
|
|
default:
|
|
ADD_FAILURE() << "Unsupported digest algorithm";
|
|
return false;
|
|
}
|
|
|
|
// For this simple replacement to work (without modifying any
|
|
// other sequence lengths) the original algorithm and replacement
|
|
// algorithm must have the same encoded length.
|
|
if (algorithm_sequence->size() != replacement_sequence.size()) {
|
|
ADD_FAILURE() << "AlgorithmIdentifier must have length "
|
|
<< replacement_sequence.size();
|
|
return false;
|
|
}
|
|
|
|
memcpy(const_cast<char*>(algorithm_sequence->data()),
|
|
replacement_sequence.data(), replacement_sequence.size());
|
|
return true;
|
|
}
|
|
|
|
// Locate the serial number bytes.
|
|
[[nodiscard]] static bool ExtractSerialNumberFromDERCert(
|
|
base::StringPiece der_cert,
|
|
base::StringPiece* serial_value) {
|
|
der::Parser parser((der::Input(der_cert)));
|
|
der::Parser certificate;
|
|
if (!parser.ReadSequence(&certificate))
|
|
return false;
|
|
|
|
der::Parser tbs_certificate;
|
|
if (!certificate.ReadSequence(&tbs_certificate))
|
|
return false;
|
|
|
|
bool unused;
|
|
if (!tbs_certificate.SkipOptionalTag(
|
|
der::kTagConstructed | der::kTagContextSpecific | 0, &unused)) {
|
|
return false;
|
|
}
|
|
|
|
// serialNumber
|
|
der::Input serial_value_der;
|
|
if (!tbs_certificate.ReadTag(der::kInteger, &serial_value_der))
|
|
return false;
|
|
|
|
*serial_value = serial_value_der.AsStringView();
|
|
return true;
|
|
}
|
|
|
|
// Creates a certificate (based on some base certificate file) using the
|
|
// specified signature algorithms.
|
|
static scoped_refptr<X509Certificate> CreateCertificate(
|
|
const CertParams& params) {
|
|
// Dosn't really matter which base certificate is used, so long as it is
|
|
// valid and uses a signature AlgorithmIdentifier with the same encoded
|
|
// length as sha1WithRSASignature.
|
|
const char* kLeafFilename = "ok_cert.pem";
|
|
|
|
auto cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), kLeafFilename, X509Certificate::FORMAT_AUTO);
|
|
if (!cert) {
|
|
ADD_FAILURE() << "Failed to load certificate: " << kLeafFilename;
|
|
return nullptr;
|
|
}
|
|
|
|
// Start with the DER bytes of a valid certificate. The der data is copied
|
|
// to a new std::string as it will modified to create a new certificate.
|
|
std::string cert_der(
|
|
x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()));
|
|
|
|
// Parse the certificate and identify the locations of interest within
|
|
// |cert_der|.
|
|
base::StringPiece cert_algorithm_sequence;
|
|
base::StringPiece tbs_algorithm_sequence;
|
|
if (!asn1::ExtractSignatureAlgorithmsFromDERCert(
|
|
cert_der, &cert_algorithm_sequence, &tbs_algorithm_sequence)) {
|
|
ADD_FAILURE() << "Failed parsing certificate algorithms";
|
|
return nullptr;
|
|
}
|
|
|
|
base::StringPiece serial_value;
|
|
if (!ExtractSerialNumberFromDERCert(cert_der, &serial_value)) {
|
|
ADD_FAILURE() << "Failed parsing certificate serial number";
|
|
return nullptr;
|
|
}
|
|
|
|
// Give each certificate a unique serial number based on its content (which
|
|
// in turn is a function of |params|, otherwise importing it may fail.
|
|
|
|
// Upper bound for last entry in DigestAlgorithm
|
|
const int kNumDigestAlgorithms = 15;
|
|
*const_cast<char*>(serial_value.data()) +=
|
|
static_cast<int>(params.tbs_algorithm) * kNumDigestAlgorithms +
|
|
static_cast<int>(params.cert_algorithm);
|
|
|
|
// Change the signature AlgorithmIdentifiers.
|
|
if (!SetAlgorithmSequence(params.cert_algorithm,
|
|
&cert_algorithm_sequence) ||
|
|
!SetAlgorithmSequence(params.tbs_algorithm, &tbs_algorithm_sequence)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// NOTE: The signature is NOT recomputed over TBSCertificate -- for these
|
|
// tests it isn't needed.
|
|
return X509Certificate::CreateFromBytes(
|
|
base::as_bytes(base::make_span(cert_der)));
|
|
}
|
|
|
|
static scoped_refptr<X509Certificate> CreateChain(
|
|
const std::vector<CertParams>& params) {
|
|
// Manufacture a chain with the given combinations of signature algorithms.
|
|
// This chain isn't actually a valid chain, but it is good enough for
|
|
// testing the base CertVerifyProc.
|
|
CertificateList certs;
|
|
for (const auto& cert_params : params) {
|
|
certs.push_back(CreateCertificate(cert_params));
|
|
if (!certs.back())
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
for (size_t i = 1; i < certs.size(); ++i)
|
|
intermediates.push_back(bssl::UpRef(certs[i]->cert_buffer()));
|
|
|
|
return X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(certs[0]->cert_buffer()), std::move(intermediates));
|
|
}
|
|
};
|
|
|
|
// This is a control test to make sure that the test helper
|
|
// VerifyLeaf() works as expected. There is no actual mismatch in the
|
|
// algorithms used here.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha1WithRSASignature
|
|
// TBSCertificate.algorithm: sha1WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha1) {
|
|
int rv = VerifyLeaf({DigestAlgorithm::Sha1, DigestAlgorithm::Sha1});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM));
|
|
}
|
|
|
|
// This is a control test to make sure that the test helper
|
|
// VerifyLeaf() works as expected. There is no actual mismatch in the
|
|
// algorithms used here.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha256WithRSASignature
|
|
// TBSCertificate.algorithm: sha256WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha256) {
|
|
int rv = VerifyLeaf({DigestAlgorithm::Sha256, DigestAlgorithm::Sha256});
|
|
ASSERT_THAT(rv, IsOk());
|
|
}
|
|
|
|
// Mismatched signature algorithms in the leaf certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha1WithRSASignature
|
|
// TBSCertificate.algorithm: sha256WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha256) {
|
|
int rv = VerifyLeaf({DigestAlgorithm::Sha1, DigestAlgorithm::Sha256});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Mismatched signature algorithms in the leaf certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha256WithRSAEncryption
|
|
// TBSCertificate.algorithm: sha1WithRSASignature
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha1) {
|
|
int rv = VerifyLeaf({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Unrecognized signature algorithm in the leaf certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha256WithRSAEncryption
|
|
// TBSCertificate.algorithm: ?
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Unknown) {
|
|
int rv = VerifyLeaf({DigestAlgorithm::Sha256, kUnknownDigestAlgorithm});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Unrecognized signature algorithm in the leaf certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: ?
|
|
// TBSCertificate.algorithm: sha256WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafUnknownSha256) {
|
|
int rv = VerifyLeaf({kUnknownDigestAlgorithm, DigestAlgorithm::Sha256});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Mismatched signature algorithms in the intermediate certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha1WithRSASignature
|
|
// TBSCertificate.algorithm: sha256WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha1Sha256) {
|
|
int rv = VerifyIntermediate({DigestAlgorithm::Sha1, DigestAlgorithm::Sha256});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Mismatched signature algorithms in the intermediate certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha256WithRSAEncryption
|
|
// TBSCertificate.algorithm: sha1WithRSASignature
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha256Sha1) {
|
|
int rv = VerifyIntermediate({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1});
|
|
ASSERT_THAT(rv, IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
// Mismatched signature algorithms in the root certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: sha256WithRSAEncryption
|
|
// TBSCertificate.algorithm: sha1WithRSASignature
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootSha256Sha1) {
|
|
int rv = VerifyRoot({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1});
|
|
ASSERT_THAT(rv, IsOk());
|
|
}
|
|
|
|
// Unrecognized signature algorithm in the root certificate.
|
|
//
|
|
// Certificate.signatureAlgorithm: ?
|
|
// TBSCertificate.algorithm: sha256WithRSAEncryption
|
|
TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootUnknownSha256) {
|
|
int rv = VerifyRoot({kUnknownDigestAlgorithm, DigestAlgorithm::Sha256});
|
|
ASSERT_THAT(rv, IsOk());
|
|
}
|
|
|
|
TEST(CertVerifyProcTest, TestHasTooLongValidity) {
|
|
struct {
|
|
const char* const file;
|
|
bool is_valid_too_long;
|
|
} tests[] = {
|
|
{"start_after_expiry.pem", true},
|
|
{"pre_br_validity_ok.pem", false},
|
|
{"pre_br_validity_bad_121.pem", true},
|
|
{"pre_br_validity_bad_2020.pem", true},
|
|
{"10_year_validity.pem", false},
|
|
{"11_year_validity.pem", true},
|
|
{"39_months_after_2015_04.pem", false},
|
|
{"40_months_after_2015_04.pem", true},
|
|
{"60_months_after_2012_07.pem", false},
|
|
{"61_months_after_2012_07.pem", true},
|
|
{"825_days_after_2018_03_01.pem", false},
|
|
{"826_days_after_2018_03_01.pem", true},
|
|
{"825_days_1_second_after_2018_03_01.pem", true},
|
|
{"39_months_based_on_last_day.pem", false},
|
|
{"398_days_after_2020_09_01.pem", false},
|
|
{"399_days_after_2020_09_01.pem", true},
|
|
{"398_days_1_second_after_2020_09_01.pem", true},
|
|
};
|
|
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
|
|
for (const auto& test : tests) {
|
|
SCOPED_TRACE(test.file);
|
|
|
|
scoped_refptr<X509Certificate> certificate =
|
|
ImportCertFromFile(certs_dir, test.file);
|
|
ASSERT_TRUE(certificate);
|
|
EXPECT_EQ(test.is_valid_too_long,
|
|
CertVerifyProc::HasTooLongValidity(*certificate));
|
|
}
|
|
}
|
|
|
|
// Integration test for CertVerifyProc::HasTooLongValidity.
|
|
// There isn't a way to add test entries to the known roots list for testing
|
|
// the full CertVerifyProc implementations, but HasTooLongValidity is checked
|
|
// by the outer CertVerifyProc::Verify. Thus the test can mock the
|
|
// VerifyInternal result to pretend there was a successful verification with
|
|
// is_issued_by_known_root and see that Verify overrides that with error.
|
|
TEST(CertVerifyProcTest, VerifyCertValidityTooLong) {
|
|
scoped_refptr<X509Certificate> cert(ImportCertFromFile(
|
|
GetTestCertsDirectory(), "900_days_after_2019_07_01.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
{
|
|
// Locally trusted cert should be ok.
|
|
CertVerifyResult dummy_result;
|
|
dummy_result.is_issued_by_known_root = false;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result);
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0u, verify_result.cert_status & CERT_STATUS_ALL_ERRORS);
|
|
}
|
|
|
|
{
|
|
// Publicly trusted cert that was otherwise okay should get changed to
|
|
// ERR_CERT_VALIDITY_TOO_LONG.
|
|
CertVerifyResult dummy_result;
|
|
dummy_result.is_issued_by_known_root = true;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result);
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_VALIDITY_TOO_LONG));
|
|
// TODO(mattm): generate a dedicated cert or use CertBuilder so that this
|
|
// test doesn't also hit CERT_STATUS_NON_UNIQUE_NAME.
|
|
EXPECT_EQ(CERT_STATUS_VALIDITY_TOO_LONG | CERT_STATUS_NON_UNIQUE_NAME,
|
|
verify_result.cert_status & CERT_STATUS_ALL_ERRORS);
|
|
}
|
|
|
|
{
|
|
// Publicly trusted cert that had some other error should retain the
|
|
// original error, but CERT_STATUS_VALIDITY_TOO_LONG should be added to
|
|
// cert_status.
|
|
CertVerifyResult dummy_result;
|
|
dummy_result.is_issued_by_known_root = true;
|
|
dummy_result.cert_status = CERT_STATUS_AUTHORITY_INVALID;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(
|
|
dummy_result, ERR_CERT_AUTHORITY_INVALID);
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
// TODO(mattm): generate a dedicated cert or use CertBuilder so that this
|
|
// test doesn't also hit CERT_STATUS_NON_UNIQUE_NAME.
|
|
EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID | CERT_STATUS_VALIDITY_TOO_LONG |
|
|
CERT_STATUS_NON_UNIQUE_NAME,
|
|
verify_result.cert_status & CERT_STATUS_ALL_ERRORS);
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, DISABLED_TestKnownRoot) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> cert_chain = CreateCertificateChainFromFile(
|
|
certs_dir, "leaf_from_known_root.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert_chain);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert_chain.get(), "horseweather.com", flags,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk()) << "This test relies on a real certificate that "
|
|
<< "expires on May 10 2024. If failing on/after "
|
|
<< "that date, please disable and file a bug "
|
|
<< "against mattm.";
|
|
EXPECT_TRUE(verify_result.is_issued_by_known_root);
|
|
#if BUILDFLAG(IS_MAC)
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_BUILTIN) {
|
|
auto* mac_trust_debug_info =
|
|
net::TrustStoreMac::ResultDebugData::Get(&verify_result);
|
|
ASSERT_TRUE(mac_trust_debug_info);
|
|
// Since this test queries the real trust store, can't know exactly
|
|
// what bits should be set in the trust debug info, but it should at
|
|
// least have something set.
|
|
EXPECT_NE(0, mac_trust_debug_info->combined_trust_debug_info());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// This tests that on successful certificate verification,
|
|
// CertVerifyResult::public_key_hashes is filled with a SHA256 hash for each
|
|
// of the certificates in the chain.
|
|
TEST_P(CertVerifyProcInternalTest, PublicKeyHashes) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
CertificateList certs = CreateCertificateListFromFile(
|
|
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(3U, certs.size());
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer()));
|
|
|
|
ScopedTestRoot scoped_root(certs[2].get());
|
|
scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(certs[0]->cert_buffer()), std::move(intermediates));
|
|
ASSERT_TRUE(cert_chain);
|
|
ASSERT_EQ(2U, cert_chain->intermediate_buffers().size());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert_chain.get(), "127.0.0.1", flags, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
|
|
EXPECT_EQ(3u, verify_result.public_key_hashes.size());
|
|
|
|
// Convert |public_key_hashes| to strings for ease of comparison.
|
|
std::vector<std::string> public_key_hash_strings;
|
|
for (const auto& public_key_hash : verify_result.public_key_hashes)
|
|
public_key_hash_strings.push_back(public_key_hash.ToString());
|
|
|
|
std::vector<std::string> expected_public_key_hashes = {
|
|
// Target
|
|
"sha256/Ru/08Ru275Zlf42sbI6lqi2OUun3r4YgrrK/vJ3+Yzk=",
|
|
|
|
// Intermediate
|
|
"sha256/D9u0epgvPYlG9YiVp7V+IMT+xhUpB5BhsS/INjDXc4Y=",
|
|
|
|
// Trust anchor
|
|
"sha256/VypP3VWL7OaqTJ7mIBehWYlv8khPuFHpWiearZI2YjI="};
|
|
|
|
// |public_key_hashes| does not have an ordering guarantee.
|
|
EXPECT_THAT(expected_public_key_hashes,
|
|
testing::UnorderedElementsAreArray(public_key_hash_strings));
|
|
}
|
|
|
|
// Tests that a Netscape Server Gated crypto is accepted in place of a
|
|
// serverAuth EKU.
|
|
// TODO(crbug.com/843735): Deprecate support for this.
|
|
TEST_P(CertVerifyProcInternalTest, Sha1IntermediateUsesServerGatedCrypto) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
root->GenerateRSAKey();
|
|
root->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha1);
|
|
|
|
intermediate->SetExtendedKeyUsages({der::Input(kNetscapeServerGatedCrypto)});
|
|
intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha1);
|
|
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
// The cert chain including the root is passed to Verify, as on recent
|
|
// Android versions (something like 11+) the verifier fails on SHA1 certs and
|
|
// then the CertVerifyProc wrapper just returns the input chain, which this
|
|
// test then depends on for its expectations. (This is all kind of silly, but
|
|
// this is just matching how the test was originally written, and we'll
|
|
// delete this sometime soon anyway so there's not much benefit to thinking
|
|
// about it too hard.)
|
|
int error =
|
|
Verify(leaf->GetX509CertificateFullChain().get(), "www.example.com",
|
|
flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_NE(error, OK);
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT);
|
|
}
|
|
|
|
// Basic test for returning the chain in CertVerifyResult. Note that the
|
|
// returned chain may just be a reflection of the originally supplied chain;
|
|
// that is, if any errors occur, the default chain returned is an exact copy
|
|
// of the certificate to be verified. The remaining VerifyReturn* tests are
|
|
// used to ensure that the actual, verified chain is being returned by
|
|
// Verify().
|
|
TEST_P(CertVerifyProcInternalTest, VerifyReturnChainBasic) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
CertificateList certs = CreateCertificateListFromFile(
|
|
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(3U, certs.size());
|
|
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer()));
|
|
|
|
ScopedTestRoot scoped_root(certs[2].get());
|
|
|
|
scoped_refptr<X509Certificate> google_full_chain =
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()),
|
|
std::move(intermediates));
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr), google_full_chain.get());
|
|
ASSERT_EQ(2U, google_full_chain->intermediate_buffers().size());
|
|
|
|
CertVerifyResult verify_result;
|
|
EXPECT_EQ(static_cast<X509Certificate*>(nullptr),
|
|
verify_result.verified_cert.get());
|
|
int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr),
|
|
verify_result.verified_cert.get());
|
|
|
|
EXPECT_NE(google_full_chain, verify_result.verified_cert);
|
|
EXPECT_TRUE(
|
|
x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(),
|
|
verify_result.verified_cert->cert_buffer()));
|
|
const auto& return_intermediates =
|
|
verify_result.verified_cert->intermediate_buffers();
|
|
ASSERT_EQ(2U, return_intermediates.size());
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(),
|
|
certs[1]->cert_buffer()));
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(),
|
|
certs[2]->cert_buffer()));
|
|
}
|
|
|
|
// Test that certificates issued for 'intranet' names (that is, containing no
|
|
// known public registry controlled domain information) issued by well-known
|
|
// CAs are flagged appropriately, while certificates that are issued by
|
|
// internal CAs are not flagged.
|
|
TEST(CertVerifyProcTest, IntranetHostsRejected) {
|
|
const std::string kIntranetHostname = "webmail";
|
|
|
|
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
|
|
leaf->SetSubjectAltName(kIntranetHostname);
|
|
|
|
scoped_refptr<X509Certificate> cert(leaf->GetX509Certificate());
|
|
|
|
CertVerifyResult verify_result;
|
|
int error = 0;
|
|
|
|
// Intranet names for public CAs should be flagged:
|
|
CertVerifyResult dummy_result;
|
|
dummy_result.is_issued_by_known_root = true;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result);
|
|
error = verify_proc->Verify(cert.get(), kIntranetHostname,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(),
|
|
&verify_result, NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME);
|
|
|
|
// However, if the CA is not well known, these should not be flagged:
|
|
dummy_result.Reset();
|
|
dummy_result.is_issued_by_known_root = false;
|
|
verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result);
|
|
error = verify_proc->Verify(cert.get(), kIntranetHostname,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(),
|
|
&verify_result, NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME);
|
|
}
|
|
|
|
// Tests that certificates issued by Symantec's legacy infrastructure
|
|
// are rejected according to the policies outlined in
|
|
// https://security.googleblog.com/2017/09/chromes-plan-to-distrust-symantec.html
|
|
// unless the caller has explicitly disabled that enforcement.
|
|
TEST(CertVerifyProcTest, SymantecCertsRejected) {
|
|
constexpr SHA256HashValue kSymantecHashValue = {
|
|
{0xb2, 0xde, 0xf5, 0x36, 0x2a, 0xd3, 0xfa, 0xcd, 0x04, 0xbd, 0x29,
|
|
0x04, 0x7a, 0x43, 0x84, 0x4f, 0x76, 0x70, 0x34, 0xea, 0x48, 0x92,
|
|
0xf8, 0x0e, 0x56, 0xbe, 0xe6, 0x90, 0x24, 0x3e, 0x25, 0x02}};
|
|
constexpr SHA256HashValue kGoogleHashValue = {
|
|
{0xec, 0x72, 0x29, 0x69, 0xcb, 0x64, 0x20, 0x0a, 0xb6, 0x63, 0x8f,
|
|
0x68, 0xac, 0x53, 0x8e, 0x40, 0xab, 0xab, 0x5b, 0x19, 0xa6, 0x48,
|
|
0x56, 0x61, 0x04, 0x2a, 0x10, 0x61, 0xc4, 0x61, 0x27, 0x76}};
|
|
|
|
// Test that certificates from the legacy Symantec infrastructure are
|
|
// rejected:
|
|
// - dec_2017.pem : A certificate issued after 2017-12-01, which is rejected
|
|
// as of M65
|
|
// - pre_june_2016.pem : A certificate issued prior to 2016-06-01, which is
|
|
// rejected as of M66.
|
|
for (const char* rejected_cert : {"dec_2017.pem", "pre_june_2016.pem"}) {
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), rejected_cert, X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
|
|
scoped_refptr<CertVerifyProc> verify_proc;
|
|
int error = 0;
|
|
|
|
// Test that a legacy Symantec certificate is rejected.
|
|
CertVerifyResult symantec_result;
|
|
symantec_result.verified_cert = cert;
|
|
symantec_result.public_key_hashes.push_back(HashValue(kSymantecHashValue));
|
|
symantec_result.is_issued_by_known_root = true;
|
|
verify_proc = base::MakeRefCounted<MockCertVerifyProc>(symantec_result);
|
|
|
|
CertVerifyResult test_result_1;
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &test_result_1,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_SYMANTEC_LEGACY));
|
|
EXPECT_TRUE(test_result_1.cert_status & CERT_STATUS_SYMANTEC_LEGACY);
|
|
|
|
// ... Unless the Symantec cert chains through a allowlisted intermediate.
|
|
CertVerifyResult allowlisted_result;
|
|
allowlisted_result.verified_cert = cert;
|
|
allowlisted_result.public_key_hashes.push_back(
|
|
HashValue(kSymantecHashValue));
|
|
allowlisted_result.public_key_hashes.push_back(HashValue(kGoogleHashValue));
|
|
allowlisted_result.is_issued_by_known_root = true;
|
|
verify_proc = base::MakeRefCounted<MockCertVerifyProc>(allowlisted_result);
|
|
|
|
CertVerifyResult test_result_2;
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &test_result_2,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(test_result_2.cert_status & CERT_STATUS_AUTHORITY_INVALID);
|
|
|
|
// ... Or the caller disabled enforcement of Symantec policies.
|
|
CertVerifyResult test_result_3;
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(),
|
|
CertVerifyProc::VERIFY_DISABLE_SYMANTEC_ENFORCEMENT, CertificateList(),
|
|
&test_result_3, NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(test_result_3.cert_status & CERT_STATUS_SYMANTEC_LEGACY);
|
|
}
|
|
|
|
// Test that certificates from the legacy Symantec infrastructure issued
|
|
// after 2016-06-01 appropriately rejected.
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "post_june_2016.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
|
|
scoped_refptr<CertVerifyProc> verify_proc;
|
|
int error = 0;
|
|
|
|
// Test that a legacy Symantec certificate is rejected if the feature
|
|
// flag is enabled, and accepted if it is not.
|
|
CertVerifyResult symantec_result;
|
|
symantec_result.verified_cert = cert;
|
|
symantec_result.public_key_hashes.push_back(HashValue(kSymantecHashValue));
|
|
symantec_result.is_issued_by_known_root = true;
|
|
verify_proc = base::MakeRefCounted<MockCertVerifyProc>(symantec_result);
|
|
|
|
CertVerifyResult test_result_1;
|
|
error = verify_proc->Verify(cert.get(), "127.0.0.1",
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(),
|
|
&test_result_1, NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_SYMANTEC_LEGACY));
|
|
EXPECT_TRUE(test_result_1.cert_status & CERT_STATUS_SYMANTEC_LEGACY);
|
|
|
|
// ... Unless the Symantec cert chains through a allowlisted intermediate.
|
|
CertVerifyResult allowlisted_result;
|
|
allowlisted_result.verified_cert = cert;
|
|
allowlisted_result.public_key_hashes.push_back(HashValue(kSymantecHashValue));
|
|
allowlisted_result.public_key_hashes.push_back(HashValue(kGoogleHashValue));
|
|
allowlisted_result.is_issued_by_known_root = true;
|
|
verify_proc = base::MakeRefCounted<MockCertVerifyProc>(allowlisted_result);
|
|
|
|
CertVerifyResult test_result_2;
|
|
error = verify_proc->Verify(cert.get(), "127.0.0.1",
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(),
|
|
&test_result_2, NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(test_result_2.cert_status & CERT_STATUS_AUTHORITY_INVALID);
|
|
|
|
// ... Or the caller disabled enforcement of Symantec policies.
|
|
CertVerifyResult test_result_3;
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(),
|
|
CertVerifyProc::VERIFY_DISABLE_SYMANTEC_ENFORCEMENT, CertificateList(),
|
|
&test_result_3, NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(test_result_3.cert_status & CERT_STATUS_SYMANTEC_LEGACY);
|
|
}
|
|
|
|
// Test that the certificate returned in CertVerifyResult is able to reorder
|
|
// certificates that are not ordered from end-entity to root. While this is
|
|
// a protocol violation if sent during a TLS handshake, if multiple sources
|
|
// of intermediate certificates are combined, it's possible that order may
|
|
// not be maintained.
|
|
TEST_P(CertVerifyProcInternalTest, VerifyReturnChainProperlyOrdered) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
CertificateList certs = CreateCertificateListFromFile(
|
|
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(3U, certs.size());
|
|
|
|
// Construct the chain out of order.
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer()));
|
|
|
|
ScopedTestRoot scoped_root(certs[2].get());
|
|
|
|
scoped_refptr<X509Certificate> google_full_chain =
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()),
|
|
std::move(intermediates));
|
|
ASSERT_TRUE(google_full_chain);
|
|
ASSERT_EQ(2U, google_full_chain->intermediate_buffers().size());
|
|
|
|
CertVerifyResult verify_result;
|
|
EXPECT_FALSE(verify_result.verified_cert);
|
|
int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
|
|
EXPECT_NE(google_full_chain, verify_result.verified_cert);
|
|
EXPECT_TRUE(
|
|
x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(),
|
|
verify_result.verified_cert->cert_buffer()));
|
|
const auto& return_intermediates =
|
|
verify_result.verified_cert->intermediate_buffers();
|
|
ASSERT_EQ(2U, return_intermediates.size());
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(),
|
|
certs[1]->cert_buffer()));
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(),
|
|
certs[2]->cert_buffer()));
|
|
}
|
|
|
|
// Test that Verify() filters out certificates which are not related to
|
|
// or part of the certificate chain being verified.
|
|
TEST_P(CertVerifyProcInternalTest, VerifyReturnChainFiltersUnrelatedCerts) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
CertificateList certs = CreateCertificateListFromFile(
|
|
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(3U, certs.size());
|
|
ScopedTestRoot scoped_root(certs[2].get());
|
|
|
|
scoped_refptr<X509Certificate> unrelated_certificate =
|
|
ImportCertFromFile(certs_dir, "duplicate_cn_1.pem");
|
|
scoped_refptr<X509Certificate> unrelated_certificate2 =
|
|
ImportCertFromFile(certs_dir, "google.single.pem");
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr),
|
|
unrelated_certificate.get());
|
|
ASSERT_NE(static_cast<X509Certificate*>(nullptr),
|
|
unrelated_certificate2.get());
|
|
|
|
// Interject unrelated certificates into the list of intermediates.
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(unrelated_certificate->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(unrelated_certificate2->cert_buffer()));
|
|
intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer()));
|
|
|
|
scoped_refptr<X509Certificate> google_full_chain =
|
|
X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()),
|
|
std::move(intermediates));
|
|
ASSERT_TRUE(google_full_chain);
|
|
ASSERT_EQ(4U, google_full_chain->intermediate_buffers().size());
|
|
|
|
CertVerifyResult verify_result;
|
|
EXPECT_FALSE(verify_result.verified_cert);
|
|
int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
|
|
EXPECT_NE(google_full_chain, verify_result.verified_cert);
|
|
EXPECT_TRUE(
|
|
x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(),
|
|
verify_result.verified_cert->cert_buffer()));
|
|
const auto& return_intermediates =
|
|
verify_result.verified_cert->intermediate_buffers();
|
|
ASSERT_EQ(2U, return_intermediates.size());
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(),
|
|
certs[1]->cert_buffer()));
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(),
|
|
certs[2]->cert_buffer()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, AdditionalTrustAnchors) {
|
|
if (!SupportsAdditionalTrustAnchors()) {
|
|
LOG(INFO) << "Skipping this test in this platform.";
|
|
return;
|
|
}
|
|
|
|
// |ca_cert| is the issuer of |cert|.
|
|
CertificateList ca_cert_list =
|
|
CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, ca_cert_list.size());
|
|
scoped_refptr<X509Certificate> ca_cert(ca_cert_list[0]);
|
|
|
|
CertificateList cert_list = CreateCertificateListFromFile(
|
|
GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, cert_list.size());
|
|
scoped_refptr<X509Certificate> cert(cert_list[0]);
|
|
|
|
// Verification of |cert| fails when |ca_cert| is not in the trust anchors
|
|
// list.
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status);
|
|
EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor);
|
|
|
|
// Now add the |ca_cert| to the |trust_anchors|, and verification should pass.
|
|
CertificateList trust_anchors;
|
|
trust_anchors.push_back(ca_cert);
|
|
error = Verify(cert.get(), "127.0.0.1", flags, trust_anchors, &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
EXPECT_TRUE(verify_result.is_issued_by_additional_trust_anchor);
|
|
|
|
// Clearing the |trust_anchors| makes verification fail again (the cache
|
|
// should be skipped).
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status);
|
|
EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor);
|
|
}
|
|
|
|
// Tests that certificates issued by user-supplied roots are not flagged as
|
|
// issued by a known root. This should pass whether or not the platform supports
|
|
// detecting known roots.
|
|
TEST_P(CertVerifyProcInternalTest, IsIssuedByKnownRootIgnoresTestRoots) {
|
|
// Load root_ca_cert.pem into the test root store.
|
|
ScopedTestRoot test_root(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem").get());
|
|
|
|
scoped_refptr<X509Certificate> cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
|
|
// Verification should pass.
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
// But should not be marked as a known root.
|
|
EXPECT_FALSE(verify_result.is_issued_by_known_root);
|
|
}
|
|
|
|
// Test verification with a leaf that does not contain embedded SCTs, and which
|
|
// has a notBefore date after 2018/10/15, and passing a valid |sct_list| to
|
|
// Verify(). Verification should succeed on all platforms. (Assuming the
|
|
// verifier trusts the SCT Logs used in |sct_list|.)
|
|
//
|
|
// Fails on multiple plaforms, see crbug.com/1050152.
|
|
TEST_P(CertVerifyProcInternalTest,
|
|
DISABLED_LeafNewerThan20181015WithTlsSctList) {
|
|
scoped_refptr<X509Certificate> chain = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "treadclimber.pem",
|
|
X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
|
|
ASSERT_TRUE(chain);
|
|
if (base::Time::Now() > chain->valid_expiry()) {
|
|
FAIL() << "This test uses a certificate chain which is now expired. Please "
|
|
"disable and file a bug against mattm.";
|
|
}
|
|
|
|
std::string sct_list;
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("treadclimber.sctlist"), &sct_list));
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc()->Verify(chain.get(), "treadclimber.com",
|
|
/*ocsp_response=*/std::string(), sct_list,
|
|
flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
|
|
// Since the valid |sct_list| was passed to Verify, verification should
|
|
// succeed on all verifiers and OS versions.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
EXPECT_TRUE(verify_result.is_issued_by_known_root);
|
|
}
|
|
|
|
// Test that CRLSets are effective in making a certificate appear to be
|
|
// revoked.
|
|
TEST_P(CertVerifyProcInternalTest, CRLSet) {
|
|
if (!SupportsCRLSet()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support CRLSet";
|
|
return;
|
|
}
|
|
|
|
CertificateList ca_cert_list =
|
|
CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, ca_cert_list.size());
|
|
ScopedTestRoot test_root(ca_cert_list[0].get());
|
|
|
|
CertificateList cert_list = CreateCertificateListFromFile(
|
|
GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, cert_list.size());
|
|
scoped_refptr<X509Certificate> cert(cert_list[0]);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
|
|
scoped_refptr<CRLSet> crl_set;
|
|
std::string crl_set_bytes;
|
|
|
|
// First test blocking by SPKI.
|
|
EXPECT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_leaf_spki.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
|
|
// Second, test revocation by serial number of a cert directly under the
|
|
// root.
|
|
crl_set_bytes.clear();
|
|
EXPECT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_root_serial.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, CRLSetLeafSerial) {
|
|
if (!SupportsCRLSet()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support CRLSet";
|
|
return;
|
|
}
|
|
|
|
CertificateList ca_cert_list =
|
|
CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, ca_cert_list.size());
|
|
ScopedTestRoot test_root(ca_cert_list[0].get());
|
|
|
|
scoped_refptr<X509Certificate> leaf = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(leaf);
|
|
ASSERT_EQ(1U, leaf->intermediate_buffers().size());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
|
|
// Test revocation by serial number of a certificate not under the root.
|
|
scoped_refptr<CRLSet> crl_set;
|
|
std::string crl_set_bytes;
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_intermediate_serial.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, CRLSetRootReturnsChain) {
|
|
if (!SupportsCRLSet()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support CRLSet";
|
|
return;
|
|
}
|
|
|
|
CertificateList ca_cert_list =
|
|
CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_EQ(1U, ca_cert_list.size());
|
|
ScopedTestRoot test_root(ca_cert_list[0].get());
|
|
|
|
scoped_refptr<X509Certificate> leaf = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(leaf);
|
|
ASSERT_EQ(1U, leaf->intermediate_buffers().size());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
|
|
// Test revocation of the root itself.
|
|
scoped_refptr<CRLSet> crl_set;
|
|
std::string crl_set_bytes;
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_root_spki.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
|
|
EXPECT_EQ(3u, verify_result.public_key_hashes.size());
|
|
ASSERT_TRUE(verify_result.verified_cert);
|
|
EXPECT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
|
|
}
|
|
|
|
// Tests that CertVerifyProc implementations apply CRLSet revocations by
|
|
// subject.
|
|
TEST_P(CertVerifyProcInternalTest, CRLSetRevokedBySubject) {
|
|
if (!SupportsCRLSet()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support CRLSet";
|
|
return;
|
|
}
|
|
|
|
scoped_refptr<X509Certificate> root(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"));
|
|
ASSERT_TRUE(root);
|
|
|
|
scoped_refptr<X509Certificate> leaf(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(leaf);
|
|
|
|
ScopedTestRoot scoped_root(root.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
|
|
// Confirm that verifying the certificate chain with an empty CRLSet succeeds.
|
|
SetUpCertVerifyProc(CRLSet::EmptyCRLSetForTesting());
|
|
int error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
|
|
std::string crl_set_bytes;
|
|
scoped_refptr<CRLSet> crl_set;
|
|
|
|
// Revoke the leaf by subject. Verification should now fail.
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_leaf_subject_no_spki.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
|
|
// Revoke the root by subject. Verification should now fail.
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_root_subject_no_spki.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
|
|
// Revoke the leaf by subject, but only if the SPKI doesn't match the given
|
|
// one. Verification should pass when using the certificate's actual SPKI.
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII("crlset_by_root_subject.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
|
|
// Ensures that CRLSets can be used to block known interception roots on
|
|
// platforms that support CRLSets, while otherwise detect known interception
|
|
// on platforms that do not.
|
|
TEST_P(CertVerifyProcInternalTest, BlockedInterceptionByRoot) {
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root);
|
|
ScopedTestRoot test_root(root.get());
|
|
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
|
|
// A default/built-in CRLSet should not block
|
|
scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet();
|
|
SetUpCertVerifyProc(crl_set);
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
|
|
// Read in a CRLSet that marks the root as blocked for interception.
|
|
std::string crl_set_bytes;
|
|
ASSERT_TRUE(
|
|
base::ReadFileToString(GetTestCertsDirectory().AppendASCII(
|
|
"crlset_blocked_interception_by_root.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
if (SupportsCRLSet()) {
|
|
EXPECT_THAT(error, IsError(ERR_CERT_KNOWN_INTERCEPTION_BLOCKED));
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED);
|
|
} else {
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_KNOWN_INTERCEPTION_DETECTED);
|
|
}
|
|
}
|
|
|
|
// Ensures that CRLSets can be used to block known interception intermediates,
|
|
// while still allowing other certificates from that root..
|
|
TEST_P(CertVerifyProcInternalTest, BlockedInterceptionByIntermediate) {
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root);
|
|
ScopedTestRoot test_root(root.get());
|
|
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
|
|
// A default/built-in CRLSEt should not block
|
|
scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet();
|
|
SetUpCertVerifyProc(crl_set);
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
|
|
// Read in a CRLSet that marks the intermediate as blocked for interception.
|
|
std::string crl_set_bytes;
|
|
ASSERT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII(
|
|
"crlset_blocked_interception_by_intermediate.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
if (SupportsCRLSet()) {
|
|
EXPECT_THAT(error, IsError(ERR_CERT_KNOWN_INTERCEPTION_BLOCKED));
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED);
|
|
} else {
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_KNOWN_INTERCEPTION_DETECTED);
|
|
}
|
|
|
|
// Load a different certificate from that root, which should be unaffected.
|
|
scoped_refptr<X509Certificate> second_cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(second_cert);
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error = Verify(second_cert.get(), "127.0.0.1", flags, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
}
|
|
|
|
// Ensures that CRLSets can be used to flag known interception roots, even
|
|
// when they are not blocked.
|
|
TEST_P(CertVerifyProcInternalTest, DetectsInterceptionByRoot) {
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root);
|
|
ScopedTestRoot test_root(root.get());
|
|
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
|
|
// A default/built-in CRLSet should not block
|
|
scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet();
|
|
SetUpCertVerifyProc(crl_set);
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_EQ(0U, verify_result.cert_status);
|
|
|
|
// Read in a CRLSet that marks the root as blocked for interception.
|
|
std::string crl_set_bytes;
|
|
ASSERT_TRUE(
|
|
base::ReadFileToString(GetTestCertsDirectory().AppendASCII(
|
|
"crlset_known_interception_by_root.raw"),
|
|
&crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
error =
|
|
Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_KNOWN_INTERCEPTION_DETECTED);
|
|
}
|
|
|
|
// Tests that CRLSets participate in path building functions, and that as
|
|
// long as a valid path exists within the verification graph, verification
|
|
// succeeds.
|
|
//
|
|
// In this test, there are two roots (D and E), and three possible paths
|
|
// to validate a leaf (A):
|
|
// 1. A(B) -> B(C) -> C(D) -> D(D)
|
|
// 2. A(B) -> B(C) -> C(E) -> E(E)
|
|
// 3. A(B) -> B(F) -> F(E) -> E(E)
|
|
//
|
|
// Each permutation of revocation is tried:
|
|
// 1. Revoking E by SPKI, so that only Path 1 is valid (as E is in Paths 2 & 3)
|
|
// 2. Revoking C(D) and F(E) by serial, so that only Path 2 is valid.
|
|
// 3. Revoking C by SPKI, so that only Path 3 is valid (as C is in Paths 1 & 2)
|
|
TEST_P(CertVerifyProcInternalTest, CRLSetDuringPathBuilding) {
|
|
if (!SupportsCRLSetsInPathBuilding()) {
|
|
LOG(INFO) << "Skipping this test on this platform.";
|
|
return;
|
|
}
|
|
|
|
CertificateList path_1_certs;
|
|
ASSERT_TRUE(
|
|
LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-C.pem",
|
|
"multi-root-C-by-D.pem", "multi-root-D-by-D.pem"},
|
|
&path_1_certs));
|
|
|
|
CertificateList path_2_certs;
|
|
ASSERT_TRUE(
|
|
LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-C.pem",
|
|
"multi-root-C-by-E.pem", "multi-root-E-by-E.pem"},
|
|
&path_2_certs));
|
|
|
|
CertificateList path_3_certs;
|
|
ASSERT_TRUE(
|
|
LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-F.pem",
|
|
"multi-root-F-by-E.pem", "multi-root-E-by-E.pem"},
|
|
&path_3_certs));
|
|
|
|
// Add D and E as trust anchors.
|
|
ScopedTestRoot test_root_D(path_1_certs[3].get()); // D-by-D
|
|
ScopedTestRoot test_root_E(path_2_certs[3].get()); // E-by-E
|
|
|
|
// Create a chain that contains all the certificate paths possible.
|
|
// CertVerifyProcInternalTest.VerifyReturnChainFiltersUnrelatedCerts already
|
|
// ensures that it's safe to send additional certificates as inputs, and
|
|
// that they're ignored if not necessary.
|
|
// This is to avoid relying on AIA or internal object caches when
|
|
// interacting with the underlying library.
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(
|
|
bssl::UpRef(path_1_certs[1]->cert_buffer())); // B-by-C
|
|
intermediates.push_back(
|
|
bssl::UpRef(path_1_certs[2]->cert_buffer())); // C-by-D
|
|
intermediates.push_back(
|
|
bssl::UpRef(path_2_certs[2]->cert_buffer())); // C-by-E
|
|
intermediates.push_back(
|
|
bssl::UpRef(path_3_certs[1]->cert_buffer())); // B-by-F
|
|
intermediates.push_back(
|
|
bssl::UpRef(path_3_certs[2]->cert_buffer())); // F-by-E
|
|
scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(path_1_certs[0]->cert_buffer()), std::move(intermediates));
|
|
ASSERT_TRUE(cert);
|
|
|
|
struct TestPermutations {
|
|
const char* crlset;
|
|
bool expect_valid;
|
|
scoped_refptr<X509Certificate> expected_intermediate;
|
|
} kTests[] = {
|
|
{"multi-root-crlset-D-and-E.raw", false, nullptr},
|
|
{"multi-root-crlset-E.raw", true, path_1_certs[2].get()},
|
|
{"multi-root-crlset-CD-and-FE.raw", true, path_2_certs[2].get()},
|
|
{"multi-root-crlset-C.raw", true, path_3_certs[2].get()},
|
|
{"multi-root-crlset-unrelated.raw", true, nullptr}};
|
|
|
|
for (const auto& testcase : kTests) {
|
|
SCOPED_TRACE(testcase.crlset);
|
|
scoped_refptr<CRLSet> crl_set;
|
|
std::string crl_set_bytes;
|
|
EXPECT_TRUE(base::ReadFileToString(
|
|
GetTestCertsDirectory().AppendASCII(testcase.crlset), &crl_set_bytes));
|
|
ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
|
|
|
|
SetUpCertVerifyProc(crl_set);
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert.get(), "127.0.0.1", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
if (!testcase.expect_valid) {
|
|
EXPECT_NE(OK, error);
|
|
EXPECT_NE(0U, verify_result.cert_status);
|
|
continue;
|
|
}
|
|
|
|
ASSERT_THAT(error, IsOk());
|
|
ASSERT_EQ(0U, verify_result.cert_status);
|
|
ASSERT_TRUE(verify_result.verified_cert.get());
|
|
|
|
if (!testcase.expected_intermediate)
|
|
continue;
|
|
|
|
const auto& verified_intermediates =
|
|
verify_result.verified_cert->intermediate_buffers();
|
|
ASSERT_EQ(3U, verified_intermediates.size());
|
|
|
|
scoped_refptr<X509Certificate> intermediate =
|
|
X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(verified_intermediates[1].get()), {});
|
|
ASSERT_TRUE(intermediate);
|
|
|
|
EXPECT_TRUE(testcase.expected_intermediate->EqualsExcludingChain(
|
|
intermediate.get()))
|
|
<< "Expected: " << testcase.expected_intermediate->subject().common_name
|
|
<< " issued by " << testcase.expected_intermediate->issuer().common_name
|
|
<< "; Got: " << intermediate->subject().common_name << " issued by "
|
|
<< intermediate->issuer().common_name;
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityDayPlus5MinutesBeforeNotBefore) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() + base::Days(1) + base::Minutes(5);
|
|
base::Time not_after = base::Time::Now() + base::Days(30);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is before certificate's notBefore. Verification should fail.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityDayBeforeNotBefore) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() + base::Days(1);
|
|
base::Time not_after = base::Time::Now() + base::Days(30);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is before certificate's notBefore. Verification should fail.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityJustBeforeNotBefore) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() + base::Minutes(5);
|
|
base::Time not_after = base::Time::Now() + base::Days(30);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is before certificate's notBefore. Verification should fail.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityJustAfterNotBefore) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() - base::Seconds(1);
|
|
base::Time not_after = base::Time::Now() + base::Days(30);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is between notBefore and notAfter. Verification should
|
|
// succeed.
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityJustBeforeNotAfter) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() - base::Days(30);
|
|
base::Time not_after = base::Time::Now() + base::Minutes(5);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is between notBefore and notAfter. Verification should
|
|
// succeed.
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, ValidityJustAfterNotAfter) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
base::Time not_before = base::Time::Now() - base::Days(30);
|
|
base::Time not_after = base::Time::Now() - base::Seconds(1);
|
|
leaf->SetValidity(not_before, not_after);
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
// Current time is after certificate's notAfter. Verification should fail.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, FailedIntermediateSignatureValidation) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Intermediate has no authorityKeyIdentifier. Also remove
|
|
// subjectKeyIdentifier from root for good measure.
|
|
intermediate->EraseExtension(der::Input(kAuthorityKeyIdentifierOid));
|
|
root->EraseExtension(der::Input(kSubjectKeyIdentifierOid));
|
|
|
|
// Get the chain with the leaf and the intermediate signed by the original
|
|
// key of |root|.
|
|
scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain();
|
|
|
|
// Generate a new key for root.
|
|
root->GenerateECKey();
|
|
|
|
// Trust the new root certificate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
// The intermediate was signed by a different root with a different key but
|
|
// with the same name as the trusted one, and the intermediate has no
|
|
// authorityKeyIdentifier, so the verifier must try verifying the signature.
|
|
// Should fail with AUTHORITY_INVALID.
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalTest, FailedTargetSignatureValidation) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Leaf has no authorityKeyIdentifier. Also remove subjectKeyIdentifier from
|
|
// intermediate for good measure.
|
|
leaf->EraseExtension(der::Input(kAuthorityKeyIdentifierOid));
|
|
intermediate->EraseExtension(der::Input(kSubjectKeyIdentifierOid));
|
|
|
|
// Get a copy of the leaf signed by the original key of intermediate.
|
|
bssl::UniquePtr<CRYPTO_BUFFER> leaf_wrong_signature = leaf->DupCertBuffer();
|
|
|
|
// Generate a new key for intermediate.
|
|
intermediate->GenerateECKey();
|
|
|
|
// Make a chain that includes the original leaf with the wrong signature and
|
|
// the new intermediate.
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(intermediate->DupCertBuffer());
|
|
|
|
scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(leaf_wrong_signature), std::move(intermediates));
|
|
ASSERT_TRUE(cert.get());
|
|
|
|
// Trust the root certificate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(cert.get(), "www.example.com", flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
// The leaf was signed by a different intermediate with a different key but
|
|
// with the same name as the one in the chain, and the leaf has no
|
|
// authorityKeyIdentifier, so the verifier must try verifying the signature.
|
|
// Should fail with AUTHORITY_INVALID.
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
|
|
class CertVerifyProcNameNormalizationTest : public CertVerifyProcInternalTest {
|
|
protected:
|
|
std::string HistogramName() const {
|
|
std::string prefix("Net.CertVerifier.NameNormalizationPrivateRoots.");
|
|
switch (verify_proc_type()) {
|
|
case CERT_VERIFY_PROC_ANDROID:
|
|
return prefix + "Android";
|
|
case CERT_VERIFY_PROC_IOS:
|
|
return prefix + "IOS";
|
|
case CERT_VERIFY_PROC_BUILTIN:
|
|
case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS:
|
|
return prefix + "Builtin";
|
|
}
|
|
}
|
|
|
|
void ExpectNormalizationHistogram(int verify_error) {
|
|
if (verify_error == OK) {
|
|
histograms_.ExpectUniqueSample(
|
|
HistogramName(), CertVerifyProc::NameNormalizationResult::kNormalized,
|
|
1);
|
|
} else {
|
|
histograms_.ExpectTotalCount(HistogramName(), 0);
|
|
}
|
|
}
|
|
|
|
void ExpectByteEqualHistogram() {
|
|
histograms_.ExpectUniqueSample(
|
|
HistogramName(), CertVerifyProc::NameNormalizationResult::kByteEqual,
|
|
1);
|
|
}
|
|
|
|
private:
|
|
base::HistogramTester histograms_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcNameNormalizationTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
// Tries to verify a chain where the leaf's issuer CN is PrintableString, while
|
|
// the intermediate's subject CN is UTF8String, and verifies the proper
|
|
// histogram is logged.
|
|
TEST_P(CertVerifyProcNameNormalizationTest, StringType) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
std::string issuer_cn = CertBuilder::MakeRandomHexString(12);
|
|
leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
issuer_cn, CBS_ASN1_PRINTABLESTRING));
|
|
intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
issuer_cn, CBS_ASN1_UTF8STRING));
|
|
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com",
|
|
flags, CertificateList(), &verify_result);
|
|
|
|
switch (verify_proc_type()) {
|
|
case CERT_VERIFY_PROC_IOS:
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
break;
|
|
case CERT_VERIFY_PROC_ANDROID:
|
|
case CERT_VERIFY_PROC_BUILTIN:
|
|
case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS:
|
|
EXPECT_THAT(error, IsOk());
|
|
break;
|
|
}
|
|
|
|
ExpectNormalizationHistogram(error);
|
|
}
|
|
|
|
// Tries to verify a chain where the leaf's issuer CN and intermediate's
|
|
// subject CN are both PrintableString but have differing case on the first
|
|
// character, and verifies the proper histogram is logged.
|
|
TEST_P(CertVerifyProcNameNormalizationTest, CaseFolding) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
std::string issuer_hex = CertBuilder::MakeRandomHexString(12);
|
|
leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
"Z" + issuer_hex, CBS_ASN1_PRINTABLESTRING));
|
|
intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
"z" + issuer_hex, CBS_ASN1_PRINTABLESTRING));
|
|
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com",
|
|
flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
ExpectNormalizationHistogram(error);
|
|
}
|
|
|
|
// Confirms that a chain generated by the same pattern as the other
|
|
// NameNormalizationTest cases which does not require normalization validates
|
|
// ok, and that the ByteEqual histogram is logged.
|
|
TEST_P(CertVerifyProcNameNormalizationTest, ByteEqual) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
std::string issuer_hex = CertBuilder::MakeRandomHexString(12);
|
|
leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
issuer_hex, CBS_ASN1_PRINTABLESTRING));
|
|
intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType(
|
|
issuer_hex, CBS_ASN1_PRINTABLESTRING));
|
|
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com",
|
|
flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
ExpectByteEqualHistogram();
|
|
}
|
|
|
|
std::string Md5WithRSAEncryption() {
|
|
const uint8_t kMd5WithRSAEncryption[] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
|
|
0x86, 0x48, 0x86, 0xf7, 0x0d,
|
|
0x01, 0x01, 0x04, 0x05, 0x00};
|
|
return std::string(std::begin(kMd5WithRSAEncryption),
|
|
std::end(kMd5WithRSAEncryption));
|
|
}
|
|
|
|
// This is the same as CertVerifyProcInternalTest, but it additionally sets up
|
|
// networking capabilities for the cert verifiers, and a test server that can be
|
|
// used to serve mock responses for AIA/OCSP/CRL.
|
|
//
|
|
// An actual HTTP test server is used rather than simply mocking the network
|
|
// layer, since the certificate fetching networking layer is not mockable for
|
|
// all of the cert verifier implementations.
|
|
//
|
|
// The approach taken in this test fixture is to generate certificates
|
|
// on the fly so they use randomly chosen URLs, subjects, and serial
|
|
// numbers, in order to defeat global caching effects from the platform
|
|
// verifiers. Moreover, the AIA needs to be chosen dynamically since the
|
|
// test server's port number cannot be known statically.
|
|
class CertVerifyProcInternalWithNetFetchingTest
|
|
: public CertVerifyProcInternalTest {
|
|
protected:
|
|
CertVerifyProcInternalWithNetFetchingTest()
|
|
: task_environment_(
|
|
base::test::TaskEnvironment::MainThreadType::DEFAULT) {}
|
|
|
|
void SetUp() override {
|
|
// Create a network thread to be used for network fetches, and wait for
|
|
// initialization to complete on that thread.
|
|
base::Thread::Options options(base::MessagePumpType::IO, 0);
|
|
network_thread_ = std::make_unique<base::Thread>("network_thread");
|
|
CHECK(network_thread_->StartWithOptions(std::move(options)));
|
|
|
|
base::WaitableEvent initialization_complete_event(
|
|
base::WaitableEvent::ResetPolicy::MANUAL,
|
|
base::WaitableEvent::InitialState::NOT_SIGNALED);
|
|
network_thread_->task_runner()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&SetUpOnNetworkThread, &context_, &cert_net_fetcher_,
|
|
&initialization_complete_event));
|
|
initialization_complete_event.Wait();
|
|
EXPECT_TRUE(cert_net_fetcher_);
|
|
|
|
CertVerifyProcInternalTest::SetUp();
|
|
|
|
EXPECT_FALSE(test_server_.Started());
|
|
|
|
// Register a single request handler with the EmbeddedTestServer, that in
|
|
// turn dispatches to the internally managed registry of request handlers.
|
|
//
|
|
// This allows registering subsequent handlers dynamically during the course
|
|
// of the test, since EmbeddedTestServer requires its handlers be registered
|
|
// prior to Start().
|
|
test_server_.RegisterRequestHandler(base::BindRepeating(
|
|
&CertVerifyProcInternalWithNetFetchingTest::DispatchToRequestHandler,
|
|
base::Unretained(this)));
|
|
EXPECT_TRUE(test_server_.Start());
|
|
}
|
|
|
|
void SetUpCertVerifyProc(scoped_refptr<CRLSet> crl_set) override {
|
|
EXPECT_TRUE(cert_net_fetcher_);
|
|
SetUpWithCertNetFetcher(cert_net_fetcher_, std::move(crl_set));
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Do cleanup on network thread.
|
|
network_thread_->task_runner()->PostTask(
|
|
FROM_HERE, base::BindOnce(&ShutdownOnNetworkThread, &context_,
|
|
&cert_net_fetcher_));
|
|
network_thread_->Stop();
|
|
network_thread_.reset();
|
|
|
|
CertVerifyProcInternalTest::TearDown();
|
|
}
|
|
|
|
// Registers a handler with the test server that responds with the given
|
|
// Content-Type, HTTP status code, and response body, for GET requests
|
|
// to |path|.
|
|
// Returns the full URL to |path| for the current test server.
|
|
GURL RegisterSimpleTestServerHandler(std::string path,
|
|
HttpStatusCode status_code,
|
|
std::string content_type,
|
|
std::string content) {
|
|
GURL handler_url(GetTestServerAbsoluteUrl(path));
|
|
base::AutoLock lock(request_handlers_lock_);
|
|
request_handlers_.push_back(base::BindRepeating(
|
|
&SimpleTestServerHandler, std::move(path), status_code,
|
|
std::move(content_type), std::move(content)));
|
|
return handler_url;
|
|
}
|
|
|
|
// Returns a random URL path (starting with /) that has the given suffix.
|
|
static std::string MakeRandomPath(base::StringPiece suffix) {
|
|
return "/" + MakeRandomHexString(12) + std::string(suffix);
|
|
}
|
|
|
|
// Returns a URL to |path| for the current test server.
|
|
GURL GetTestServerAbsoluteUrl(const std::string& path) {
|
|
return test_server_.GetURL(path);
|
|
}
|
|
|
|
// Creates a certificate chain for www.example.com, where the leaf certificate
|
|
// has an AIA URL pointing to the test server.
|
|
void CreateSimpleChainWithAIA(
|
|
scoped_refptr<X509Certificate>* out_leaf,
|
|
std::string* ca_issuers_path,
|
|
bssl::UniquePtr<CRYPTO_BUFFER>* out_intermediate,
|
|
scoped_refptr<X509Certificate>* out_root) {
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Make the leaf certificate have an AIA (CA Issuers) that points to the
|
|
// embedded test server. This uses a random URL for predictable behavior in
|
|
// the presence of global caching.
|
|
*ca_issuers_path = MakeRandomPath(".cer");
|
|
GURL ca_issuers_url = GetTestServerAbsoluteUrl(*ca_issuers_path);
|
|
leaf->SetCaIssuersUrl(ca_issuers_url);
|
|
|
|
// The chain being verified is solely the leaf certificate (missing the
|
|
// intermediate and root).
|
|
*out_leaf = leaf->GetX509Certificate();
|
|
*out_root = root->GetX509Certificate();
|
|
*out_intermediate = intermediate->DupCertBuffer();
|
|
}
|
|
|
|
// Creates a CRL issued and signed by |crl_issuer|, marking |revoked_serials|
|
|
// as revoked, and registers it to be served by the test server.
|
|
// Returns the full URL to retrieve the CRL from the test server.
|
|
GURL CreateAndServeCrl(
|
|
CertBuilder* crl_issuer,
|
|
const std::vector<uint64_t>& revoked_serials,
|
|
absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt) {
|
|
std::string crl = BuildCrl(crl_issuer->GetSubject(), crl_issuer->GetKey(),
|
|
revoked_serials, signature_algorithm);
|
|
std::string crl_path = MakeRandomPath(".crl");
|
|
return RegisterSimpleTestServerHandler(crl_path, HTTP_OK,
|
|
"application/pkix-crl", crl);
|
|
}
|
|
|
|
GURL CreateAndServeCrlWithAlgorithmTlvAndDigest(
|
|
CertBuilder* crl_issuer,
|
|
const std::vector<uint64_t>& revoked_serials,
|
|
const std::string& signature_algorithm_tlv,
|
|
const EVP_MD* digest) {
|
|
std::string crl = BuildCrlWithAlgorithmTlvAndDigest(
|
|
crl_issuer->GetSubject(), crl_issuer->GetKey(), revoked_serials,
|
|
signature_algorithm_tlv, digest);
|
|
std::string crl_path = MakeRandomPath(".crl");
|
|
return RegisterSimpleTestServerHandler(crl_path, HTTP_OK,
|
|
"application/pkix-crl", crl);
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<test_server::HttpResponse> DispatchToRequestHandler(
|
|
const test_server::HttpRequest& request) {
|
|
// Called on the embedded test server's IO thread.
|
|
base::AutoLock lock(request_handlers_lock_);
|
|
for (const auto& handler : request_handlers_) {
|
|
auto response = handler.Run(request);
|
|
if (response)
|
|
return response;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Serves (|status_code|, |content_type|, |content|) in response to GET
|
|
// requests for |path|.
|
|
static std::unique_ptr<test_server::HttpResponse> SimpleTestServerHandler(
|
|
const std::string& path,
|
|
HttpStatusCode status_code,
|
|
const std::string& content_type,
|
|
const std::string& content,
|
|
const test_server::HttpRequest& request) {
|
|
if (request.relative_url != path)
|
|
return nullptr;
|
|
|
|
auto http_response = std::make_unique<test_server::BasicHttpResponse>();
|
|
|
|
http_response->set_code(status_code);
|
|
http_response->set_content_type(content_type);
|
|
http_response->set_content(content);
|
|
return http_response;
|
|
}
|
|
|
|
static void SetUpOnNetworkThread(
|
|
std::unique_ptr<URLRequestContext>* context,
|
|
scoped_refptr<CertNetFetcherURLRequest>* cert_net_fetcher,
|
|
base::WaitableEvent* initialization_complete_event) {
|
|
URLRequestContextBuilder url_request_context_builder;
|
|
url_request_context_builder.set_user_agent("cert_verify_proc_unittest/0.1");
|
|
url_request_context_builder.set_proxy_config_service(
|
|
std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation()));
|
|
*context = url_request_context_builder.Build();
|
|
|
|
*cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>();
|
|
(*cert_net_fetcher)->SetURLRequestContext(context->get());
|
|
initialization_complete_event->Signal();
|
|
}
|
|
|
|
static void ShutdownOnNetworkThread(
|
|
std::unique_ptr<URLRequestContext>* context,
|
|
scoped_refptr<net::CertNetFetcherURLRequest>* cert_net_fetcher) {
|
|
(*cert_net_fetcher)->Shutdown();
|
|
cert_net_fetcher->reset();
|
|
context->reset();
|
|
}
|
|
|
|
base::test::TaskEnvironment task_environment_;
|
|
|
|
std::unique_ptr<base::Thread> network_thread_;
|
|
|
|
// Owned by this thread, but initialized, used, and shutdown on the network
|
|
// thread.
|
|
std::unique_ptr<URLRequestContext> context_;
|
|
scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher_;
|
|
|
|
EmbeddedTestServer test_server_;
|
|
|
|
// The list of registered handlers. Can only be accessed when the lock is
|
|
// held, as this data is shared between the embedded server's IO thread, and
|
|
// the test main thread.
|
|
base::Lock request_handlers_lock_;
|
|
std::vector<test_server::EmbeddedTestServer::HandleRequestCallback>
|
|
request_handlers_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcInternalWithNetFetchingTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
// Tries verifying a certificate chain that is missing an intermediate. The
|
|
// intermediate is available via AIA, however the server responds with a 404.
|
|
//
|
|
// NOTE: This test is separate from IntermediateFromAia200 as a different URL
|
|
// needs to be used to avoid having the result depend on globally cached success
|
|
// or failure of the fetch.
|
|
// Test is flaky on iOS crbug.com/860189
|
|
#if BUILDFLAG(IS_IOS)
|
|
#define MAYBE_IntermediateFromAia404 DISABLED_IntermediateFromAia404
|
|
#else
|
|
#define MAYBE_IntermediateFromAia404 IntermediateFromAia404
|
|
#endif
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
MAYBE_IntermediateFromAia404) {
|
|
const char kHostname[] = "www.example.com";
|
|
|
|
// Create a chain where the leaf has an AIA that points to test server.
|
|
scoped_refptr<X509Certificate> leaf;
|
|
std::string ca_issuers_path;
|
|
bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
|
|
scoped_refptr<X509Certificate> root;
|
|
CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
|
|
|
|
// Serve a 404 for the AIA url.
|
|
RegisterSimpleTestServerHandler(ca_issuers_path, HTTP_NOT_FOUND, "text/plain",
|
|
"Not Found");
|
|
|
|
// Trust the root certificate.
|
|
ScopedTestRoot scoped_root(root.get());
|
|
|
|
// The chain being verified is solely the leaf certificate (missing the
|
|
// intermediate and root).
|
|
ASSERT_EQ(0u, leaf->intermediate_buffers().size());
|
|
|
|
const int flags = 0;
|
|
int error;
|
|
CertVerifyResult verify_result;
|
|
|
|
// Verifying the chain should fail as the intermediate is missing, and
|
|
// cannot be fetched via AIA.
|
|
error =
|
|
Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
EXPECT_NE(OK, error);
|
|
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
#undef MAYBE_IntermediateFromAia404
|
|
|
|
// Tries verifying a certificate chain that is missing an intermediate. The
|
|
// intermediate is available via AIA.
|
|
// TODO(crbug.com/860189): Failing on iOS
|
|
#if BUILDFLAG(IS_IOS)
|
|
#define MAYBE_IntermediateFromAia200Der DISABLED_IntermediateFromAia200Der
|
|
#else
|
|
#define MAYBE_IntermediateFromAia200Der IntermediateFromAia200Der
|
|
#endif
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
MAYBE_IntermediateFromAia200Der) {
|
|
const char kHostname[] = "www.example.com";
|
|
|
|
// Create a chain where the leaf has an AIA that points to test server.
|
|
scoped_refptr<X509Certificate> leaf;
|
|
std::string ca_issuers_path;
|
|
bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
|
|
scoped_refptr<X509Certificate> root;
|
|
CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
|
|
|
|
// Setup the test server to reply with the correct intermediate.
|
|
RegisterSimpleTestServerHandler(
|
|
ca_issuers_path, HTTP_OK, "application/pkix-cert",
|
|
std::string(x509_util::CryptoBufferAsStringPiece(intermediate.get())));
|
|
|
|
// Trust the root certificate.
|
|
ScopedTestRoot scoped_root(root.get());
|
|
|
|
// The chain being verified is solely the leaf certificate (missing the
|
|
// intermediate and root).
|
|
ASSERT_EQ(0u, leaf->intermediate_buffers().size());
|
|
|
|
// VERIFY_DISABLE_NETWORK_FETCHES flag is not implemented in
|
|
// CertVerifyProcIOS, only test it on other verifiers.
|
|
if (verify_proc_type() != CERT_VERIFY_PROC_IOS) {
|
|
CertVerifyResult verify_result;
|
|
// If VERIFY_DISABLE_NETWORK_FETCHES is specified, AIA should not be
|
|
// attempted and verifying the chain should fail since the intermediate
|
|
// can't be found.
|
|
int error = Verify(leaf.get(), kHostname,
|
|
CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES,
|
|
CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_EQ(0u, verify_result.verified_cert->intermediate_buffers().size());
|
|
}
|
|
|
|
{
|
|
CertVerifyResult verify_result;
|
|
// Verifying the chain should succeed as the missing intermediate can be
|
|
// fetched via AIA.
|
|
int error = Verify(leaf.get(), kHostname, /*flags=*/0, CertificateList(),
|
|
&verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
}
|
|
|
|
// This test is the same as IntermediateFromAia200Der, except the certificate is
|
|
// served as PEM rather than DER.
|
|
//
|
|
// Tries verifying a certificate chain that is missing an intermediate. The
|
|
// intermediate is available via AIA, however is served as a PEM file rather
|
|
// than DER.
|
|
// TODO(crbug.com/860189): Failing on iOS
|
|
#if BUILDFLAG(IS_IOS)
|
|
#define MAYBE_IntermediateFromAia200Pem DISABLED_IntermediateFromAia200Pem
|
|
#else
|
|
#define MAYBE_IntermediateFromAia200Pem IntermediateFromAia200Pem
|
|
#endif
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
MAYBE_IntermediateFromAia200Pem) {
|
|
const char kHostname[] = "www.example.com";
|
|
|
|
// Create a chain where the leaf has an AIA that points to test server.
|
|
scoped_refptr<X509Certificate> leaf;
|
|
std::string ca_issuers_path;
|
|
bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
|
|
scoped_refptr<X509Certificate> root;
|
|
CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
|
|
|
|
std::string intermediate_pem;
|
|
ASSERT_TRUE(
|
|
X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem));
|
|
|
|
// Setup the test server to reply with the correct intermediate.
|
|
RegisterSimpleTestServerHandler(
|
|
ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem);
|
|
|
|
// Trust the root certificate.
|
|
ScopedTestRoot scoped_root(root.get());
|
|
|
|
// The chain being verified is solely the leaf certificate (missing the
|
|
// intermediate and root).
|
|
ASSERT_EQ(0u, leaf->intermediate_buffers().size());
|
|
|
|
const int flags = 0;
|
|
int error;
|
|
CertVerifyResult verify_result;
|
|
|
|
// Verifying the chain should succeed as the missing intermediate can be
|
|
// fetched via AIA.
|
|
error =
|
|
Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
// Android doesn't support PEM - https://crbug.com/725180
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
}
|
|
|
|
// This test is the same as IntermediateFromAia200Pem, but with a different
|
|
// formatting on the PEM data.
|
|
//
|
|
// TODO(crbug.com/860189): Failing on iOS
|
|
#if BUILDFLAG(IS_IOS)
|
|
#define MAYBE_IntermediateFromAia200Pem2 DISABLED_IntermediateFromAia200Pem2
|
|
#else
|
|
#define MAYBE_IntermediateFromAia200Pem2 IntermediateFromAia200Pem2
|
|
#endif
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
MAYBE_IntermediateFromAia200Pem2) {
|
|
const char kHostname[] = "www.example.com";
|
|
|
|
// Create a chain where the leaf has an AIA that points to test server.
|
|
scoped_refptr<X509Certificate> leaf;
|
|
std::string ca_issuers_path;
|
|
bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
|
|
scoped_refptr<X509Certificate> root;
|
|
CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
|
|
|
|
std::string intermediate_pem;
|
|
ASSERT_TRUE(
|
|
X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem));
|
|
intermediate_pem = "Text at start of file\n" + intermediate_pem;
|
|
|
|
// Setup the test server to reply with the correct intermediate.
|
|
RegisterSimpleTestServerHandler(
|
|
ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem);
|
|
|
|
// Trust the root certificate.
|
|
ScopedTestRoot scoped_root(root.get());
|
|
|
|
// The chain being verified is solely the leaf certificate (missing the
|
|
// intermediate and root).
|
|
ASSERT_EQ(0u, leaf->intermediate_buffers().size());
|
|
|
|
const int flags = 0;
|
|
int error;
|
|
CertVerifyResult verify_result;
|
|
|
|
// Verifying the chain should succeed as the missing intermediate can be
|
|
// fetched via AIA.
|
|
error =
|
|
Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
// Android doesn't support PEM - https://crbug.com/725180
|
|
EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(error, IsOk());
|
|
}
|
|
}
|
|
|
|
// Tries verifying a certificate chain that uses a SHA1 intermediate,
|
|
// however, chasing the AIA can discover a SHA256 version of the intermediate.
|
|
//
|
|
// Path building should discover the stronger intermediate and use it.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
Sha1IntermediateButAIAHasSha256) {
|
|
const char kHostname[] = "www.example.com";
|
|
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Make the leaf certificate have an AIA (CA Issuers) that points to the
|
|
// embedded test server. This uses a random URL for predictable behavior in
|
|
// the presence of global caching.
|
|
std::string ca_issuers_path = MakeRandomPath(".cer");
|
|
GURL ca_issuers_url = GetTestServerAbsoluteUrl(ca_issuers_path);
|
|
leaf->SetCaIssuersUrl(ca_issuers_url);
|
|
leaf->SetSubjectAltName(kHostname);
|
|
|
|
// Make two versions of the intermediate - one that is SHA256 signed, and one
|
|
// that is SHA1 signed. Note that the subjectKeyIdentifier for `intermediate`
|
|
// is intentionally not changed, so that path building will consider both
|
|
// certificate paths.
|
|
intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
|
|
intermediate->SetRandomSerialNumber();
|
|
auto intermediate_sha256 = intermediate->DupCertBuffer();
|
|
|
|
intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1);
|
|
intermediate->SetRandomSerialNumber();
|
|
auto intermediate_sha1 = intermediate->DupCertBuffer();
|
|
|
|
// Trust the root certificate.
|
|
auto root_cert = root->GetX509Certificate();
|
|
ScopedTestRoot scoped_root(root_cert.get());
|
|
|
|
// Setup the test server to reply with the SHA256 intermediate.
|
|
RegisterSimpleTestServerHandler(
|
|
ca_issuers_path, HTTP_OK, "application/pkix-cert",
|
|
std::string(
|
|
x509_util::CryptoBufferAsStringPiece(intermediate_sha256.get())));
|
|
|
|
// Build a chain to verify that includes the SHA1 intermediate.
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
intermediates.push_back(bssl::UpRef(intermediate_sha1.get()));
|
|
scoped_refptr<X509Certificate> chain_sha1 = X509Certificate::CreateFromBuffer(
|
|
leaf->DupCertBuffer(), std::move(intermediates));
|
|
ASSERT_TRUE(chain_sha1.get());
|
|
|
|
const int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = Verify(chain_sha1.get(), kHostname, flags, CertificateList(),
|
|
&verify_result);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Should have built a chain through the SHA256 intermediate. This was only
|
|
// available via AIA, and not the (SHA1) one provided directly to path
|
|
// building.
|
|
ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
|
|
EXPECT_TRUE(x509_util::CryptoBufferEqual(
|
|
verify_result.verified_cert->intermediate_buffers()[0].get(),
|
|
intermediate_sha256.get()));
|
|
ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
|
|
|
|
EXPECT_FALSE(verify_result.has_sha1);
|
|
EXPECT_THAT(error, IsOk());
|
|
} else {
|
|
EXPECT_NE(OK, error);
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID &&
|
|
error == ERR_CERT_AUTHORITY_INVALID) {
|
|
// Newer Android versions reject the chain due to the SHA1 intermediate,
|
|
// but do not build the correct chain by AIA. Since only the partial
|
|
// chain is returned, CertVerifyProc does not mark it as SHA1 as it does
|
|
// not examine the last cert in the chain. Therefore, if
|
|
// ERR_CERT_AUTHORITY_INVALID is returned, don't check the rest of the
|
|
// statuses. See https://crbug.com/1191795.
|
|
return;
|
|
}
|
|
EXPECT_TRUE(verify_result.cert_status &
|
|
CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT);
|
|
EXPECT_TRUE(verify_result.has_sha1);
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest, RevocationHardFailNoCrls) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
// Create certs which have no AIA or CRL distribution points.
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsError(ERR_CERT_NO_REVOCATION_MECHANISM));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailNoCrlsDisableNetworkFetches) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
// Create certs which have no AIA or CRL distribution points.
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with flags for both hard-fail revocation checking for local anchors
|
|
// and disabling network fetches.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS |
|
|
CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should succeed, VERIFY_DISABLE_NETWORK_FETCHES takes priority.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL hard fail test where both leaf and intermediate are covered by valid
|
|
// CRLs which have empty (non-present) revokedCertificates list. Verification
|
|
// should succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailCrlGoodNoRevokedCertificates) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Serve an intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should pass, leaf and intermediate were covered by CRLs and were not
|
|
// revoked.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL hard fail test where both leaf and intermediate are covered by valid
|
|
// CRLs which have revokedCertificates lists that revoke other irrelevant
|
|
// serial numbers. Verification should succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailCrlGoodIrrelevantSerialsRevoked) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL revokes leaf's serial number. This is irrelevant.
|
|
intermediate->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(root.get(), {leaf->GetSerialNumber()}));
|
|
|
|
// Intermediate-issued CRL revokes intermediates's serial number. This is
|
|
// irrelevant.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {intermediate->GetSerialNumber()}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should pass, leaf and intermediate were covered by CRLs and were not
|
|
// revoked.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailLeafRevokedByCrl) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Leaf is revoked by intermediate issued CRL.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail, leaf is revoked.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailIntermediateRevokedByCrl) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Intermediate is revoked by root issued CRL.
|
|
intermediate->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(root.get(), {intermediate->GetSerialNumber()}));
|
|
|
|
// Intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail, intermediate is revoked.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL hard fail test where the intermediate certificate has a good CRL, but
|
|
// the leaf's distribution point returns an http error. Verification should
|
|
// fail.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailLeafCrlDpHttpError) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Serve a 404 for the intermediate-issued CRL distribution point url.
|
|
leaf->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler(
|
|
MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found"));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail since no revocation information was available for the leaf.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_UNABLE_TO_CHECK_REVOCATION));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL hard fail test where the leaf certificate has a good CRL, but
|
|
// the intermediate's distribution point returns an http error. Verification
|
|
// should fail.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationHardFailIntermediateCrlDpHttpError) {
|
|
if (!SupportsRevCheckingRequiredLocalAnchors()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a 404 for the root-issued CRL distribution point url.
|
|
intermediate->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler(
|
|
MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found"));
|
|
|
|
// Serve an intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with hard-fail revocation checking for local anchors.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail since no revocation information was available for the
|
|
// intermediate.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_UNABLE_TO_CHECK_REVOCATION));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest, RevocationSoftFailNoCrls) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
// Create certs which have no AIA or CRL distribution points.
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_NO_REVOCATION_MECHANISM);
|
|
EXPECT_FALSE(verify_result.cert_status &
|
|
CERT_STATUS_UNABLE_TO_CHECK_REVOCATION);
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL soft fail test where both leaf and intermediate are covered by valid
|
|
// CRLs which have empty (non-present) revokedCertificates list. Verification
|
|
// should succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailCrlGoodNoRevokedCertificates) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Serve an intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL soft fail test where both leaf and intermediate are covered by valid
|
|
// CRLs which have revokedCertificates lists that revoke other irrelevant
|
|
// serial numbers. Verification should succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailCrlGoodIrrelevantSerialsRevoked) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL revokes leaf's serial number. This is irrelevant.
|
|
intermediate->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(root.get(), {leaf->GetSerialNumber()}));
|
|
|
|
// Intermediate-issued CRL revokes intermediates's serial number. This is
|
|
// irrelevant.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {intermediate->GetSerialNumber()}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailLeafRevokedByCrl) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Leaf is revoked by intermediate issued CRL.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail, leaf is revoked.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailLeafRevokedByCrlDisableNetworkFetches) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Leaf is revoked by intermediate issued CRL.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with flags for both soft-fail revocation checking and disabling
|
|
// network fetches.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED |
|
|
CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should succeed, VERIFY_DISABLE_NETWORK_FETCHES takes priority.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailIntermediateRevokedByCrl) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Intermediate is revoked by root issued CRL.
|
|
intermediate->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(root.get(), {intermediate->GetSerialNumber()}));
|
|
|
|
// Intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail, intermediate is revoked.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailLeafRevokedBySha1Crl) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Leaf is revoked by intermediate issued CRL which is signed with
|
|
// ecdsaWithSha256.
|
|
leaf->SetCrlDistributionPointUrl(
|
|
CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()},
|
|
SignatureAlgorithm::kEcdsaSha1));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should fail, leaf is revoked.
|
|
EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailLeafRevokedByMd5Crl) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
// This test wants to check handling of MD5 CRLs, but ecdsa-with-md5
|
|
// signatureAlgorithm does not exist. Use an RSA private key for intermediate
|
|
// so that the CRL will be signed with the md5WithRSAEncryption algorithm.
|
|
intermediate->GenerateRSAKey();
|
|
leaf->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha256);
|
|
|
|
// Leaf is revoked by intermediate issued CRL which is signed with
|
|
// md5WithRSAEncryption.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrlWithAlgorithmTlvAndDigest(
|
|
intermediate.get(), {leaf->GetSerialNumber()}, Md5WithRSAEncryption(),
|
|
EVP_md5()));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Verification should succeed: MD5 signature algorithm is not supported
|
|
// and soft-fail checking will ignore the inability to get revocation
|
|
// status.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL soft fail test where the intermediate certificate has a good CRL, but
|
|
// the leaf's distribution point returns an http error. Verification should
|
|
// succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailLeafCrlDpHttpError) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a root-issued CRL which does not revoke intermediate.
|
|
intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
|
|
|
|
// Serve a 404 for the intermediate-issued CRL distribution point url.
|
|
leaf->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler(
|
|
MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found"));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should succeed due to soft-fail revocation checking.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// CRL soft fail test where the leaf certificate has a good CRL, but
|
|
// the intermediate's distribution point returns an http error. Verification
|
|
// should succeed.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
RevocationSoftFailIntermediateCrlDpHttpError) {
|
|
if (!SupportsSoftFailRevChecking()) {
|
|
LOG(INFO) << "Skipping test as verifier doesn't support "
|
|
"VERIFY_REV_CHECKING_ENABLED";
|
|
return;
|
|
}
|
|
|
|
const char kHostname[] = "www.example.com";
|
|
auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
|
|
|
|
// Serve a 404 for the root-issued CRL distribution point url.
|
|
intermediate->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler(
|
|
MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found"));
|
|
|
|
// Serve an intermediate-issued CRL which does not revoke leaf.
|
|
leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {}));
|
|
|
|
// Trust the root and build a chain to verify that includes the intermediate.
|
|
ScopedTestRoot scoped_root(root->GetX509Certificate().get());
|
|
scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Verify with soft-fail revocation checking.
|
|
const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result);
|
|
|
|
// Should succeed due to soft-fail revocation checking.
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// Tests that an EV cert verification with successful online OCSP revocation
|
|
// checks is marked as CERT_STATUS_IS_EV.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
EVOnlineOCSPRevocationCheckingGood) {
|
|
if (!SupportsEV()) {
|
|
LOG(INFO) << "Skipping test as EV verification is not yet supported";
|
|
return;
|
|
}
|
|
|
|
const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
EmbeddedTestServer::ServerCertificateConfig cert_config;
|
|
cert_config.policy_oids = {kEVTestCertPolicy};
|
|
cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig(
|
|
{{OCSPRevocationStatus::GOOD,
|
|
EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
|
|
|
|
EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS);
|
|
ocsp_test_server.SetSSLConfig(cert_config);
|
|
EXPECT_TRUE(ocsp_test_server.Start());
|
|
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root.get());
|
|
|
|
scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Consider the root of the test chain a valid EV root for the test policy.
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(root->cert_buffer()),
|
|
kEVTestCertPolicy);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(),
|
|
flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// Tests that an EV cert verification with that could not retrieve online OCSP
|
|
// revocation information is verified but still marked as CERT_STATUS_IS_EV.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
EVOnlineOCSPRevocationCheckingSoftFail) {
|
|
if (!SupportsEV()) {
|
|
LOG(INFO) << "Skipping test as EV verification is not yet supported";
|
|
return;
|
|
}
|
|
|
|
const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
EmbeddedTestServer::ServerCertificateConfig cert_config;
|
|
cert_config.policy_oids = {kEVTestCertPolicy};
|
|
// Retrieving OCSP status returns an error.
|
|
cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig(
|
|
EmbeddedTestServer::OCSPConfig::ResponseType::kInternalError);
|
|
|
|
EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS);
|
|
ocsp_test_server.SetSSLConfig(cert_config);
|
|
EXPECT_TRUE(ocsp_test_server.Start());
|
|
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root.get());
|
|
|
|
scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Consider the root of the test chain a valid EV root for the test policy.
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(root->cert_buffer()),
|
|
kEVTestCertPolicy);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(),
|
|
flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// Tests that an EV cert verification with online OCSP returning affirmatively
|
|
// revoked is marked as CERT_STATUS_IS_EV.
|
|
TEST_P(CertVerifyProcInternalWithNetFetchingTest,
|
|
EVOnlineOCSPRevocationCheckingRevoked) {
|
|
if (!SupportsEV()) {
|
|
LOG(INFO) << "Skipping test as EV verification is not yet supported";
|
|
return;
|
|
}
|
|
|
|
const char kEVTestCertPolicy[] = "1.2.3.4";
|
|
EmbeddedTestServer::ServerCertificateConfig cert_config;
|
|
cert_config.policy_oids = {kEVTestCertPolicy};
|
|
cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig(
|
|
{{OCSPRevocationStatus::REVOKED,
|
|
EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}});
|
|
|
|
EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS);
|
|
ocsp_test_server.SetSSLConfig(cert_config);
|
|
EXPECT_TRUE(ocsp_test_server.Start());
|
|
|
|
scoped_refptr<X509Certificate> root =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
|
|
ASSERT_TRUE(root.get());
|
|
|
|
scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate();
|
|
ASSERT_TRUE(chain.get());
|
|
|
|
// Consider the root of the test chain a valid EV root for the test policy.
|
|
ScopedTestEVPolicy scoped_test_ev_policy(
|
|
EVRootCAMetadata::GetInstance(),
|
|
X509Certificate::CalculateFingerprint256(root->cert_buffer()),
|
|
kEVTestCertPolicy);
|
|
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(),
|
|
flags, CertificateList(), &verify_result);
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
|
|
}
|
|
|
|
// A set of tests that check how various constraints are enforced when they
|
|
// appear at different points in the chain, such as on the trust anchor versus
|
|
// on intermediates.
|
|
class CertVerifyProcConstraintsTest : public CertVerifyProcInternalTest {
|
|
protected:
|
|
void SetUp() override {
|
|
CertVerifyProcInternalTest::SetUp();
|
|
|
|
chain_ = CertBuilder::CreateSimpleChain(/*chain_length=*/4);
|
|
}
|
|
|
|
int VerifyWithTrust(CertificateTrust trust) {
|
|
ScopedTestRoot test_root(chain_.back()->GetX509Certificate().get(), trust);
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
return CertVerifyProcInternalTest::Verify(
|
|
chain_.front()->GetX509CertificateChain().get(), "www.example.com",
|
|
flags, CertificateList(), &verify_result);
|
|
}
|
|
|
|
int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); }
|
|
|
|
int VerifyWithExpiryAndConstraints() {
|
|
return VerifyWithTrust(CertificateTrust::ForTrustAnchor()
|
|
.WithEnforceAnchorExpiry()
|
|
.WithEnforceAnchorConstraints());
|
|
}
|
|
|
|
int VerifyWithExpiryAndFullConstraints() {
|
|
return VerifyWithTrust(CertificateTrust::ForTrustAnchor()
|
|
.WithEnforceAnchorExpiry()
|
|
.WithEnforceAnchorConstraints()
|
|
.WithRequireAnchorBasicConstraints());
|
|
}
|
|
|
|
int ExpectedIntermediateConstraintError() {
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID)
|
|
return ERR_CERT_AUTHORITY_INVALID;
|
|
return ERR_CERT_INVALID;
|
|
}
|
|
|
|
std::vector<std::unique_ptr<CertBuilder>> chain_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcConstraintsTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BaseCase) {
|
|
// Without changing anything on the test chain, it should validate
|
|
// successfully. If this is not true then the rest of the tests in this class
|
|
// are unlikely to be useful.
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()),
|
|
IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotCaRoot) {
|
|
chain_[3]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/-1);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotCaIntermediate) {
|
|
chain_[2]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/-1);
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsIsCaLeaf) {
|
|
for (bool has_key_usage_cert_sign : {false, true}) {
|
|
chain_[0]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1);
|
|
|
|
if (has_key_usage_cert_sign) {
|
|
chain_[0]->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
} else {
|
|
chain_[0]->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
}
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen0Root) {
|
|
chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (VerifyProcTypeIsIOSAtMostOS14() ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen1Root) {
|
|
chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/1);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (VerifyProcTypeIsIOSAtMostOS14() ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen2Root) {
|
|
chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/2);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest,
|
|
BasicConstraintsPathlen0IntermediateParent) {
|
|
chain_[2]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0);
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest,
|
|
BasicConstraintsPathlen1IntermediateParent) {
|
|
chain_[2]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/1);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest,
|
|
BasicConstraintsPathlen0IntermediateChild) {
|
|
chain_[1]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentRoot) {
|
|
chain_[3]->EraseExtension(der::Input(kBasicConstraintsOid));
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentRootX509V1) {
|
|
chain_[3]->SetCertificateVersion(CertificateVersion::V1);
|
|
chain_[3]->ClearExtensions();
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentIntermediate) {
|
|
chain_[2]->EraseExtension(der::Input(kBasicConstraintsOid));
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentLeaf) {
|
|
chain_[0]->EraseExtension(der::Input(kBasicConstraintsOid));
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingRoot) {
|
|
chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingIntermediate) {
|
|
chain_[2]->SetNameConstraintsDnsNames(
|
|
/*permitted_dns_names=*/{"example.org"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, NameConstraintsMatchingRoot) {
|
|
chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.com"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, NameConstraintsMatchingIntermediate) {
|
|
chain_[2]->SetNameConstraintsDnsNames(
|
|
/*permitted_dns_names=*/{"example.com"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, NameConstraintsOnLeaf) {
|
|
chain_[0]->SetNameConstraintsDnsNames(
|
|
/*permitted_dns_names=*/{"example.com"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
// TODO(mattm): this should be an error
|
|
// RFC 5280 4.2.1.10 says: "The name constraints extension, which MUST be
|
|
// used only in a CA certificate, ..."
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ValidityExpiredRoot) {
|
|
chain_[3]->SetValidity(base::Time::Now() - base::Days(14),
|
|
base::Time::Now() - base::Days(7));
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_DATE_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ValidityNotYetValidRoot) {
|
|
chain_[3]->SetValidity(base::Time::Now() + base::Days(7),
|
|
base::Time::Now() + base::Days(14));
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_DATE_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_DATE_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ValidityExpiredIntermediate) {
|
|
chain_[2]->SetValidity(base::Time::Now() - base::Days(14),
|
|
base::Time::Now() - base::Days(7));
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ValidityNotYetValidIntermediate) {
|
|
chain_[2]->SetValidity(base::Time::Now() + base::Days(7),
|
|
base::Time::Now() + base::Days(14));
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Root) {
|
|
for (bool leaf_has_policy : {false, true}) {
|
|
SCOPED_TRACE(leaf_has_policy);
|
|
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.4.5";
|
|
static const char kPolicy3[] = "1.2.3.5";
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
chain_[3]->SetCertificatePolicies({kPolicy1, kPolicy2});
|
|
chain_[2]->SetCertificatePolicies({kPolicy3, kPolicy1});
|
|
chain_[1]->SetCertificatePolicies({kPolicy1});
|
|
|
|
if (leaf_has_policy) {
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
} else {
|
|
chain_[0]->SetCertificatePolicies({});
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints4Root) {
|
|
// Explicit policy is required after 4 certs. Since the chain is 4 certs
|
|
// long, an explicit policy is never required.
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/4,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints3Root) {
|
|
// Explicit policy is required after 3 certs. Since the chain is 4 certs
|
|
// long, an explicit policy is required and the chain should fail if anchor
|
|
// constraints are enforced.
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/3,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
// Windows seems to have an off-by-one error in how it enforces
|
|
// requireExplicitPolicy.
|
|
// (The mac/android verifiers are Ok here since they don't enforce
|
|
// policyConstraints on anchors.)
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints2Root) {
|
|
// Explicit policy is required after 2 certs. Since the chain is 4 certs
|
|
// long, an explicit policy is required and the chain should fail if anchor
|
|
// constraints are enforced.
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/2,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
// This is also a regression test for https://crbug.com/31497: If an
|
|
// intermediate has requireExplicitPolicy in its policyConstraints extension,
|
|
// verification should still succeed as long as some policy is valid for the
|
|
// chain, since Chrome does not specify any required policy as an input to
|
|
// certificate verification (allows anyPolicy).
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Intermediate) {
|
|
for (bool leaf_has_policy : {false, true}) {
|
|
SCOPED_TRACE(leaf_has_policy);
|
|
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.4.5";
|
|
static const char kPolicy3[] = "1.2.3.5";
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
chain_[2]->SetCertificatePolicies({kPolicy1, kPolicy2});
|
|
chain_[1]->SetCertificatePolicies({kPolicy3, kPolicy1});
|
|
|
|
if (leaf_has_policy) {
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
} else {
|
|
chain_[0]->SetCertificatePolicies({});
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints3Intermediate) {
|
|
// Explicit policy is required after 3 certs. Since the chain up to
|
|
// |chain_[2]| is 3 certs long, an explicit policy is never required.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/3,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints2Intermediate) {
|
|
// Explicit policy is required after 2 certs. Since the chain up to
|
|
// |chain_[2]| is 3 certs long, an explicit policy will be required and this
|
|
// should fail to verify.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/2,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints1Intermediate) {
|
|
// Explicit policy is required after 1 cert. Since the chain up to
|
|
// |chain_[2]| is 3 certs long, an explicit policy will be required and this
|
|
// should fail to verify.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/1,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Leaf) {
|
|
// Setting requireExplicitPolicy to 0 on the target certificate should make
|
|
// an explicit policy required for the chain. (Ref: RFC 5280 section 6.1.5.b
|
|
// and the final paragraph of 6.1.5)
|
|
chain_[0]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitPolicyMapping0Root) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.5";
|
|
|
|
// Root inhibits policy mapping immediately.
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/absl::nullopt,
|
|
/*inhibit_policy_mapping=*/0);
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
// Intermediate uses policy mappings. This should not be allowed if the root
|
|
// constraints were enforced.
|
|
chain_[2]->SetCertificatePolicies({kPolicy1});
|
|
chain_[2]->SetPolicyMappings({{kPolicy1, kPolicy2}});
|
|
|
|
// Children require the policy mapping to have a valid policy.
|
|
chain_[1]->SetCertificatePolicies({kPolicy2});
|
|
chain_[0]->SetCertificatePolicies({kPolicy2});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
// Windows enforces inhibitPolicyMapping on the root.
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitPolicyMapping1Root) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.5";
|
|
|
|
// Root inhibits policy mapping after 1 cert.
|
|
chain_[3]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/absl::nullopt,
|
|
/*inhibit_policy_mapping=*/1);
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
// Intermediate uses policy mappings. This should be allowed even if the root
|
|
// constraints were enforced, since policy mapping was allowed for 1 cert
|
|
// following the root.
|
|
chain_[2]->SetCertificatePolicies({kPolicy1});
|
|
chain_[2]->SetPolicyMappings({{kPolicy1, kPolicy2}});
|
|
|
|
// Children require the policy mapping to have a valid policy.
|
|
chain_[1]->SetCertificatePolicies({kPolicy2});
|
|
chain_[0]->SetCertificatePolicies({kPolicy2});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy0Root) {
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
// Since inhibitAnyPolicy is 0, anyPolicy should not be allow for any certs
|
|
// after the root.
|
|
chain_[3]->SetInhibitAnyPolicy(0);
|
|
chain_[3]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
// This intermediate only asserts anyPolicy, so this chain should
|
|
// be invalid if policyConstraints from the root cert are enforced.
|
|
chain_[2]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
chain_[1]->SetCertificatePolicies({kPolicy1});
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy1Root) {
|
|
for (bool chain_1_has_any_policy : {false, true}) {
|
|
SCOPED_TRACE(chain_1_has_any_policy);
|
|
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
// Since inhibitAnyPolicy is 1, anyPolicy should be allowed for the root's
|
|
// immediate child, but not after that.
|
|
chain_[3]->SetInhibitAnyPolicy(1);
|
|
chain_[3]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
// AnyPolicy should be allowed in this cert.
|
|
chain_[2]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
|
|
if (chain_1_has_any_policy) {
|
|
// AnyPolicy should not be allowed in this cert if the inhibitAnyPolicy
|
|
// constraint from the root is honored.
|
|
chain_[1]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
} else {
|
|
chain_[1]->SetCertificatePolicies({kPolicy1});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy0Intermediate) {
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
chain_[2]->SetInhibitAnyPolicy(0);
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
chain_[2]->SetCertificatePolicies({kAnyPolicy});
|
|
// This shouldn't be allowed as the parent cert set inhibitAnyPolicy=0.
|
|
chain_[1]->SetCertificatePolicies({kAnyPolicy});
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy1Intermediate) {
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
chain_[2]->SetInhibitAnyPolicy(1);
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
chain_[2]->SetCertificatePolicies({kAnyPolicy});
|
|
// This is okay as the parent cert set inhibitAnyPolicy=1.
|
|
chain_[1]->SetCertificatePolicies({kAnyPolicy});
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PoliciesRoot) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.5";
|
|
|
|
for (bool root_has_matching_policy : {false, true}) {
|
|
SCOPED_TRACE(root_has_matching_policy);
|
|
|
|
if (root_has_matching_policy) {
|
|
// This chain should be valid whether or not policies from the root are
|
|
// processed.
|
|
chain_[3]->SetCertificatePolicies({kPolicy1});
|
|
} else {
|
|
// If the policies from the root are processed, this chain will not be
|
|
// valid for any policy.
|
|
chain_[3]->SetCertificatePolicies({kPolicy2});
|
|
}
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
chain_[2]->SetCertificatePolicies({kPolicy1});
|
|
chain_[1]->SetCertificatePolicies({kPolicy1});
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
|
|
if (root_has_matching_policy) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
} else {
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, PolicyMappingsRoot) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
static const char kPolicy2[] = "1.2.3.5";
|
|
static const char kPolicy3[] = "1.2.3.6";
|
|
|
|
for (bool root_has_matching_policy_mapping : {false, true}) {
|
|
SCOPED_TRACE(root_has_matching_policy_mapping);
|
|
|
|
if (root_has_matching_policy_mapping) {
|
|
// This chain should be valid if the policies and policy mapping on the
|
|
// root are processed, or if neither is processed. It will not be valid
|
|
// if the policies were processed and the policyMappings were not.
|
|
chain_[3]->SetCertificatePolicies({kPolicy1});
|
|
chain_[3]->SetPolicyMappings({{kPolicy1, kPolicy2}});
|
|
} else {
|
|
// This chain should not be valid if the policies and policyMappings on
|
|
// the root were processed. It will be valid if the policies were
|
|
// processed and policyMappings were not.
|
|
chain_[3]->SetCertificatePolicies({kPolicy2});
|
|
chain_[3]->SetPolicyMappings({{kPolicy2, kPolicy3}});
|
|
}
|
|
|
|
// Policy constraints are specified on an intermediate so that an explicit
|
|
// policy will be required regardless if root constraints are applied.
|
|
chain_[2]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
|
|
chain_[2]->SetCertificatePolicies({kPolicy2});
|
|
chain_[1]->SetCertificatePolicies({kPolicy2});
|
|
chain_[0]->SetCertificatePolicies({kPolicy2});
|
|
|
|
if (root_has_matching_policy_mapping) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
} else {
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoCertSignRoot) {
|
|
chain_[3]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentRoot) {
|
|
chain_[3]->EraseExtension(der::Input(kKeyUsageOid));
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoCertSignIntermediate) {
|
|
chain_[2]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
|
|
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentIntermediate) {
|
|
chain_[2]->EraseExtension(der::Input(kKeyUsageOid));
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoDigitalSignatureLeaf) {
|
|
// This test is mostly uninteresting since keyUsage on the end-entity is only
|
|
// checked at the TLS layer, not during cert verification.
|
|
chain_[0]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentLeaf) {
|
|
// This test is mostly uninteresting since keyUsage on the end-entity is only
|
|
// checked at the TLS layer, not during cert verification.
|
|
chain_[0]->EraseExtension(der::Input(kKeyUsageOid));
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, KeyUsageCertSignLeaf) {
|
|
// Test a leaf that has keyUsage asserting keyCertSign and basicConstraints
|
|
// asserting CA=false. This should be an error according to 5280 section
|
|
// 4.2.1.3 and 4.2.1.9, however most implementations seem to allow it.
|
|
// Perhaps because 5280 section 6 does not explicitly say to enforce this on
|
|
// the target cert.
|
|
chain_[0]->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthRoot) {
|
|
chain_[3]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_IOS) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageServerAuthRoot) {
|
|
chain_[3]->SetExtendedKeyUsages({der::Input(kServerAuth)});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest,
|
|
ExtendedKeyUsageNoServerAuthIntermediate) {
|
|
chain_[2]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID ||
|
|
VerifyProcTypeIsIOSAtMostOS15()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageServerAuthIntermediate) {
|
|
chain_[2]->SetExtendedKeyUsages({der::Input(kServerAuth)});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthLeaf) {
|
|
chain_[0]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
|
|
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmRoot) {
|
|
chain_[3]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmIntermediate) {
|
|
chain_[2]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_IOS) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmLeaf) {
|
|
chain_[0]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
|
|
|
|
if (verify_proc_type() == CERT_VERIFY_PROC_IOS) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionRoot) {
|
|
for (bool critical : {true, false}) {
|
|
SCOPED_TRACE(critical);
|
|
chain_[3]->SetExtension(TestOid0(), "hello world", critical);
|
|
|
|
if (critical) {
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyWithExpiryAndFullConstraints(),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS ||
|
|
verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionIntermediate) {
|
|
for (bool critical : {true, false}) {
|
|
SCOPED_TRACE(critical);
|
|
chain_[2]->SetExtension(TestOid0(), "hello world", critical);
|
|
|
|
if (critical) {
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionLeaf) {
|
|
for (bool critical : {true, false}) {
|
|
SCOPED_TRACE(critical);
|
|
chain_[0]->SetExtension(TestOid0(), "hello world", critical);
|
|
|
|
if (critical) {
|
|
EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
// A set of tests that check how various constraints are enforced when they
|
|
// are applied to a directly trusted non-self-signed leaf certificate.
|
|
class CertVerifyProcConstraintsTrustedLeafTest
|
|
: public CertVerifyProcInternalTest {
|
|
protected:
|
|
void SetUp() override {
|
|
CertVerifyProcInternalTest::SetUp();
|
|
|
|
chain_ = CertBuilder::CreateSimpleChain(/*chain_length=*/2);
|
|
}
|
|
|
|
int VerifyWithTrust(CertificateTrust trust) {
|
|
ScopedTestRoot test_root(chain_[0]->GetX509Certificate().get(), trust);
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
return CertVerifyProcInternalTest::Verify(
|
|
chain_.front()->GetX509Certificate().get(), "www.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
}
|
|
|
|
int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); }
|
|
|
|
int VerifyAsTrustedLeaf() {
|
|
return VerifyWithTrust(CertificateTrust::ForTrustedLeaf());
|
|
}
|
|
|
|
std::vector<std::unique_ptr<CertBuilder>> chain_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcConstraintsTrustedLeafTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BaseCase) {
|
|
// Without changing anything on the test chain, it should validate
|
|
// successfully. If this is not true then the rest of the tests in this class
|
|
// are unlikely to be useful.
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()),
|
|
IsOk());
|
|
EXPECT_THAT(
|
|
VerifyWithTrust(
|
|
CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithRequireLeafSelfSigned()),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, RootAlsoTrusted) {
|
|
// Test verifying a chain where both the leaf and the root are marked as
|
|
// trusted.
|
|
// (Repeating the ScopedTestRoot before each call is due to the limitation
|
|
// with destroying any ScopedTestRoot removing all test roots.)
|
|
{
|
|
ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get());
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
{
|
|
ScopedTestRoot test_root1(chain_[1]->GetX509Certificate().get());
|
|
// An explicit trust entry for the leaf with a value of Unspecified
|
|
// should be no different than the leaf not being in the trust store at
|
|
// all.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForUnspecified()), IsOk());
|
|
}
|
|
{
|
|
ScopedTestRoot test_root1(chain_[1]->GetX509Certificate().get());
|
|
// If the leaf is explicitly distrusted, verification should fail even if
|
|
// the root is trusted.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForDistrusted()),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
}
|
|
{
|
|
ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get());
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
}
|
|
{
|
|
ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()),
|
|
IsOk());
|
|
}
|
|
{
|
|
ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get());
|
|
EXPECT_THAT(
|
|
VerifyWithTrust(
|
|
CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()),
|
|
IsOk());
|
|
}
|
|
{
|
|
ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithRequireLeafSelfSigned()),
|
|
IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsIsCa) {
|
|
for (bool has_key_usage_cert_sign : {false, true}) {
|
|
chain_[0]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1);
|
|
|
|
if (has_key_usage_cert_sign) {
|
|
chain_[0]->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
} else {
|
|
chain_[0]->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
}
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsPathlen) {
|
|
chain_[0]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/0);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsMissing) {
|
|
chain_[0]->EraseExtension(der::Input(kBasicConstraintsOid));
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, NameConstraintsNotMatching) {
|
|
chain_[0]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, ValidityExpired) {
|
|
chain_[0]->SetValidity(base::Time::Now() - base::Days(14),
|
|
base::Time::Now() - base::Days(7));
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_DATE_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, PolicyConstraints) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
for (bool leaf_has_policy : {false, true}) {
|
|
SCOPED_TRACE(leaf_has_policy);
|
|
|
|
chain_[0]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
if (leaf_has_policy) {
|
|
chain_[0]->SetCertificatePolicies({kPolicy1});
|
|
} else {
|
|
chain_[0]->SetCertificatePolicies({});
|
|
}
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
// Succeeds since the ios/android verifiers appear to not enforce
|
|
// this constraint in the "directly trusted leaf" case.
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, InhibitAnyPolicy) {
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
chain_[0]->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
chain_[0]->SetInhibitAnyPolicy(0);
|
|
chain_[0]->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, KeyUsageNoDigitalSignature) {
|
|
// This test is mostly uninteresting since keyUsage on the end-entity is only
|
|
// checked at the TLS layer, not during cert verification.
|
|
chain_[0]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, KeyUsageCertSignLeaf) {
|
|
// Test a leaf that has keyUsage asserting keyCertSign with basicConstraints
|
|
// CA=false, which is an error according to 5280 (4.2.1.3 and 4.2.1.9).
|
|
chain_[0]->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, ExtendedKeyUsageNoServerAuth) {
|
|
chain_[0]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, UnknownSignatureAlgorithm) {
|
|
chain_[0]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Since no chain is found, signature is not checked, fails with generic
|
|
// error for untrusted chain.
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
// Valid since signature on directly trusted leaf is not checked.
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, WeakSignatureAlgorithm) {
|
|
chain_[0]->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Since no chain is found, signature is not checked, fails with generic
|
|
// error for untrusted chain.
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
|
|
// Valid since signature on directly trusted leaf is not checked.
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
|
|
// Cert is not self-signed so directly trusted leaf with
|
|
// require_leaf_selfsigned should fail.
|
|
EXPECT_THAT(
|
|
VerifyWithTrust(
|
|
CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
} else if (verify_proc_type() == CERT_VERIFY_PROC_IOS) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedLeafTest, UnknownExtension) {
|
|
for (bool critical : {true, false}) {
|
|
SCOPED_TRACE(critical);
|
|
chain_[0]->SetExtension(TestOid0(), "hello world", critical);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
if (critical) {
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk());
|
|
}
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
// A set of tests that check how various constraints are enforced when they
|
|
// are applied to a directly trusted self-signed leaf certificate.
|
|
class CertVerifyProcConstraintsTrustedSelfSignedTest
|
|
: public CertVerifyProcInternalTest {
|
|
protected:
|
|
void SetUp() override {
|
|
CertVerifyProcInternalTest::SetUp();
|
|
|
|
cert_ = std::move(CertBuilder::CreateSimpleChain(/*chain_length=*/1)[0]);
|
|
}
|
|
|
|
int VerifyWithTrust(CertificateTrust trust) {
|
|
ScopedTestRoot test_root(cert_->GetX509Certificate().get(), trust);
|
|
CertVerifyResult verify_result;
|
|
int flags = 0;
|
|
return CertVerifyProcInternalTest::Verify(
|
|
cert_->GetX509Certificate().get(), "www.example.com", flags,
|
|
CertificateList(), &verify_result);
|
|
}
|
|
|
|
int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); }
|
|
|
|
int VerifyAsTrustedSelfSignedLeaf() {
|
|
return VerifyWithTrust(
|
|
CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned());
|
|
}
|
|
|
|
std::unique_ptr<CertBuilder> cert_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
testing::ValuesIn(kAllCertVerifiers),
|
|
VerifyProcTypeToName);
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, BaseCase) {
|
|
// Without changing anything on the test cert, it should validate
|
|
// successfully. If this is not true then the rest of the tests in this class
|
|
// are unlikely to be useful.
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Should succeed when verified as a trusted leaf.
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()),
|
|
IsOk());
|
|
|
|
// Should also be allowed by verifying as anchor for itself.
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
|
|
// Should fail if verified as anchor of itself with constraints enabled,
|
|
// enforcing the basicConstraints on the anchor will fail since the cert
|
|
// has CA=false.
|
|
EXPECT_THAT(
|
|
VerifyWithTrust(
|
|
CertificateTrust::ForTrustAnchor().WithEnforceAnchorConstraints()),
|
|
IsError(ERR_CERT_INVALID));
|
|
|
|
// Should be allowed since it will be evaluated as a trusted leaf, so
|
|
// anchor constraints being enabled doesn't matter.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithEnforceAnchorConstraints()),
|
|
IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, BasicConstraintsIsCa) {
|
|
for (bool has_key_usage_cert_sign : {false, true}) {
|
|
cert_->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1);
|
|
|
|
if (has_key_usage_cert_sign) {
|
|
cert_->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
} else {
|
|
cert_->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
}
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
BasicConstraintsNotCaPathlen) {
|
|
cert_->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/0);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
BasicConstraintsIsCaPathlen) {
|
|
cert_->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0);
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
BasicConstraintsMissing) {
|
|
cert_->EraseExtension(der::Input(kBasicConstraintsOid));
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
NameConstraintsNotMatching) {
|
|
cert_->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
|
|
/*excluded_dns_names=*/{});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, ValidityExpired) {
|
|
cert_->SetValidity(base::Time::Now() - base::Days(14),
|
|
base::Time::Now() - base::Days(7));
|
|
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID));
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(),
|
|
IsError(ERR_CERT_DATE_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, PolicyConstraints) {
|
|
static const char kPolicy1[] = "1.2.3.4";
|
|
|
|
for (bool leaf_has_policy : {false, true}) {
|
|
SCOPED_TRACE(leaf_has_policy);
|
|
|
|
cert_->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
if (leaf_has_policy) {
|
|
cert_->SetCertificatePolicies({kPolicy1});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
} else {
|
|
cert_->SetCertificatePolicies({});
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, InhibitAnyPolicy) {
|
|
static const char kAnyPolicy[] = "2.5.29.32.0";
|
|
cert_->SetPolicyConstraints(
|
|
/*require_explicit_policy=*/0,
|
|
/*inhibit_policy_mapping=*/absl::nullopt);
|
|
cert_->SetInhibitAnyPolicy(0);
|
|
cert_->SetCertificatePolicies({kAnyPolicy});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
KeyUsageNoDigitalSignature) {
|
|
// This test is mostly uninteresting since keyUsage on the end-entity is only
|
|
// checked at the TLS layer, not during cert verification.
|
|
cert_->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, KeyUsageCertSignLeaf) {
|
|
// Test a leaf that has keyUsage asserting keyCertSign with basicConstraints
|
|
// CA=false, which is an error according to 5280 (4.2.1.3 and 4.2.1.9).
|
|
cert_->SetKeyUsages(
|
|
{KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE});
|
|
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(
|
|
VerifyWithTrust(
|
|
CertificateTrust::ForTrustAnchor().WithEnforceAnchorConstraints()),
|
|
IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithEnforceAnchorConstraints()
|
|
.WithRequireLeafSelfSigned()),
|
|
IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
ExtendedKeyUsageNoServerAuth) {
|
|
cert_->SetExtendedKeyUsages({der::Input(kCodeSigning)});
|
|
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsError(ERR_CERT_INVALID));
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest,
|
|
UnknownSignatureAlgorithm) {
|
|
cert_->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Attempts to verify as anchor of itself, which fails when verifying the
|
|
// signature.
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
|
|
// Signature not checked when verified as a directly trusted leaf without
|
|
// require_leaf_selfsigned.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk());
|
|
|
|
// PathBuilder override ignores require_leaf_selfsigned due to the
|
|
// self-signed check returning false (due to the invalid signature
|
|
// algorithm), thus this fails with AUTHORITY_INVALID due to failing to
|
|
// find a chain to another root.
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(),
|
|
IsError(ERR_CERT_AUTHORITY_INVALID));
|
|
|
|
// PathBuilder override ignores require_leaf_selfsigned due to the invalid
|
|
// signature algorithm, thus this tries to verify as anchor of itself,
|
|
// which fails when verifying the signature.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithRequireLeafSelfSigned()),
|
|
IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, WeakSignatureAlgorithm) {
|
|
cert_->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1);
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
// Attempts to verify as anchor of itself, which fails due to the weak
|
|
// signature algorithm.
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM));
|
|
|
|
// Signature not checked when verified as a directly trusted leaf without
|
|
// require_leaf_selfsigned.
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk());
|
|
|
|
// require_leaf_selfsigned allows any supported signature algorithm when
|
|
// doing the self-signed check, so this is okay.
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()
|
|
.WithRequireLeafSelfSigned()),
|
|
IsOk());
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
|
|
TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, UnknownExtension) {
|
|
for (bool critical : {true, false}) {
|
|
SCOPED_TRACE(critical);
|
|
cert_->SetExtension(TestOid0(), "hello world", critical);
|
|
|
|
if (VerifyProcTypeIsBuiltin()) {
|
|
if (critical) {
|
|
EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsError(ERR_CERT_INVALID));
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk());
|
|
}
|
|
} else {
|
|
EXPECT_THAT(Verify(), IsOk());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(CertVerifyProcTest, RejectsPublicSHA1) {
|
|
scoped_refptr<X509Certificate> cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
CertVerifyResult result;
|
|
result.has_sha1 = true;
|
|
result.is_issued_by_known_root = true;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
|
|
|
|
// VERIFY_ENABLE_SHA1_LOCAL_ANCHORS should not impact this.
|
|
flags = CertVerifyProc::VERIFY_ENABLE_SHA1_LOCAL_ANCHORS;
|
|
verify_result.Reset();
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
|
|
}
|
|
|
|
TEST(CertVerifyProcTest, RejectsPrivateSHA1UnlessFlag) {
|
|
scoped_refptr<X509Certificate> cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
CertVerifyResult result;
|
|
result.has_sha1 = true;
|
|
result.is_issued_by_known_root = false;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
// SHA-1 should be rejected by default for private roots...
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT);
|
|
|
|
// ... unless VERIFY_ENABLE_SHA1_LOCAL_ANCHORS was supplied.
|
|
flags = CertVerifyProc::VERIFY_ENABLE_SHA1_LOCAL_ANCHORS;
|
|
verify_result.Reset();
|
|
error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT);
|
|
}
|
|
|
|
enum ExpectedAlgorithms {
|
|
EXPECT_SHA1 = 1 << 0,
|
|
EXPECT_STATUS_INVALID = 1 << 1,
|
|
};
|
|
|
|
struct WeakDigestTestData {
|
|
const char* root_cert_filename;
|
|
const char* intermediate_cert_filename;
|
|
const char* ee_cert_filename;
|
|
int expected_algorithms;
|
|
};
|
|
|
|
const char* StringOrDefault(const char* str, const char* default_value) {
|
|
if (!str)
|
|
return default_value;
|
|
return str;
|
|
}
|
|
|
|
// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how
|
|
// to output the parameter that was passed. Without this, it will simply
|
|
// attempt to print out the first twenty bytes of the object, which depending
|
|
// on platform and alignment, may result in an invalid read.
|
|
void PrintTo(const WeakDigestTestData& data, std::ostream* os) {
|
|
*os << "root: " << StringOrDefault(data.root_cert_filename, "none")
|
|
<< "; intermediate: "
|
|
<< StringOrDefault(data.intermediate_cert_filename, "none")
|
|
<< "; end-entity: " << data.ee_cert_filename;
|
|
}
|
|
|
|
class CertVerifyProcWeakDigestTest
|
|
: public testing::TestWithParam<WeakDigestTestData> {
|
|
public:
|
|
CertVerifyProcWeakDigestTest() = default;
|
|
~CertVerifyProcWeakDigestTest() override = default;
|
|
};
|
|
|
|
// Tests that the CertVerifyProc::Verify() properly surfaces the (weak) hash
|
|
// algorithms used in the chain.
|
|
TEST_P(CertVerifyProcWeakDigestTest, VerifyDetectsAlgorithm) {
|
|
WeakDigestTestData data = GetParam();
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
|
|
// Build |intermediates| as the full chain (including trust anchor).
|
|
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
|
|
|
|
if (data.intermediate_cert_filename) {
|
|
scoped_refptr<X509Certificate> intermediate_cert =
|
|
ImportCertFromFile(certs_dir, data.intermediate_cert_filename);
|
|
ASSERT_TRUE(intermediate_cert);
|
|
intermediates.push_back(bssl::UpRef(intermediate_cert->cert_buffer()));
|
|
}
|
|
|
|
if (data.root_cert_filename) {
|
|
scoped_refptr<X509Certificate> root_cert =
|
|
ImportCertFromFile(certs_dir, data.root_cert_filename);
|
|
ASSERT_TRUE(root_cert);
|
|
intermediates.push_back(bssl::UpRef(root_cert->cert_buffer()));
|
|
}
|
|
|
|
scoped_refptr<X509Certificate> ee_cert =
|
|
ImportCertFromFile(certs_dir, data.ee_cert_filename);
|
|
ASSERT_TRUE(ee_cert);
|
|
|
|
scoped_refptr<X509Certificate> ee_chain = X509Certificate::CreateFromBuffer(
|
|
bssl::UpRef(ee_cert->cert_buffer()), std::move(intermediates));
|
|
ASSERT_TRUE(ee_chain);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
|
|
// Use a mock CertVerifyProc that returns success with a verified_cert of
|
|
// |ee_chain|.
|
|
//
|
|
// This is sufficient for the purposes of this test, as the checking for weak
|
|
// hash algorithms is done by CertVerifyProc::Verify().
|
|
auto proc = base::MakeRefCounted<MockCertVerifyProc>(CertVerifyResult());
|
|
int error = proc->Verify(ee_chain.get(), "127.0.0.1",
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(),
|
|
&verify_result, NetLogWithSource());
|
|
EXPECT_EQ(!!(data.expected_algorithms & EXPECT_SHA1), verify_result.has_sha1);
|
|
EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID),
|
|
!!(verify_result.cert_status & CERT_STATUS_INVALID));
|
|
EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID),
|
|
error == ERR_CERT_INVALID);
|
|
}
|
|
|
|
// The signature algorithm of the root CA should not matter.
|
|
const WeakDigestTestData kVerifyRootCATestData[] = {
|
|
{"weak_digest_md5_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_SHA1},
|
|
{"weak_digest_md4_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_SHA1},
|
|
{"weak_digest_md2_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_SHA1},
|
|
};
|
|
INSTANTIATE_TEST_SUITE_P(VerifyRoot,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyRootCATestData));
|
|
|
|
// The signature algorithm of intermediates should be properly detected.
|
|
const WeakDigestTestData kVerifyIntermediateCATestData[] = {
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem",
|
|
"weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VerifyIntermediate,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyIntermediateCATestData));
|
|
|
|
// The signature algorithm of end-entity should be properly detected.
|
|
const WeakDigestTestData kVerifyEndEntityTestData[] = {
|
|
{"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_md4_ee.pem", EXPECT_STATUS_INVALID},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
|
|
"weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VerifyEndEntity,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyEndEntityTestData));
|
|
|
|
// Incomplete chains do not report the status of the intermediate.
|
|
// Note: really each of these tests should also expect the digest algorithm of
|
|
// the intermediate (included as a comment). However CertVerifyProc::Verify() is
|
|
// unable to distinguish that this is an intermediate and not a trust anchor, so
|
|
// this intermediate is treated like a trust anchor.
|
|
const WeakDigestTestData kVerifyIncompleteIntermediateTestData[] = {
|
|
{nullptr, "weak_digest_md5_intermediate.pem", "weak_digest_sha1_ee.pem",
|
|
EXPECT_SHA1},
|
|
{nullptr, "weak_digest_md4_intermediate.pem", "weak_digest_sha1_ee.pem",
|
|
EXPECT_SHA1},
|
|
{nullptr, "weak_digest_md2_intermediate.pem", "weak_digest_sha1_ee.pem",
|
|
EXPECT_SHA1},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
MAYBE_VerifyIncompleteIntermediate,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyIncompleteIntermediateTestData));
|
|
|
|
// Incomplete chains should report the status of the end-entity.
|
|
// since the intermediate is treated as a trust anchor these should
|
|
// be still simply be invalid.
|
|
const WeakDigestTestData kVerifyIncompleteEETestData[] = {
|
|
{nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md5_ee.pem",
|
|
EXPECT_STATUS_INVALID},
|
|
{nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md4_ee.pem",
|
|
EXPECT_STATUS_INVALID},
|
|
{nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md2_ee.pem",
|
|
EXPECT_STATUS_INVALID},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VerifyIncompleteEndEntity,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyIncompleteEETestData));
|
|
|
|
// Md2, Md4, and Md5 are all considered invalid.
|
|
const WeakDigestTestData kVerifyMixedTestData[] = {
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem",
|
|
"weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem",
|
|
"weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID},
|
|
{"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem",
|
|
"weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VerifyMixed,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyMixedTestData));
|
|
|
|
// The EE is a trusted certificate. Even though it uses weak hashes, these
|
|
// should not be reported.
|
|
const WeakDigestTestData kVerifyTrustedEETestData[] = {
|
|
{nullptr, nullptr, "weak_digest_md5_ee.pem", 0},
|
|
{nullptr, nullptr, "weak_digest_md4_ee.pem", 0},
|
|
{nullptr, nullptr, "weak_digest_md2_ee.pem", 0},
|
|
{nullptr, nullptr, "weak_digest_sha1_ee.pem", 0},
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VerifyTrustedEE,
|
|
CertVerifyProcWeakDigestTest,
|
|
testing::ValuesIn(kVerifyTrustedEETestData));
|
|
|
|
// Test fixture for verifying certificate names.
|
|
class CertVerifyProcNameTest : public ::testing::Test {
|
|
protected:
|
|
void VerifyCertName(const char* hostname, bool valid) {
|
|
scoped_refptr<X509Certificate> cert(ImportCertFromFile(
|
|
GetTestCertsDirectory(), "subjectAltName_sanity_check.pem"));
|
|
ASSERT_TRUE(cert);
|
|
CertVerifyResult result;
|
|
result.is_issued_by_known_root = false;
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), hostname, /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), 0, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
if (valid) {
|
|
EXPECT_THAT(error, IsOk());
|
|
EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_COMMON_NAME_INVALID);
|
|
} else {
|
|
EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID));
|
|
EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_COMMON_NAME_INVALID);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Don't match the common name
|
|
TEST_F(CertVerifyProcNameTest, DontMatchCommonName) {
|
|
VerifyCertName("127.0.0.1", false);
|
|
}
|
|
|
|
// Matches the iPAddress SAN (IPv4)
|
|
TEST_F(CertVerifyProcNameTest, MatchesIpSanIpv4) {
|
|
VerifyCertName("127.0.0.2", true);
|
|
}
|
|
|
|
// Matches the iPAddress SAN (IPv6)
|
|
TEST_F(CertVerifyProcNameTest, MatchesIpSanIpv6) {
|
|
VerifyCertName("FE80:0:0:0:0:0:0:1", true);
|
|
}
|
|
|
|
// Should not match the iPAddress SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchIpSanIpv6) {
|
|
VerifyCertName("[FE80:0:0:0:0:0:0:1]", false);
|
|
}
|
|
|
|
// Compressed form matches the iPAddress SAN (IPv6)
|
|
TEST_F(CertVerifyProcNameTest, MatchesIpSanCompressedIpv6) {
|
|
VerifyCertName("FE80::1", true);
|
|
}
|
|
|
|
// IPv6 mapped form should NOT match iPAddress SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchIpSanIPv6Mapped) {
|
|
VerifyCertName("::127.0.0.2", false);
|
|
}
|
|
|
|
// Matches the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, MatchesDnsSan) {
|
|
VerifyCertName("test.example", true);
|
|
}
|
|
|
|
// Matches the dNSName SAN (trailing . ignored)
|
|
TEST_F(CertVerifyProcNameTest, MatchesDnsSanTrailingDot) {
|
|
VerifyCertName("test.example.", true);
|
|
}
|
|
|
|
// Should not match the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSan) {
|
|
VerifyCertName("www.test.example", false);
|
|
}
|
|
|
|
// Should not match the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanInvalid) {
|
|
VerifyCertName("test..example", false);
|
|
}
|
|
|
|
// Should not match the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanTwoTrailingDots) {
|
|
VerifyCertName("test.example..", false);
|
|
}
|
|
|
|
// Should not match the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanLeadingAndTrailingDot) {
|
|
VerifyCertName(".test.example.", false);
|
|
}
|
|
|
|
// Should not match the dNSName SAN
|
|
TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanTrailingDot) {
|
|
VerifyCertName(".test.example", false);
|
|
}
|
|
|
|
// Test that trust anchors are appropriately recorded via UMA.
|
|
TEST(CertVerifyProcTest, HasTrustAnchorVerifyUMA) {
|
|
base::HistogramTester histograms;
|
|
scoped_refptr<X509Certificate> cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
CertVerifyResult result;
|
|
|
|
// Simulate a certificate chain issued by "C=US, O=Google Trust Services LLC,
|
|
// CN=GTS Root R4". This publicly-trusted root was chosen as it was included
|
|
// in 2017 and is not anticipated to be removed from all supported platforms
|
|
// for a few decades.
|
|
// Note: The actual cert in |cert| does not matter for this testing, so long
|
|
// as it's not violating any CertVerifyProc::Verify() policies.
|
|
SHA256HashValue leaf_hash = {{0}};
|
|
SHA256HashValue intermediate_hash = {{1}};
|
|
SHA256HashValue root_hash = {
|
|
{0x98, 0x47, 0xe5, 0x65, 0x3e, 0x5e, 0x9e, 0x84, 0x75, 0x16, 0xe5,
|
|
0xcb, 0x81, 0x86, 0x06, 0xaa, 0x75, 0x44, 0xa1, 0x9b, 0xe6, 0x7f,
|
|
0xd7, 0x36, 0x6d, 0x50, 0x69, 0x88, 0xe8, 0xd8, 0x43, 0x47}};
|
|
result.public_key_hashes.push_back(HashValue(leaf_hash));
|
|
result.public_key_hashes.push_back(HashValue(intermediate_hash));
|
|
result.public_key_hashes.push_back(HashValue(root_hash));
|
|
|
|
const base::HistogramBase::Sample kGTSRootR4HistogramID = 486;
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_EQ(OK, error);
|
|
histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram,
|
|
kGTSRootR4HistogramID, 1);
|
|
}
|
|
|
|
// Test that certificates with multiple trust anchors present result in
|
|
// only a single trust anchor being recorded, and that being the most specific
|
|
// trust anchor.
|
|
TEST(CertVerifyProcTest, LogsOnlyMostSpecificTrustAnchorUMA) {
|
|
base::HistogramTester histograms;
|
|
scoped_refptr<X509Certificate> cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
CertVerifyResult result;
|
|
|
|
// Simulate a chain of "C=US, O=Google Trust Services LLC, CN=GTS Root R4"
|
|
// signing "C=US, O=Google Trust Services LLC, CN=GTS Root R3" signing an
|
|
// intermediate and a leaf.
|
|
// Note: The actual cert in |cert| does not matter for this testing, so long
|
|
// as it's not violating any CertVerifyProc::Verify() policies.
|
|
SHA256HashValue leaf_hash = {{0}};
|
|
SHA256HashValue intermediate_hash = {{1}};
|
|
SHA256HashValue gts_root_r3_hash = {
|
|
{0x41, 0x79, 0xed, 0xd9, 0x81, 0xef, 0x74, 0x74, 0x77, 0xb4, 0x96,
|
|
0x26, 0x40, 0x8a, 0xf4, 0x3d, 0xaa, 0x2c, 0xa7, 0xab, 0x7f, 0x9e,
|
|
0x08, 0x2c, 0x10, 0x60, 0xf8, 0x40, 0x96, 0x77, 0x43, 0x48}};
|
|
SHA256HashValue gts_root_r4_hash = {
|
|
{0x98, 0x47, 0xe5, 0x65, 0x3e, 0x5e, 0x9e, 0x84, 0x75, 0x16, 0xe5,
|
|
0xcb, 0x81, 0x86, 0x06, 0xaa, 0x75, 0x44, 0xa1, 0x9b, 0xe6, 0x7f,
|
|
0xd7, 0x36, 0x6d, 0x50, 0x69, 0x88, 0xe8, 0xd8, 0x43, 0x47}};
|
|
result.public_key_hashes.push_back(HashValue(leaf_hash));
|
|
result.public_key_hashes.push_back(HashValue(intermediate_hash));
|
|
result.public_key_hashes.push_back(HashValue(gts_root_r3_hash));
|
|
result.public_key_hashes.push_back(HashValue(gts_root_r4_hash));
|
|
|
|
const base::HistogramBase::Sample kGTSRootR3HistogramID = 485;
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_EQ(OK, error);
|
|
|
|
// Only GTS Root R3 should be recorded.
|
|
histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram,
|
|
kGTSRootR3HistogramID, 1);
|
|
}
|
|
|
|
// Test that trust anchors histograms record whether or not
|
|
// is_issued_by_known_root was derived from the OS.
|
|
TEST(CertVerifyProcTest, HasTrustAnchorVerifyOutOfDateUMA) {
|
|
base::HistogramTester histograms;
|
|
scoped_refptr<X509Certificate> cert(ImportCertFromFile(
|
|
GetTestCertsDirectory(), "39_months_based_on_last_day.pem"));
|
|
ASSERT_TRUE(cert);
|
|
|
|
CertVerifyResult result;
|
|
|
|
// Simulate a certificate chain that is recognized as trusted (from a known
|
|
// root), but no certificates in the chain are tracked as known trust
|
|
// anchors.
|
|
SHA256HashValue leaf_hash = {{0}};
|
|
SHA256HashValue intermediate_hash = {{1}};
|
|
SHA256HashValue root_hash = {{2}};
|
|
result.public_key_hashes.push_back(HashValue(leaf_hash));
|
|
result.public_key_hashes.push_back(HashValue(intermediate_hash));
|
|
result.public_key_hashes.push_back(HashValue(root_hash));
|
|
result.is_issued_by_known_root = true;
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0);
|
|
histograms.ExpectTotalCount(kTrustAnchorVerifyOutOfDateHistogram, 0);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_EQ(OK, error);
|
|
const base::HistogramBase::Sample kUnknownRootHistogramID = 0;
|
|
histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram,
|
|
kUnknownRootHistogramID, 1);
|
|
histograms.ExpectUniqueSample(kTrustAnchorVerifyOutOfDateHistogram, true, 1);
|
|
}
|
|
|
|
// If the CertVerifyProc::VerifyInternal implementation calculated the stapled
|
|
// OCSP results in the CertVerifyResult, CertVerifyProc::Verify should not
|
|
// re-calculate them.
|
|
TEST(CertVerifyProcTest, DoesNotRecalculateStapledOCSPResult) {
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
ASSERT_EQ(1U, cert->intermediate_buffers().size());
|
|
|
|
CertVerifyResult result;
|
|
|
|
result.ocsp_result.response_status = OCSPVerifyResult::PROVIDED;
|
|
result.ocsp_result.revocation_status = OCSPRevocationStatus::GOOD;
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error =
|
|
verify_proc->Verify(cert.get(), "127.0.0.1",
|
|
/*ocsp_response=*/"invalid OCSP data",
|
|
/*sct_list=*/std::string(), flags, CertificateList(),
|
|
&verify_result, NetLogWithSource());
|
|
EXPECT_EQ(OK, error);
|
|
|
|
EXPECT_EQ(OCSPVerifyResult::PROVIDED,
|
|
verify_result.ocsp_result.response_status);
|
|
EXPECT_EQ(OCSPRevocationStatus::GOOD,
|
|
verify_result.ocsp_result.revocation_status);
|
|
}
|
|
|
|
TEST(CertVerifyProcTest, CalculateStapledOCSPResultIfNotAlreadyDone) {
|
|
scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile(
|
|
GetTestCertsDirectory(), "ok_cert_by_intermediate.pem",
|
|
X509Certificate::FORMAT_AUTO);
|
|
ASSERT_TRUE(cert);
|
|
ASSERT_EQ(1U, cert->intermediate_buffers().size());
|
|
|
|
CertVerifyResult result;
|
|
|
|
// Confirm the default-constructed values are as expected.
|
|
EXPECT_EQ(OCSPVerifyResult::NOT_CHECKED, result.ocsp_result.response_status);
|
|
EXPECT_EQ(OCSPRevocationStatus::UNKNOWN,
|
|
result.ocsp_result.revocation_status);
|
|
|
|
auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result);
|
|
|
|
int flags = 0;
|
|
CertVerifyResult verify_result;
|
|
int error = verify_proc->Verify(
|
|
cert.get(), "127.0.0.1", /*ocsp_response=*/"invalid OCSP data",
|
|
/*sct_list=*/std::string(), flags, CertificateList(), &verify_result,
|
|
NetLogWithSource());
|
|
EXPECT_EQ(OK, error);
|
|
|
|
EXPECT_EQ(OCSPVerifyResult::PARSE_RESPONSE_ERROR,
|
|
verify_result.ocsp_result.response_status);
|
|
EXPECT_EQ(OCSPRevocationStatus::UNKNOWN,
|
|
verify_result.ocsp_result.revocation_status);
|
|
}
|
|
|
|
} // namespace net
|