Verify the certificate using CertVerifier for Signed Exchange


Bug: 803774
Change-Id: I9c888a507913c54e11f3f6b3014001586151eed9
Reviewed-on: https://chromium-review.googlesource.com/920803
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Kouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541432}
diff --git a/content/browser/loader/navigation_url_loader_network_service.cc b/content/browser/loader/navigation_url_loader_network_service.cc
index 89b03eec..a3322f6b 100644
--- a/content/browser/loader/navigation_url_loader_network_service.cc
+++ b/content/browser/loader/navigation_url_loader_network_service.cc
@@ -339,7 +339,8 @@
               resource_context_, url_request_context_getter),
           base::BindRepeating(
               &URLLoaderRequestController::CreateURLLoaderThrottles,
-              base::Unretained(this))));
+              base::Unretained(this)),
+          url_request_context_getter));
     }
 
     // The ResourceDispatcherHostImpl can be null in unit tests.
@@ -482,7 +483,8 @@
           default_url_loader_factory_getter_->GetNetworkFactory(),
           base::BindRepeating(
               &URLLoaderRequestController::CreateURLLoaderThrottles,
-              base::Unretained(this))));
+              base::Unretained(this)),
+          url_request_context_getter));
     }
 
     Restart();
diff --git a/content/browser/web_package/signed_exchange_handler.cc b/content/browser/web_package/signed_exchange_handler.cc
index da36b355..d164bb5 100644
--- a/content/browser/web_package/signed_exchange_handler.cc
+++ b/content/browser/web_package/signed_exchange_handler.cc
@@ -5,20 +5,28 @@
 #include "content/browser/web_package/signed_exchange_handler.h"
 
 #include "base/feature_list.h"
+#include "base/strings/string_number_conversions.h"
 #include "components/cbor/cbor_reader.h"
 #include "content/browser/loader/merkle_integrity_source_stream.h"
 #include "content/browser/web_package/signed_exchange_cert_fetcher.h"
 #include "content/browser/web_package/signed_exchange_consts.h"
 #include "content/browser/web_package/signed_exchange_header_parser.h"
+#include "content/browser/web_package/signed_exchange_signature_verifier.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/shared_url_loader_factory.h"
 #include "content/public/common/url_loader_throttle.h"
 #include "mojo/public/cpp/system/string_data_pipe_producer.h"
 #include "net/base/io_buffer.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
 #include "net/cert/x509_certificate.h"
 #include "net/filter/source_stream.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
+#include "net/ssl/ssl_config.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
@@ -31,6 +39,8 @@
 
 constexpr char kMiHeader[] = "MI";
 
+net::CertVerifier* g_cert_verifier_for_testing = nullptr;
+
 cbor::CBORValue BytestringFromString(base::StringPiece in_string) {
   return cbor::CBORValue(
       std::vector<uint8_t>(in_string.begin(), in_string.end()));
@@ -65,17 +75,28 @@
 
 }  // namespace
 
+// static
+void SignedExchangeHandler::SetCertVerifierForTesting(
+    net::CertVerifier* cert_verifier) {
+  g_cert_verifier_for_testing = cert_verifier;
+}
+
 SignedExchangeHandler::SignedExchangeHandler(
     std::unique_ptr<net::SourceStream> body,
     ExchangeHeadersCallback headers_callback,
     url::Origin request_initiator,
     scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-    URLLoaderThrottlesGetter url_loader_throttles_getter)
+    URLLoaderThrottlesGetter url_loader_throttles_getter,
+    scoped_refptr<net::URLRequestContextGetter> request_context_getter)
     : headers_callback_(std::move(headers_callback)),
       source_(std::move(body)),
       request_initiator_(std::move(request_initiator)),
       url_loader_factory_(std::move(url_loader_factory)),
       url_loader_throttles_getter_(std::move(url_loader_throttles_getter)),
+      request_context_getter_(std::move(request_context_getter)),
+      net_log_(net::NetLogWithSource::Make(
+          request_context_getter_->GetURLRequestContext()->net_log(),
+          net::NetLogSourceType::CERT_VERIFIER_JOB)),
       weak_factory_(this) {
   DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
 
@@ -114,6 +135,8 @@
     return;
   }
 
+  // TODO(https://crbug.com/815019): This logic can cause browser-side DoS. We
+  // MUST fix it before shipping.
   original_body_string_.append(read_buf_->data(), result);
 
   if (completed_syncly) {
@@ -209,8 +232,18 @@
   }
   base::StringPiece status_code_str =
       status_iter->second.GetBytestringAsString();
+  int status_code;
+  if (!base::StringToInt(status_code_str, &status_code)) {
+    DVLOG(1) << "Invalid status code " << status_code_str;
+    return false;
+  }
 
   base::Optional<std::vector<SignedExchangeHeaderParser::Signature>> signatures;
+  // TODO(https://crbug.com/803774): Rename
+  // SignedExchangeSignatureVerifier::Input to SignedExchangeSignatureHeader and
+  // implement the following logic in SignedExchangeHeaderParser.
+  auto verifier_input =
+      std::make_unique<SignedExchangeSignatureVerifier::Input>();
 
   std::string fake_header_str("HTTP/1.1 ");
   status_code_str.AppendToString(&fake_header_str);
@@ -225,13 +258,18 @@
     if (name == kMethodKey)
       continue;
 
+    // TODO(https://crbug.com/803774): Stop going through
+    // net::HttpResponseHeaders but just use
+    // SignedExchangeSignatureVerifier::Input.
+    // TODO(https://crbug.com/803774): Check that name and value don't contain
+    // special characters using IsValidHeaderName and IsValidHeaderValue.
     name.AppendToString(&fake_header_str);
     fake_header_str.append(": ");
     value.AppendToString(&fake_header_str);
     fake_header_str.append("\r\n");
-    if (std::string(name) == "signature") {
+    if (name == "signature")
       signatures = SignedExchangeHeaderParser::ParseSignature(value);
-    }
+    verifier_input->response_headers[name.as_string()] = value.as_string();
   }
   fake_header_str.append("\r\n");
   response_head_.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
@@ -256,15 +294,26 @@
   if (!signatures || signatures->empty())
     return false;
 
+  verifier_input->method = request_method_;
+  verifier_input->url = request_url_.spec();
+  verifier_input->response_code = status_code;
+  verifier_input->signature = (*signatures)[0];
+
+  // Copy |cert_url| to keep after |verifier_input| is passed to base::BindOnce.
+  const GURL cert_url = verifier_input->signature.cert_url;
+  // TODO(https://crbug.com/819467): When we will support ed25519Key, |cert_url|
+  // may be empty.
+  DCHECK(cert_url.is_valid());
+
   DCHECK(url_loader_factory_);
   DCHECK(url_loader_throttles_getter_);
   std::vector<std::unique_ptr<URLLoaderThrottle>> throttles =
       std::move(url_loader_throttles_getter_).Run();
   cert_fetcher_ = SignedExchangeCertFetcher::CreateAndStart(
-      std::move(url_loader_factory_), std::move(throttles),
-      GURL((*signatures)[0].cert_url), std::move(request_initiator_), false,
+      std::move(url_loader_factory_), std::move(throttles), cert_url,
+      std::move(request_initiator_), false,
       base::BindOnce(&SignedExchangeHandler::OnCertReceived,
-                     base::Unretained(this)));
+                     base::Unretained(this), std::move(verifier_input)));
   return true;
 }
 
