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