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.

Introduction

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.

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.

RTCQuicTransport Interface

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.

Operation

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).

Interface Definition

[Exposed=Window]
interface RTCQuicTransport : WebTransport {
    constructor(RTCIceTransport transport, optional sequence<RTCCertificate> certificates);
    readonly attribute RTCIceTransport transport;
    RTCQuicParameters     getLocalParameters ();
    RTCQuicParameters?    getRemoteParameters ();
    sequence<RTCCertificate> getCertificates ();
    sequence<ArrayBuffer> getRemoteCertificates ();
    undefined start (RTCQuicParameters remoteParameters);
};

Constructors

When RTCQuicTransport.constructor() is invoked, the user agent MUST run the following steps:

  1. Let transport be the first argument.
  2. If transport's state attribute has the value "closed" [= exception/throw =] an InvalidStateError and abort these steps.
  3. If transport has been used to construct another RTCQuicTransport whose {{RTCQuicTransport/[[State]]}} internal slot is not "closed", [= exception/throw =] an InvalidStateError and abort these steps.
  4. Let certificates be the second argument if provided, null otherwise.
  5. If certificates is non-null and is non-empty, check that the expires attribute of each RTCCertificate object is in the future. If a certificate has expired, [= exception/throw =] an InvalidAccessError and abort these steps.
  6. Let quictransport be a newly constructed RTCQuicTransport object.
  7. Let quictransport have a {{RTCQuicTransport/[[State]]}} internal slot, initialized to "connecting".
  8. Let quictransport have a [[\Certificates]] internal slot.
  9. If certificates is non-null and is non-empty, initialize the {{RTCQuicTransport/[[Certificates]]}} internal slot to certificates.
  10. If certificates is null or is empty, generate a certificate using the default key generation algorithm and store it in the {{RTCQuicTransport/[[Certificates]]}} internal slot.
  11. Return quictransport.
RTCQuicTransport
Parameter Type Nullable Optional Description
transport RTCIceTransport
certificates sequence<RTCCertificate>

Attributes

transport of type RTCIceTransport, readonly

The associated RTCIceTransport instance. When the RTCIceTransport's state attribute changes values, the user agent MUST run the following steps:

  1. Let transport be the associated {{RTCIceTransport}} instance.
  2. If transport's state attribute is not "closed", abort these steps.
  3. Let quictransport be the RTCQuicTransport.
  4. Set quictransport's {{RTCQuicTransport/[[State]]}} internal slot to "closed".

Methods

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.

No parameters.
Return type: RTCQuicParameters
getRemoteParameters

getRemoteParameters() obtains the remote QUIC parameters passed in the start() method. Prior to calling start(), null is returned.

No parameters.
Return type: RTCQuicParameters, nullable
getCertificates

getCertificates() returns the value of the RTCQuicTransport's {{RTCQuicTransport/[[Certificates]]}} internal slot.

No parameters.
Return type: 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".

No parameters.
Return type: 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
Return type: void

RTCQuicParameters Dictionary

The RTCQuicParameters dictionary includes information relating to QUIC configuration.

dictionary RTCQuicParameters {
             RTCQuicRole role = "auto";
             required sequence<RTCDtlsFingerprint> fingerprints;
};

Dictionary RTCQuicParameters Members

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 Enum

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 "controlled" role acts as the QUIC client and the ICE "controlling" role acts as the QUIC server.

client

The QUIC client role.

server

The QUIC server role.

QUIC role determination

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.

Examples

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.

}
                

Privacy and Security Considerations

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]].

Impact on same origin policy

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.

Impact on local network

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.

Persistent information

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.