| // Copyright (c) 2013 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 <memory> |
| |
| #include "base/files/file_path.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "net/cert/cert_verify_result.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/third_party/quic/core/crypto/proof_source.h" |
| #include "net/third_party/quic/core/crypto/proof_verifier.h" |
| #include "net/third_party/quic/test_tools/crypto_test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/boringssl/src/include/openssl/ssl.h" |
| |
| using std::string; |
| |
| namespace net { |
| namespace test { |
| namespace { |
| |
| // TestProofVerifierCallback is a simple callback for a quic::ProofVerifier that |
| // signals a TestCompletionCallback when called and stores the results from the |
| // quic::ProofVerifier in pointers passed to the constructor. |
| class TestProofVerifierCallback : public quic::ProofVerifierCallback { |
| public: |
| TestProofVerifierCallback(TestCompletionCallback* comp_callback, |
| bool* ok, |
| string* error_details) |
| : comp_callback_(comp_callback), ok_(ok), error_details_(error_details) {} |
| |
| void Run(bool ok, |
| const string& error_details, |
| std::unique_ptr<quic::ProofVerifyDetails>* details) override { |
| *ok_ = ok; |
| *error_details_ = error_details; |
| |
| comp_callback_->callback().Run(0); |
| } |
| |
| private: |
| TestCompletionCallback* const comp_callback_; |
| bool* const ok_; |
| string* const error_details_; |
| }; |
| |
| // RunVerification runs |verifier->VerifyProof| and asserts that the result |
| // matches |expected_ok|. |
| void RunVerification(quic::ProofVerifier* verifier, |
| const string& hostname, |
| const uint16_t port, |
| const string& server_config, |
| quic::QuicTransportVersion quic_version, |
| quic::QuicStringPiece chlo_hash, |
| const std::vector<string>& certs, |
| const string& proof, |
| bool expected_ok) { |
| std::unique_ptr<quic::ProofVerifyDetails> details; |
| TestCompletionCallback comp_callback; |
| bool ok; |
| string error_details; |
| std::unique_ptr<quic::ProofVerifyContext> verify_context( |
| quic::test::crypto_test_utils::ProofVerifyContextForTesting()); |
| std::unique_ptr<TestProofVerifierCallback> callback( |
| new TestProofVerifierCallback(&comp_callback, &ok, &error_details)); |
| |
| quic::QuicAsyncStatus status = verifier->VerifyProof( |
| hostname, port, server_config, quic_version, chlo_hash, certs, "", proof, |
| verify_context.get(), &error_details, &details, std::move(callback)); |
| |
| switch (status) { |
| case quic::QUIC_FAILURE: |
| ASSERT_FALSE(expected_ok); |
| ASSERT_NE("", error_details); |
| return; |
| case quic::QUIC_SUCCESS: |
| ASSERT_TRUE(expected_ok); |
| ASSERT_EQ("", error_details); |
| return; |
| case quic::QUIC_PENDING: |
| comp_callback.WaitForResult(); |
| ASSERT_EQ(expected_ok, ok); |
| break; |
| } |
| } |
| |
| class TestCallback : public quic::ProofSource::Callback { |
| public: |
| explicit TestCallback( |
| bool* called, |
| bool* ok, |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain>* chain, |
| quic::QuicCryptoProof* proof) |
| : called_(called), ok_(ok), chain_(chain), proof_(proof) {} |
| |
| void Run( |
| bool ok, |
| const quic::QuicReferenceCountedPointer<quic::ProofSource::Chain>& chain, |
| const quic::QuicCryptoProof& proof, |
| std::unique_ptr<quic::ProofSource::Details> /* details */) override { |
| *ok_ = ok; |
| *chain_ = chain; |
| *proof_ = proof; |
| *called_ = true; |
| } |
| |
| private: |
| bool* called_; |
| bool* ok_; |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain>* chain_; |
| quic::QuicCryptoProof* proof_; |
| }; |
| |
| class ProofTest : public ::testing::TestWithParam<quic::QuicTransportVersion> { |
| }; |
| |
| } // namespace |
| |
| INSTANTIATE_TEST_SUITE_P( |
| QuicTransportVersion, |
| ProofTest, |
| ::testing::ValuesIn(quic::AllSupportedTransportVersions())); |
| |
| TEST_P(ProofTest, Verify) { |
| std::unique_ptr<quic::ProofSource> source( |
| quic::test::crypto_test_utils::ProofSourceForTesting()); |
| std::unique_ptr<quic::ProofVerifier> verifier( |
| quic::test::crypto_test_utils::ProofVerifierForTesting()); |
| |
| const string server_config = "server config bytes"; |
| const string hostname = "test.example.com"; |
| const uint16_t port = 8443; |
| const string first_chlo_hash = "first chlo hash bytes"; |
| const string second_chlo_hash = "first chlo hash bytes"; |
| const quic::QuicTransportVersion quic_version = GetParam(); |
| |
| bool called = false; |
| bool first_called = false; |
| bool ok, first_ok; |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain> chain; |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain> first_chain; |
| string error_details; |
| quic::QuicCryptoProof proof, first_proof; |
| quic::QuicSocketAddress server_addr; |
| |
| std::unique_ptr<quic::ProofSource::Callback> cb( |
| new TestCallback(&called, &ok, &chain, &proof)); |
| std::unique_ptr<quic::ProofSource::Callback> first_cb( |
| new TestCallback(&first_called, &first_ok, &first_chain, &first_proof)); |
| |
| // GetProof here expects the async method to invoke the callback |
| // synchronously. |
| source->GetProof(server_addr, hostname, server_config, quic_version, |
| first_chlo_hash, std::move(first_cb)); |
| source->GetProof(server_addr, hostname, server_config, quic_version, |
| second_chlo_hash, std::move(cb)); |
| ASSERT_TRUE(called); |
| ASSERT_TRUE(first_called); |
| ASSERT_TRUE(ok); |
| ASSERT_TRUE(first_ok); |
| |
| // Check that the proof source is caching correctly: |
| ASSERT_EQ(first_chain->certs, chain->certs); |
| ASSERT_NE(proof.signature, first_proof.signature); |
| ASSERT_EQ(first_proof.leaf_cert_scts, proof.leaf_cert_scts); |
| |
| RunVerification(verifier.get(), hostname, port, server_config, quic_version, |
| first_chlo_hash, chain->certs, proof.signature, true); |
| |
| RunVerification(verifier.get(), "foo.com", port, server_config, quic_version, |
| first_chlo_hash, chain->certs, proof.signature, false); |
| |
| RunVerification(verifier.get(), server_config.substr(1, string::npos), port, |
| server_config, quic_version, first_chlo_hash, chain->certs, |
| proof.signature, false); |
| |
| const string corrupt_signature = "1" + proof.signature; |
| RunVerification(verifier.get(), hostname, port, server_config, quic_version, |
| first_chlo_hash, chain->certs, corrupt_signature, false); |
| |
| std::vector<string> wrong_certs; |
| for (size_t i = 1; i < chain->certs.size(); i++) { |
| wrong_certs.push_back(chain->certs[i]); |
| } |
| |
| RunVerification(verifier.get(), "foo.com", port, server_config, quic_version, |
| first_chlo_hash, wrong_certs, corrupt_signature, false); |
| } |
| |
| namespace { |
| |
| class TestingSignatureCallback : public quic::ProofSource::SignatureCallback { |
| public: |
| TestingSignatureCallback(bool* ok_out, std::string* signature_out) |
| : ok_out_(ok_out), signature_out_(signature_out) {} |
| |
| void Run(bool ok, std::string signature) override { |
| *ok_out_ = ok; |
| *signature_out_ = std::move(signature); |
| } |
| |
| private: |
| bool* ok_out_; |
| std::string* signature_out_; |
| }; |
| |
| } // namespace |
| |
| TEST_P(ProofTest, TlsSignature) { |
| std::unique_ptr<quic::ProofSource> source( |
| quic::test::crypto_test_utils::ProofSourceForTesting()); |
| |
| quic::QuicSocketAddress server_address; |
| const string hostname = "test.example.com"; |
| |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain> chain = |
| source->GetCertChain(server_address, hostname); |
| ASSERT_GT(chain->certs.size(), 0ul); |
| |
| // Generate a value to be signed similar to the example in TLS 1.3 section |
| // 4.4.3. The value to be signed starts with octed 0x20 repeated 64 times, |
| // followed by the context string, followed by a single 0 byte, followed by |
| // the transcript hash. Since there's no TLS stack here, we're using 32 bytes |
| // of 01 as the transcript hash. |
| string to_be_signed(64, ' '); |
| to_be_signed.append("TLS 1.3, server CertificateVerify"); |
| to_be_signed.append(1, '\0'); |
| to_be_signed.append(32, 1); |
| |
| string sig; |
| bool success; |
| std::unique_ptr<TestingSignatureCallback> callback = |
| quic::QuicMakeUnique<TestingSignatureCallback>(&success, &sig); |
| source->ComputeTlsSignature(server_address, hostname, SSL_SIGN_RSA_PSS_SHA256, |
| to_be_signed, std::move(callback)); |
| EXPECT_TRUE(success); |
| |
| // Verify that the signature from ComputeTlsSignature can be verified with the |
| // leaf cert from GetCertChain. |
| const uint8_t* data; |
| const uint8_t* orig_data; |
| orig_data = data = reinterpret_cast<const uint8_t*>(chain->certs[0].data()); |
| bssl::UniquePtr<X509> leaf(d2i_X509(nullptr, &data, chain->certs[0].size())); |
| ASSERT_NE(leaf.get(), nullptr); |
| EXPECT_EQ(data - orig_data, static_cast<ptrdiff_t>(chain->certs[0].size())); |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(leaf.get())); |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| EVP_PKEY_CTX* ctx; |
| ASSERT_EQ(EVP_DigestVerifyInit(md_ctx.get(), &ctx, EVP_sha256(), nullptr, |
| pkey.get()), |
| 1); |
| ASSERT_EQ(EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING), 1); |
| ASSERT_EQ(EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1), 1); |
| ASSERT_EQ(EVP_DigestVerifyUpdate(md_ctx.get(), to_be_signed.data(), |
| to_be_signed.size()), |
| 1); |
| EXPECT_EQ(EVP_DigestVerifyFinal(md_ctx.get(), |
| reinterpret_cast<const uint8_t*>(sig.data()), |
| sig.size()), |
| 1); |
| } |
| |
| TEST_P(ProofTest, UseAfterFree) { |
| std::unique_ptr<quic::ProofSource> source( |
| quic::test::crypto_test_utils::ProofSourceForTesting()); |
| |
| const string server_config = "server config bytes"; |
| const string hostname = "test.example.com"; |
| const string chlo_hash = "proof nonce bytes"; |
| bool called = false; |
| bool ok; |
| quic::QuicReferenceCountedPointer<quic::ProofSource::Chain> chain; |
| string error_details; |
| quic::QuicCryptoProof proof; |
| quic::QuicSocketAddress server_addr; |
| std::unique_ptr<quic::ProofSource::Callback> cb( |
| new TestCallback(&called, &ok, &chain, &proof)); |
| |
| // GetProof here expects the async method to invoke the callback |
| // synchronously. |
| source->GetProof(server_addr, hostname, server_config, GetParam(), chlo_hash, |
| std::move(cb)); |
| ASSERT_TRUE(called); |
| ASSERT_TRUE(ok); |
| |
| // Make sure we can safely access results after deleting where they came from. |
| EXPECT_FALSE(chain->HasOneRef()); |
| source = nullptr; |
| EXPECT_TRUE(chain->HasOneRef()); |
| |
| EXPECT_FALSE(chain->certs.empty()); |
| for (const string& cert : chain->certs) { |
| EXPECT_FALSE(cert.empty()); |
| } |
| } |
| |
| } // namespace test |
| } // namespace net |