@@ -276,17 +325,73 @@
 }
 
 void SignedExchangeHandler::OnCertReceived(
+    std::unique_ptr<SignedExchangeSignatureVerifier::Input> verifier_input,
     scoped_refptr<net::X509Certificate> cert) {
   if (!cert) {
     DVLOG(1) << "Fetching certificate error";
     RunErrorCallback(net::ERR_FAILED);
     return;
   }
-  // TODO(https://crbug.com/803774): Verify the certificate and generate a
-  // SSLInfo.
+  verifier_input->certificate = cert;
+  if (SignedExchangeSignatureVerifier::Verify(*verifier_input) !=
+      SignedExchangeSignatureVerifier::Result::kSuccess) {
+    RunErrorCallback(net::ERR_FAILED);
+    return;
+  }
+  net::URLRequestContext* request_context =
+      request_context_getter_->GetURLRequestContext();
+  if (!request_context) {
+    RunErrorCallback(net::ERR_CONTEXT_SHUT_DOWN);
+    return;
+  }
+
+  unverified_cert_ = cert;
+
+  net::SSLConfig config;
+  request_context->ssl_config_service()->GetSSLConfig(&config);
+
+  net::CertVerifier* cert_verifier = g_cert_verifier_for_testing
+                                         ? g_cert_verifier_for_testing
+                                         : request_context->cert_verifier();
+  // TODO(https://crbug.com/815024): Get the OCSP response from the
+  // “status_request” extension of the main-certificate, and check the lifetime
+  // (nextUpdate - thisUpdate) is less than 7 days.
+  int result = cert_verifier->Verify(
+      net::CertVerifier::RequestParams(
+          unverified_cert_, request_url_.host(), config.GetCertVerifyFlags(),
+          std::string() /* ocsp_response */, net::CertificateList()),
+      net::SSLConfigService::GetCRLSet().get(), &cert_verify_result_,
+      base::BindRepeating(&SignedExchangeHandler::OnCertVerifyComplete,
+                          base::Unretained(this)),
+      &cert_verifier_request_, net_log_);
+  // TODO(https://crbug.com/803774): Avoid these recursive patterns by using
+  // explicit state machines (eg: DoLoop() in //net).
+  if (result != net::ERR_IO_PENDING)
+    OnCertVerifyComplete(result);
+}
+
+void SignedExchangeHandler::OnCertVerifyComplete(int result) {
+  if (result != net::OK) {
+    DVLOG(1) << "Certificate verification error: " << result;
+    RunErrorCallback(static_cast<net::Error>(result));
+    return;
+  }
+
+  net::SSLInfo ssl_info;
+  ssl_info.cert = cert_verify_result_.verified_cert;
+  ssl_info.unverified_cert = unverified_cert_;
+  ssl_info.cert_status = cert_verify_result_.cert_status;
+  ssl_info.is_issued_by_known_root =
+      cert_verify_result_.is_issued_by_known_root;
+  ssl_info.public_key_hashes = cert_verify_result_.public_key_hashes;
+  ssl_info.ocsp_result = cert_verify_result_.ocsp_result;
+  ssl_info.is_fatal_cert_error =
+      net::IsCertStatusError(ssl_info.cert_status) &&
+      !net::IsCertStatusMinorError(ssl_info.cert_status);
+  // TODO(https://crbug.com/815025): Verify the Certificate Transparency status.
   std::move(headers_callback_)
       .Run(net::OK, request_url_, request_method_, response_head_,
-           std::move(mi_stream_), base::Optional<net::SSLInfo>());
+           std::move(mi_stream_), ssl_info);
 }
 
 }  // namespace content
diff --git a/content/browser/web_package/signed_exchange_handler.h b/content/browser/web_package/signed_exchange_handler.h
index 648fb01..c1fc20f 100644
--- a/content/browser/web_package/signed_exchange_handler.h
+++ b/content/browser/web_package/signed_exchange_handler.h
@@ -9,18 +9,26 @@
 
 #include "base/callback.h"
 #include "base/optional.h"
+#include "content/browser/web_package/signed_exchange_signature_verifier.h"
 #include "content/common/content_export.h"
 #include "content/public/common/shared_url_loader_factory.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/completion_callback.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/log/net_log_with_source.h"
 #include "net/ssl/ssl_info.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
 namespace net {
+class CertVerifier;
+class CertVerifyResult;
 class SourceStream;
-}
+class URLRequestContextGetter;
+class X509Certificate;
+}  // namespace net
 
 namespace content {
 
@@ -29,7 +37,7 @@
 class URLLoaderThrottle;
 class MerkleIntegritySourceStream;
 
-// IMPORTANT: Currenly SignedExchangeHandler doesn't implement any verifying
+// IMPORTANT: Currenly SignedExchangeHandler partially implements the verifying
 // logic.
 // TODO(https://crbug.com/803774): Implement verifying logic.
 class CONTENT_EXPORT SignedExchangeHandler {
@@ -46,6 +54,10 @@
   using URLLoaderThrottlesGetter = base::RepeatingCallback<
       std::vector<std::unique_ptr<content::URLLoaderThrottle>>()>;
 
+  // TODO(https://crbug.com/817187): Find a more sophisticated way to use a
+  // MockCertVerifier in browser tests instead of using the static method.
+  static void SetCertVerifierForTesting(net::CertVerifier* cert_verifier);
+
   // Once constructed |this| starts reading the |body| and parses the response
   // as a signed HTTP exchange. The response body of the exchange can be read
   // from |payload_stream| passed to |headers_callback|. |url_loader_factory|
@@ -56,7 +68,8 @@
       ExchangeHeadersCallback headers_callback,
       url::Origin request_initiator,
       scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-      URLLoaderThrottlesGetter url_loader_throttles_getter);
+      URLLoaderThrottlesGetter url_loader_throttles_getter,
+      scoped_refptr<net::URLRequestContextGetter> request_context_getter);
   ~SignedExchangeHandler();
 
  protected:
@@ -68,7 +81,10 @@
   bool RunHeadersCallback();
   void RunErrorCallback(net::Error);
 
-  void OnCertReceived(scoped_refptr<net::X509Certificate> cert);
+  void OnCertReceived(
+      std::unique_ptr<SignedExchangeSignatureVerifier::Input> verifier_input,
+      scoped_refptr<net::X509Certificate> cert);
+  void OnCertVerifyComplete(int result);
 
   // Signed exchange contents.
   GURL request_url_;
@@ -94,6 +110,19 @@
 
   std::unique_ptr<SignedExchangeCertFetcher> cert_fetcher_;
 
