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) { |