| // Copyright 2018 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 "content/browser/web_package/signed_exchange_handler.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/web_package/signed_exchange_cert_fetcher_factory.h" |
| #include "content/browser/web_package/signed_exchange_devtools_proxy.h" |
| #include "content/browser/web_package/signed_exchange_signature_verifier.h" |
| #include "content/browser/web_package/signed_exchange_test_utils.h" |
| #include "content/browser/web_package/signed_exchange_utils.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_paths.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/ct_policy_enforcer.h" |
| #include "net/cert/ct_verifier.h" |
| #include "net/cert/mock_cert_verifier.h" |
| #include "net/filter/mock_source_stream.h" |
| #include "net/http/transport_security_state.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "services/network/network_context.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/web_package/signed_exchange_request_matcher.h" |
| |
| using testing::_; |
| using testing::DoAll; |
| using testing::ElementsAre; |
| using testing::Property; |
| using testing::Return; |
| using testing::SetArgPointee; |
| using testing::Truly; |
| |
| namespace content { |
| |
| namespace { |
| |
| const uint64_t kSignatureHeaderDate = 1564272000; // 2019-07-28T00:00:00Z |
| const uint64_t kCertValidityPeriodEnforcementDate = |
| 1564617600; // 2019-08-01T00:00:00Z |
| const int kOutputBufferSize = 4096; |
| |
| constexpr char kTestSxgInnerURL[] = "https://test.example.org/test/"; |
| |
| // "wildcard_example.org.public.pem.cbor" has these dummy data in "ocsp" and |
| // "sct" fields. |
| constexpr base::StringPiece kDummyOCSPDer = "OCSP"; |
| constexpr char kDummySCTBytes[] = { |
| 0x00, 0x05, // Length of the sct list |
| 0x00, 0x03, 'S', 'C', 'T' // List entry: length and body |
| }; |
| constexpr base::StringPiece kDummySCTList(kDummySCTBytes, |
| sizeof(kDummySCTBytes)); |
| |
| class TestBrowserClient : public ContentBrowserClient { |
| bool CanAcceptUntrustedExchangesIfNeeded() override { return true; } |
| }; |
| |
| std::string GetTestFileContents(base::StringPiece name) { |
| base::FilePath path; |
| base::PathService::Get(content::DIR_TEST_DATA, &path); |
| path = path.AppendASCII("sxg").AppendASCII(name); |
| |
| std::string contents; |
| CHECK(base::ReadFileToString(path, &contents)); |
| return contents; |
| } |
| |
| scoped_refptr<net::X509Certificate> LoadCertificate( |
| const std::string& cert_file) { |
| base::FilePath dir_path; |
| base::PathService::Get(content::DIR_TEST_DATA, &dir_path); |
| dir_path = dir_path.AppendASCII("sxg"); |
| |
| base::ScopedAllowBlockingForTesting allow_io; |
| return net::CreateCertificateChainFromFile( |
| dir_path, cert_file, net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE); |
| } |
| |
| class MockSignedExchangeCertFetcherFactory |
| : public SignedExchangeCertFetcherFactory { |
| public: |
| void ExpectFetch(const GURL& cert_url, const std::string& cert_str) { |
| expected_cert_url_ = cert_url; |
| cert_str_ = cert_str; |
| } |
| |
| std::unique_ptr<SignedExchangeCertFetcher> CreateFetcherAndStart( |
| const GURL& cert_url, |
| bool force_fetch, |
| SignedExchangeCertFetcher::CertificateCallback callback, |
| SignedExchangeDevToolsProxy* devtools_proxy, |
| SignedExchangeReporter* reporter) override { |
| EXPECT_EQ(cert_url, expected_cert_url_); |
| |
| auto cert_chain = SignedExchangeCertificateChain::Parse( |
| base::as_bytes(base::make_span(cert_str_)), devtools_proxy); |
| EXPECT_TRUE(cert_chain); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), SignedExchangeLoadResult::kSuccess, |
| std::move(cert_chain))); |
| return nullptr; |
| } |
| |
| private: |
| GURL expected_cert_url_; |
| std::string cert_str_; |
| }; |
| |
| class GMockCertVerifier : public net::CertVerifier { |
| public: |
| // net::CompletionOnceCallback is move-only, which GMock does not support. |
| int Verify(const net::CertVerifier::RequestParams& params, |
| net::CertVerifyResult* verify_result, |
| net::CompletionOnceCallback callback, |
| std::unique_ptr<net::CertVerifier::Request>* out_req, |
| const net::NetLogWithSource& net_log) override { |
| return VerifyImpl(params, verify_result, out_req, net_log); |
| } |
| |
| MOCK_METHOD4(VerifyImpl, |
| int(const net::CertVerifier::RequestParams& params, |
| net::CertVerifyResult* verify_result, |
| std::unique_ptr<net::CertVerifier::Request>* out_req, |
| const net::NetLogWithSource& net_log)); |
| MOCK_METHOD1(SetConfig, void(const net::CertVerifier::Config& config)); |
| }; |
| |
| class MockCTVerifier : public net::CTVerifier { |
| public: |
| MOCK_METHOD6(Verify, |
| void(base::StringPiece hostname, |
| net::X509Certificate* cert, |
| base::StringPiece stapled_ocsp_response, |
| base::StringPiece sct_list_from_tls_extension, |
| net::SignedCertificateTimestampAndStatusList* output_scts, |
| const net::NetLogWithSource& net_log)); |
| }; |
| |
| class MockCTPolicyEnforcer : public net::CTPolicyEnforcer { |
| public: |
| MOCK_METHOD3( |
| CheckCompliance, |
| net::ct::CTPolicyCompliance(net::X509Certificate* cert, |
| const net::ct::SCTList& verified_scts, |
| const net::NetLogWithSource& net_log)); |
| }; |
| |
| // Matcher to compare two net::X509Certificates |
| MATCHER_P(CertEqualsIncludingChain, cert, "") { |
| return arg->EqualsIncludingChain(cert.get()); |
| } |
| |
| } // namespace |
| |
| class SignedExchangeHandlerTest |
| : public ::testing::TestWithParam<net::MockSourceStream::Mode> { |
| public: |
| SignedExchangeHandlerTest() |
| : request_initiator_( |
| url::Origin::Create(GURL("https://sxg.example.com/test.sxg"))) {} |
| |
| virtual std::string ContentType() { |
| return "application/signed-exchange;v=b3"; |
| } |
| |
| void SetUp() override { |
| original_client_ = SetBrowserClientForTesting(&browser_client_); |
| signed_exchange_utils::SetVerificationTimeForTesting( |
| base::Time::UnixEpoch() + |
| base::TimeDelta::FromSeconds(kSignatureHeaderDate)); |
| feature_list_.InitAndEnableFeature(features::kSignedHTTPExchange); |
| |
| source_stream_ = std::make_unique<net::MockSourceStream>(); |
| source_stream_->set_read_one_byte_at_a_time(true); |
| source_ = source_stream_.get(); |
| cert_fetcher_factory_ = |
| std::make_unique<MockSignedExchangeCertFetcherFactory>(); |
| mock_cert_fetcher_factory_ = cert_fetcher_factory_.get(); |
| mock_ct_policy_enforcer_ = std::make_unique<MockCTPolicyEnforcer>(); |
| |
| // Lets mock CT policy enforcer return CT_POLICY_COMPLIES_VIA_SCTS by |
| // default. This may be overridden by test cases. |
| EXPECT_CALL(*mock_ct_policy_enforcer_, CheckCompliance(_, _, _)) |
| .WillRepeatedly( |
| Return(net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS)); |
| } |
| |
| void TearDown() override { |
| if (original_ignore_errors_spki_list_) { |
| SignedExchangeCertificateChain::IgnoreErrorsSPKIList:: |
| SetInstanceForTesting(std::move(original_ignore_errors_spki_list_)); |
| } |
| SignedExchangeHandler::SetNetworkContextForTesting(nullptr); |
| network::NetworkContext::SetCertVerifierForTesting(nullptr); |
| signed_exchange_utils::SetVerificationTimeForTesting( |
| base::Optional<base::Time>()); |
| SetBrowserClientForTesting(original_client_); |
| } |
| |
| void SetCertVerifier(std::unique_ptr<net::CertVerifier> cert_verifier) { |
| cert_verifier_ = std::move(cert_verifier); |
| network::NetworkContext::SetCertVerifierForTesting(cert_verifier_.get()); |
| } |
| |
| void SetIgnoreCertificateErrorsSPKIList(const std::string value) { |
| DCHECK(!original_ignore_errors_spki_list_); |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII( |
| network::switches::kIgnoreCertificateErrorsSPKIList, value); |
| original_ignore_errors_spki_list_ = SignedExchangeCertificateChain:: |
| IgnoreErrorsSPKIList::SetInstanceForTesting( |
| std::make_unique< |
| SignedExchangeCertificateChain::IgnoreErrorsSPKIList>( |
| command_line)); |
| } |
| |
| // Creates a net::CertVerifyResult with some useful default values. |
| net::CertVerifyResult CreateCertVerifyResult() { |
| net::CertVerifyResult result; |
| result.cert_status = net::OK; |
| result.ocsp_result.response_status = net::OCSPVerifyResult::PROVIDED; |
| result.ocsp_result.revocation_status = net::OCSPRevocationStatus::GOOD; |
| return result; |
| } |
| |
| // Sets up a MockCertVerifier that returns |result| for certificate in |
| // |cert_file| and "test.example.org". |
| void SetupMockCertVerifier(const std::string& cert_file, |
| net::CertVerifyResult result) { |
| scoped_refptr<net::X509Certificate> original_cert = |
| LoadCertificate(cert_file); |
| result.verified_cert = original_cert; |
| auto mock_cert_verifier = std::make_unique<net::MockCertVerifier>(); |
| mock_cert_verifier->AddResultForCertAndHost( |
| original_cert, "test.example.org", result, net::OK); |
| SetCertVerifier(std::move(mock_cert_verifier)); |
| } |
| |
| // Sets up |source_| stream with the contents of |file|. |
| void SetSourceStreamContents(base::StringPiece file) { |
| // MockSourceStream doesn't take ownership of the buffer, so we must keep it |
| // alive. |
| source_stream_contents_ = GetTestFileContents(file); |
| source_->AddReadResult(source_stream_contents_.data(), |
| source_stream_contents_.size(), net::OK, GetParam()); |
| source_->AddReadResult(nullptr, 0, net::OK, GetParam()); |
| } |
| |
| // Reads from |stream| until an error occurs or the EOF is reached. |
| // When an error occurs, returns the net error code. When an EOF is reached, |
| // returns the number of bytes read. If |output| is non-null, appends data |
| // read to it. |
| int ReadStream(net::SourceStream* stream, std::string* output) { |
| scoped_refptr<net::IOBuffer> output_buffer = |
| base::MakeRefCounted<net::IOBuffer>(kOutputBufferSize); |
| int bytes_read = 0; |
| while (true) { |
| net::TestCompletionCallback callback; |
| int rv = stream->Read(output_buffer.get(), kOutputBufferSize, |
| callback.callback()); |
| if (rv == net::ERR_IO_PENDING) { |
| while (source_->awaiting_completion()) |
| source_->CompleteNextRead(); |
| rv = callback.WaitForResult(); |
| } |
| if (rv == net::OK) |
| break; |
| if (rv < net::OK) |
| return rv; |
| EXPECT_GT(rv, net::OK); |
| bytes_read += rv; |
| if (output) |
| output->append(output_buffer->data(), rv); |
| } |
| return bytes_read; |
| } |
| |
| int ReadPayloadStream(std::string* output) { |
| return ReadStream(payload_stream_.get(), output); |
| } |
| |
| bool read_header() const { return read_header_; } |
| SignedExchangeLoadResult result() const { return result_; } |
| net::Error error() const { return error_; } |
| const GURL& inner_url() const { return inner_url_; } |
| const network::ResourceResponseHead& resource_response() const { |
| return resource_response_; |
| } |
| |
| // Creates a TestURLRequestContext that uses |mock_ct_policy_enforcer_|. |
| std::unique_ptr<net::TestURLRequestContext> CreateTestURLRequestContext() { |
| auto test_url_request_context = |
| std::make_unique<net::TestURLRequestContext>( |
| true /* delay_initialization */); |
| test_url_request_context->set_ct_policy_enforcer( |
| mock_ct_policy_enforcer_.get()); |
| test_url_request_context->Init(); |
| return test_url_request_context; |
| } |
| |
| void CreateSignedExchangeHandler( |
| std::unique_ptr<net::TestURLRequestContext> context) { |
| url_request_context_ = std::move(context); |
| network_context_ = std::make_unique<network::NetworkContext>( |
| nullptr, network_context_remote_.BindNewPipeAndPassReceiver(), |
| url_request_context_.get(), |
| /*cors_exempt_header_list=*/std::vector<std::string>()); |
| SignedExchangeHandler::SetNetworkContextForTesting(network_context_.get()); |
| |
| handler_ = std::make_unique<SignedExchangeHandler>( |
| true /* is_secure_transport */, true /* has_nosniff */, ContentType(), |
| std::move(source_stream_), |
| base::BindOnce(&SignedExchangeHandlerTest::OnHeaderFound, |
| base::Unretained(this)), |
| std::move(cert_fetcher_factory_), net::LOAD_NORMAL, |
| std::make_unique<blink::SignedExchangeRequestMatcher>( |
| net::HttpRequestHeaders(), std::string() /* accept_langs */), |
| nullptr /* devtools_proxy */, nullptr /* reporter */, |
| base::RepeatingCallback<int(void)>()); |
| } |
| |
| void WaitForHeader() { |
| while (!read_header()) { |
| while (source_->awaiting_completion()) |
| source_->CompleteNextRead(); |
| task_environment_.RunUntilIdle(); |
| } |
| } |
| |
| void ExpectHistogramValues( |
| base::Optional<SignedExchangeSignatureVerifier::Result> signature_result, |
| base::Optional<int32_t> cert_result, |
| base::Optional<net::ct::CTPolicyCompliance> ct_result, |
| base::Optional<net::OCSPVerifyResult::ResponseStatus> |
| ocsp_response_status, |
| base::Optional<net::OCSPRevocationStatus> ocsp_revocation_status) { |
| // CertVerificationResult histogram records negated net::Error code. |
| if (cert_result.has_value()) |
| *cert_result = -*cert_result; |
| |
| ExpectZeroOrUniqueSample("SignedExchange.SignatureVerificationResult", |
| signature_result); |
| ExpectZeroOrUniqueSample("SignedExchange.CertVerificationResult", |
| cert_result); |
| ExpectZeroOrUniqueSample("SignedExchange.CTVerificationResult", ct_result); |
| ExpectZeroOrUniqueSample("SignedExchange.OCSPResponseStatus", |
| ocsp_response_status); |
| ExpectZeroOrUniqueSample("SignedExchange.OCSPRevocationStatus", |
| ocsp_revocation_status); |
| } |
| |
| protected: |
| const base::HistogramTester histogram_tester_; |
| MockSignedExchangeCertFetcherFactory* mock_cert_fetcher_factory_; |
| std::unique_ptr<net::CertVerifier> cert_verifier_; |
| std::unique_ptr<MockCTVerifier> mock_ct_verifier_; |
| std::unique_ptr<MockCTPolicyEnforcer> mock_ct_policy_enforcer_; |
| net::MockSourceStream* source_; |
| std::unique_ptr<SignedExchangeHandler> handler_; |
| |
| private: |
| void OnHeaderFound(SignedExchangeLoadResult result, |
| net::Error error, |
| const GURL& url, |
| const network::ResourceResponseHead& resource_response, |
| std::unique_ptr<net::SourceStream> payload_stream) { |
| read_header_ = true; |
| result_ = result; |
| error_ = error; |
| inner_url_ = url; |
| resource_response_ = resource_response; |
| payload_stream_ = std::move(payload_stream); |
| } |
| |
| template <typename T> |
| void ExpectZeroOrUniqueSample(const std::string& histogram_name, |
| base::Optional<T> expected_value) { |
| if (expected_value.has_value()) |
| histogram_tester_.ExpectUniqueSample(histogram_name, *expected_value, 1); |
| else |
| histogram_tester_.ExpectTotalCount(histogram_name, 0); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| content::BrowserTaskEnvironment task_environment_; |
| TestBrowserClient browser_client_; |
| ContentBrowserClient* original_client_; |
| std::unique_ptr<net::TestURLRequestContext> url_request_context_; |
| std::unique_ptr<network::NetworkContext> network_context_; |
| mojo::Remote<network::mojom::NetworkContext> network_context_remote_; |
| const url::Origin request_initiator_; |
| std::unique_ptr<SignedExchangeCertificateChain::IgnoreErrorsSPKIList> |
| original_ignore_errors_spki_list_; |
| std::unique_ptr<net::MockSourceStream> source_stream_; |
| std::unique_ptr<MockSignedExchangeCertFetcherFactory> cert_fetcher_factory_; |
| |
| bool read_header_ = false; |
| SignedExchangeLoadResult result_; |
| net::Error error_; |
| GURL inner_url_; |
| network::ResourceResponseHead resource_response_; |
| std::unique_ptr<net::SourceStream> payload_stream_; |
| std::string source_stream_contents_; |
| }; |
| |
| TEST_P(SignedExchangeHandlerTest, Empty) { |
| source_->AddReadResult(nullptr, 0, net::OK, GetParam()); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kFallbackURLParseError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_TRUE(inner_url().is_empty()); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, Simple) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| EXPECT_EQ(200, resource_response().headers->response_code()); |
| EXPECT_EQ("text/html", resource_response().mime_type); |
| EXPECT_EQ("utf-8", resource_response().charset); |
| EXPECT_FALSE(resource_response().load_timing.request_start_time.is_null()); |
| EXPECT_FALSE(resource_response().load_timing.request_start.is_null()); |
| EXPECT_FALSE(resource_response().load_timing.send_start.is_null()); |
| EXPECT_FALSE(resource_response().load_timing.send_end.is_null()); |
| EXPECT_FALSE(resource_response().load_timing.receive_headers_end.is_null()); |
| |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(payload, expected_payload); |
| EXPECT_EQ(rv, static_cast<int>(expected_payload.size())); |
| ExpectHistogramValues( |
| SignedExchangeSignatureVerifier::Result::kSuccess, net::OK, |
| net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| net::OCSPVerifyResult::PROVIDED, net::OCSPRevocationStatus::GOOD); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, MimeType) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_hello.txt.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| EXPECT_EQ(200, resource_response().headers->response_code()); |
| EXPECT_EQ("text/plain", resource_response().mime_type); |
| EXPECT_EQ("iso-8859-1", resource_response().charset); |
| |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| |
| std::string expected_payload = GetTestFileContents("hello.txt"); |
| |
| EXPECT_EQ(payload, expected_payload); |
| EXPECT_EQ(rv, static_cast<int>(expected_payload.size())); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, AdditionalContentEncodingShouldBeRejected) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_test.html.gz.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kInvalidIntegrityHeader, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, HeaderParseError) { |
| const uint8_t data[] = {'s', 'x', 'g', '1', '-', 'b', '2', '\0', |
| 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00}; |
| source_->AddReadResult(reinterpret_cast<const char*>(data), sizeof(data), |
| net::OK, GetParam()); |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kFallbackURLParseError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_TRUE(inner_url().is_empty()); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, TruncatedAfterFallbackUrl) { |
| std::string contents = GetTestFileContents("test.example.org_test.sxg"); |
| contents.resize(50); |
| source_->AddReadResult(contents.data(), contents.size(), net::OK, GetParam()); |
| source_->AddReadResult(nullptr, 0, net::OK, GetParam()); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kHeaderParseError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_TRUE(inner_url().is_valid()); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CertWithoutExtensionShouldBeRejected) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org-noext.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256-noext.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_noext_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kCertRequirementsNotMet, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CertValidMoreThan90DaysShouldBeRejected) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents( |
| "test.example.org-validity-too-long.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256-validity-too-long.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_cert_validity_too_long.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kCertValidityPeriodTooLong, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, |
| CertValidMoreThan90DaysShouldBeAllowedByIgnoreErrorsSPKIListFlag) { |
| SetIgnoreCertificateErrorsSPKIList(kPEMECDSAP256SPKIHash); |
| |
| signed_exchange_utils::SetVerificationTimeForTesting( |
| base::Time::UnixEpoch() + |
| base::TimeDelta::FromSeconds(kCertValidityPeriodEnforcementDate)); |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CertWithoutExtensionAllowedByFeatureFlag) { |
| base::test::ScopedFeatureList scoped_feature_list_; |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kAllowSignedHTTPExchangeCertsWithoutExtension); |
| |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org-noext.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256-noext.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_noext_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, |
| CertWithoutExtensionAllowedByIgnoreErrorsSPKIListFlag) { |
| SetIgnoreCertificateErrorsSPKIList(kPEMECDSAP256SPKIHash); |
| |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org-noext.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256-noext.public.pem", |
| CreateCertVerifyResult()); |
| SetSourceStreamContents("test.example.org_noext_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CertSha256Mismatch) { |
| // The certificate is for "127.0.0.1". And the SHA 256 hash of the certificate |
| // is different from the cert-sha256 of the signature in the sxg file. So the |
| // certification verification must fail. |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("127.0.0.1.public.pem.cbor")); |
| |
| // Set the default result of MockCertVerifier to OK, to check that the |
| // verification of SignedExchange must fail even if the certificate is valid. |
| auto mock_cert_verifier = std::make_unique<net::MockCertVerifier>(); |
| mock_cert_verifier->set_default_result(net::OK); |
| SetCertVerifier(std::move(mock_cert_verifier)); |
| |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSignatureVerificationError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| ExpectHistogramValues( |
| SignedExchangeSignatureVerifier::Result::kErrCertificateSHA256Mismatch, |
| base::nullopt /* cert_result */, base::nullopt /* ct_result */, |
| base::nullopt /* ocsp_response_status */, |
| base::nullopt /* ocsp_revocation_status */); |
| |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, VerifyCertFailure) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| |
| // The certificate is for "test.example.org". But the request URL of the sxg |
| // file is "https://test.example.com/test/". So the certification verification |
| // must fail. |
| SetSourceStreamContents("test.example.com_invalid_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kCertVerificationError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ("https://test.example.com/test/", inner_url()); |
| ExpectHistogramValues( |
| SignedExchangeSignatureVerifier::Result::kSuccess, net::ERR_CERT_INVALID, |
| net::ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE, |
| base::nullopt /* ocsp_response_status */, |
| base::nullopt /* ocsp_revocation_status */); |
| |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, OCSPNotChecked) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.ocsp_result.response_status = net::OCSPVerifyResult::NOT_CHECKED; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kOCSPError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, OCSPNotProvided) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.ocsp_result.response_status = net::OCSPVerifyResult::MISSING; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kOCSPError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, OCSPInvalid) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.ocsp_result.response_status = net::OCSPVerifyResult::INVALID_DATE; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kOCSPError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, OCSPRevoked) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.ocsp_result.response_status = net::OCSPVerifyResult::PROVIDED; |
| cert_result.ocsp_result.revocation_status = |
| net::OCSPRevocationStatus::REVOKED; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kOCSPError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| // Test that fetching a signed exchange properly extracts and |
| // attempts to verify both the certificate and the OCSP response. |
| TEST_P(SignedExchangeHandlerTest, CertVerifierParams) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| |
| scoped_refptr<net::X509Certificate> original_cert = |
| LoadCertificate("prime256v1-sha256.public.pem"); |
| net::CertVerifyResult fake_result; |
| fake_result.verified_cert = original_cert; |
| fake_result.cert_status = net::OK; |
| fake_result.ocsp_result.response_status = net::OCSPVerifyResult::PROVIDED; |
| fake_result.ocsp_result.revocation_status = net::OCSPRevocationStatus::GOOD; |
| |
| std::unique_ptr<GMockCertVerifier> gmock_cert_verifier = |
| std::make_unique<GMockCertVerifier>(); |
| EXPECT_CALL( |
| *gmock_cert_verifier, |
| VerifyImpl( |
| AllOf(Property(&net::CertVerifier::RequestParams::ocsp_response, |
| kDummyOCSPDer), |
| Property(&net::CertVerifier::RequestParams::certificate, |
| CertEqualsIncludingChain(original_cert)), |
| Property(&net::CertVerifier::RequestParams::hostname, |
| "test.example.org")), |
| _ /* verify_result */, _ /* out_req */, _ /* net_log */ |
| )) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_result), Return(net::OK))); |
| SetCertVerifier(std::move(gmock_cert_verifier)); |
| |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, NotEnoughSCTsFromPubliclyTrustedCert) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.is_issued_by_known_root = true; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| |
| // Lets the mock CT policy enforcer return CT_POLICY_NOT_ENOUGH_SCTS. |
| EXPECT_CALL(*mock_ct_policy_enforcer_, CheckCompliance(_, _, _)) |
| .WillOnce(Return(net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS)); |
| |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kCTVerificationError, result()); |
| EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error()); |
| EXPECT_EQ(kTestSxgInnerURL, inner_url()); |
| ExpectHistogramValues(SignedExchangeSignatureVerifier::Result::kSuccess, |
| net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED, |
| net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| base::nullopt /* ocsp_response_status */, |
| base::nullopt /* ocsp_revocation_status */); |
| // Drain the MockSourceStream, otherwise its destructer causes DCHECK failure. |
| ReadStream(source_, nullptr); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CTRequirementsMetForPubliclyTrustedCert) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.is_issued_by_known_root = true; |
| cert_result.cert_status = net::CERT_STATUS_IS_EV; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| |
| // The mock CT policy enforcer will return CT_POLICY_COMPLIES_VIA_SCTS, as |
| // configured in SetUp(). |
| |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| // EV status should be preserved. |
| EXPECT_TRUE(resource_response().ssl_info->cert_status & |
| net::CERT_STATUS_IS_EV); |
| EXPECT_FALSE(resource_response().ssl_info->cert_status & |
| net::CERT_STATUS_CT_COMPLIANCE_FAILED); |
| EXPECT_TRUE(resource_response().ssl_info->ct_policy_compliance_required); |
| EXPECT_EQ(net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| resource_response().ssl_info->ct_policy_compliance); |
| ExpectHistogramValues( |
| SignedExchangeSignatureVerifier::Result::kSuccess, net::OK, |
| net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, |
| net::OCSPVerifyResult::PROVIDED, net::OCSPRevocationStatus::GOOD); |
| |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| TEST_P(SignedExchangeHandlerTest, CTNotRequiredForLocalAnchors) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| |
| net::CertVerifyResult cert_result = CreateCertVerifyResult(); |
| cert_result.is_issued_by_known_root = false; // Local anchor. |
| cert_result.cert_status = net::CERT_STATUS_IS_EV; |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", cert_result); |
| |
| // Lets the mock CT policy enforcer return CT_POLICY_NOT_ENOUGH_SCTS. |
| EXPECT_CALL(*mock_ct_policy_enforcer_, CheckCompliance(_, _, _)) |
| .WillOnce(Return(net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS)); |
| |
| SetSourceStreamContents("test.example.org_test.sxg"); |
| |
| CreateSignedExchangeHandler(CreateTestURLRequestContext()); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| // EV status should be removed. |
| EXPECT_FALSE(resource_response().ssl_info->cert_status & |
| net::CERT_STATUS_IS_EV); |
| EXPECT_TRUE(resource_response().ssl_info->cert_status & |
| net::CERT_STATUS_CT_COMPLIANCE_FAILED); |
| EXPECT_FALSE(resource_response().ssl_info->ct_policy_compliance_required); |
| EXPECT_EQ(net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| resource_response().ssl_info->ct_policy_compliance); |
| ExpectHistogramValues( |
| SignedExchangeSignatureVerifier::Result::kSuccess, net::OK, |
| net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS, |
| net::OCSPVerifyResult::PROVIDED, net::OCSPRevocationStatus::GOOD); |
| |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| // Test that SignedExchangeHandler calls CTVerifier and CTPolicyEnforcer |
| // with appropriate arguments. |
| TEST_P(SignedExchangeHandlerTest, CTVerifierParams) { |
| mock_cert_fetcher_factory_->ExpectFetch( |
| GURL("https://cert.example.org/cert.msg"), |
| GetTestFileContents("test.example.org.public.pem.cbor")); |
| scoped_refptr<net::X509Certificate> original_cert = |
| LoadCertificate("prime256v1-sha256.public.pem"); |
| |
| net::SignedCertificateTimestampAndStatusList fake_sct_list; |
| auto good_sct = base::MakeRefCounted<net::ct::SignedCertificateTimestamp>(); |
| fake_sct_list.emplace_back(good_sct, net::ct::SCT_STATUS_OK); |
| auto bad_sct = base::MakeRefCounted<net::ct::SignedCertificateTimestamp>(); |
| fake_sct_list.emplace_back(bad_sct, net::ct::SCT_STATUS_INVALID_TIMESTAMP); |
| |
| EXPECT_CALL(*mock_ct_policy_enforcer_, |
| CheckCompliance(CertEqualsIncludingChain(original_cert), |
| ElementsAre(good_sct), _ /* net_log */)) |
| .WillOnce( |
| Return(net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS)); |
| |
| mock_ct_verifier_ = std::make_unique<MockCTVerifier>(); |
| EXPECT_CALL(*mock_ct_verifier_, |
| Verify(base::StringPiece("test.example.org"), |
| CertEqualsIncludingChain(original_cert), kDummyOCSPDer, |
| kDummySCTList, _ /* output_scts */, _ /* net_log */)) |
| .WillOnce(SetArgPointee<4>(fake_sct_list)); |
| |
| auto test_url_request_context = std::make_unique<net::TestURLRequestContext>( |
| true /* delay_initialization */); |
| test_url_request_context->set_ct_policy_enforcer( |
| mock_ct_policy_enforcer_.get()); |
| test_url_request_context->set_cert_transparency_verifier( |
| mock_ct_verifier_.get()); |
| test_url_request_context->Init(); |
| |
| SetupMockCertVerifier("prime256v1-sha256.public.pem", |
| CreateCertVerifyResult()); |
| std::string contents = GetTestFileContents("test.example.org_test.sxg"); |
| source_->AddReadResult(contents.data(), contents.size(), net::OK, |
| net::MockSourceStream::ASYNC); |
| source_->AddReadResult(nullptr, 0, net::OK, net::MockSourceStream::ASYNC); |
| |
| CreateSignedExchangeHandler(std::move(test_url_request_context)); |
| WaitForHeader(); |
| |
| ASSERT_TRUE(read_header()); |
| EXPECT_EQ(SignedExchangeLoadResult::kSuccess, result()); |
| EXPECT_EQ(net::OK, error()); |
| std::string payload; |
| int rv = ReadPayloadStream(&payload); |
| std::string expected_payload = GetTestFileContents("test.html"); |
| |
| EXPECT_EQ(expected_payload, payload); |
| EXPECT_EQ(static_cast<int>(expected_payload.size()), rv); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(SignedExchangeHandlerTests, |
| SignedExchangeHandlerTest, |
| ::testing::Values(net::MockSourceStream::SYNC, |
| net::MockSourceStream::ASYNC)); |
| |
| } // namespace content |