+  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+
+  scoped_refptr<net::X509Certificate> unverified_cert_;
+
+  // CertVerifyResult must be freed after the Request has been destructed.
+  // So |cert_verify_result_| must be written before |cert_verifier_request_|.
+  net::CertVerifyResult cert_verify_result_;
+  std::unique_ptr<net::CertVerifier::Request> cert_verifier_request_;
+
+  // TODO(https://crbug.com/767450): figure out what we should do for NetLog
+  // with Network Service.
+  net::NetLogWithSource net_log_;
+
   base::WeakPtrFactory<SignedExchangeHandler> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SignedExchangeHandler);
diff --git a/content/browser/web_package/signed_exchange_header_parser.cc b/content/browser/web_package/signed_exchange_header_parser.cc
index c1e7f87..78d8d88 100644
--- a/content/browser/web_package/signed_exchange_header_parser.cc
+++ b/content/browser/web_package/signed_exchange_header_parser.cc
@@ -9,6 +9,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
+#include "crypto/sha2.h"
 
 namespace content {
 
@@ -213,12 +214,42 @@
     Signature& sig = signatures.back();
     sig.label = value.label;
     sig.sig = value.params["sig"];
+    if (sig.sig.empty()) {
+      DVLOG(1) << "ParseSignature: 'sig' parameter is not set";
+      return base::nullopt;
+    }
     sig.integrity = value.params["integrity"];
-    sig.cert_url = value.params["certUrl"];
-    sig.cert_sha256 = value.params["certSha256"];
-    sig.ed25519_key = value.params["ed25519Key"];
-    sig.validity_url = value.params["validityUrl"];
-
+    if (sig.integrity.empty()) {
+      DVLOG(1) << "ParseSignature: 'integrity' parameter is not set";
+      return base::nullopt;
+    }
+    sig.cert_url = GURL(value.params["certUrl"]);
+    if (!sig.cert_url.is_valid()) {
+      // TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
+      // params may not have "certUrl".
+      DVLOG(1) << "ParseSignature: 'certUrl' parameter is not a valid URL: "
+               << value.params["certUrl"];
+      return base::nullopt;
+    }
+    const std::string cert_sha256_string = value.params["certSha256"];
+    if (cert_sha256_string.size() != crypto::kSHA256Length) {
+      // TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
+      // params may not have "certSha256".
+      DVLOG(1) << "ParseSignature: 'certSha256' parameter is not a valid "
+                  "SHA-256 digest.";
+      return base::nullopt;
+    }
+    net::SHA256HashValue cert_sha256;
+    memcpy(&cert_sha256.data, cert_sha256_string.data(), crypto::kSHA256Length);
+    sig.cert_sha256 = std::move(cert_sha256);
+    // TODO(https://crbug.com/819467): Support ed25519key.
+    // sig.ed25519_key = value.params["ed25519Key"];
+    sig.validity_url = GURL(value.params["validityUrl"]);
+    if (!sig.validity_url.is_valid()) {
+      DVLOG(1) << "ParseSignature: 'validityUrl' parameter is not a valid URL: "
+               << value.params["validityUrl"];
+      return base::nullopt;
+    }
     if (!base::StringToUint64(value.params["date"], &sig.date)) {
       DVLOG(1) << "ParseSignature: 'date' parameter is not a number: "
                << sig.date;
@@ -229,18 +260,6 @@
                << sig.expires;
       return base::nullopt;
     }
-
-    bool has_cert = !sig.cert_url.empty() && !sig.cert_sha256.empty();
-    bool has_ed25519_key = !sig.ed25519_key.empty();
-    if (sig.sig.empty() || sig.integrity.empty() || sig.validity_url.empty() ||
-        (!has_cert && !has_ed25519_key)) {
-      DVLOG(1) << "ParseSignature: incomplete signature";
-      return base::nullopt;
-    }
-    if (has_cert && has_ed25519_key) {
-      DVLOG(1) << "ParseSignature: signature has both certUrl and ed25519Key";
-      return base::nullopt;
-    }
   }
   return signatures;
 }
diff --git a/content/browser/web_package/signed_exchange_header_parser.h b/content/browser/web_package/signed_exchange_header_parser.h
index e2a884ceb..74a34d5 100644
--- a/content/browser/web_package/signed_exchange_header_parser.h
+++ b/content/browser/web_package/signed_exchange_header_parser.h
@@ -8,10 +8,13 @@
 #include <stdint.h>
 #include <string>
 #include <vector>
+
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/strings/string_piece.h"
 #include "content/common/content_export.h"
+#include "net/base/hash_value.h"
+#include "url/gurl.h"
 
 namespace content {
 
@@ -27,10 +30,11 @@
     std::string label;
     std::string sig;
     std::string integrity;
-    std::string cert_url;
-    std::string cert_sha256;
-    std::string ed25519_key;
-    std::string validity_url;
+    GURL cert_url;
+    base::Optional<net::SHA256HashValue> cert_sha256;
+    // TODO(https://crbug.com/819467): Support ed25519key.
+    // std::string ed25519_key;
+    GURL validity_url;
     uint64_t date;
     uint64_t expires;
   };
diff --git a/content/browser/web_package/signed_exchange_header_parser_unittest.cc b/content/browser/web_package/signed_exchange_header_parser_unittest.cc
index eb6e18c8..7569563 100644
--- a/content/browser/web_package/signed_exchange_header_parser_unittest.cc
+++ b/content/browser/web_package/signed_exchange_header_parser_unittest.cc
@@ -55,13 +55,15 @@
       " certUrl=\"https://example.com/oldcerts\";"
       " certSha256=*W7uB969dFW3Mb5ZefPS9Tq5ZbH5iSmOILpjv2qEArmI;"
       " date=1511128380; expires=1511733180,"
-      "srisig;"
-      " sig=*lGZVaJJM5f2oGczFlLmBdKTDL+QADza4BgeO494ggACYJOvrof6uh5OJCcwKrk7DK+"
-      "LBch0jssDYPp5CLc1SDA;"
+      "sig2;"
+      " sig=*MEQCIGjZRqTRf9iKNkGFyzRMTFgwf/BrY2ZNIP/dykhUV0aYAiBTXg+8wujoT4n/W+"
+      "cNgb7pGqQvIUGYZ8u8HZJ5YH26Qg;"
       " integrity=\"mi\";"
       " validityUrl=\"https://example.com/resource.validity.1511128380\";"
-      " ed25519Key=*zsSevyFsxyZHiUluVBDd4eypdRLTqyWRVOJuuKUz+A8;"
+      " certUrl=\"https://example.com/newcerts\";"
+      " certSha256=*J/lEm9kNRODdCmINbvitpvdYKNQ+YgBj99DlYp4fEXw;"
       " date=1511128380; expires=1511733180";
