Commit dfa2b44b authored by Adam Rice's avatar Adam Rice Committed by Commit Bot

[QuicTransport] Implement options argument

Permit an "options" argument to be supplied to the QuicTransport
constructor providing a list of certificate fingerprints that are
permitted to be used for the handshake.

Add a unit test and browser test for the new feature.

BUG=1011392

Change-Id: Ia98ab05d0d5e6fc34ae57472119f1444dab7b330
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2251869
Commit-Queue: Adam Rice <ricea@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780842}
parent d862f263
......@@ -335,5 +335,53 @@ IN_PROC_BROWSER_TEST_F(QuicTransportBrowserTest, BidirectionalStream) {
ASSERT_TRUE(WaitForTitle(ASCIIToUTF16("PASS"), {ASCIIToUTF16("FAIL")}));
}
IN_PROC_BROWSER_TEST_F(QuicTransportBrowserTest, CertificateFingerprint) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
ASSERT_TRUE(WaitForTitle(ASCIIToUTF16("Title Of Awesomeness")));
ASSERT_TRUE(ExecuteScript(
shell(), base::StringPrintf(R"JS(
async function run() {
// The connection fails because the fingerprint does not match.
const transport = new QuicTransport(
'quic-transport://localhost:%d/echo', {
serverCertificateFingerprints: [
{
algorithm: "sha-256",
value: "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:" +
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
},
],
});
let fulfilled = false;
try {
await transport.ready;
fulfilled = true
} catch {}
if (fulfilled) {
throw Error('ready should be rejected');
}
try {
await transport.closed;
} catch (e) {
return;
}
throw Error('closed should be rejected');
}
run().then(() => { document.title = 'PASS'; },
(e) => { console.log(e); document.title = 'FAIL'; });
)JS",
server_.server_address().port())));
ASSERT_TRUE(WaitForTitle(ASCIIToUTF16("PASS"), {ASCIIToUTF16("FAIL")}));
}
} // namespace
} // namespace content
......@@ -922,6 +922,7 @@ static_idl_files_in_modules = get_path_info(
"//third_party/blink/renderer/modules/websockets/websocket_stream_options.idl",
"//third_party/blink/renderer/modules/webtransport/bidirectional_stream.idl",
"//third_party/blink/renderer/modules/webtransport/quic_transport.idl",
"//third_party/blink/renderer/modules/webtransport/quic_transport_options.idl",
"//third_party/blink/renderer/modules/webtransport/receive_stream.idl",
"//third_party/blink/renderer/modules/webtransport/send_stream.idl",
"//third_party/blink/renderer/modules/webtransport/stream_abort_info.idl",
......
......@@ -31,6 +31,7 @@
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/modules/webtransport/quic_transport.h"
#include "third_party/blink/renderer/modules/webtransport/quic_transport_options.h"
#include "third_party/blink/renderer/modules/webtransport/test_utils.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
......@@ -181,9 +182,9 @@ class ScopedQuicTransport : public mojom::blink::QuicTransportConnector {
mojom::blink::QuicTransportConnector::Name_,
base::BindRepeating(&ScopedQuicTransport::BindConnector,
weak_ptr_factory_.GetWeakPtr()));
quic_transport_ = QuicTransport::Create(scope.GetScriptState(),
"quic-transport://example.com/",
ASSERT_NO_EXCEPTION);
quic_transport_ = QuicTransport::Create(
scope.GetScriptState(), "quic-transport://example.com/",
MakeGarbageCollected<QuicTransportOptions>(), ASSERT_NO_EXCEPTION);
test::RunPendingTasks();
}
......
......@@ -10,6 +10,7 @@ modules_idl_files = [
]
modules_dictionary_idl_files = [
"web_transport_close_info.idl",
"quic_transport_options.idl",
"stream_abort_info.idl",
"web_transport_close_info.idl",
]
......@@ -25,7 +25,9 @@
#include "third_party/blink/renderer/core/streams/underlying_source_base.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_dtls_fingerprint.h"
#include "third_party/blink/renderer/modules/webtransport/bidirectional_stream.h"
#include "third_party/blink/renderer/modules/webtransport/quic_transport_options.h"
#include "third_party/blink/renderer/modules/webtransport/receive_stream.h"
#include "third_party/blink/renderer/modules/webtransport/send_stream.h"
#include "third_party/blink/renderer/modules/webtransport/web_transport_stream.h"
......@@ -36,6 +38,7 @@
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
......@@ -340,11 +343,13 @@ class QuicTransport::BidirectionalStreamVendor final
QuicTransport* QuicTransport::Create(ScriptState* script_state,
const String& url,
QuicTransportOptions* options,
ExceptionState& exception_state) {
DVLOG(1) << "QuicTransport::Create() url=" << url;
DCHECK(options);
auto* transport =
MakeGarbageCollected<QuicTransport>(PassKey(), script_state, url);
transport->Init(url, exception_state);
transport->Init(url, *options, exception_state);
return transport;
}
......@@ -596,7 +601,9 @@ void QuicTransport::Trace(Visitor* visitor) const {
ScriptWrappable::Trace(visitor);
}
void QuicTransport::Init(const String& url, ExceptionState& exception_state) {
void QuicTransport::Init(const String& url,
const QuicTransportOptions& options,
ExceptionState& exception_state) {
DVLOG(1) << "QuicTransport::Init() url=" << url << " this=" << this;
if (!url_.IsValid()) {
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
......@@ -640,6 +647,16 @@ void QuicTransport::Init(const String& url, ExceptionState& exception_state) {
return;
}
Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
fingerprints;
if (options.hasServerCertificateFingerprints()) {
for (const auto& fingerprint : options.serverCertificateFingerprints()) {
fingerprints.push_back(
network::mojom::blink::QuicTransportCertificateFingerprint::New(
fingerprint->algorithm(), fingerprint->value()));
}
}
// TODO(ricea): Register SchedulingPolicy so that we don't get throttled and
// to disable bfcache. Must be done before shipping.
......@@ -652,7 +669,7 @@ void QuicTransport::Init(const String& url, ExceptionState& exception_state) {
execution_context->GetTaskRunner(TaskType::kNetworking)));
connector->Connect(
url_, /*fingerprints=*/{},
url_, std::move(fingerprints),
handshake_client_receiver_.BindNewPipeAndPassRemote(
execution_context->GetTaskRunner(TaskType::kNetworking)));
......
......@@ -27,15 +27,16 @@
namespace blink {
class ExceptionState;
class QuicTransportOptions;
class ReadableStream;
class ReadableStreamDefaultControllerWithScriptScope;
class ScriptPromise;
class ScriptPromiseResolver;
class ScriptPromiseResolver;
class ScriptState;
class WebTransportCloseInfo;
class WritableStream;
class ScriptPromise;
class ScriptPromiseResolver;
class WebTransportStream;
class WritableStream;
// https://wicg.github.io/web-transport/#quic-transport
class MODULES_EXPORT QuicTransport final
......@@ -50,8 +51,9 @@ class MODULES_EXPORT QuicTransport final
public:
using PassKey = util::PassKey<QuicTransport>;
static QuicTransport* Create(ScriptState* script_state,
static QuicTransport* Create(ScriptState*,
const String& url,
QuicTransportOptions*,
ExceptionState&);
QuicTransport(PassKey, ScriptState*, const String& url);
......@@ -110,7 +112,7 @@ class MODULES_EXPORT QuicTransport final
QuicTransport(ScriptState*, const String& url, ExecutionContext* context);
void Init(const String& url, ExceptionState&);
void Init(const String& url, const QuicTransportOptions&, ExceptionState&);
// Reset the QuicTransport object and all associated streams.
void ResetAll();
......
......@@ -8,7 +8,7 @@
Exposed=(Window,Worker),
RuntimeEnabled=QuicTransport
] interface QuicTransport {
[CallWith=ScriptState, RaisesException, MeasureAs=QuicTransport] constructor(USVString url);
[CallWith=ScriptState, RaisesException, MeasureAs=QuicTransport] constructor(USVString url, optional QuicTransportOptions options = {});
// QuicTransport is the first, and at this moment only, transport which is
// implemented. In the (draft) spec there are many mix-in interfaces which
// QuicTransport includes, but we define all their methods/attributes here
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/web-transport/#quic-transport-configuration
dictionary QuicTransportOptions {
sequence<RTCDtlsFingerprint> serverCertificateFingerprints;
};
......@@ -37,6 +37,8 @@
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_dtls_fingerprint.h"
#include "third_party/blink/renderer/modules/webtransport/quic_transport_options.h"
#include "third_party/blink/renderer/modules/webtransport/receive_stream.h"
#include "third_party/blink/renderer/modules/webtransport/send_stream.h"
#include "third_party/blink/renderer/modules/webtransport/test_utils.h"
......@@ -66,11 +68,17 @@ class QuicTransportConnector final
struct ConnectArgs {
ConnectArgs(
const KURL& url,
Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
fingerprints,
mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
handshake_client)
: url(url), handshake_client(std::move(handshake_client)) {}
: url(url),
fingerprints(std::move(fingerprints)),
handshake_client(std::move(handshake_client)) {}
KURL url;
Vector<network::mojom::blink::QuicTransportCertificateFingerprintPtr>
fingerprints;
mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
handshake_client;
};
......@@ -81,7 +89,8 @@ class QuicTransportConnector final
fingerprints,
mojo::PendingRemote<network::mojom::blink::QuicTransportHandshakeClient>
handshake_client) override {
connect_args_.push_back(ConnectArgs(url, std::move(handshake_client)));
connect_args_.push_back(
ConnectArgs(url, std::move(fingerprints), std::move(handshake_client)));
}
Vector<ConnectArgs> TakeConnectArgs() { return std::move(connect_args_); }
......@@ -146,10 +155,14 @@ class QuicTransportTest : public ::testing::Test {
weak_ptr_factory_.GetWeakPtr()));
}
static QuicTransportOptions* EmptyOptions() {
return MakeGarbageCollected<QuicTransportOptions>();
}
// Creates a QuicTransport object with the given |url|.
QuicTransport* Create(const V8TestingScope& scope, const String& url) {
AddBinder(scope);
return QuicTransport::Create(scope.GetScriptState(), url,
return QuicTransport::Create(scope.GetScriptState(), url, EmptyOptions(),
ASSERT_NO_EXCEPTION);
}
......@@ -286,7 +299,8 @@ class QuicTransportTest : public ::testing::Test {
TEST_F(QuicTransportTest, FailWithNullURL) {
V8TestingScope scope;
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(), String(), exception_state);
QuicTransport::Create(scope.GetScriptState(), String(), EmptyOptions(),
exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -295,7 +309,8 @@ TEST_F(QuicTransportTest, FailWithNullURL) {
TEST_F(QuicTransportTest, FailWithEmptyURL) {
V8TestingScope scope;
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(), String(""), exception_state);
QuicTransport::Create(scope.GetScriptState(), String(""), EmptyOptions(),
exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -306,7 +321,7 @@ TEST_F(QuicTransportTest, FailWithNoScheme) {
V8TestingScope scope;
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(), String("no-scheme"),
exception_state);
EmptyOptions(), exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -317,7 +332,7 @@ TEST_F(QuicTransportTest, FailWithHttpsURL) {
V8TestingScope scope;
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(), String("https://example.com/"),
exception_state);
EmptyOptions(), exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -330,7 +345,7 @@ TEST_F(QuicTransportTest, FailWithNoHost) {
V8TestingScope scope;
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(), String("quic-transport:///"),
exception_state);
EmptyOptions(), exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -343,7 +358,7 @@ TEST_F(QuicTransportTest, FailWithURLFragment) {
auto& exception_state = scope.GetExceptionState();
QuicTransport::Create(scope.GetScriptState(),
String("quic-transport://example.com/#failing"),
exception_state);
EmptyOptions(), exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSyntaxError),
exception_state.Code());
......@@ -362,7 +377,7 @@ TEST_F(QuicTransportTest, FailByCSP) {
network::mojom::ContentSecurityPolicyType::kEnforce,
network::mojom::ContentSecurityPolicySource::kHTTP);
QuicTransport::Create(scope.GetScriptState(),
String("quic-transport://example.com/"),
String("quic-transport://example.com/"), EmptyOptions(),
exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(static_cast<int>(DOMExceptionCode::kSecurityError),
......@@ -383,7 +398,7 @@ TEST_F(QuicTransportTest, PassCSP) {
network::mojom::ContentSecurityPolicyType::kEnforce,
network::mojom::ContentSecurityPolicySource::kHTTP);
QuicTransport::Create(scope.GetScriptState(),
String("quic-transport://example.com/"),
String("quic-transport://example.com/"), EmptyOptions(),
exception_state);
EXPECT_FALSE(exception_state.HadException());
}
......@@ -393,13 +408,14 @@ TEST_F(QuicTransportTest, SendConnect) {
AddBinder(scope);
auto* quic_transport = QuicTransport::Create(
scope.GetScriptState(), String("quic-transport://example.com/"),
ASSERT_NO_EXCEPTION);
EmptyOptions(), ASSERT_NO_EXCEPTION);
test::RunPendingTasks();
auto args = connector_.TakeConnectArgs();
ASSERT_EQ(1u, args.size());
EXPECT_EQ(KURL("quic-transport://example.com/"), args[0].url);
EXPECT_TRUE(args[0].fingerprints.IsEmpty());
EXPECT_TRUE(quic_transport->HasPendingActivity());
}
......@@ -421,7 +437,7 @@ TEST_F(QuicTransportTest, FailedConnect) {
AddBinder(scope);
auto* quic_transport = QuicTransport::Create(
scope.GetScriptState(), String("quic-transport://example.com/"),
ASSERT_NO_EXCEPTION);
EmptyOptions(), ASSERT_NO_EXCEPTION);
ScriptPromiseTester ready_tester(scope.GetScriptState(),
quic_transport->ready());
ScriptPromiseTester closed_tester(scope.GetScriptState(),
......@@ -443,12 +459,37 @@ TEST_F(QuicTransportTest, FailedConnect) {
EXPECT_TRUE(closed_tester.IsRejected());
}
TEST_F(QuicTransportTest, SendConnectWithFingerprint) {
V8TestingScope scope;
AddBinder(scope);
auto* fingerprints = MakeGarbageCollected<RTCDtlsFingerprint>();
fingerprints->setAlgorithm("sha-256");
fingerprints->setValue(
"ED:3D:D7:C3:67:10:94:68:D1:DC:D1:26:5C:B2:74:D7:1C:A2:63:3E:94:94:C0:84:"
"39:D6:64:FA:08:B9:77:37");
auto* options = MakeGarbageCollected<QuicTransportOptions>();
options->setServerCertificateFingerprints({fingerprints});
QuicTransport::Create(scope.GetScriptState(),
String("quic-transport://example.com/"), options,
ASSERT_NO_EXCEPTION);
test::RunPendingTasks();
auto args = connector_.TakeConnectArgs();
ASSERT_EQ(1u, args.size());
ASSERT_EQ(1u, args[0].fingerprints.size());
EXPECT_EQ(args[0].fingerprints[0]->algorithm, "sha-256");
EXPECT_EQ(args[0].fingerprints[0]->fingerprint,
"ED:3D:D7:C3:67:10:94:68:D1:DC:D1:26:5C:B2:74:D7:1C:A2:63:3E:94:94:"
"C0:84:39:D6:64:FA:08:B9:77:37");
}
TEST_F(QuicTransportTest, CloseDuringConnect) {
V8TestingScope scope;
AddBinder(scope);
auto* quic_transport = QuicTransport::Create(
scope.GetScriptState(), String("quic-transport://example.com/"),
ASSERT_NO_EXCEPTION);
EmptyOptions(), ASSERT_NO_EXCEPTION);
ScriptPromiseTester ready_tester(scope.GetScriptState(),
quic_transport->ready());
ScriptPromiseTester closed_tester(scope.GetScriptState(),
......@@ -770,8 +811,9 @@ TEST_F(QuicTransportTest, CreateSendStreamBeforeConnect) {
V8TestingScope scope;
auto* script_state = scope.GetScriptState();
auto* quic_transport = QuicTransport::Create(
script_state, "quic-transport://example.com", ASSERT_NO_EXCEPTION);
auto* quic_transport =
QuicTransport::Create(script_state, "quic-transport://example.com",
EmptyOptions(), ASSERT_NO_EXCEPTION);
auto& exception_state = scope.GetExceptionState();
ScriptPromise send_stream_promise =
quic_transport->createSendStream(script_state, exception_state);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment