Chromium Code Reviews| Index: net/cert/internal/parse_certificate_unittest.cc |
| diff --git a/net/cert/internal/parse_certificate_unittest.cc b/net/cert/internal/parse_certificate_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0dc87fb6bbfae825868ec26760509cfe2493439b |
| --- /dev/null |
| +++ b/net/cert/internal/parse_certificate_unittest.cc |
| @@ -0,0 +1,261 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "net/cert/internal/parse_certificate.h" |
| + |
| +#include "base/base64.h" |
| +#include "base/base_paths.h" |
| +#include "base/files/file_util.h" |
| +#include "base/path_service.h" |
| +#include "net/base/test_data_directory.h" |
| +#include "net/cert/pem_tokenizer.h" |
| +#include "net/der/input.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace net { |
| + |
| +namespace der { |
| + |
| +// These functions are used by GTEST to support EXPECT_EQ() for |
|
davidben
2015/08/11 20:31:57
Nit: I think GTest is the more common capitalizati
eroman
2015/08/12 00:37:10
Done.
|
| +// der::Input. |
| + |
| +void PrintTo(const Input& data, ::std::ostream* os) { |
|
davidben
2015/08/11 20:31:57
#include <ostream>
eroman
2015/08/12 00:37:10
Done.
|
| + std::string b64; |
| + base::Base64Encode( |
| + base::StringPiece(reinterpret_cast<const char*>(data.UnsafeData()), |
| + data.Length()), |
| + &b64); |
| + |
| + *os << "[" << b64 << "]"; |
| +} |
| + |
| +bool operator==(const Input& a, const Input& b) { |
| + return a.Equals(b); |
| +} |
| + |
| +} // namespace der |
| + |
| +namespace { |
| + |
| +// Returns a path to the file |file_name| within the unittest data directory. |
| +base::FilePath GetTestFilePath(const std::string& file_name) { |
| + base::FilePath src_root; |
| + PathService::Get(base::DIR_SOURCE_ROOT, &src_root); |
| + return src_root.Append( |
| + FILE_PATH_LITERAL("net/data/parse_certificate_unittest")) |
| + .AppendASCII(file_name); |
| +} |
| + |
| +// Contains the expected values in a ParsedCertificate. |
| +struct ParsedCertificateExpectations { |
| + std::string signature; |
| + std::string signature_algorithm; |
| +}; |
| + |
| +// Contains the expected values in a ParsedTbsCertificate. |
| +struct ParsedTbsCertificateExpectations { |
| + std::string serial_number; |
| + std::string signature_algorithm; |
| + std::string issuer; |
| + std::string validity; |
| + std::string subject; |
| + std::string spki; |
| + std::string extensions; |
| +}; |
| + |
| +// Reads a test file |file_name| which contains both a certificate |
| +// (CERTIFICATE), and all the expected values for the parsed files (like SPKI). |
| +// On success fills |cert| with the cert DER data, and fills |
| +// |cert_expectations| and |tbs_expectations| with the expected values for its |
| +// consitituent fields. |
| +::testing::AssertionResult LoadTestCertificatePemData( |
| + const std::string& file_name, |
| + std::string* cert, |
| + ParsedCertificateExpectations* cert_expectations, |
| + ParsedTbsCertificateExpectations* tbs_expectations) { |
| + base::FilePath path = GetTestFilePath(file_name); |
| + std::string file_data; |
| + if (!base::ReadFileToString(path, &file_data)) { |
| + return ::testing::AssertionFailure() << "Couldn't read file: " |
| + << path.value(); |
| + } |
| + |
| + const char kCertificateBlock[] = "CERTIFICATE"; |
| + const char kSignatureBlock[] = "SIGNATURE"; |
| + const char kSignatureAlgorithmBlock[] = "SIGNATURE ALGORITHM"; |
| + const char kSerialNumber[] = "SERIAL NUMBER"; |
| + const char kIssuerBlock[] = "ISSUER"; |
| + const char kValidityBlock[] = "VALIDITY"; |
| + const char kSubjectBlock[] = "SUBJECT"; |
| + const char kSpkiBlock[] = "SPKI"; |
| + const char kExtensionsBlock[] = "EXTENSIONS"; |
| + |
| + std::vector<std::string> pem_headers; |
| + pem_headers.push_back(kCertificateBlock); |
| + pem_headers.push_back(kSignatureBlock); |
| + pem_headers.push_back(kSignatureAlgorithmBlock); |
| + pem_headers.push_back(kSerialNumber); |
| + pem_headers.push_back(kIssuerBlock); |
| + pem_headers.push_back(kValidityBlock); |
| + pem_headers.push_back(kSubjectBlock); |
| + pem_headers.push_back(kSpkiBlock); |
| + pem_headers.push_back(kExtensionsBlock); |
| + |
| + PEMTokenizer pem_tokenizer(file_data, pem_headers); |
| + while (pem_tokenizer.GetNext()) { |
| + const std::string& block_type = pem_tokenizer.block_type(); |
| + if (block_type == kCertificateBlock) { |
| + cert->assign(pem_tokenizer.data()); |
| + } else if (block_type == kSignatureBlock) { |
| + cert_expectations->signature.assign(pem_tokenizer.data()); |
| + } else if (block_type == kSignatureAlgorithmBlock) { |
| + cert_expectations->signature_algorithm.assign(pem_tokenizer.data()); |
| + tbs_expectations->signature_algorithm.assign(pem_tokenizer.data()); |
| + } else if (block_type == kSerialNumber) { |
| + tbs_expectations->serial_number.assign(pem_tokenizer.data()); |
| + } else if (block_type == kIssuerBlock) { |
| + tbs_expectations->issuer.assign(pem_tokenizer.data()); |
| + } else if (block_type == kValidityBlock) { |
| + tbs_expectations->validity.assign(pem_tokenizer.data()); |
| + } else if (block_type == kSubjectBlock) { |
| + tbs_expectations->subject.assign(pem_tokenizer.data()); |
| + } else if (block_type == kSpkiBlock) { |
| + tbs_expectations->spki.assign(pem_tokenizer.data()); |
| + } else if (block_type == kExtensionsBlock) { |
| + tbs_expectations->extensions.assign(pem_tokenizer.data()); |
| + } |
| + } |
| + |
| + return ::testing::AssertionSuccess(); |
| +} |
| + |
| +// TODO(eroman): This function was copied from verify_signed_data_unittest.cc |
| +// |
| +// Creates a der::Input from an std::string. The lifetimes are a bit subtle |
| +// when using this function: |
| +// |
| +// The returned der::Input() is only valid so long as the input string is alive |
| +// and is not mutated. |
| +// |
| +// Note that the input parameter has been made a pointer to prevent callers |
| +// from accidentally passing an r-value. |
| +der::Input InputFromString(const std::string* s) { |
| + return der::Input(reinterpret_cast<const uint8_t*>(s->data()), s->size()); |
| +} |
| + |
| +// Verifies that the given |cert| matches |expectations|. |
| +void EnsureParsedCertificate( |
| + const ParsedCertificate& cert, |
| + const ParsedCertificateExpectations& expectations) { |
| + EXPECT_EQ(0, cert.signature_value.unused_bits()); |
| + EXPECT_EQ(InputFromString(&expectations.signature), |
| + cert.signature_value.bytes()); |
| + EXPECT_EQ(InputFromString(&expectations.signature_algorithm), |
| + cert.signature_algorithm_tlv); |
| +} |
| + |
| +// Verifies that the given |tbs| matches |expectations|. |
| +void EnsureParsedTbsCertificate( |
| + const ParsedTbsCertificate& tbs, |
| + const ParsedTbsCertificateExpectations& expectations, |
| + CertificateVersion expected_version) { |
| + EXPECT_EQ(expected_version, tbs.version); |
| + |
| + EXPECT_EQ(InputFromString(&expectations.serial_number), tbs.serial_number); |
| + EXPECT_EQ(InputFromString(&expectations.signature_algorithm), |
| + tbs.signature_algorithm_tlv); |
| + |
| + EXPECT_EQ(InputFromString(&expectations.issuer), tbs.issuer_tlv); |
| + EXPECT_EQ(InputFromString(&expectations.validity), tbs.validity_tlv); |
| + EXPECT_EQ(InputFromString(&expectations.subject), tbs.subject_tlv); |
| + EXPECT_EQ(InputFromString(&expectations.spki), tbs.spki_tlv); |
| + |
| + EXPECT_FALSE(tbs.has_issuer_unique_id); |
| + EXPECT_FALSE(tbs.has_subject_unique_id); |
| + |
| + EXPECT_EQ(InputFromString(&expectations.extensions), tbs.extensions_tlv); |
| + EXPECT_EQ(!expectations.extensions.empty(), tbs.has_extensions); |
| +} |
| + |
| +// Loads certificate data from the PEM file |file_name| and verifies that both |
| +// parsing the Certificate and TBSCertificate succeed. Upon return |cert| and |
| +// |tbs| are filled accordingly. |
| +void EnsureParsingTbsSucceds(const std::string& file_name, |
| + CertificateVersion expected_version) { |
| + std::string cert_data; |
| + ParsedCertificateExpectations cert_expectations; |
| + ParsedTbsCertificateExpectations tbs_expectations; |
| + |
| + // Read the certificate data and test expectations from a single PEM file. |
| + ASSERT_TRUE(LoadTestCertificatePemData( |
| + file_name, &cert_data, &cert_expectations, &tbs_expectations)); |
| + |
| + // Parsing the certificate should succeed. |
| + ParsedCertificate cert; |
| + ASSERT_TRUE(ParseCertificate(InputFromString(&cert_data), &cert)); |
| + |
| + // Ensure that the ParsedCertificate matches expectations. |
| + EnsureParsedCertificate(cert, cert_expectations); |
| + |
| + ParsedTbsCertificate tbs; |
| + ASSERT_TRUE(ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs)); |
| + |
| + // Ensure that the ParsedTbdCertificate matches expectations. |
| + EnsureParsedTbsCertificate(tbs, tbs_expectations, expected_version); |
| +} |
| + |
| +// Loads certificate data from the PEM file |file_name| and verifies that the |
| +// Certificate parsing succeed, however the TBSCertificate parsing fails. |
| +void EnsureParsingTbsFails(const std::string& file_name) { |
| + std::string cert_data; |
| + ParsedCertificateExpectations cert_expectations; |
| + ParsedTbsCertificateExpectations tbs_expectations; |
| + |
| + ASSERT_TRUE(LoadTestCertificatePemData( |
| + file_name, &cert_data, &cert_expectations, &tbs_expectations)); |
| + |
| + ParsedCertificate cert; |
| + ASSERT_TRUE(ParseCertificate(InputFromString(&cert_data), &cert)); |
| + |
| + EnsureParsedCertificate(cert, cert_expectations); |
| + |
| + ParsedTbsCertificate tbs; |
| + ASSERT_FALSE(ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs)); |
| +} |
| + |
| +// Tests parsing a TBSCertificate with a negative serial number. |
| +// |
| +// CAs are not supposed to include negative serial numbers, however RFC 5280 |
| +// says that consumers should be expected to deal with them (so they are allowed |
| +// by ParseTbsCertificate()). |
| +TEST(ParseCertificateTest, NegativeSerialNumber) { |
| + EnsureParsingTbsSucceds("negative_serial.pem", CertificateVersion::V3); |
| +} |
| + |
| +// Tests parsing a TBSCertificate with a serial number that is 21 octets long |
| +// (and the first byte is 0). |
| +TEST(ParseCertificateTest, SerialNumber21OctetsLeading0) { |
| + EnsureParsingTbsFails("serial_number_21_octets_leading_0.pem"); |
| +} |
| + |
| +// Tests parsing a TBSCertificate with a serial number that is 26 octets long |
| +// (and does not contain a leading 0). |
| +TEST(ParseCertificateTest, SerialNumber26Octets) { |
| + EnsureParsingTbsFails("serial_number_26_octets.pem"); |
| +} |
| + |
| +// Tests parsing a TBSCertificate which lacks a version number (causing it to |
| +// default to v1). |
| +TEST(ParseCertificateTest, CertificateVersion1) { |
| + EnsureParsingTbsSucceds("version1.pem", CertificateVersion::V1); |
| +} |
| + |
| +// Tests parsing a TBSCertificate with version 3. |
| +TEST(ParseCertificateTest, CertificateVersion3) { |
| + EnsureParsingTbsSucceds("version3.pem", CertificateVersion::V3); |
| +} |
| + |
| +} // namespace |
| + |
| +} // namespace net |