+
   const uint8_t decoded_sig1[] = {
       0x30, 0x45, 0x02, 0x21, 0x00, 0xd7, 0x94, 0x8d, 0xa0, 0x37, 0x74, 0x4d,
       0x06, 0x58, 0x05, 0x8a, 0xe4, 0x4d, 0x16, 0x96, 0x57, 0x70, 0x32, 0x1a,
@@ -69,24 +71,24 @@
       0x2c, 0x02, 0x20, 0x6b, 0xd0, 0xec, 0x54, 0xe3, 0x0c, 0xfa, 0x0e, 0x58,
       0xa7, 0x01, 0x01, 0x74, 0x65, 0xb7, 0xb1, 0x2f, 0x9b, 0xbe, 0x79, 0x80,
       0x24, 0x98, 0x92, 0x33, 0x08, 0x6e, 0x05, 0xda, 0xa9, 0xe5, 0x46};
-  const uint8_t decoded_cert_sha256[] = {
-      0x5b, 0xbb, 0x81, 0xf7, 0xaf, 0x5d, 0x15, 0x6d, 0xcc, 0x6f, 0x96,
-      0x5e, 0x7c, 0xf4, 0xbd, 0x4e, 0xae, 0x59, 0x6c, 0x7e, 0x62, 0x4a,
-      0x63, 0x88, 0x2e, 0x98, 0xef, 0xda, 0xa1, 0x00, 0xae, 0x62};
+  const net::SHA256HashValue decoded_cert_sha256_1 = {
+      {0x5b, 0xbb, 0x81, 0xf7, 0xaf, 0x5d, 0x15, 0x6d, 0xcc, 0x6f, 0x96,
+       0x5e, 0x7c, 0xf4, 0xbd, 0x4e, 0xae, 0x59, 0x6c, 0x7e, 0x62, 0x4a,
+       0x63, 0x88, 0x2e, 0x98, 0xef, 0xda, 0xa1, 0x00, 0xae, 0x62}};
   const uint8_t decoded_sig2[] = {
-      0x94, 0x66, 0x55, 0x68, 0x92, 0x4c, 0xe5, 0xfd, 0xa8, 0x19, 0xcc,
-      0xc5, 0x94, 0xb9, 0x81, 0x74, 0xa4, 0xc3, 0x2f, 0xe4, 0x00, 0x0f,
-      0x36, 0xb8, 0x06, 0x07, 0x8e, 0xe3, 0xde, 0x20, 0x80, 0x00, 0x98,
-      0x24, 0xeb, 0xeb, 0xa1, 0xfe, 0xae, 0x87, 0x93, 0x89, 0x09, 0xcc,
-      0x0a, 0xae, 0x4e, 0xc3, 0x2b, 0xe2, 0xc1, 0x72, 0x1d, 0x23, 0xb2,
-      0xc0, 0xd8, 0x3e, 0x9e, 0x42, 0x2d, 0xcd, 0x52, 0x0c};
-  const uint8_t decoded_ed25519_key[] = {
-      0xce, 0xc4, 0x9e, 0xbf, 0x21, 0x6c, 0xc7, 0x26, 0x47, 0x89, 0x49,
-      0x6e, 0x54, 0x10, 0xdd, 0xe1, 0xec, 0xa9, 0x75, 0x12, 0xd3, 0xab,
-      0x25, 0x91, 0x54, 0xe2, 0x6e, 0xb8, 0xa5, 0x33, 0xf8, 0x0f};
+      0x30, 0x44, 0x02, 0x20, 0x68, 0xd9, 0x46, 0xa4, 0xd1, 0x7f, 0xd8, 0x8a,
+      0x36, 0x41, 0x85, 0xcb, 0x34, 0x4c, 0x4c, 0x58, 0x30, 0x7f, 0xf0, 0x6b,
+      0x63, 0x66, 0x4d, 0x20, 0xff, 0xdd, 0xca, 0x48, 0x54, 0x57, 0x46, 0x98,
+      0x02, 0x20, 0x53, 0x5e, 0x0f, 0xbc, 0xc2, 0xe8, 0xe8, 0x4f, 0x89, 0xff,
+      0x5b, 0xe7, 0x0d, 0x81, 0xbe, 0xe9, 0x1a, 0xa4, 0x2f, 0x21, 0x41, 0x98,
+      0x67, 0xcb, 0xbc, 0x1d, 0x92, 0x79, 0x60, 0x7d, 0xba, 0x42};
+  const net::SHA256HashValue decoded_cert_sha256_2 = {
+      {0x27, 0xf9, 0x44, 0x9b, 0xd9, 0x0d, 0x44, 0xe0, 0xdd, 0x0a, 0x62,
+       0x0d, 0x6e, 0xf8, 0xad, 0xa6, 0xf7, 0x58, 0x28, 0xd4, 0x3e, 0x62,
+       0x00, 0x63, 0xf7, 0xd0, 0xe5, 0x62, 0x9e, 0x1f, 0x11, 0x7c}};
 
   auto signatures = SignedExchangeHeaderParser::ParseSignature(hdr_string);
-  EXPECT_TRUE(signatures.has_value());
+  ASSERT_TRUE(signatures.has_value());
   ASSERT_EQ(signatures->size(), 2u);
 
   EXPECT_EQ(signatures->at(0).label, "sig1");
@@ -97,22 +99,19 @@
   EXPECT_EQ(signatures->at(0).validity_url,
             "https://example.com/resource.validity.1511128380");
   EXPECT_EQ(signatures->at(0).cert_url, "https://example.com/oldcerts");
-  EXPECT_EQ(signatures->at(0).cert_sha256,
-            std::string(reinterpret_cast<const char*>(decoded_cert_sha256),
-                        sizeof(decoded_cert_sha256)));
+  EXPECT_EQ(signatures->at(0).cert_sha256, decoded_cert_sha256_1);
   EXPECT_EQ(signatures->at(0).date, 1511128380ul);
   EXPECT_EQ(signatures->at(0).expires, 1511733180ul);
 
-  EXPECT_EQ(signatures->at(1).label, "srisig");
+  EXPECT_EQ(signatures->at(1).label, "sig2");
   EXPECT_EQ(signatures->at(1).sig,
             std::string(reinterpret_cast<const char*>(decoded_sig2),
                         sizeof(decoded_sig2)));
   EXPECT_EQ(signatures->at(1).integrity, "mi");
   EXPECT_EQ(signatures->at(1).validity_url,
             "https://example.com/resource.validity.1511128380");
-  EXPECT_EQ(signatures->at(1).ed25519_key,
-            std::string(reinterpret_cast<const char*>(decoded_ed25519_key),
-                        sizeof(decoded_ed25519_key)));
+  EXPECT_EQ(signatures->at(1).cert_url, "https://example.com/newcerts");
+  EXPECT_EQ(signatures->at(1).cert_sha256, decoded_cert_sha256_2);
   EXPECT_EQ(signatures->at(1).date, 1511128380ul);
   EXPECT_EQ(signatures->at(1).expires, 1511733180ul);
 }
@@ -131,21 +130,6 @@
   EXPECT_FALSE(signatures.has_value());
 }
 
