| // 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 "base/memory/raw_ptr.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/quiche/src/quiche/quic/core/crypto/proof_source.h" |
| #include "net/third_party/quiche/src/quiche/quic/core/crypto/proof_verifier.h" |
| #include "net/third_party/quiche/src/quiche/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::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: |
| const raw_ptr<TestCompletionCallback> comp_callback_; |
| const raw_ptr<bool> ok_; |
| const raw_ptr<string> 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, |
| absl::string_view 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()); |
| auto callback = std::make_unique<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, |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain>* chain, |
| quic::QuicCryptoProof* proof) |
| : called_(called), ok_(ok), chain_(chain), proof_(proof) {} |
| |
| void Run( |
| bool ok, |
| const quiche::QuicheReferenceCountedPointer<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: |
| raw_ptr<bool> called_; |
| raw_ptr<bool> ok_; |
| raw_ptr<quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain>> |
| chain_; |
| raw_ptr<quic::QuicCryptoProof> proof_; |
| }; |
| |
| class ProofTest : public ::testing::TestWithParam<quic::ParsedQuicVersion> {}; |
| |
| } // namespace |
| |
| INSTANTIATE_TEST_SUITE_P(QuicTransportVersion, |
| ProofTest, |
| ::testing::ValuesIn(quic::AllSupportedVersions()), |
| ::testing::PrintToStringParamName()); |
| |
| 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().transport_version; |
| |
| bool called = false; |
| bool first_called = false; |
| bool ok, first_ok; |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain> chain; |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain> first_chain; |
| string error_details; |
| quic::QuicCryptoProof proof, first_proof; |
| quic::QuicSocketAddress server_addr; |
| quic::QuicSocketAddress client_addr; |
| |
| auto cb = std::make_unique<TestCallback>(&called, &ok, &chain, &proof); |
| auto first_cb = std::make_unique<TestCallback>(&first_called, &first_ok, |
| &first_chain, &first_proof); |
| |
| // GetProof here expects the async method to invoke the callback |
| // synchronously. |
| source->GetProof(server_addr, client_addr, hostname, server_config, |
| quic_version, first_chlo_hash, std::move(first_cb)); |
| source->GetProof(server_addr, client_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, |
| std::unique_ptr<quic::ProofSource::Details> /*details*/) override { |
| *ok_out_ = ok; |
| *signature_out_ = std::move(signature); |
| } |
| |
| private: |
| raw_ptr<bool> ok_out_; |
| raw_ptr<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::QuicSocketAddress client_address; |
| |
| bool cert_matched_sni; |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain> chain = |
| source->GetCertChain(server_address, client_address, hostname, |
| &cert_matched_sni); |
| 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 = |
| std::make_unique<TestingSignatureCallback>(&success, &sig); |
| source->ComputeTlsSignature(server_address, client_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; |
| quiche::QuicheReferenceCountedPointer<quic::ProofSource::Chain> chain; |
| string error_details; |
| quic::QuicCryptoProof proof; |
| quic::QuicSocketAddress server_addr; |
| quic::QuicSocketAddress client_addr; |
| auto cb = std::make_unique<TestCallback>(&called, &ok, &chain, &proof); |
| |
| // GetProof here expects the async method to invoke the callback |
| // synchronously. |
| source->GetProof(server_addr, client_addr, hostname, server_config, |
| GetParam().transport_version, 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 net::test |