This document defines a set of ECMAScript APIs in WebIDL to allow data to be sent and received from another browser or device implementing the QUIC protocol. The specification for multiplexing of QUIC with STUN/TURN/ZRTP/DTLS/RTP/RTCP [[RFC9443]] was developed within the IETF AVTCORE Working Group.
This specification extends the WebRTC [[WEBRTC]], ORTC [[ORTC]] and WebTransport [[WEBTRANSPORT]] APIs to enable peer-to-peer operation using QUIC [[RFC9000]]. This specification supports the exchange of arbitrary data with remote peers using NAT-traversal technologies such as ICE, STUN, and TURN. As specified in [[RFC9443]], QUIC can be multiplexed on the same port as RTP, RTCP, DTLS, STUN and TURN, allowing the API defined in this specification to be utilized along with the functionality defined in [[WEBRTC]] and [[ORTC]] including communication using audio/video media and SCTP data channels.
This specification defines an interface to QUIC streams [[RFC9000]] as well as datagrams [[RFC9221]]. By utilizing a QUIC stream per message, it is possible to implement support for message-based communications (such as {{RTCDataChannel}}) on top.
This specification extends the WebTransport API [[WEBTRANSPORT]] under development within the W3C WebTransport WG.
Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)
Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [[!WEBIDL]], as this specification uses that specification and terminology.
The {{EventHandler}} interface, representing a callback used for event handlers, is defined in [[!HTML]].
The concepts [= queue a task =] and [= networking task source =] are defined in [[!HTML]].
The concept [= fire an event =] is defined in [[!DOM]].
The terms [= event =], [= event handlers =] and [= event handler event types =] are defined in [[!HTML]].
{{Performance.timeOrigin}} and {{Performance.now()}} are defined in [[!hr-time]].
The terms serializable objects, [= serialization steps =], and [= deserialization steps =] are defined in [[!HTML]].
When referring to exceptions, the terms [= exception/throw =] and [= exception/created =] are defined in [[!WEBIDL]].
The callback {{VoidFunction}} is defined in [[!WEBIDL]].
The term "throw" is used as specified in [[!INFRA]]: it terminates the current processing steps.
The terms fulfilled, rejected, resolved, and settled used in the context of Promises are defined in [[!ECMASCRIPT-6.0]].
WebTransport is defined in [[!WEBTRANSPORT]] Section 5.
RTCIceTransport is defined in [[!WEBRTC]] Section 5.6 and [[!ORTC]] Section 3.
RTCCertificate is defined in [[!WEBRTC]] Section 4.9 and [[!ORTC]] Section 15.
RTCDtlsFingerprint is defined in [[!WEBRTC]] Section 5.5.2 and [[!ORTC]] Section 4.5.
[[\State]] is defined in [[!WEBTRANSPORT]] Section 5.1.
The RTCQuicTransport
interface extends the WebTransport
interface
to support peer-to-peer use cases, by adding information relating to use of a
QUIC transport with an ICE transport.
A RTCQuicTransport
instance is constructed
using an RTCIceTransport
and an optional sequence of
RTCCertificate
objects. A
RTCQuicTransport
object whose {{RTCQuicTransport/[[State]]}} internal
slot is "closed"
or "failed"
can be
garbage-collected when it is no longer referenced.
The QUIC negotiation occurs between transport endpoints determined via ICE. Multiplexing of QUIC with STUN, TURN, DTLS, RTP and RTCP is defined in [[RFC9443]].
A newly constructed RTCQuicTransport
MUST listen and respond to incoming QUIC packets before
start()
is called. However, to complete the negotiation it is
necessary to verify the remote fingerprint by computing fingerprints for
the selected remote certificate using the digest algorithms provided
in remoteParameters.fingerprints[].algorithm
. If a
calculated fingerprint and algorithm matches a fingerprint and algorithm
included in remoteParameters.fingerprints[]
,
the remote fingerprint is verified. After the QUIC handshake exchange
completes (but before the remote fingerprint is verified) incoming media packets
may be received. A modest buffer MUST be
provided to avoid loss of media prior to remote fingerprint validation (which can
begin after start()
is called).
[Exposed=Window] interface RTCQuicTransport : WebTransport { constructor(RTCIceTransport
transport, optional sequence<RTCCertificate
> certificates); readonly attributeRTCIceTransport
transport; RTCQuicParameters getLocalParameters (); RTCQuicParameters? getRemoteParameters (); sequence<RTCCertificate
> getCertificates (); sequence<ArrayBuffer> getRemoteCertificates (); undefined start (RTCQuicParameters remoteParameters); };
When RTCQuicTransport.constructor()
is invoked,
the user agent MUST run the
following steps:
state
attribute has the
value "closed"
[= exception/throw =] an
InvalidStateError
and abort these steps.
RTCQuicTransport
whose {{RTCQuicTransport/[[State]]}} internal
slot is not "closed"
, [= exception/throw =] an
InvalidStateError
and abort these steps.
null
otherwise.
expires
attribute of each RTCCertificate
object is in the future. If a certificate has expired, [= exception/throw =]
an InvalidAccessError
and abort these steps.
RTCQuicTransport
object.
"connecting"
.
null
or is empty,
generate a certificate using the default key generation algorithm
and store it in the {{RTCQuicTransport/[[Certificates]]}} internal slot.
RTCQuicTransport
Parameter | Type | Nullable | Optional | Description |
---|---|---|---|---|
transport | RTCIceTransport |
✘ | ✘ | |
certificates |
sequence <RTCCertificate > |
✘ | ✔ |
transport
of type RTCIceTransport
, readonlyThe associated RTCIceTransport
instance.
When the RTCIceTransport
's state
attribute changes values, the user agent MUST
run the following steps:
state
attribute is not
"closed"
, abort these steps.
RTCQuicTransport
.
"closed"
.
getLocalParameters
getLocalParameters() obtains the QUIC parameters of
the local RTCQuicTransport
upon construction.
If multiple certificates were provided in the constructor, then
multiple fingerprints will be returned, one for each certificate.
getLocalParameters().role
always returns the default
role of a newly constructed RTCQuicTransport
;
for a browser this will be auto
.
RTCQuicParameters
getRemoteParameters
getRemoteParameters() obtains
the remote QUIC parameters passed in the
start()
method. Prior to calling
start()
, null is returned.
RTCQuicParameters
, nullable
getCertificates
getCertificates() returns the value of the RTCQuicTransport
's
{{RTCQuicTransport/[[Certificates]]}} internal slot.
sequence<RTCCertificate>
getRemoteCertificates
getRemoteCertificates() returns the certificate chain in use by the remote side, with each
certificate encoded in binary Distinguished Encoding Rules (DER) [[!X690]].
getRemoteCertificates()
returns an empty list prior to
selection of the remote certificate, which is completed once quictransport's
{{RTCQuicTransport/[[State]]}} internal slot transitions to "connected"
.
sequence<ArrayBuffer>
start
Start QUIC transport negotiation with the parameters of the remote QUIC transport, including verification of the remote fingerprint. During connection establishment, use of this API must be indicated by selecting the ALPN token "q2q" in the crypto handshake.
Only a single QUIC transport can be multiplexed over an ICE transport.
Therefore if a RTCQuicTransport
object
quicTransportB is constructed with an
{{RTCIceTransport}} object iceTransport
previously used to construct another RTCQuicTransport
object quicTransportA, then if
quicTransportB.start()
is called prior to having called
quicTransportA.stop()
, then [= exception/throw =] an
InvalidStateError
.
If start
is called after a previous start
call, or if quictransport's {{RTCQuicTransport/[[State]]}} internal slot is
"closed"
, [= exception/throw = ] an InvalidStateError
.
If all of the values of
remoteParameters.fingerprints[j].algorithm
are unsupported, where j goes from 0 to the number of fingerprints,
[= exception/throw =] a NotSupportedError
.
Parameter | Type | Nullable | Optional | Description |
---|---|---|---|---|
remoteParameters | RTCQuicParameters |
✘ | ✘ |
void
The RTCQuicParameters
dictionary includes information
relating to QUIC configuration.
dictionary RTCQuicParameters { RTCQuicRole role = "auto"; required sequence<RTCDtlsFingerprint> fingerprints; };
role
of type RTCQuicRole, defaulting to
"auto"
The QUIC role, with a default of auto
.
fingerprints
of type sequence<{{RTCDtlsFingerprint}}>Sequence of fingerprints, at least one fingerprint for each certificate (with one computed with the digest algorithm used in the certificate signature).
RTCQuicRole
indicates the role of the QUIC
transport.
enum RTCQuicRole { "auto", "client", "server" };
Enumeration description | |
---|---|
auto |
The QUIC role is determined based on the resolved ICE role:
the ICE |
client |
The QUIC client role. |
server |
The QUIC server role. |
To diagnose QUIC role issues, an application may wish to determine
the desired and actual QUIC role of an RTCQuicTransport
.
For a browser implementing ORTC, a RTCQuicTransport
object assumes a QUIC role of auto
upon construction.
This implies that the QUIC role is determined by the ICE role. Since
getLocalParameters().role
always returns the role assigned
to an RTCQuicTransport
object upon construction
(auto
for a browser), the getLocalParameters
method cannot be used to determine the desired or actual role of an
RTCQuicTransport
.
An application can determine the desired role of an
RTCQuicTransport
from the value of
remoteParameters.role
passed to
RTCQuicTransport.start(remoteParameters)
.
If remoteParameters.role
is server
then the desired role of the RTCQuicTransport
is client
. If remoteParameters.role
is client
then the desired role of the
RTCQuicTransport
is server
.
The RTCQuicTransport.transport.onstatechange
EventHandler
can be used to determine whether an RTCQuicTransport
transitions to the desired role. When
RTCQuicTransport.transport.state
transitions to
connected
, if RTCQuicTransport.transport.role
is controlled
then the role of the
RTCQuicTransport
is client
.
If RTCQuicTransport.transport.role
is controlling
then the role of the
RTCQuicTransport
is server
.
function initiate(mySignaller) { // Prepare the ICE gatherer var gatherOptions = { gatherPolicy: "all", iceServers: [ { urls: "stun:stun1.example.net" }, { urls: "turn:turn.example.org", username: "user", credential: "myPassword", credentialType: "password" } ] }; var iceGatherer = new RTCIceGatherer(gatherOptions); // Handle state changes iceGatherer.onstatechange = function(event) { myIceGathererStateChange("iceGatherer", event.state); }; // Handle errors iceGatherer.onerror = errorHandler; // Prepare to signal local candidates iceGatherer.onlocalcandidate = function(event) { mySignaller.mySendLocalCandidate(event.candidate); }; // Start gathering iceGatherer.gather(); // Create ICE transport var ice = new RTCIceTransport(iceGatherer); // Prepare to handle remote ICE candidates mySignaller.onRemoteCandidate = function(remote) { ice.addRemoteCandidate(remote.candidate); }; // Create the DTLS certificate var certs; var keygenAlgorithm = { name: "ECDSA", namedCurve: "P-256" }; RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){ certs[0] = certificate; }, function(){ trace('Certificate could not be created'); }); // Create DTLS and QUIC transport var dtls = new RTCDtlsTransport(ice, certs); var quic = new RTCQuicTransport(ice, certs); mySignaller.sendInitiate({ ice: iceGatherer.getLocalParameters(), dtls: dtls.getLocalParameters(), quic: quic.getLocalParameters(), // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.5 Example 9. }, function(remote) { // Start the ICE, DTLS and QUIC transports ice.start(iceGatherer, remote.ice, RTCIceRole.controlling); dtls.start(remote.dtls); quic.start(remote.quic); // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.5 Example 9. }); }
// This is an example of how to answer // Include some helper functions import {trace, errorHandler, mySendLocalCandidate, myIceGathererStateChange, myIceTransportStateChange, myDtlsTransportStateChange} from 'helper'; function accept(mySignaller, remote) { var gatherOptions = { gatherPolicy: "all", iceServers: [ { urls: "stun:stun1.example.net" }, { urls: "turn:turn.example.org", username: "user", credential: "myPassword", credentialType: "password" } ] }; var iceGatherer = new RTCIceGatherer(gatherOptions); // Handle state changes iceGatherer.onstatechange = function(event) { myIceGathererStateChange("iceGatherer", event.state); }; // Handle errors iceGatherer.onerror = errorHandler; // Prepare to signal local candidates iceGatherer.onlocalcandidate = function(event) { mySignaller.mySendLocalCandidate(event.candidate); }; // Start gathering iceGatherer.gather(); // Create ICE transport var ice = new RTCIceTransport(iceGatherer); // Prepare to handle remote ICE candidates mySignaller.onRemoteCandidate = function(remote) { ice.addRemoteCandidate(remote.candidate); }; // Create the DTLS certificate var certs; var keygenAlgorithm = { name: "ECDSA", namedCurve: "P-256" }; RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){ certs[0] = certificate; }, function(){ trace('Certificate could not be created'); }); // Create DTLS and SCTP transport var dtls = new RTCDtlsTransport(ice, certs); var quic = new RTCQuicTransport(ice, certs); mySignaller.sendAccept({ ice: iceGatherer.getLocalParameters(), dtls: dtls.getLocalParameters(), quic: quic.getLocalParameters(), // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.5 Example 9. }); // Start the ICE, DTLS and SCTP transports ice.start(iceGatherer, remote.ice, RTCIceRole.controlled); dtls.start(remote.dtls); // Start the QuicTransport quic.start(remote.quic); // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.5 Example 9. }
This section is non-normative; it specifies no new behaviour, but instead summarizes information already present in other parts of the specification. The overall security considerations of the APIs and protocols used in WebRTC are described in [[RFC8827]].
The QUIC API enables data to be communicated between browsers and other devices, including other browsers.
This means that data can be shared between applications running in different browsers, or between an application running in the same browser and something that is not a browser. This is an extension to the Web model which has had barriers against sending data between entities with different origins.
This specification provides no user prompts or chrome indicators for communication; it assumes that once the Web page has been allowed to access data, it is free to share that data with other entities as it chooses. Peer-to-peer exchanges of data via QUIC can therefore occur without any user explicit consent or involvement.
Since the browser is an active platform executing in a trusted network environment (inside the firewall), it is important to limit the damage that the browser can do to other elements on the local network, and it is important to protect data from interception, manipulation and modification by untrusted participants.
Mitigations include:
These measures are specified in the relevant IETF documents.
Utilizing the generateCertificate()
API in [[!WEBRTC]], it is possible to
generate and store certificates that can subsequently be reused in constructing
RTCQuicTransport
objects. These persistent certificates
can therefore be used to identify a user.