-TEST_F(SignedExchangeHeaderParserTest, HasBothCertAndEd25519Key) {
-  const char hdr_string[] =
-      "sig1;"
-      " sig=*MEUCIQDXlI2gN3RNBlgFiuRNFpZXcDIaUpX6HIEwcZEc0cZYLAIga9DsVOMM+"
-      "g5YpwEBdGW3sS+bvnmAJJiSMwhuBdqp5UY;"
-      " integrity=\"mi\";"
-      " validityUrl=\"https://example.com/resource.validity.1511128380\";"
-      " certUrl=\"https://example.com/oldcerts\";"
-      " certSha256=*W7uB969dFW3Mb5ZefPS9Tq5ZbH5iSmOILpjv2qEArmI;"
-      " ed25519Key=*zsSevyFsxyZHiUluVBDd4eypdRLTqyWRVOJuuKUz+A8;"
-      " date=1511128380; expires=1511733180";
-  auto signatures = SignedExchangeHeaderParser::ParseSignature(hdr_string);
-  EXPECT_FALSE(signatures.has_value());
-}
-
 TEST_F(SignedExchangeHeaderParserTest, DuplicatedParam) {
   const char hdr_string[] =
       "sig1;"
@@ -161,4 +145,46 @@
   EXPECT_FALSE(signatures.has_value());
 }
 
+TEST_F(SignedExchangeHeaderParserTest, InvalidCertURL) {
+  const char hdr_string[] =
+      "sig1;"
+      " sig=*MEUCIQDXlI2gN3RNBlgFiuRNFpZXcDIaUpX6HIEwcZEc0cZYLAIga9DsVOMM+"
+      "g5YpwEBdGW3sS+bvnmAJJiSMwhuBdqp5UY;"
+      " integrity=\"mi\";"
+      " validityUrl=\"https://example.com/resource.validity.1511128380\";"
+      " certUrl=\"https:://example.com/oldcerts\";"
+      " certSha256=*W7uB969dFW3Mb5ZefPS9Tq5ZbH5iSmOILpjv2qEArmI;"
+      " date=1511128380; expires=1511733180";
+  auto signatures = SignedExchangeHeaderParser::ParseSignature(hdr_string);
+  EXPECT_FALSE(signatures.has_value());
+}
+
+TEST_F(SignedExchangeHeaderParserTest, InvalidValidityUrl) {
+  const char hdr_string[] =
+      "sig1;"
+      " sig=*MEUCIQDXlI2gN3RNBlgFiuRNFpZXcDIaUpX6HIEwcZEc0cZYLAIga9DsVOMM+"
+      "g5YpwEBdGW3sS+bvnmAJJiSMwhuBdqp5UY;"
+      " integrity=\"mi\";"
+      " validityUrl=\"https:://example.com/resource.validity.1511128380\";"
+      " certUrl=\"https://example.com/oldcerts\";"
+      " certSha256=*W7uB969dFW3Mb5ZefPS9Tq5ZbH5iSmOILpjv2qEArmI;"
+      " date=1511128380; expires=1511733180";
+  auto signatures = SignedExchangeHeaderParser::ParseSignature(hdr_string);
+  EXPECT_FALSE(signatures.has_value());
+}
+
+TEST_F(SignedExchangeHeaderParserTest, InvalidCertSHA256) {
+  const char hdr_string[] =
+      "sig1;"
+      " sig=*MEUCIQDXlI2gN3RNBlgFiuRNFpZXcDIaUpX6HIEwcZEc0cZYLAIga9DsVOMM+"
+      "g5YpwEBdGW3sS+bvnmAJJiSMwhuBdqp5UY;"
+      " integrity=\"mi\";"
+      " validityUrl=\"https://example.com/resource.validity.1511128380\";"
+      " certUrl=\"https://example.com/oldcerts\";"
+      " certSha256=*W7uB969dFW3Mb5ZefPS9;"
+      " date=1511128380; expires=1511733180";
+  auto signatures = SignedExchangeHeaderParser::ParseSignature(hdr_string);
+  EXPECT_FALSE(signatures.has_value());
+}
+
 }  // namespace content
diff --git a/content/browser/web_package/signed_exchange_signature_verifier.cc b/content/browser/web_package/signed_exchange_signature_verifier.cc
index 64254bc..4967cee 100644
--- a/content/browser/web_package/signed_exchange_signature_verifier.cc
+++ b/content/browser/web_package/signed_exchange_signature_verifier.cc
@@ -116,15 +116,19 @@
   cbor::CBORValue::MapValue map;
   // 11.4.1. "If certSha256 is set: The text string "certSha256" to the byte
   // string value of certSha256." [spec text]
-  if (!input.signature.cert_sha256.empty()) {
-    map.insert_or_assign(cbor::CBORValue(kCertSha256Key),
-                         cbor::CBORValue(input.signature.cert_sha256,
-                                         cbor::CBORValue::Type::BYTE_STRING));
+  if (input.signature.cert_sha256.has_value()) {
+    map.insert_or_assign(
+        cbor::CBORValue(kCertSha256Key),
+        cbor::CBORValue(
+            base::StringPiece(reinterpret_cast<const char*>(
+                                  input.signature.cert_sha256->data),
+                              sizeof(input.signature.cert_sha256->data)),
+            cbor::CBORValue::Type::BYTE_STRING));
   }
   // 11.4.2. "The text string "validityUrl" to the byte string value of
   // validityUrl." [spec text]
   map.insert_or_assign(cbor::CBORValue(kValidityUrlKey),
-                       cbor::CBORValue(input.signature.validity_url,
+                       cbor::CBORValue(input.signature.validity_url.spec(),
                                        cbor::CBORValue::Type::BYTE_STRING));
   // 11.4.3. "The text string "date" to the integer value of date." [spec text]
   if (!base::IsValueInRangeForNumericType<int64_t>(input.signature.date))
@@ -214,14 +218,30 @@
 
 SignedExchangeSignatureVerifier::Input::~Input() = default;
 
-bool SignedExchangeSignatureVerifier::Verify(const Input& input) {
-  // TODO(crbug.com/803774): Verify input.signature.certSha256 against
-  // input.certificate.
+SignedExchangeSignatureVerifier::Result SignedExchangeSignatureVerifier::Verify(
+    const Input& input) {
+  if (!input.certificate) {
+    DVLOG(1) << "No certificate set.";
+    return Result::kErrNoCertificate;
+  }
+
+  if (!input.signature.cert_sha256.has_value()) {
+    DVLOG(1) << "No certSha256 set.";
+    return Result::kErrNoCertificateSHA256;
+  }
+
+  // The main-certificate is the first certificate in certificate-chain.
+  if (*input.signature.cert_sha256 !=
+      net::X509Certificate::CalculateFingerprint256(
+          input.certificate->cert_buffer())) {
+    DVLOG(1) << "certSha256 mismatch.";
+    return Result::kErrCertificateSHA256Mismatch;
+  }
 
   auto message = GenerateSignedMessage(input);
   if (!message) {
     DVLOG(1) << "Failed to reconstruct signed message.";
-    return false;
+    return Result::kErrInvalidSignatureFormat;
   }
 
   const std::string& sig = input.signature.sig;
@@ -230,18 +250,18 @@
                           sig.size()),
           *message, input.certificate)) {
     DVLOG(1) << "Failed to verify signature \"sig\".";
-    return false;
+    return Result::kErrSignatureVerificationFailed;
   }
 
   if (!base::EqualsCaseInsensitiveASCII(input.signature.integrity, "mi")) {
     DVLOG(1)
         << "The current implemention only supports \"mi\" integrity scheme.";
-    return false;
+    return Result::kErrInvalidSignatureIntegrity;
   }
 
   // TODO(crbug.com/803774): Verify input.signature.{date,expires}.
 
-  return true;
+  return Result::kSuccess;
 }
 
 base::Optional<std::vector<uint8_t>>
