Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(135)

Unified Diff: net/cert/cert_verify_proc_unittest.cc

Issue 2731603002: Check TBSCertificate.algorithm and Certificate.signatureAlgorithm for (Closed)
Patch Set: fix Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/cert/cert_verify_proc_unittest.cc
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index 18596457988892f3a1d8276f2760e09fc49a1b7f..6396d33d77f8db37f612e861f4ecf1dccc95b435 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -24,8 +24,11 @@
#include "net/cert/cert_verify_result.h"
#include "net/cert/crl_set.h"
#include "net/cert/crl_set_storage.h"
+#include "net/cert/internal/signature_algorithm.h"
#include "net/cert/test_root_certs.h"
#include "net/cert/x509_certificate.h"
+#include "net/der/input.h"
+#include "net/der/parser.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_certificate_data.h"
@@ -272,18 +275,11 @@ TEST_P(CertVerifyProcInternalTest, DISABLED_EVVerification) {
return;
}
- CertificateList certs =
- CreateCertificateListFromFile(GetTestCertsDirectory(), "comodo.chain.pem",
- X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
- ASSERT_EQ(3U, certs.size());
-
- X509Certificate::OSCertHandles intermediates;
- intermediates.push_back(certs[1]->os_cert_handle());
- intermediates.push_back(certs[2]->os_cert_handle());
-
- scoped_refptr<X509Certificate> comodo_chain =
- X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
- intermediates);
+ auto comodo_chain = CreateCertificateChainFromFile(
+ GetTestCertsDirectory(), "comodo.chain.pem",
+ X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+ ASSERT_TRUE(comodo_chain);
+ ASSERT_EQ(2U, comodo_chain->GetIntermediateCertificates().size());
scoped_refptr<CRLSet> crl_set(CRLSet::ForTesting(false, NULL, ""));
CertVerifyResult verify_result;
@@ -383,13 +379,9 @@ TEST_P(CertVerifyProcInternalTest, RejectExpiredCert) {
ScopedTestRoot test_root(
ImportCertFromFile(certs_dir, "root_ca_cert.pem").get());
- CertificateList certs = CreateCertificateListFromFile(
- certs_dir, "expired_cert.pem", X509Certificate::FORMAT_AUTO);
- ASSERT_EQ(1U, certs.size());
-
- X509Certificate::OSCertHandles intermediates;
- scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
- certs[0]->os_cert_handle(), intermediates);
+ auto cert = CreateCertificateChainFromFile(certs_dir, "expired_cert.pem",
+ X509Certificate::FORMAT_AUTO);
mattm 2017/03/04 02:34:26 ASSERT_TRUE(cert)
eroman 2017/03/07 23:43:00 Sorry for including these unrelated refactors -- I
+ ASSERT_EQ(0U, cert->GetIntermediateCertificates().size());
int flags = 0;
CertVerifyResult verify_result;
@@ -612,14 +604,10 @@ TEST_P(CertVerifyProcInternalTest, NameConstraintsOk) {
ASSERT_EQ(1U, ca_cert_list.size());
ScopedTestRoot test_root(ca_cert_list[0].get());
- CertificateList cert_list = CreateCertificateListFromFile(
- GetTestCertsDirectory(), "name_constraint_good.pem",
- X509Certificate::FORMAT_AUTO);
- ASSERT_EQ(1U, cert_list.size());
-
- X509Certificate::OSCertHandles intermediates;
- scoped_refptr<X509Certificate> leaf = X509Certificate::CreateFromHandle(
- cert_list[0]->os_cert_handle(), intermediates);
+ auto leaf = CreateCertificateChainFromFile(GetTestCertsDirectory(),
+ "name_constraint_good.pem",
+ X509Certificate::FORMAT_AUTO);
mattm 2017/03/04 02:34:26 ASSERT_TRUE(leaf)
eroman 2017/03/07 23:43:00 Ditto.
+ ASSERT_EQ(0U, leaf->GetIntermediateCertificates().size());
int flags = 0;
CertVerifyResult verify_result;
@@ -634,6 +622,299 @@ TEST_P(CertVerifyProcInternalTest, NameConstraintsOk) {
EXPECT_EQ(0U, verify_result.cert_status);
}
+// 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).
+//
+// X509 certificates contain two redundant descriptors for the signature
+// algorithm. Although RFC 5280 states that they must be the same, in practice
+// system verifiers don't check this. This can lead to confusion as the
+// signature itself may be checked using algorithm A, but then subsequent
+// consumers may inspect the certificate and look at signature algorithm B when
+// making policy choices.
+class CertVerifyProcInspectSignatureAlgorithmsTest : public ::testing::Test {
+ protected:
+ // In the test setup, SHA384 is given special treatment as an unkown
mattm 2017/03/04 02:34:26 unknown
eroman 2017/03/07 23:43:00 Done.
+ // algorithm.
+ static constexpr DigestAlgorithm kUnknownDigestAlgorithm =
+ DigestAlgorithm::Sha384;
+
+ struct CertParams {
+ DigestAlgorithm cert_algorithm;
+ DigestAlgorithm tbs_algorithm;
+
+ static CertParams Sha256() {
+ return {
+ DigestAlgorithm::Sha256, DigestAlgorithm::Sha256,
+ };
+ }
+ };
+
+ // Shorthand for VerifyChain() where only the leaf parameters need
+ // to be specified.
+ WARN_UNUSED_RESULT int VerifyLeaf(DigestAlgorithm cert_algorithm,
+ DigestAlgorithm tbs_algorithm) {
+ return VerifyChain({{cert_algorithm, tbs_algorithm},
+ // Shouldn't matter what root uses.
+ {DigestAlgorithm::Sha1, DigestAlgorithm::Sha256}});
+ }
+
+ // Manufactures a certificate chain where each certificate has the indicated
+ // signature algorithms, and then returns the result of verifying this chain.
+ WARN_UNUSED_RESULT 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 verify_result;
+ verify_result.verified_cert = chain;
mattm 2017/03/04 02:34:27 MockCertVerifyProc already sets verified_cert
eroman 2017/03/07 23:43:00 Done -- thanks!
+
+ scoped_refptr<CertVerifyProc> verify_proc =
+ new MockCertVerifyProc(verify_result);
+
+ return verify_proc->Verify(chain.get(), "test.example.com", std::string(),
+ flags, NULL, CertificateList(), &verify_result);
mattm 2017/03/04 02:34:26 Using the same CertVerifyResult object as was used
eroman 2017/03/07 23:43:00 Done.
+ }
+
+ private:
+ // Overwrites the AlgorithmIdentifier pointed to by |algorithm_sequence| with
+ // |algorithm|. Note this violates the constness of StringPiece.
+ WARN_UNUSED_RESULT 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.
+ WARN_UNUSED_RESULT 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.AsStringPiece();
+ 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 = "name_constraint_good.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. This will be the basis
+ // for building a modified certificate.
+ std::string cert_der;
+ if (!X509Certificate::GetDEREncoded(cert->os_cert_handle(), &cert_der)) {
+ ADD_FAILURE() << "Failed getting DER bytes";
+ return nullptr;
+ }
+
+ // 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 the certificate a new serial number -- this is necessary because
+ // otherwise importing it may fail. Changing the high order byte is good
+ // enough for these tests.
+ *const_cast<char*>(serial_value.data()) = next_serial_number_++;
mattm 2017/03/04 02:34:26 what happens if you run the test under gtest_repea
eroman 2017/03/07 23:43:00 I expect that once it has created around 255 certi
mattm 2017/03/08 01:41:34 Hmm. I would say it's worth avoiding that. I guess
eroman 2017/03/08 01:59:28 Here are two possible routes forward: (1) I can m
eroman 2017/03/08 17:09:24 Done. I went with static serial numbers, and may
+
+ // 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(cert_der.data(), cert_der.size());
+ }
+
+ 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;
+ }
+
+ X509Certificate::OSCertHandles intermediates;
+ for (size_t i = 1; i < certs.size(); ++i)
+ intermediates.push_back(certs[i]->os_cert_handle());
+
+ return X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ }
+
+ static int next_serial_number_;
+};
+
+// Global counter to ensure that during test runs the same issuer/serialnumber
+// isn't used when manufacturing certificates.
+int CertVerifyProcInspectSignatureAlgorithmsTest::next_serial_number_ = 9;
+
+// 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));
+}
+
+// Mismatched signature algorithms in the intermediate certificate.
+//
+// Certificate.signatureAlgorithm: sha1WithRSASignature
+// TBSCertificate.algorithm: sha256WithRSAEncryption
+TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha1Sha256) {
+ int rv = VerifyChain({CertParams::Sha256(),
mattm 2017/03/04 02:34:26 imo this is more confusing than just putting {Dige
eroman 2017/03/07 23:43:00 Done.
+ {DigestAlgorithm::Sha1, DigestAlgorithm::Sha256},
+ CertParams::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 = VerifyChain({CertParams::Sha256(),
+ {DigestAlgorithm::Sha256, DigestAlgorithm::Sha1},
+ CertParams::Sha256()});
+ 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));
+}
+
TEST_P(CertVerifyProcInternalTest, NameConstraintsFailure) {
if (!SupportsReturningVerifiedChain()) {
LOG(INFO) << "Skipping this test in this platform.";

Powered by Google App Engine
This is Rietveld 408576698