| Index: net/cert/internal/test_helpers.cc
|
| diff --git a/net/cert/internal/test_helpers.cc b/net/cert/internal/test_helpers.cc
|
| index ab8ed5510949b2a4f5496c06ca4d451f8ae86521..16c39cf14ef3732e2f2573a248a1fbb0b2b0b487 100644
|
| --- a/net/cert/internal/test_helpers.cc
|
| +++ b/net/cert/internal/test_helpers.cc
|
| @@ -8,6 +8,8 @@
|
| #include "base/base_paths.h"
|
| #include "base/files/file_util.h"
|
| #include "base/path_service.h"
|
| +#include "base/strings/string_split.h"
|
| +#include "net/cert/internal/cert_error_params.h"
|
| #include "net/cert/internal/cert_errors.h"
|
| #include "net/cert/pem_tokenizer.h"
|
| #include "net/der/parser.h"
|
| @@ -16,6 +18,27 @@
|
|
|
| namespace net {
|
|
|
| +namespace {
|
| +
|
| +bool GetValue(base::StringPiece prefix,
|
| + base::StringPiece line,
|
| + std::string* value,
|
| + bool* has_value) {
|
| + if (!line.starts_with(prefix))
|
| + return false;
|
| +
|
| + if (*has_value) {
|
| + ADD_FAILURE() << "Duplicated " << prefix;
|
| + return false;
|
| + }
|
| +
|
| + *has_value = true;
|
| + *value = line.substr(prefix.size()).as_string();
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| namespace der {
|
|
|
| void PrintTo(const Input& data, ::std::ostream* os) {
|
| @@ -56,8 +79,8 @@ der::Input SequenceValueFromString(const std::string* s) {
|
| // Read the full contents of the PEM file.
|
| std::string file_data;
|
| if (!base::ReadFileToString(filepath, &file_data)) {
|
| - return ::testing::AssertionFailure() << "Couldn't read file: "
|
| - << filepath.value();
|
| + return ::testing::AssertionFailure()
|
| + << "Couldn't read file: " << filepath.value();
|
| }
|
|
|
| // mappings_copy is used to keep track of which mappings have already been
|
| @@ -94,8 +117,8 @@ der::Input SequenceValueFromString(const std::string* s) {
|
| // Ensure that all specified blocks were found.
|
| for (const auto& mapping : mappings_copy) {
|
| if (mapping.value && !mapping.optional) {
|
| - return ::testing::AssertionFailure() << "PEM block missing: "
|
| - << mapping.block_name;
|
| + return ::testing::AssertionFailure()
|
| + << "PEM block missing: " << mapping.block_name;
|
| }
|
| }
|
|
|
| @@ -105,103 +128,167 @@ der::Input SequenceValueFromString(const std::string* s) {
|
| VerifyCertChainTest::VerifyCertChainTest() = default;
|
| VerifyCertChainTest::~VerifyCertChainTest() = default;
|
|
|
| -void ReadVerifyCertChainTestFromFile(const std::string& file_path_ascii,
|
| +bool VerifyCertChainTest::HasHighSeverityErrors() const {
|
| + // This function assumes that high severity warnings are prefixed with
|
| + // "ERROR: " and warnings are prefixed with "WARNING: ". This is an
|
| + // implementation detail of CertError::ToDebugString).
|
| + //
|
| + // Do a quick sanity-check to confirm this.
|
| + CertError error(CertError::SEVERITY_HIGH, "unused", nullptr);
|
| + EXPECT_EQ("ERROR: unused\n", error.ToDebugString());
|
| + CertError warning(CertError::SEVERITY_WARNING, "unused", nullptr);
|
| + EXPECT_EQ("WARNING: unused\n", warning.ToDebugString());
|
| +
|
| + // Do a simple substring test (not perfect, but good enough for our test
|
| + // corpus).
|
| + return expected_errors.find("ERROR: ") != std::string::npos;
|
| +}
|
| +
|
| +bool ReadCertChainFromFile(const std::string& file_path_ascii,
|
| + ParsedCertificateList* chain) {
|
| + // Reset all the out parameters to their defaults.
|
| + *chain = ParsedCertificateList();
|
| +
|
| + std::string file_data = ReadTestFileToString(file_path_ascii);
|
| + if (file_data.empty())
|
| + return false;
|
| +
|
| + std::vector<std::string> pem_headers = {"CERTIFICATE"};
|
| +
|
| + PEMTokenizer pem_tokenizer(file_data, pem_headers);
|
| + while (pem_tokenizer.GetNext()) {
|
| + const std::string& block_data = pem_tokenizer.data();
|
| +
|
| + CertErrors errors;
|
| + if (!net::ParsedCertificate::CreateAndAddToVector(
|
| + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
| + reinterpret_cast<const uint8_t*>(block_data.data()),
|
| + block_data.size(), nullptr)),
|
| + {}, chain, &errors)) {
|
| + ADD_FAILURE() << errors.ToDebugString();
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool ReadVerifyCertChainTestFromFile(const std::string& file_path_ascii,
|
| VerifyCertChainTest* test) {
|
| // Reset all the out parameters to their defaults.
|
| *test = {};
|
|
|
| std::string file_data = ReadTestFileToString(file_path_ascii);
|
| + if (file_data.empty())
|
| + return false;
|
|
|
| - std::vector<std::string> pem_headers;
|
| -
|
| - // For details on the file format refer to:
|
| - // net/data/verify_certificate_chain_unittest/README.
|
| - const char kCertificateHeader[] = "CERTIFICATE";
|
| - const char kTrustAnchorUnconstrained[] = "TRUST_ANCHOR_UNCONSTRAINED";
|
| - const char kTrustAnchorConstrained[] = "TRUST_ANCHOR_CONSTRAINED";
|
| - const char kTimeHeader[] = "TIME";
|
| - const char kResultHeader[] = "VERIFY_RESULT";
|
| - const char kErrorsHeader[] = "ERRORS";
|
| - const char kKeyPurpose[] = "KEY_PURPOSE";
|
| -
|
| - pem_headers.push_back(kCertificateHeader);
|
| - pem_headers.push_back(kTrustAnchorUnconstrained);
|
| - pem_headers.push_back(kTrustAnchorConstrained);
|
| - pem_headers.push_back(kTimeHeader);
|
| - pem_headers.push_back(kResultHeader);
|
| - pem_headers.push_back(kErrorsHeader);
|
| - pem_headers.push_back(kKeyPurpose);
|
| + std::vector<std::string> lines = base::SplitString(
|
| + file_data, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
|
|
|
| + bool has_chain = false;
|
| + bool has_trust = false;
|
| bool has_time = false;
|
| - bool has_result = false;
|
| bool has_errors = false;
|
| bool has_key_purpose = false;
|
| - bool has_trust_anchor = false;
|
|
|
| - PEMTokenizer pem_tokenizer(file_data, pem_headers);
|
| - while (pem_tokenizer.GetNext()) {
|
| - const std::string& block_type = pem_tokenizer.block_type();
|
| - const std::string& block_data = pem_tokenizer.data();
|
| + base::StringPiece kExpectedErrors = "expected_errors:";
|
| +
|
| + for (const std::string& line : lines) {
|
| + base::StringPiece line_piece(line);
|
|
|
| - if (block_type == kCertificateHeader) {
|
| - ASSERT_FALSE(has_trust_anchor) << "Trust anchor must appear last";
|
| - CertErrors errors;
|
| - ASSERT_TRUE(net::ParsedCertificate::CreateAndAddToVector(
|
| - bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
| - reinterpret_cast<const uint8_t*>(block_data.data()),
|
| - block_data.size(), nullptr)),
|
| - {}, &test->chain, &errors))
|
| - << errors.ToDebugString();
|
| - } else if (block_type == kTrustAnchorUnconstrained ||
|
| - block_type == kTrustAnchorConstrained) {
|
| - ASSERT_FALSE(has_trust_anchor) << "Duplicate trust anchor";
|
| - CertErrors errors;
|
| - scoped_refptr<ParsedCertificate> root = net::ParsedCertificate::Create(
|
| - bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
| - reinterpret_cast<const uint8_t*>(block_data.data()),
|
| - block_data.size(), nullptr)),
|
| - {}, &errors);
|
| - ASSERT_TRUE(root) << errors.ToDebugString();
|
| - test->chain.push_back(std::move(root));
|
| - test->last_cert_trust =
|
| - (block_type == kTrustAnchorUnconstrained)
|
| - ? CertificateTrust::ForTrustAnchor()
|
| - : CertificateTrust::ForTrustAnchorEnforcingConstraints();
|
| - has_trust_anchor = true;
|
| - } else if (block_type == kTimeHeader) {
|
| - ASSERT_FALSE(has_time) << "Duplicate " << kTimeHeader;
|
| - has_time = true;
|
| - ASSERT_TRUE(der::ParseUTCTime(der::Input(&block_data), &test->time));
|
| - } else if (block_type == kKeyPurpose) {
|
| - ASSERT_FALSE(has_key_purpose) << "Duplicate " << kKeyPurpose;
|
| - has_key_purpose = true;
|
| -
|
| - if (block_data == "anyExtendedKeyUsage") {
|
| + std::string value;
|
| +
|
| + // For details on the file format refer to:
|
| + // net/data/verify_certificate_chain_unittest/README.
|
| + if (GetValue("chain: ", line_piece, &value, &has_chain)) {
|
| + // Interpret the |chain| path as being relative to the .test file.
|
| + size_t slash = file_path_ascii.rfind('/');
|
| + if (slash == std::string::npos) {
|
| + ADD_FAILURE() << "Bad path - expecting slashes";
|
| + return false;
|
| + }
|
| + std::string chain_path = file_path_ascii.substr(0, slash) + "/" + value;
|
| +
|
| + ReadCertChainFromFile(chain_path, &test->chain);
|
| + } else if (GetValue("utc_time: ", line_piece, &value, &has_time)) {
|
| + if (!der::ParseUTCTime(der::Input(&value), &test->time)) {
|
| + ADD_FAILURE() << "Failed parsing UTC time";
|
| + return false;
|
| + }
|
| + } else if (GetValue("key_purpose: ", line_piece, &value,
|
| + &has_key_purpose)) {
|
| + if (value == "ANY_EKU") {
|
| test->key_purpose = KeyPurpose::ANY_EKU;
|
| - } else if (block_data == "serverAuth") {
|
| + } else if (value == "SERVER_AUTH") {
|
| test->key_purpose = KeyPurpose::SERVER_AUTH;
|
| - } else if (block_data == "clientAuth") {
|
| + } else if (value == "CLIENT_AUTH") {
|
| test->key_purpose = KeyPurpose::CLIENT_AUTH;
|
| } else {
|
| - ADD_FAILURE() << "Unrecognized " << block_type << ": " << block_data;
|
| + ADD_FAILURE() << "Unrecognized key_purpose: " << value;
|
| + return false;
|
| + }
|
| + } else if (GetValue("last_cert_trust: ", line_piece, &value, &has_trust)) {
|
| + if (value == "TRUSTED_ANCHOR") {
|
| + test->last_cert_trust = CertificateTrust::ForTrustAnchor();
|
| + } else if (value == "TRUSTED_ANCHOR_WITH_CONSTRAINTS") {
|
| + test->last_cert_trust =
|
| + CertificateTrust::ForTrustAnchorEnforcingConstraints();
|
| + } else if (value == "DISTRUSTED") {
|
| + test->last_cert_trust = CertificateTrust::ForDistrusted();
|
| + } else if (value == "UNSPECIFIED") {
|
| + test->last_cert_trust = CertificateTrust::ForUnspecified();
|
| + } else {
|
| + ADD_FAILURE() << "Unrecognized last_cert_trust: " << value;
|
| + return false;
|
| }
|
| - } else if (block_type == kResultHeader) {
|
| - ASSERT_FALSE(has_result) << "Duplicate " << kResultHeader;
|
| - ASSERT_TRUE(block_data == "SUCCESS" || block_data == "FAIL")
|
| - << "Unrecognized result: " << block_data;
|
| - has_result = true;
|
| - test->expected_result = block_data == "SUCCESS";
|
| - } else if (block_type == kErrorsHeader) {
|
| - ASSERT_FALSE(has_errors) << "Duplicate " << kErrorsHeader;
|
| + } else if (line_piece.starts_with("#")) {
|
| + // Skip comments.
|
| + continue;
|
| + } else if (line_piece == kExpectedErrors) {
|
| has_errors = true;
|
| - test->expected_errors = block_data;
|
| + // The errors start on the next line, and extend until the end of the
|
| + // file.
|
| + std::string prefix =
|
| + std::string("\n") + kExpectedErrors.as_string() + std::string("\n");
|
| + size_t errors_start = file_data.find(prefix);
|
| + if (errors_start == std::string::npos) {
|
| + ADD_FAILURE() << "expected_errors not found";
|
| + return false;
|
| + }
|
| + test->expected_errors = file_data.substr(errors_start + prefix.size());
|
| + break;
|
| + } else {
|
| + ADD_FAILURE() << "Unknown line: " << line_piece;
|
| + return false;
|
| }
|
| }
|
|
|
| - ASSERT_TRUE(has_time);
|
| - ASSERT_TRUE(has_result);
|
| - ASSERT_TRUE(has_trust_anchor);
|
| - ASSERT_TRUE(has_key_purpose);
|
| + if (!has_chain) {
|
| + ADD_FAILURE() << "Missing chain: ";
|
| + return false;
|
| + }
|
| +
|
| + if (!has_trust) {
|
| + ADD_FAILURE() << "Missing last_cert_trust: ";
|
| + return false;
|
| + }
|
| +
|
| + if (!has_time) {
|
| + ADD_FAILURE() << "Missing time: ";
|
| + return false;
|
| + }
|
| +
|
| + if (!has_key_purpose) {
|
| + ADD_FAILURE() << "Missing key_purpose: ";
|
| + return false;
|
| + }
|
| +
|
| + if (!has_errors) {
|
| + ADD_FAILURE() << "Missing errors:";
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| }
|
|
|
| std::string ReadTestFileToString(const std::string& file_path_ascii) {
|
|
|