diff --git a/content/browser/web_package/signed_exchange_signature_verifier.h b/content/browser/web_package/signed_exchange_signature_verifier.h
index d30dde0..e16fa838 100644
--- a/content/browser/web_package/signed_exchange_signature_verifier.h
+++ b/content/browser/web_package/signed_exchange_signature_verifier.h
@@ -44,7 +44,17 @@
     scoped_refptr<net::X509Certificate> certificate;
   };
 
-  static bool Verify(const Input& input);
+  enum class Result {
+    kSuccess,
+    kErrNoCertificate,
+    kErrNoCertificateSHA256,
+    kErrCertificateSHA256Mismatch,
+    kErrInvalidSignatureFormat,
+    kErrSignatureVerificationFailed,
+    kErrInvalidSignatureIntegrity
+  };
+
+  static Result Verify(const Input& input);
 
   static base::Optional<std::vector<uint8_t>> EncodeCanonicalExchangeHeaders(
       const Input& input);
diff --git a/content/browser/web_package/signed_exchange_signature_verifier_unittest.cc b/content/browser/web_package/signed_exchange_signature_verifier_unittest.cc
index 17ade678..54af4fe 100644
--- a/content/browser/web_package/signed_exchange_signature_verifier_unittest.cc
+++ b/content/browser/web_package/signed_exchange_signature_verifier_unittest.cc
@@ -128,15 +128,26 @@
   input.signature = (*signature)[0];
   input.certificate = certlist[0];
 
-  EXPECT_TRUE(SignedExchangeSignatureVerifier::Verify(input));
+  EXPECT_EQ(SignedExchangeSignatureVerifier::Result::kSuccess,
+            SignedExchangeSignatureVerifier::Verify(input));
 
   SignedExchangeSignatureVerifier::Input corrupted_input(input);
   corrupted_input.url = "https://example.com/bad.html";
-  EXPECT_FALSE(SignedExchangeSignatureVerifier::Verify(corrupted_input));
+  EXPECT_EQ(
+      SignedExchangeSignatureVerifier::Result::kErrSignatureVerificationFailed,
+      SignedExchangeSignatureVerifier::Verify(corrupted_input));
 
   SignedExchangeSignatureVerifier::Input badsig_input(input);
   badsig_input.signature.sig[0]++;
-  EXPECT_FALSE(SignedExchangeSignatureVerifier::Verify(badsig_input));
+  EXPECT_EQ(
+      SignedExchangeSignatureVerifier::Result::kErrSignatureVerificationFailed,
+      SignedExchangeSignatureVerifier::Verify(badsig_input));
+
+  SignedExchangeSignatureVerifier::Input badsigsha256_input(input);
+  badsigsha256_input.signature.cert_sha256->data[0]++;
+  EXPECT_EQ(
+      SignedExchangeSignatureVerifier::Result::kErrCertificateSHA256Mismatch,
+      SignedExchangeSignatureVerifier::Verify(badsigsha256_input));
 }
 
 }  // namespace
diff --git a/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.h b/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.h
index 8ae02c93..5de19e5a 100644
--- a/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.h
+++ b/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.h
@@ -14,7 +14,6 @@
 
 namespace content {
 
-class URLRequestContextGetter;
 class ResourceContext;
 
 // A URLLoaderFactory used for fetching certificate of signed HTTP exchange
diff --git a/content/browser/web_package/web_package_loader.cc b/content/browser/web_package/web_package_loader.cc
index d055039..17bfb72ff 100644
--- a/content/browser/web_package/web_package_loader.cc
+++ b/content/browser/web_package/web_package_loader.cc
@@ -15,6 +15,7 @@
 #include "content/public/common/shared_url_loader_factory.h"
 #include "net/cert/cert_status_flags.h"
 #include "net/http/http_util.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
@@ -79,7 +80,8 @@
     url::Origin request_initiator,
     uint32_t url_loader_options,
     scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-    URLLoaderThrottlesGetter url_loader_throttles_getter)
+    URLLoaderThrottlesGetter url_loader_throttles_getter,
+    scoped_refptr<net::URLRequestContextGetter> request_context_getter)
     : original_response_timing_info_(
           std::make_unique<ResponseTimingInfo>(original_response)),
       forwarding_client_(std::move(forwarding_client)),
@@ -88,6 +90,7 @@
       url_loader_options_(url_loader_options),
       url_loader_factory_(std::move(url_loader_factory)),
       url_loader_throttles_getter_(std::move(url_loader_throttles_getter)),
+      request_context_getter_(std::move(request_context_getter)),
       weak_factory_(this) {
   DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
   url_loader_.Bind(std::move(endpoints->url_loader));
@@ -171,7 +174,8 @@
       base::BindOnce(&WebPackageLoader::OnHTTPExchangeFound,
                      weak_factory_.GetWeakPtr()),
       std::move(request_initiator_), std::move(url_loader_factory_),
-      std::move(url_loader_throttles_getter_));
+      std::move(url_loader_throttles_getter_),
+      std::move(request_context_getter_));
 }
 
 void WebPackageLoader::OnComplete(
diff --git a/content/browser/web_package/web_package_loader.h b/content/browser/web_package/web_package_loader.h
index 80fbddf9..2ebf52d5 100644
--- a/content/browser/web_package/web_package_loader.h
+++ b/content/browser/web_package/web_package_loader.h
@@ -17,6 +17,7 @@
 
 namespace net {
 class SourceStream;
+class URLRequestContextGetter;
 }  // namespace net
 
 namespace content {
@@ -38,13 +39,15 @@
   using URLLoaderThrottlesGetter = base::RepeatingCallback<
       std::vector<std::unique_ptr<content::URLLoaderThrottle>>()>;
 
-  WebPackageLoader(const network::ResourceResponseHead& original_response,
-                   network::mojom::URLLoaderClientPtr forwarding_client,
-                   network::mojom::URLLoaderClientEndpointsPtr endpoints,
-                   url::Origin request_initiator,
-                   uint32_t url_loader_options,
-                   scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-                   URLLoaderThrottlesGetter url_loader_throttles_getter);
+  WebPackageLoader(
+      const network::ResourceResponseHead& original_response,
+      network::mojom::URLLoaderClientPtr forwarding_client,
+      network::mojom::URLLoaderClientEndpointsPtr endpoints,
+      url::Origin request_initiator,
+      uint32_t url_loader_options,
+      scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
+      URLLoaderThrottlesGetter url_loader_throttles_getter,
+      scoped_refptr<net::URLRequestContextGetter> request_context_getter);
   ~WebPackageLoader() override;
 
   // network::mojom::URLLoaderClient implementation
@@ -123,6 +126,7 @@
   const uint32_t url_loader_options_;
   scoped_refptr<SharedURLLoaderFactory> url_loader_factory_;
   URLLoaderThrottlesGetter url_loader_throttles_getter_;
+  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
 
   base::Optional<net::SSLInfo> ssl_info_;
 
diff --git a/content/browser/web_package/web_package_prefetch_handler.cc b/content/browser/web_package/web_package_prefetch_handler.cc
index 0827f95..fea0efb 100644
--- a/content/browser/web_package/web_package_prefetch_handler.cc
+++ b/content/browser/web_package/web_package_prefetch_handler.cc
@@ -56,7 +56,8 @@
   web_package_loader_ = std::make_unique<WebPackageLoader>(
       response, std::move(client), std::move(endpoints),
       std::move(request_initiator), network::mojom::kURLLoadOptionNone,
-      std::move(url_loader_factory), loader_throttles_getter);
+      std::move(url_loader_factory), loader_throttles_getter,
+      request_context_getter);
 }
 
 WebPackagePrefetchHandler::~WebPackagePrefetchHandler() = default;
