Index: net/cert/internal/trust_store_nss_unittest.cc |
diff --git a/net/cert/internal/trust_store_nss_unittest.cc b/net/cert/internal/trust_store_nss_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..28ce2ea3b484f23bd08235f9588a265b4e870a2c |
--- /dev/null |
+++ b/net/cert/internal/trust_store_nss_unittest.cc |
@@ -0,0 +1,431 @@ |
+// Copyright 2016 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/trust_store_nss.h" |
+ |
+#include <cert.h> |
+#include <certdb.h> |
+ |
+#include "base/bind.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/run_loop.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "crypto/scoped_test_nss_db.h" |
+#include "net/cert/internal/test_helpers.h" |
+#include "net/cert/internal/trust_store_test_helpers.h" |
+#include "net/cert/scoped_nss_types.h" |
+#include "net/cert/x509_certificate.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+class TrustStoreNSSTest : public testing::Test { |
+ public: |
+ void SetUp() override { |
+ ASSERT_TRUE(test_nssdb_.is_open()); |
+ |
+ ParsedCertificateList chain; |
+ ParsedCertificateList roots; |
+ bool unused_verify_result; |
+ der::GeneralizedTime unused_time; |
+ |
+ ReadCertChainTestFromFile( |
+ "net/data/verify_certificate_chain_unittest/key-rollover-oldchain.pem", |
+ &chain, &roots, &unused_time, &unused_verify_result); |
+ ASSERT_EQ(2U, chain.size()); |
+ ASSERT_EQ(1U, roots.size()); |
+ target_ = chain[0]; |
+ oldintermediate_ = chain[1]; |
+ oldroot_ = roots[0]; |
+ ASSERT_TRUE(target_); |
+ ASSERT_TRUE(oldintermediate_); |
+ ASSERT_TRUE(oldroot_); |
+ |
+ ReadCertChainTestFromFile( |
+ "net/data/verify_certificate_chain_unittest/" |
+ "key-rollover-longrolloverchain.pem", |
+ &chain, &roots, &unused_time, &unused_verify_result); |
+ ASSERT_EQ(4U, chain.size()); |
+ newintermediate_ = chain[1]; |
+ newroot_ = chain[2]; |
+ newrootrollover_ = chain[3]; |
+ ASSERT_TRUE(newintermediate_); |
+ ASSERT_TRUE(newroot_); |
+ ASSERT_TRUE(newrootrollover_); |
+ |
+ trust_store_nss_.reset( |
+ new TrustStoreNSS(base::ThreadTaskRunnerHandle::Get())); |
+ } |
+ |
+ std::string GetUniqueNickname() { |
+ return "trust_store_nss_unittest" + base::UintToString(nickname_counter_++); |
+ } |
+ |
+ void AddCertToNSS(const ParsedCertificate* cert) { |
+ std::string nickname = GetUniqueNickname(); |
+ ScopedCERTCertificate nss_cert( |
+ X509Certificate::CreateOSCertHandleFromBytesWithNickname( |
+ cert->der_cert().AsStringPiece().data(), cert->der_cert().Length(), |
+ nickname.c_str())); |
+ ASSERT_TRUE(nss_cert); |
+ SECStatus srv = |
+ PK11_ImportCert(test_nssdb_.slot(), nss_cert.get(), CK_INVALID_HANDLE, |
+ nickname.c_str(), PR_FALSE /* includeTrust (unused) */); |
+ ASSERT_EQ(SECSuccess, srv); |
+ } |
+ |
+ void AddCertsToNSS() { |
+ AddCertToNSS(target_.get()); |
+ AddCertToNSS(oldintermediate_.get()); |
+ AddCertToNSS(newintermediate_.get()); |
+ AddCertToNSS(oldroot_.get()); |
+ AddCertToNSS(newroot_.get()); |
+ AddCertToNSS(newrootrollover_.get()); |
+ } |
+ |
+ // Trusts |cert|. Assumes the cert was already imported into NSS. |
+ void TrustCert(const ParsedCertificate* cert) { |
+ SECItem der_cert; |
+ der_cert.data = const_cast<uint8_t*>(cert->der_cert().UnsafeData()); |
+ der_cert.len = base::checked_cast<unsigned>(cert->der_cert().Length()); |
+ der_cert.type = siDERCertBuffer; |
+ |
+ // XXX Is this an acceptable way to get the cert for checking trust? Should it |
+ // use CERT_NewTempCertificate instead? Or is there a way to get a trust value |
+ // directly without going through CERT_GetCertTrust? |
+ ScopedCERTCertificate nss_cert( |
+ CERT_FindCertByDERCert(CERT_GetDefaultCertDB(), &der_cert)); |
+ ASSERT_TRUE(nss_cert); |
+ |
+ CERTCertTrust trust = {0}; |
+ trust.sslFlags = |
+ CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA | CERTDB_VALID_CA; |
+ SECStatus srv = |
+ CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nss_cert.get(), &trust); |
+ ASSERT_EQ(SECSuccess, srv); |
+ } |
+ |
+ bool IsTrusted(scoped_refptr<ParsedCertificate> cert) { |
+ bool unused_trusted = false; |
+ TrustResultRecorder trust_results; |
+ std::unique_ptr<TrustStore::Request> trust_req; |
+ trust_store_nss_->IsTrustedCertificate(std::move(cert), trust_results.Callback(), |
+ &unused_trusted, &trust_req); |
+ EXPECT_FALSE(unused_trusted); |
+ if (!trust_req) { |
+ ADD_FAILURE() << "!trust_req"; |
+ return false; |
+ } |
+ trust_results.Run(); |
+ if (trust_results.results().size() != 1U) { |
+ ADD_FAILURE() << "trust_results.results().size() == " |
+ << trust_results.results().size(); |
+ return false; |
+ } |
+ return trust_results.results()[0]; |
+ } |
+ |
+ protected: |
+ scoped_refptr<ParsedCertificate> target_; |
+ scoped_refptr<ParsedCertificate> oldintermediate_; |
+ scoped_refptr<ParsedCertificate> newintermediate_; |
+ scoped_refptr<ParsedCertificate> oldroot_; |
+ scoped_refptr<ParsedCertificate> newroot_; |
+ scoped_refptr<ParsedCertificate> newrootrollover_; |
+ crypto::ScopedTestNSSDB test_nssdb_; |
+ std::unique_ptr<TrustStoreNSS> trust_store_nss_; |
+ unsigned nickname_counter_ = 0; |
+}; |
+ |
+class IssuerResultRecorder { |
+ public: |
+ IssuerResultRecorder() { |
+ run_loops_.push_back(base::WrapUnique(new base::RunLoop())); |
+ } |
+ |
+ void HandleResult(CertIssuerSource::Request* req) { |
+ results_.push_back(req); |
+ run_loops_.back()->Quit(); |
+ run_loops_.push_back(base::WrapUnique(new base::RunLoop())); |
+ } |
+ |
+ const std::vector<CertIssuerSource::Request*>& results() { return results_; } |
+ |
+ CertIssuerSource::Request* Run() { |
+ run_loops_[cur_]->Run(); |
+ return results_[cur_++]; |
+ } |
+ |
+ CertIssuerSource::IssuerCallback Callback() { |
+ return base::Bind(&IssuerResultRecorder::HandleResult, base::Unretained(this)); |
+ } |
+ |
+ private: |
+ std::vector<CertIssuerSource::Request*> results_; |
+ std::vector<std::unique_ptr<base::RunLoop>> run_loops_; |
+ size_t cur_ = 0; |
+}; |
+ |
+void IssuerRequestDeleter(std::unique_ptr<CertIssuerSource::Request>* req_owner, |
+ base::Closure done_callback, |
+ CertIssuerSource::Request* req) { |
+ ASSERT_EQ(req, req_owner->get()); |
+ req_owner->reset(); |
+ done_callback.Run(); |
+} |
+ |
+// Without adding the test certs to the NSS DB, should get no issuer results and |
+// none of the certs should be trusted. |
+TEST_F(TrustStoreNSSTest, CertsNotPresent) { |
+ ParsedCertificateList issuers; |
+ trust_store_nss_->SyncGetIssuersOf(target_.get(), &issuers); |
+ EXPECT_TRUE(issuers.empty()); |
+ |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> req; |
+ trust_store_nss_->AsyncGetIssuersOf(target_, |
+ issuer_results.Callback(), &req); |
+ ASSERT_TRUE(req); |
+ CertIssuerSource::Request* done_req = issuer_results.Run(); |
+ EXPECT_EQ(done_req, req.get()); |
+ scoped_refptr<ParsedCertificate> result_cert; |
+ EXPECT_EQ(CompletionStatus::SYNC, req->GetNext(&result_cert)); |
+ EXPECT_FALSE(result_cert); |
+ |
+ EXPECT_FALSE(IsTrusted(oldroot_)); |
+ EXPECT_FALSE(IsTrusted(newrootrollover_)); |
+ EXPECT_FALSE(IsTrusted(newroot_)); |
+ EXPECT_FALSE(IsTrusted(oldintermediate_)); |
+ EXPECT_FALSE(IsTrusted(newintermediate_)); |
+ EXPECT_FALSE(IsTrusted(target_)); |
+} |
+ |
+// Certs added to the NSS DB, but not trusted. Should get issuer matches, but |
+// nothing should be marked trusted. |
+TEST_F(TrustStoreNSSTest, UntrustedIntermediates) { |
+ AddCertsToNSS(); |
+ |
+ // Request issuers of the target cert. TrustStoreNSS never returns results |
+ // synchronously. |
+ ParsedCertificateList issuers; |
+ trust_store_nss_->SyncGetIssuersOf(target_.get(), &issuers); |
+ EXPECT_TRUE(issuers.empty()); |
+ |
+ // Async get should return both issuers, even though they aren't trusted they |
+ // can be used for path building. |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> issuer_req; |
+ trust_store_nss_->AsyncGetIssuersOf(target_, |
+ issuer_results.Callback(), &issuer_req); |
+ ASSERT_TRUE(issuer_req); |
+ CertIssuerSource::Request* done_issuer_req = issuer_results.Run(); |
+ EXPECT_EQ(done_issuer_req, issuer_req.get()); |
+ scoped_refptr<ParsedCertificate> result_cert1; |
+ ASSERT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert1)); |
+ ASSERT_TRUE(result_cert1); |
+ scoped_refptr<ParsedCertificate> result_cert2; |
+ ASSERT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert2)); |
+ ASSERT_TRUE(result_cert2); |
+ scoped_refptr<ParsedCertificate> result_cert3; |
+ EXPECT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert3)); |
+ EXPECT_FALSE(result_cert3); |
+ |
+ if (result_cert1->der_cert() == oldintermediate_->der_cert()) { |
+ EXPECT_EQ(result_cert2->der_cert(), newintermediate_->der_cert()); |
+ } else { |
+ EXPECT_EQ(result_cert1->der_cert(), newintermediate_->der_cert()); |
+ EXPECT_EQ(result_cert2->der_cert(), oldintermediate_->der_cert()); |
+ } |
+ |
+ // None of the certs are trusted. |
+ EXPECT_FALSE(IsTrusted(oldroot_)); |
+ EXPECT_FALSE(IsTrusted(newrootrollover_)); |
+ EXPECT_FALSE(IsTrusted(newroot_)); |
+ EXPECT_FALSE(IsTrusted(oldintermediate_)); |
+ EXPECT_FALSE(IsTrusted(newintermediate_)); |
+ EXPECT_FALSE(IsTrusted(target_)); |
+} |
+ |
+// Certs added to the NSS DB, and one of the CA certs trusted. |
+// Should get issuer matches, and that one cert should return as trusted. |
+TEST_F(TrustStoreNSSTest, TrustedCA) { |
+ AddCertsToNSS(); |
+ TrustCert(newroot_.get()); |
+ ParsedCertificateList issuers; |
+ |
+ // Getting issuers of the intermediate should return all the matching roots in |
+ // NSS DB, regardless if they were trusted. |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> req; |
+ trust_store_nss_->AsyncGetIssuersOf(oldintermediate_, |
+ issuer_results.Callback(), &req); |
+ ASSERT_TRUE(req); |
+ CertIssuerSource::Request* done_req = issuer_results.Run(); |
+ EXPECT_EQ(done_req, req.get()); |
+ scoped_refptr<ParsedCertificate> result_cert1; |
+ ASSERT_EQ(CompletionStatus::SYNC, req->GetNext(&result_cert1)); |
+ ASSERT_TRUE(result_cert1); |
+ scoped_refptr<ParsedCertificate> result_cert2; |
+ ASSERT_EQ(CompletionStatus::SYNC, req->GetNext(&result_cert2)); |
+ ASSERT_TRUE(result_cert2); |
+ scoped_refptr<ParsedCertificate> result_cert3; |
+ ASSERT_EQ(CompletionStatus::SYNC, req->GetNext(&result_cert3)); |
+ ASSERT_TRUE(result_cert3); |
+ scoped_refptr<ParsedCertificate> result_cert4; |
+ EXPECT_EQ(CompletionStatus::SYNC, req->GetNext(&result_cert4)); |
+ EXPECT_FALSE(result_cert4); |
+ |
+ std::vector<base::StringPiece> expected_issuers = { |
+ oldroot_->der_cert().AsStringPiece(), |
+ newroot_->der_cert().AsStringPiece(), |
+ newrootrollover_->der_cert().AsStringPiece(), |
+ }; |
+ std::vector<base::StringPiece> got_issuers = { |
+ result_cert1->der_cert().AsStringPiece(), |
+ result_cert2->der_cert().AsStringPiece(), |
+ result_cert3->der_cert().AsStringPiece(), |
+ }; |
+ std::sort(expected_issuers.begin(), expected_issuers.end()); |
+ std::sort(got_issuers.begin(), got_issuers.end()); |
+ EXPECT_EQ(got_issuers, expected_issuers); |
+ |
+ // Only newroot is trusted. |
+ EXPECT_FALSE(IsTrusted(oldroot_)); |
+ EXPECT_FALSE(IsTrusted(newrootrollover_)); |
+ EXPECT_TRUE(IsTrusted(newroot_)); |
+ EXPECT_FALSE(IsTrusted(oldintermediate_)); |
+ EXPECT_FALSE(IsTrusted(newintermediate_)); |
+ EXPECT_FALSE(IsTrusted(target_)); |
+} |
+ |
+// Cancel a AsyncGetIssuersOf request before it has returned any results. |
+// Callback should not be called. |
+TEST_F(TrustStoreNSSTest, CancelIssuerRequest) { |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> req; |
+ trust_store_nss_->AsyncGetIssuersOf(target_, |
+ issuer_results.Callback(), &req); |
+ ASSERT_TRUE(req); |
+ req.reset(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_TRUE(issuer_results.results().empty()); |
+} |
+ |
+// Cancel a AsyncGetIssuersOf request after the callback was called, but during |
+// the middle of retrieving the results with GetNext. Mostly just tests that |
+// nothing crashes. |
+TEST_F(TrustStoreNSSTest, CancelIssuerRequestDuringResults) { |
+ AddCertsToNSS(); |
+ |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> issuer_req; |
+ trust_store_nss_->AsyncGetIssuersOf(target_, |
+ issuer_results.Callback(), &issuer_req); |
+ ASSERT_TRUE(issuer_req); |
+ CertIssuerSource::Request* done_issuer_req = issuer_results.Run(); |
+ EXPECT_EQ(done_issuer_req, issuer_req.get()); |
+ scoped_refptr<ParsedCertificate> result_cert1; |
+ ASSERT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert1)); |
+ ASSERT_TRUE(result_cert1); |
+ |
+ issuer_req.reset(); |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+// Cancel a AsyncGetIssuersOf request during the callback. Should not crash. |
+TEST_F(TrustStoreNSSTest, CancelIssuerRequestDuringCallback) { |
+ AddCertsToNSS(); |
+ |
+ base::RunLoop run_loop; |
+ std::unique_ptr<CertIssuerSource::Request> issuer_req; |
+ trust_store_nss_->AsyncGetIssuersOf( |
+ target_, |
+ base::Bind(&IssuerRequestDeleter, &issuer_req, run_loop.QuitClosure()), |
+ &issuer_req); |
+ ASSERT_TRUE(issuer_req); |
+ run_loop.Run(); |
+ ASSERT_FALSE(issuer_req); |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+// Cancel a IsTrustedCertificate request before it has returned any results. |
+// Callback should not be called. |
+TEST_F(TrustStoreNSSTest, CancelTrustRequest) { |
+ bool unused_trusted = false; |
+ std::unique_ptr<TrustStore::Request> trust_req; |
+ TrustResultRecorder trust_results; |
+ trust_store_nss_->IsTrustedCertificate(newroot_, trust_results.Callback(), |
+ &unused_trusted, &trust_req); |
+ ASSERT_TRUE(trust_req); |
+ trust_req.reset(); |
+ base::RunLoop().RunUntilIdle(); |
+ EXPECT_TRUE(trust_results.results().empty()); |
+} |
+ |
+// Cancel a IsTrustedCertificate request during the callback. Should not crash. |
+TEST_F(TrustStoreNSSTest, CancelTrustRequestDuringCallback) { |
+ bool unused_trusted = false; |
+ base::RunLoop run_loop; |
+ std::unique_ptr<TrustStore::Request> trust_req; |
+ trust_store_nss_->IsTrustedCertificate( |
+ newroot_, |
+ base::Bind(&TrustRequestDeleter, &trust_req, run_loop.QuitClosure()), |
+ &unused_trusted, &trust_req); |
+ ASSERT_TRUE(trust_req); |
+ run_loop.Run(); |
+ ASSERT_FALSE(trust_req); |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+ |
+// XXX this test fails |
+TEST_F(TrustStoreNSSTest, Normalziatison) { |
+ ParsedCertificateList chain; |
+ ParsedCertificateList roots; |
+ bool unused_verify_result; |
+ der::GeneralizedTime unused_time; |
+ |
+ ReadCertChainTestFromFile( |
+ "net/data/verify_certificate_chain_unittest/" |
+ "issuer-and-subject-not-byte-for-byte-equal-anchor.pem", |
+ &chain, &roots, &unused_time, &unused_verify_result); |
+ ASSERT_EQ(2U, chain.size()); |
+ ASSERT_EQ(1U, roots.size()); |
+ scoped_refptr<ParsedCertificate> target = chain[0]; |
+ scoped_refptr<ParsedCertificate> intermediate = chain[1]; |
+ scoped_refptr<ParsedCertificate> root = roots[0]; |
+ ASSERT_TRUE(target); |
+ ASSERT_TRUE(intermediate); |
+ ASSERT_TRUE(root); |
+ |
+ EXPECT_NE(intermediate->tbs().issuer_tlv, root->tbs().subject_tlv); |
+ EXPECT_EQ(intermediate->normalized_issuer(), root->normalized_subject()); |
+ |
+ AddCertToNSS(root.get()); |
+ |
+ // Async get should return both issuers, even though they aren't trusted they |
+ // can be used for path building. |
+ IssuerResultRecorder issuer_results; |
+ std::unique_ptr<CertIssuerSource::Request> issuer_req; |
+ trust_store_nss_->AsyncGetIssuersOf(intermediate, issuer_results.Callback(), |
+ &issuer_req); |
+ ASSERT_TRUE(issuer_req); |
+ CertIssuerSource::Request* done_issuer_req = issuer_results.Run(); |
+ EXPECT_EQ(done_issuer_req, issuer_req.get()); |
+ scoped_refptr<ParsedCertificate> result_cert1; |
+ ASSERT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert1)); |
+ ASSERT_TRUE(result_cert1); |
+ scoped_refptr<ParsedCertificate> result_cert2; |
+ EXPECT_EQ(CompletionStatus::SYNC, issuer_req->GetNext(&result_cert2)); |
+ EXPECT_FALSE(result_cert2); |
+ |
+ EXPECT_EQ(result_cert1->der_cert(), root->der_cert()); |
+} |
+ |
+} // namespace |
+ |
+} // namespace net |