blob: 7bdb9a57db7485d5155ebb85dc156668a7476857 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/ignore_errors_cert_verifier.h"
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "crypto/sha2.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/cert/asn1_util.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/log/net_log_with_source.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/network_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::CertVerifier;
using net::MockCertVerifier;
using net::HashValue;
using net::SHA256HashValue;
using net::X509Certificate;
using net::TestCompletionCallback;
using net::CertVerifyResult;
using net::NetLogWithSource;
using net::ERR_CERT_INVALID;
using net::ERR_IO_PENDING;
using net::OK;
using net::test::IsError;
using net::test::IsOk;
namespace network {
static const char kTestUserDataDirSwitch[] = "test-user-data-dir";
static std::vector<std::string> MakeWhitelist() {
base::FilePath certs_dir = net::GetTestCertsDirectory();
net::CertificateList certs = net::CreateCertificateListFromFile(
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
std::string hash_base64;
base::StringPiece cert_spki;
SHA256HashValue hash;
net::asn1::ExtractSPKIFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(certs[1]->cert_buffer()),
&cert_spki);
crypto::SHA256HashString(cert_spki, &hash, sizeof(SHA256HashValue));
base::Base64Encode(base::StringPiece(reinterpret_cast<const char*>(hash.data),
sizeof(hash.data)),
&hash_base64);
return {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "foobar", hash_base64,
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="};
}
class IgnoreErrorsCertVerifierTest : public ::testing::Test {
public:
IgnoreErrorsCertVerifierTest()
: mock_verifier_(new MockCertVerifier()),
verifier_(base::WrapUnique(mock_verifier_),
IgnoreErrorsCertVerifier::SPKIHashSet()) {}
~IgnoreErrorsCertVerifierTest() override {}
protected:
void SetUp() override {
verifier_.set_whitelist(
IgnoreErrorsCertVerifier::MakeWhitelist(MakeWhitelist()));
};
// The wrapped CertVerifier. Defaults to returning ERR_CERT_INVALID. Owned by
// |verifier_|.
MockCertVerifier* mock_verifier_;
IgnoreErrorsCertVerifier verifier_;
};
static void GetNonWhitelistedTestCert(scoped_refptr<X509Certificate>* out) {
base::FilePath certs_dir = net::GetTestCertsDirectory();
scoped_refptr<X509Certificate> test_cert(
net::ImportCertFromFile(certs_dir, "ok_cert.pem"));
ASSERT_TRUE(test_cert);
out->swap(test_cert);
}
static CertVerifier::RequestParams MakeRequestParams(
const scoped_refptr<X509Certificate>& cert) {
return CertVerifier::RequestParams(cert, "example.com", 0, "");
}
static void GetWhitelistedTestCert(scoped_refptr<X509Certificate>* out) {
base::FilePath certs_dir = net::GetTestCertsDirectory();
*out = net::CreateCertificateChainFromFile(
certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO);
ASSERT_TRUE(*out);
ASSERT_EQ(2U, (*out)->intermediate_buffers().size());
}
TEST_F(IgnoreErrorsCertVerifierTest, TestNoMatchCertOk) {
mock_verifier_->set_default_result(OK);
scoped_refptr<X509Certificate> test_cert;
ASSERT_NO_FATAL_FAILURE(GetNonWhitelistedTestCert(&test_cert));
CertVerifyResult verify_result;
TestCompletionCallback callback;
std::unique_ptr<CertVerifier::Request> request;
EXPECT_THAT(callback.GetResult(verifier_.Verify(
MakeRequestParams(test_cert), &verify_result,
callback.callback(), &request, NetLogWithSource())),
IsOk());
}
TEST_F(IgnoreErrorsCertVerifierTest, TestNoMatchCertError) {
scoped_refptr<X509Certificate> test_cert;
ASSERT_NO_FATAL_FAILURE(GetNonWhitelistedTestCert(&test_cert));
CertVerifyResult verify_result;
TestCompletionCallback callback;
std::unique_ptr<CertVerifier::Request> request;
EXPECT_THAT(callback.GetResult(verifier_.Verify(
MakeRequestParams(test_cert), &verify_result,
callback.callback(), &request, NetLogWithSource())),
IsError(ERR_CERT_INVALID));
}
TEST_F(IgnoreErrorsCertVerifierTest, TestMatch) {
scoped_refptr<X509Certificate> test_cert;
ASSERT_NO_FATAL_FAILURE(GetWhitelistedTestCert(&test_cert));
CertVerifyResult verify_result;
TestCompletionCallback callback;
std::unique_ptr<CertVerifier::Request> request;
EXPECT_THAT(callback.GetResult(verifier_.Verify(
MakeRequestParams(test_cert), &verify_result,
callback.callback(), &request, NetLogWithSource())),
IsOk());
}
class IgnoreCertificateErrorsSPKIListFlagTest
: public ::testing::TestWithParam<bool> {
public:
IgnoreCertificateErrorsSPKIListFlagTest() {
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
if (GetParam()) {
command_line.AppendSwitchASCII(kTestUserDataDirSwitch, "/foo/bar/baz");
}
command_line.AppendSwitchASCII(switches::kIgnoreCertificateErrorsSPKIList,
base::JoinString(MakeWhitelist(), ","));
auto mock_verifier = std::make_unique<MockCertVerifier>();
mock_verifier->set_default_result(ERR_CERT_INVALID);
verifier_ = IgnoreErrorsCertVerifier::MaybeWrapCertVerifier(
command_line, kTestUserDataDirSwitch, std::move(mock_verifier));
}
~IgnoreCertificateErrorsSPKIListFlagTest() override {}
protected:
std::unique_ptr<CertVerifier> verifier_;
};
// Only if both --user-data-dir and --ignore-certificate-errors-from-spki-list
// are present, certificate verification is bypassed.
TEST_P(IgnoreCertificateErrorsSPKIListFlagTest, TestUserDataDirSwitchRequired) {
scoped_refptr<X509Certificate> test_cert;
ASSERT_NO_FATAL_FAILURE(GetWhitelistedTestCert(&test_cert));
CertVerifyResult verify_result;
TestCompletionCallback callback;
std::unique_ptr<CertVerifier::Request> request;
if (GetParam()) {
EXPECT_THAT(callback.GetResult(verifier_->Verify(
MakeRequestParams(test_cert), &verify_result,
callback.callback(), &request, NetLogWithSource())),
IsOk());
} else {
EXPECT_THAT(callback.GetResult(verifier_->Verify(
MakeRequestParams(test_cert), &verify_result,
callback.callback(), &request, NetLogWithSource())),
IsError(ERR_CERT_INVALID));
}
}
INSTANTIATE_TEST_CASE_P(WithUserDataDirSwitchPresent,
IgnoreCertificateErrorsSPKIListFlagTest,
::testing::Bool());
} // namespace network