diff --git a/content/browser/web_package/web_package_request_handler.cc b/content/browser/web_package/web_package_request_handler.cc
index 91a5860..a8a62be 100644
--- a/content/browser/web_package/web_package_request_handler.cc
+++ b/content/browser/web_package/web_package_request_handler.cc
@@ -14,6 +14,7 @@
 #include "content/public/common/shared_url_loader_factory.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 
@@ -30,11 +31,13 @@
     url::Origin request_initiator,
     uint32_t url_loader_options,
     scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-    URLLoaderThrottlesGetter url_loader_throttles_getter)
+    URLLoaderThrottlesGetter url_loader_throttles_getter,
+    scoped_refptr<net::URLRequestContextGetter> request_context_getter)
     : request_initiator_(std::move(request_initiator)),
       url_loader_options_(url_loader_options),
       url_loader_factory_(url_loader_factory),
       url_loader_throttles_getter_(std::move(url_loader_throttles_getter)),
+      request_context_getter_(std::move(request_context_getter)),
       weak_factory_(this) {
   DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
 }
@@ -79,7 +82,8 @@
   web_package_loader_ = std::make_unique<WebPackageLoader>(
       response, std::move(client), url_loader->Unbind(),
       std::move(request_initiator_), url_loader_options_,
-      std::move(url_loader_factory_), std::move(url_loader_throttles_getter_));
+      std::move(url_loader_factory_), std::move(url_loader_throttles_getter_),
+      std::move(request_context_getter_));
   return true;
 }
 
diff --git a/content/browser/web_package/web_package_request_handler.h b/content/browser/web_package/web_package_request_handler.h
index dda5c7e..e64841d 100644
--- a/content/browser/web_package/web_package_request_handler.h
+++ b/content/browser/web_package/web_package_request_handler.h
@@ -10,6 +10,10 @@
 #include "content/public/common/resource_type.h"
 #include "url/origin.h"
 
+namespace net {
+class URLRequestContextGetter;
+}  // namespace net
+
 namespace content {
 
 class SharedURLLoaderFactory;
@@ -27,7 +31,8 @@
       url::Origin request_initiator,
       uint32_t url_loader_options,
       scoped_refptr<SharedURLLoaderFactory> url_loader_factory,
-      URLLoaderThrottlesGetter url_loader_throttles_getter);
+      URLLoaderThrottlesGetter url_loader_throttles_getter,
+      scoped_refptr<net::URLRequestContextGetter> request_context_getter);
   ~WebPackageRequestHandler() override;
 
   // URLLoaderRequestHandler implementation
@@ -53,6 +58,7 @@
   const uint32_t url_loader_options_;
   scoped_refptr<SharedURLLoaderFactory> url_loader_factory_;
   URLLoaderThrottlesGetter url_loader_throttles_getter_;
+  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
 
   base::WeakPtrFactory<WebPackageRequestHandler> weak_factory_;
 
diff --git a/content/browser/web_package/web_package_request_handler_browsertest.cc b/content/browser/web_package/web_package_request_handler_browsertest.cc
index ff5805c05..f3dd933 100644
--- a/content/browser/web_package/web_package_request_handler_browsertest.cc
+++ b/content/browser/web_package/web_package_request_handler_browsertest.cc
@@ -7,9 +7,11 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
+#include "content/browser/web_package/signed_exchange_handler.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/ssl_status.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_features.h"
@@ -20,6 +22,9 @@
 #include "content/public/test/test_navigation_throttle.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "content/shell/browser/shell.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/test/cert_test_util.h"
 #include "net/test/test_data_directory.h"
 #include "net/test/url_request/url_request_mock_http_job.h"
 #include "net/url_request/url_request_filter.h"
@@ -64,10 +69,12 @@
     : public ContentBrowserTest,
       public testing::WithParamInterface<bool> {
  public:
-  WebPackageRequestHandlerBrowserTest() = default;
+  WebPackageRequestHandlerBrowserTest()
+      : mock_cert_verifier_(std::make_unique<net::MockCertVerifier>()){};
   ~WebPackageRequestHandlerBrowserTest() = default;
 
   void SetUp() override {
+    SignedExchangeHandler::SetCertVerifierForTesting(mock_cert_verifier_.get());
     if (is_network_service_enabled()) {
       feature_list_.InitWithFeatures(
           {features::kSignedHTTPExchange, network::features::kNetworkService},
@@ -78,9 +85,20 @@
     ContentBrowserTest::SetUp();
   }
 
-  void TearDownOnMainThread() override { interceptor_.reset(); }
+  void TearDownOnMainThread() override {
+    interceptor_.reset();
+    SignedExchangeHandler::SetCertVerifierForTesting(nullptr);
+  }
 
  protected:
+  static scoped_refptr<net::X509Certificate> LoadCertificate(
+      const std::string& cert_file) {
+    base::ScopedAllowBlockingForTesting allow_io;
+    return net::CreateCertificateChainFromFile(
+        net::GetTestCertsDirectory(), cert_file,
+        net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+  }
+
   void InstallUrlInterceptor(const GURL& url, const std::string& data_path) {
     if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
       if (!interceptor_) {
@@ -97,6 +115,8 @@
     }
   }
 
+  std::unique_ptr<net::MockCertVerifier> mock_cert_verifier_;
+
  private:
   static std::string ReadFile(const std::string& data_path) {
     base::ScopedAllowBlockingForTesting allow_io;
@@ -153,6 +173,16 @@
       GURL("https://cert.example.org/cert.msg"),
       "content/test/data/htxg/wildcard_example.org.public.pem.msg");
 
+  // Make the MockCertVerifier treat the certificate "wildcard.pem" as valid for
+  // "*.example.org".
+  scoped_refptr<net::X509Certificate> original_cert =
+      LoadCertificate("wildcard.pem");
+  net::CertVerifyResult dummy_result;
+  dummy_result.verified_cert = original_cert;
+  dummy_result.cert_status = net::OK;
+  mock_cert_verifier_->AddResultForCertAndHost(original_cert, "*.example.org",
+                                               dummy_result, net::OK);
+
   embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url = embedded_test_server()->GetURL("/htxg/test.example.org_test.htxg");
@@ -160,6 +190,23 @@
   TitleWatcher title_watcher(shell()->web_contents(), title);
   NavigateToURL(shell(), url);
   EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
+
+  NavigationEntry* entry =
+      shell()->web_contents()->GetController().GetVisibleEntry();
+  EXPECT_TRUE(entry->GetSSL().initialized);
+  EXPECT_FALSE(!!(entry->GetSSL().content_status &
+                  SSLStatus::DISPLAYED_INSECURE_CONTENT));
+  ASSERT_TRUE(entry->GetSSL().certificate);
+
+  // "wildcard_example.org.public.pem.msg" is generated from "wildcard.pem". So
+  // the SHA256 of the certificates must match.
+  const net::SHA256HashValue fingerprint =
+      net::X509Certificate::CalculateFingerprint256(
+          entry->GetSSL().certificate->cert_buffer());
+  const net::SHA256HashValue original_fingerprint =
+      net::X509Certificate::CalculateFingerprint256(
+          original_cert->cert_buffer());
+  EXPECT_EQ(original_fingerprint, fingerprint);
 }
 
 IN_PROC_BROWSER_TEST_P(WebPackageRequestHandlerBrowserTest, CertNotFound) {
@@ -178,6 +225,60 @@
   EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
 }
 
+IN_PROC_BROWSER_TEST_P(WebPackageRequestHandlerBrowserTest,
+                       CertSha256Mismatch) {
+  // The certificate is for "127.0.0.1". And the SHA 256 hash of the certificate
+  // is different from the certSha256 of the signature in the htxg file. So the
+  // certification verification must fail.
+  InstallUrlInterceptor(GURL("https://cert.example.org/cert.msg"),
+                        "content/test/data/htxg/127.0.0.1.public.pem.msg");
+
+  // Set the default result of MockCertVerifier to OK, to check that the
+  // verification of SignedExchange must fail even if the certificate is valid.
+  mock_cert_verifier_->set_default_result(net::OK);
+
+  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url = embedded_test_server()->GetURL("/htxg/test.example.org_test.htxg");
+
+  NavigationFailureObserver failure_observer(shell()->web_contents());
+  NavigateToURL(shell(), url);
+  EXPECT_TRUE(failure_observer.did_fail());
+  NavigationEntry* entry =
+      shell()->web_contents()->GetController().GetVisibleEntry();
+  EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
+}
+
+IN_PROC_BROWSER_TEST_P(WebPackageRequestHandlerBrowserTest, VerifyCertFailure) {
+  // The certificate is for "*.example.com". But the request URL of the htxg
+  // file is "https://test.example.com/test/". So the certification verification
+  // must fail.
+  InstallUrlInterceptor(
+      GURL("https://cert.example.org/cert.msg"),
+      "content/test/data/htxg/wildcard_example.org.public.pem.msg");
+
+  // Make the MockCertVerifier treat the certificate "wildcard.pem" as valid for
+  // "*.example.org".
+  scoped_refptr<net::X509Certificate> original_cert =
+      LoadCertificate("wildcard.pem");
+  net::CertVerifyResult dummy_result;
+  dummy_result.verified_cert = original_cert;
+  dummy_result.cert_status = net::OK;
+  mock_cert_verifier_->AddResultForCertAndHost(original_cert, "*.example.org",
+                                               dummy_result, net::OK);
+
+  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url = embedded_test_server()->GetURL(
+      "/htxg/test.example.com_invalid_test.htxg");
+  NavigationFailureObserver failure_observer(shell()->web_contents());
+  NavigateToURL(shell(), url);
+  EXPECT_TRUE(failure_observer.did_fail());
+  NavigationEntry* entry =
+      shell()->web_contents()->GetController().GetVisibleEntry();
+  EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
+}
+
 INSTANTIATE_TEST_CASE_P(WebPackageRequestHandlerBrowserTest,
                         WebPackageRequestHandlerBrowserTest,
                         testing::Bool());
diff --git a/content/test/data/htxg/127.0.0.1.public.pem.msg b/content/test/data/htxg/127.0.0.1.public.pem.msg
new file mode 100644
index 0000000..ae612984b
--- /dev/null
+++ b/content/test/data/htxg/127.0.0.1.public.pem.msg
Binary files differ
diff --git a/content/test/data/htxg/README b/content/test/data/htxg/README
index 5d1bc49..50a64c3 100644
--- a/content/test/data/htxg/README
+++ b/content/test/data/htxg/README
@@ -21,10 +21,18 @@
   ../../../../net/data/ssl/certificates/wildcard.pem \
   > /tmp/wildcard_example.org.public.pem
 
-# Generate the certificate message file.
+# Generate the certificate message file of "*.example.org".
 gen-certurl  \
   /tmp/wildcard_example.org.public.pem > wildcard_example.org.public.pem.msg
 
+# Get the public key of "127.0.0.1".
+sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \
+  ../../../../net/data/ssl/certificates/ok_cert.pem \
+  > /tmp/127.0.0.1.public.pem
+
+# Generate the certificate message file of"127.0.0.1".
+gen-certurl /tmp/127.0.0.1.public.pem > 127.0.0.1.public.pem.msg
+
 # Generate the signed exchange file.
 gen-signedexchange \
   -uri https://test.example.org/test/ \
@@ -35,4 +43,16 @@
   -validityUrl https://cert.example.org/resource.validity.msg \
   -privateKey /tmp/wildcard_example.org.private.pem \
   -o test.example.org_test.htxg \
-  -miRecordSize=100
+  -miRecordSize 100
+
+# Generate the signed exchange file with invalid URL.
+gen-signedexchange \
+  -uri https://test.example.com/test/ \
+  -status 200 \
+  -content test.html \
+  -certificate /tmp/wildcard_example.org.public.pem \
+  -certUrl https://cert.example.org/cert.msg \
+  -validityUrl https://cert.example.org/resource.validity.msg \
+  -privateKey /tmp/wildcard_example.org.private.pem \
+  -o test.example.com_invalid_test.htxg \
+  -miRecordSize 100
diff --git a/content/test/data/htxg/test.example.com_invalid_test.htxg b/content/test/data/htxg/test.example.com_invalid_test.htxg
new file mode 100644
index 0000000..da10e45
--- /dev/null
+++ b/content/test/data/htxg/test.example.com_invalid_test.htxg
Binary files differ
diff --git a/content/test/data/htxg/test.example.com_invalid_test.htxg.mock-http-headers b/content/test/data/htxg/test.example.com_invalid_test.htxg.mock-http-headers
new file mode 100644
index 0000000..acfdd18
--- /dev/null
+++ b/content/test/data/htxg/test.example.com_invalid_test.htxg.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/http-exchange+cbor