Loading Signed Exchanges

Editor’s Draft,

This version:
https://wicg.github.io/webpackage/loading.html
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google Inc.)

Abstract

How UAs load signed exchanges.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

This section is non-normative.

The Signed Exchanges specification [draft-yasskin-http-origin-signed-responses] describes a way to provide one or more signatures for an HTTP exchange and to check whether any of those signatures is trusted as authoritative for a particular origin. This specification describes how web browsers load those exchanges. It is expressed as several monkeypatches to the [FETCH] specification which call algorithms defined here.

1.1. Overview

When fetching a resource (https://distributor.example.org/foo.sxg) with the application/signed-exchange MIME type, the UA parses it, checks its signatures, and then if all is well, redirects to its request URL (https://publisher.example.org/foo) with a "stashed" exchange attached to the request. The redirect applies all the usual processing, and then when it would normally check for an HTTP cache hit, it also checks whether the stashed request matches the redirected request and which of the stashed exchange or HTTP cache contents is newer. If the stashed exchange matches and is newer, the UA returns the stashed response.

A Service Worker for https://distributor.example.org/ gets to handle the original request. A Service Worker for https://publisher.example.org/ can then handle the redirect. If it needs to know that signed exchange content is available for the request it’s handling, it has two options:

  1. If navigationPreload is enabled, the signed response will be available in the FetchEvent's preloadResponse. Note that this will also cause a network request for requests that aren’t served from a signed exchange.

  2. clone() the request and set its cache to "only-if-cached", to retrieve the matching response from either the signed exchange or the HTTP cache. Note that fetch()ing a new Request with the same url will not retrieve the response from the signed exchange.

1.2. Other interesting details

2. Fetch monkeypatches

When fetching a signed exchange, the UA needs to look for a trusted and valid signature and then redirect to the contained resource. We don’t put the contained resource in the HTTP cache, so redirects get a new field to store it.

2.1. A request’s stashed exchange

A request has an associated stashed exchange, which is null or an exchange.

2.2. Request clone

Rewrite clone a request to run these steps:

  1. Let newRequest be a copy of request, except for its body and stashed exchange .
  2. If request’s body is non-null, set newRequest’s body to the result of cloning request’s body.
  3. If request’s stashed exchange is non-null, set newRequest’s stashed exchange to an exchange whose request URL is a copy of request’s stashed exchange's request URL and whose response is the clone of request’s stashed exchange's response.
  4. Return newRequest.

2.3. Response date

A response response’s date is the result of:

  1. Let date be the result of extracting header list values given `Date` and response’s header list.

  2. If date is a failure, return the point in time of the beginning of the universe.

  3. Return the point in time represented by date, as interpreted for the Date header field.

2.4. Monkeypatch HTTP fetch

In HTTP fetch, before

  1. If actualResponse’s status is a redirect status, then: ...

add the following steps:

  1. If the signed exchange version of actualResponse is:

    undefined

    Do nothing.

    "b2" or "b3"
    1. Let report be the result of create a new signed exchange report with request and actualResponse.

    2. Let parsedExchange be the result of parsing a signed exchange of version b2 or b3, respectively, from actualResponse in the context of request’s client, reporting to report.

    3. If parsedExchange is not an exchange, run queue a signed exchange report report with parsedExchange as the result, and return a network error.

    4. In parallel, wait and queue a report for parsedExchange and report.

    5. Set actualResponse’s status to 303.

    6. Set actualResponse’s `Location` header to the ASCII encoding of the serialization of parsedExchange’s request URL.

    7. Set request’s stashed exchange to parsedExchange.

    Anything else
    1. Let fallbackUrlBytes be the result of extracting the fallback URL from actualResponse.

    2. If fallbackUrlBytes is a failure, return a network error.

    3. Set actualResponse’s status to 303.

    4. Set actualResponse’s `Location` header to fallbackUrlBytes.

    Note: The final [draft-yasskin-http-origin-signed-responses] will use a version of `1`, but this specification tracks what’s actually implemented in browsers, which still uses draft versions.

2.5. Monkeypatch HTTP-network-or-cache fetch

In HTTP-network-or-cache fetch, after

5.19. If httpRequest’s cache mode is neither "no-store" nor "reload", then: ...

add the following steps:

  1. If httpRequest’s stashed exchange isn’t null:

    1. Let stashedExchange be httpRequest’s stashed exchange.

    2. If

      then set response to httpRequest’s stashed exchange's response.

    3. If response is null and httpRequest’s initiator is "prefetch" or "preload", return a network error.

      Note: This ensures that prefetching a signed exchange from one origin won’t accidentally do a network request from another origin, which could compromise the user’s privacy.

Note: Applying the signed exchange’s response here has the effect of letting a newer HTTP cache entry override a signed exchange’s content, and of not storing the signed exchange’s response in the HTTP cache.

3. Structures

3.1. Exchange

An exchange is a struct with the following items:

3.2. Read buffer

A read buffer is a struct with the following items:

3.3. Augmented Certificate

An augmented certificate is a tuple with the following items:

  1. certificate, a byte sequence that’s expected to hold a DER-encoded X.509v3 certificate ([RFC5280]).

  2. OCSP response, a byte sequence that’s expected to hold a DER-encoded OCSPResponse for the certificate.

  3. SCT, a byte sequence that’s expected to hold a SignedCertificateTimestampList for the certificate.

These fields are byte sequences instead of parsed and validated structures because we expect some UAs to pass them to other systems for validation, and some of those systems expect plain byte sequences.

A certificate contains a public key (Subject Public Key Info), which has an algorithm (AlgorithmIdentifier).

A certificate contains an extensions map (Certificate Extensions) from OIDs to byte sequences.

A certificate chain is a list of augmented certificates, of which the first item is the leaf.

3.4. Signed Exchange report

A signed exchange report is a struct with the following items:

result

The result string of loading signed exchange. This must be unset or one of "ok", "mi_error", "non_secure_distributor", "parse_error", "invalid_integrity_header", "signature_verification_error", "cert_verification_error", "cert_fetch_error", "cert_parse_error",

outer request

The request which the user agent sent to the server to load the signed exchange.

outer response

The response which the user agent received from the server.

inner URL

The logical URL of the signed exchange, if available. Otherwise, an empty string.

cert URL list

The list of URLs in "cert-url" parameters for the signed exchange’s signatures, if available. Otherwise, an empty list.

server IP

The IP address of the server from which the user agent received the signed exchange, if available. Otherwise, an empty string.

cert server IP list

The list of IP addresses of the servers from which the user agent received the certificates listed in cert URL list.

3.5. Exchange Signature

An exchange signature is a struct with the following items:

signature

A byte sequence holding a signature of the exchange.

certificate chain

A certificate chain whose leaf's public key can verify the signature and from which the UA will try to build a path from the leaf to a trusted root.

certSha256

A byte sequence holding the SHA-256 hash that verified the certificate chain's leaf.

integrity header

A list of ASCII strings that describes the response header and any of its parameters that guard the integrity of the response payload.

validityUrl

A URL describing where to update this signature.

validityUrlBytes

The bytes that validityUrl was parsed from.

date

The POSIX time at which the signature starts being valid.

expiration time

The POSIX time at which the signature stops being valid.

4. Algorithms

4.1. Identifying signed exchanges

The signed exchange version of a response response is the result of the following steps:

  1. If determine nosniff on response’s header list returns false, return undefined.

    Note: This requires servers to include the X-Content-Type-Options: nosniff header when they serve signed exchanges, which prevents some clients that don’t understand signed exchanges from interpreting one as another content type.

  2. Let mimeType be the result of extracting a MIME type from response’s header list.

  3. If mimeType is a failure, return undefined.

  4. If mimeType’s essence is not "application/signed-exchange", return undefined.

  5. Let params be mimeType’s parameters

  6. If params["v"] exists, return it. Otherwise, return undefined.

4.2. Extracting the fallback URL

This section defines how to load a the fallback URL from its invariant location in an unrecognized signed exchange version.

Extracting the fallback URL from a response response returns the result of the following steps:

  1. Assert: This algorithm is running in parallel.

  2. Assert: The signed exchange version of response is not undefined.

  3. Let bodyStream be response’s body's stream.

  4. If bodyStream is null, return failure.

  5. Let stream be a new read buffer for bodyStream.

  6. Let (magic, fallbackUrlBytes, fallbackUrl) be the result of parsing the invariant prefix from stream. If returns a failure, return that failure.

  7. Return fallbackUrlBytes.

4.3. Parsing signed exchanges

This section defines how to load the formats defined in [draft-yasskin-httpbis-origin-signed-exchanges-impl-02] and [draft-yasskin-httpbis-origin-signed-exchanges-impl-03].

Parsing a signed exchange of version version from a response response in the context of an environment settings object client, reporting to a signed exchange report report, returns an exchange or a string which indicates a result as described by the following steps

  1. Assert: This algorithm is running in parallel.

  2. Assert: The signed exchange version of response is, if version is

    b2

    "b2"

    b3

    "b3"

  3. If response’s URL's origin is not a potentially trustworthy origin, return "non_secure_distributor".

    Note: This ensures that the privacy properties of retrieving an HTTPS resource via a signed exchange are no worse than retrieving it via TLS.

  4. Let bodyStream be response’s body's stream.

  5. If bodyStream is null, return "parse_error".

  6. Let stream be a new read buffer for bodyStream.

  7. Let (magic, requestUrlBytes, requestUrl) be the result of parsing the invariant prefix from stream. If returns a failure, return "parse_error".

  8. Set report’s inner URL to requestUrl.

  9. If magic is not the following value, depending on version, return "parse_error":

    b2

    `sxg1-b2\0`

    b3

    `sxg1-b3\0`

  10. Assert: requestUrlBytes should match the result of extracting the fallback URL from response.

  11. Let encodedSigLength be the result of reading 3 bytes from stream.

  12. Let encodedHeaderLength be the result of reading 3 bytes from stream.

  13. If encodedSigLength or encodedHeaderLength is a failure, return "parse_error".

  14. Let sigLength be the result of decoding encodedSigLength as a big-endian integer.

  15. Let headerLength be the result of decoding encodedHeaderLength as a big-endian integer.

  16. If sigLength > 16384 or headerLength > 524288, return "parse_error".

  17. Let signature be the result of reading sigLength bytes from stream.

  18. If signature is a failure, return "parse_error".

  19. Let parsedSignature be the result of parsing the Signature header field signature in the context of client reporting to with report.

  20. If parsedSignature is not an exchange signature, return it.

  21. Let headerBytes be the result of reading headerLength bytes from stream.

  22. If headerBytes is a failure, return "parse_error".

  23. If parsedSignature is not valid for headerBytes and requestUrlBytes, and signed exchange version version, return "signature_verification_error".

  24. Let parsedExchange be, if version is:

    b2

    the result of parsing b2 CBOR headers given headerBytes and requestUrl.

    b3

    the result of parsing b3 CBOR headers given headerBytes and requestUrl.

  25. If parsedSignature does not establish cross-origin trust for parsedExchange, return "cert_verification_error".

  26. Set parsedExchange’s response's HTTPS state to either "deprecated" or "modern".

    Note: See HTTP-network fetch for details of this choice.

  27. If parsedExchange’s response's status is a redirect status or the signed exchange version of parsedExchange’s response is not undefined, return "parse_error".

    Note: This might simplify the UA’s implementation, since it doesn’t have to handle nested signed exchanges.

  28. Read a body from stream into parsedExchange’s response using parsedSignature to check its integrity. If this returns an error string, return it.

    Note: Typically this body’s stream is still being enqueued to after returning.

  29. Return parsedExchange.

4.4. Parsing the invariant prefix

All signed exchange versions start with the same initial bytes, parsed by this section.

Parsing the invariant prefix from a read buffer stream returns a failure or the triple of a byte sequence magic, byte sequence fallbackUrlBytes, and URL fallbackUrl, as described by the following steps:

  1. Assert: This algorithm is running in parallel.

  2. Let magic be the result of reading 8 bytes from stream.

  3. If magic is a failure, return it.

  4. Let encodedFallbackUrlLength be the result of reading 2 bytes from stream.

  5. If encodedFallbackUrlLength is a failure, return it.

  6. Let fallbackUrlLength be the result of decoding encodedFallbackUrlLength as a big-endian integer.

  7. Let fallbackUrlBytes be the result of reading fallbackUrlLength bytes from stream.

  8. If fallbackUrlBytes is a failure, return it.

  9. Let fallbackUrlString be the result of UTF-8 decode without BOM or fail on fallbackUrlBytes.

  10. If fallbackUrlString is a failure, return it.

  11. Let fallbackUrl be the result of running the URL parser on fallbackUrlString.

  12. If fallbackUrl is a failure, if it has a non-null fragment, or if its scheme is something other than "https", return a failure.

  13. Return (magic, fallbackUrlBytes, fallbackUrl).

4.5. Parsing a Signature Header Field

Parsing the Signature header field signatureString in the context of an environment settings object client, reporting to a signed exchange report report, returns an exchange signature or a string which indicates a result, as described by the following steps:

  1. Assert: This algorithm is running in parallel.

  2. If signatureString contains any bytes that aren’t ASCII bytes, return "parse_error".

  3. Let parsed be the result of Parsing HTTP1 Header Fields into Structured Headers given an input_string of the ASCII decoding of signatureString and a header_type of "param-list".

  4. If parsed has more than one element, "parse_error".

    Note: This limitation of current implementations will go away in the future.

  5. If any of the parameters of parsed[0] listed here doesn’t have the associated type, "parse_error".

    Byte sequence

    "sig", "cert-sha256"

    String

    "integrity", "cert-url", "validity-url"

    Integer

    "date", "expires"

  6. Let result be a new exchange signature struct.

  7. Set result’s signature to the "sig" parameter of parsed[0].

  8. Set result’s integrity header to the result of strictly splitting the "integrity" parameter of parsed[0] on U+002F (/).

  9. Let certUrl be the result of running the URL parser on the "cert-url" parameter of parsed[0].

  10. Append certUrl to report’s cert URL list.

  11. If certUrl is a failure, if it has a non-null fragment, or if its scheme is something other than "https" or "data", return "parse_error".

  12. Set result’s certSha256 to the "cert-sha256" parameter of parsed[0].

  13. Set result’s validityUrlBytes to the ASCII encoding of the "validity-url" parameter of parsed[0].

  14. Let validityUrl be the result of running the URL parser on the "validity-url" parameter of parsed[0]..

  15. If validityUrl is a failure, if it has a non-null fragment, or if its scheme is something other than "https", return "parse_error".

  16. Set result’s validityUrl to validityUrl.

  17. Set result’s date to the "date" parameter of parsed[0].

  18. Set result’s expiration time to the "expires" parameter of parsed[0].

  19. If result’s expiration time or result’s date is less than 0 or greater than 263-1, return "parse_error".

  20. If result’s expiration time <= result’s date, return "parse_error".

  21. Set result’s certificate chain to the result of handling the certificate reference certUrl with a hash of result’s certSha256 and report in the context of client. If this is not a certificate chain, return it.

  22. Return result.

4.5.1. Handling the certificate reference

Handling the certificate reference certUrl with the SHA-256 hash certSha256 in the context of an environment settings object client, reporting to a signed exchange report report, returns a certificate chain or a string which indicates a result, as described by the following steps:

  1. Assert: This algorithm is running in parallel.

  2. Let certRequest be a new request with the following items:

    url

    certUrl

    header list

    «`Accept`: `application/cert-chain+cbor`»

    client

    client

    service-workers mode

    "none"

    mode

    "cors"

  3. Let certResponse be the result of fetching certRequest.

  4. Append the IP address of the server from which the user agent received the certResponse to report’s cert server IP list, if available.

  5. If certResponse’s status is not 200, return "cert_fetch_error".

  6. Let certMimeType be the result of extracting a MIME type from certResponse’s header list.

  7. If certMimeType is a failure or its essence is not "application/cert-chain+cbor", return "cert_fetch_error".

  8. If certResponse’s body is null or that body’s stream is null, return "cert_parse_error".

  9. Let bytes be the result of reading all bytes from certResponse’s body's stream with a new reader over the same stream.

  10. Wait for bytes to settle.

  11. If bytes was rejected, return "cert_parse_error".

  12. Let chain be the certificate chain produced by parsing bytes’ value using the cert-chain CDDL. If bytes’s value doesn’t match this CDDL or isn’t canonically-encoded CBOR, return "cert_parse_error".

  13. Assert: chain has at least one item.

  14. If the SHA-256 hash of chain’s leaf's certificate is not equal to certSha256, return "signature_verification_error".

  15. Return chain.

4.6. The signed message

The signed message for a version version, an exchange signature signature and byte sequences requestUrlBytes and headerBytes is the concatenation of the following byte sequences:

  1. The byte 0x20 (SP) repeated 64 times. This matches the TLS 1.3 ([RFC8446]) format to avoid cross- protocol attacks if anyone uses the same key in a TLS certificate and an exchange-signing certificate.

  2. A context string consisting of, if version is:

    b2

    `HTTP Exchange 1 b2`

    b3

    `HTTP Exchange 1 b3`

    Note: Each draft of [draft-yasskin-httpbis-origin-signed-exchanges-impl-02] and the final RFC for [draft-yasskin-http-origin-signed-responses] will use distinct context strings.

  3. A single 0x00 byte which serves as a separator.

  4. A single 0x20 (SP) byte, representing the length of the next field.

  5. signature’s certSha256.

  6. The 8-byte big-endian encoding of the length in bytes of signature’s validityUrlBytes.

  7. signature’s validityUrlBytes.

  8. The 8-byte big-endian encoding of signature’s date.

  9. The 8-byte big-endian encoding of signature’s expiration time.

  10. The 8-byte big-endian encoding of the length in bytes of requestUrlBytes.

  11. requestUrlBytes.

  12. The 8-byte big-endian encoding of the length in bytes of headerBytes.

  13. headerBytes.

4.7. Validating a signature

An exchange signature signature is valid for byte sequences requestUrlBytes and headerBytes, and signed exchange version version, if the following steps return valid:

  1. Let clockSkew be the uncertainty in the UA’s estimate of the current time caused by clock skew on the client. The UA MAY set this to 0 or use a more sophisticated estimate.

  2. If the UA’s estimate of the current time is more than clockSkew before signature’s date, return "untrusted".

    Note: We take estimated clock skew into account when checking the signature’s date because we want well-behaved servers to use the time they created the signature, but if they immediately start serving that signature, and skewed clients don’t try to correct for their skew, those clients will reject the signature.

    Our security reviewers aren’t sure we should allow UAs to take clock skew into account. <https://github.com/WICG/webpackage/issues/141>

  3. If the UA’s estimate of the current time is after signature’s expiration time, return "untrusted".

    Note: We use the client’s best guess of the current time to check the expiration time so that attackers trying to get an exchange trusted for longer, are constrained to modify the client’s clock and can’t also attack its estimate of its skew.

  4. Let message be the signed message for version, signature, requestUrlBytes, and headerBytes.

  5. Let publicKey be the public key of parsedSignature’s certificate chain's leaf. If the certificate can’t be parsed enough to find this public key, return invalid.

  6. If publicKey’s algorithm is not id-ecPublicKey on the secp256r1 named curve, return invalid.

  7. If parsedSignature’s signature is not a valid signature of message by publicKey using the ecdsa_secp256r1_sha256 algorithm, return invalid.

  8. Return valid.

4.8. Cross-origin trust

A valid exchange signature signature establishes cross-origin trust in an exchange exchange if the following steps return "trusted":

  1. Let requestUrl be exchange’s request URL.

  2. If signature’s validityUrl's origin is not same origin with requestUrl’s origin, return "untrusted".

  3. If exchange’s response's header list includes an uncached response header, return "untrusted".

  4. If signature’s expiration time is more than 604800 seconds (7 days) after signature’s date, return "untrusted".

  5. If signature’s certificate chain does not have a trusted leaf for requestUrl’s origin, return "untrusted".

  6. Return "trusted".

4.9. Establishing trust in a certificate

The certificate chain chain has a trusted leaf for an origin origin if the following steps return trusted:

  1. Let leaf be chain’s leaf.

  2. Attempt to build a trustworthy path from leaf’s certificate to a trusted root with

    as input, using [RFC5280] and any other conventions used in making TLS ([RFC8446]) connections. The UA SHOULD support Certificate Transparency ([RFC6962]) for this check. (See § 5.1 Certificate Transparency.) The UA MUST check that it has evidence the leaf’s certificate was not revoked 7 or more days ago (for example using the leaf’s OCSP response). If no such path can be built, return untrusted.

  3. If leaf’s certificate is not trusted for origin’s host, return untrusted.

  4. If leaf’s extensions don’t map a CanSignHttpExchanges OID to the ASN.1/DER encoding of NULL (0x05 0x00), return untrusted.

  5. Return trusted.

4.10. Parsing b2 CBOR headers

Parsing b2 CBOR headers from a byte sequence headerBytes and a URL requestUrl returns a failure or an exchange via the following steps:

  1. Let headers be the result of parsing a CBOR item from headerBytes, matching the following CDDL rule:

        headers = [
          {
            ':method': bstr,
            * bstr => bstr,
          },
          {
            ':status': bstr,
            * bstr => bstr,
          }
        ]
    
  2. If any of the following is true, return a failure:

    • headers is an error.

    • headers[0] contains any key starting with `:` that isn’t `:method`.

    • headers[0] contains a `host` key.

    • headers[0][`:method`] is not `GET`.

    • headers[1] contains any key starting with `:` that isn’t `:status`.

    • headers[1][`:status`] is not 200.

  3. Let requestHeaders be the result of creating a header list from the CBOR map headers[0].

  4. If requestHeaders is a failure, return it.

  5. Let responseHeaders be the result of creating a header list from the CBOR map headers[1].

  6. If responseHeaders is a failure, return it.

  7. If responseHeaders does not contain `Content-Type`, return a failure.

  8. Set `X-Content-Type-Options`/`nosniff` in responseHeaders.

  9. Let response be a new response with status headers[1][`:status`] and header list responseHeaders.

  10. Return an exchange of requestUrl and response.

    Note: This ignores requestHeaders, which can’t be encoded in b3 and later.

4.11. Parsing b3 CBOR headers

Parsing b3 CBOR headers from a byte sequence headerBytes and a URL requestUrl returns a failure or an exchange via the following steps:

  1. Let headers be the result of parsing a CBOR item from headerBytes, matching the following CDDL rule:

        headers = {
          ':status': bstr,
          * bstr => bstr,
        }
    
  2. If any of the following is true, return a failure:

    • headers is an error.

    • headers contains any key starting with `:` that isn’t `:status`.

    • headers[`:status`] is not 200.

  3. Let responseHeaders be the result of creating a header list from the CBOR map headers.

  4. If responseHeaders is a failure, return it.

  5. Let response be a new response with status headers[`:status`] and header list responseHeaders.

  6. Return an exchange of requestUrl and response.

4.11.1. Converting a map to a header list

The result of creating a header list from the CBOR map map is returned by the following steps:

  1. Let headers be a new empty header list.

  2. For each keyvalue of map:

    1. If key starts with `:`, continue.

    2. If the isomorphic decoding of key contains any ASCII upper alpha, return a failure.

    3. If key doesn’t match the constraints on a name or value doesn’t match the constraints on a value, return a failure.

    4. Assert: headers does not contain key.

    5. Append key/value to headers.

  3. Return headers.

4.12. Creating the response stream.

To read a body from a read buffer stream into a response response using an exchange signature signature to check its integrity, the UA MUST:

  1. If signature’s integrity header is:

    «"digest", "mi-sha256-03
    1. Let instance-digests be the result of getting, decoding, and splitting `digest` from response’s header list.

      Note: No Digest algorithm uses non-ASCII characters or 0x22 ("), so this is equivalent to parsing from the Digest ABNF <encoded digest output>.

    2. Let mi be the element of instance-digests that starts with "mi-sha256-03=". If there is no such element, return an error string "invalid_integrity_header".

    3. Let codings be the result of getting, decoding, and splitting `content-encoding` in response’s header list.

    4. If codings doesn’t include "mi-sha256-03", return an error string "invalid_integrity_header".

    5. Assert: Handle content codings used the value of mi as the integrity proof for the first record when decoding the mi-sha256-03 content encoding to produce the bytes in stream.

    Anything else

    Return an error string "invalid_integrity_header".

  2. Let body be a new body.

  3. Let cancel be the following steps, taking reason as an argument:

    1. Cancel stream’s stream with reason.

  4. Let outputStream be the result of constructing a ReadableStream with cancel.

  5. Set body’s stream to outputStream.

  6. Set response’s body to body.

  7. In parallel:

    1. Dump stream to outputStream.

4.13. Request matching

A request browserRequest matches the stored exchange storedExchange if the following steps return "match":

  1. If browserRequest’s method is not `GET` or `HEAD`, return "mismatch".

    Note: The browserRequest’s method can be something other than `GET` if a Service Worker intercepts the redirect and modifies the request before re-fetching it.

  2. If browserRequest’s url is not equal to storedExchange’s request URL, return "mismatch".

  3. If storedExchange’s response's header list contains:

    Neither a `Variants` nor a `Variant-Key` header

    Return "match".

    Note: This states that exactly one resource lives at the request URL, and no content negotiation is intended.

    A `Variant-Key` header but no `Variants` header

    Return "mismatch".

    Note: This indicates a likely misconfiguration, and returning "mismatch" makes that fail fast.

    A `Variants` header but no `Variant-Key` header

    Return "mismatch".

    Note: This behavior is implied by the below steps, but we make it explicit here.

    Both a `Variants` and a `Variant-Key` header

    Proceed to the following steps.

  4. If getting `Variants` from storedExchange’s response's header list returns a value that fails to parse according to the instructions for the Variants Header Field, return "mismatch".

  5. Let acceptableVariantKeys be the result of running the Variants Cache Behavior on an incoming-request of browserRequest and stored-responses of a list containing storedExchange’s response.

  6. Let variantKeys be the result of getting `Variant-Key` from storedExchange’s response's header list, and parsing it into a list of lists as described in the Variant-Key Header Field.

  7. If parsing variantKeys failed, return "mismatch".

  8. If the intersection of acceptableVariantKeys and variantKeys is empty, return "mismatch".

    This depends on the Variants Cache Behavior returning a list of lists. <https://github.com/httpwg/http-extensions/issues/744>

  9. Return "match".

4.14. Create a new signed exchange report

To create a new signed exchange report with request and actualResponse, the UA MUST:
  1. Let report be a new signed exchange report struct.

  2. Set report’s outer request to request.

  3. Set report’s outer response to actualResponse.

  4. Set report’s server IP to the IP address of the server from which the user agent received the actualResponse, if available.

  5. Return report.

4.15. Wait and queue a report

To wait and queue a report for parsedExchange and report, the UA MUST:
  1. Wait until parsedExchange’s body's stream is closed or errored.

  2. If parsedExchange’s body's stream is closed, run queue a signed exchange report report with "ok" as the result and abort these steps.

  3. If parsedExchange’s body's stream is errored, run queue a signed exchange report report with "mi_error" as the result.

4.16. Queuing signed exchange report

To queue a signed exchange report report with result as the result, the UA MUST:

  1. Set report’s result to result.

  2. Let report body and policy be the result of generate a network error report with report’s outer request. If the result is null, abort these steps.

  3. If report body’s "type" is "dns.address_changed", abort these steps.

    Note: This means that the NEL report was downgraded because the IP addresses of the server and the policy don’t match. In this case, the UA has called deliver a network report algorithm with the error report while handling the response. So we don’t need to send the same error report while processing the response as a signed exchange.

  4. Add a new property "sxg" to report body with a new ECMAScript object with the following properties:

  5. Set report body’s "phase" to "sxg".

  6. If the report’s result is "ok", set report body’s "type" to "ok". Otherwise, set report body’s "type" to the result of concatenating a string "sxg." and the report’s result.

  7. If report body’s "sxg"'s "cert_url"'s scheme is not "data" and report’s result is "signature_verification_error" or "cert_verification_error" or "cert_fetch_error" or "cert_parse_error":

    1. If report’s outer request's url's origin is different from any origin of the URLs in report’s cert URL list, or report’s server IP is different from any of the IP address in report’s cert server IP list:

      1. Set report body’s "type" to "sxg.failed".

      2. Set report body’s "elapsed_time" to 0.

    Note: This step "downgrades" a Signed Exchange report if the certificate was served from the different server from the server of "outer_url". This is intended to avoid leaking the information about the certificate server.

  8. Deliver a network report with report body and policy and report’s outer request.

    If a NEL policy was received from the distributor’s origin, distributor.example, this step will send the following JSON data to describe an invalid signature:
    {
      "type": "network-error",
      "url": "https://publisher.example/article.html",
      "age": 234,
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) ...",
      "body": {
        "referrer": "https://aggregator.example/article.html",
        "sampling_fraction": 1,
        "server_ip": "192.0.2.42",  // The IP address of distributor.example.
        "protocol": "http/1.1",
        "method": "GET",
        "status_code": 200,
        "elapsed_time": 1234,
        "phase": "sxg",
        "type": "sxg.signature_verification_error",
        "sxg": {
          "outer_url": "https://distributor.example/publisher.example/article.html.sxg",
          "inner_url": "https://publisher.example/article.html",
          "cert_url": ["https://distributor.example/publisher.example/cert"]
        },
      }
    }
    

4.17. Stream algorithms

The algorithms in this section create and operate over ECMAScript objects like ReadableStream despite not having a Realm to attach them to. <https://github.com/whatwg/fetch/issues/730>

4.17.1. Create a read buffer

A new read buffer for a ReadableStream stream is a new read buffer struct whose items are:

stream

stream

reader

The result of getting a reader from stream.

bytes

An empty byte sequence.

4.17.2. Read up to bytes

To read up to N bytes from a read buffer buffer, the UA MUST:

  1. Assert: This algorithm is running in parallel.

  2. Let done be false.

  3. While done is false and the length of buffer’s bytes item is less than N bytes:

    1. Let chunk be the result of reading a chunk with buffer’s reader.

    2. Wait for chunk to settle.

    3. If chunk is :

      Fulfilled with an object whose done property is false and whose value property is a Uint8Array object:
      1. Let bs be the byte sequence represented by the Uint8Array object.

      2. Set buffer to the concatenation of buffer and bs.

      Fulfilled with an object whose done property is true:

      Set done to true.

      Rejected with e:

      Return a failure with reason e.

  4. If buffer’s bytes item is at least N bytes long:

    1. Let result be a byte sequence consisting of the first N bytes of buffer’s bytes item.

    2. Set buffer’s bytes item to a byte sequence consisting of the bytes after the Nth from its old value.

  5. Otherwise:

    1. Let result be buffer’s bytes item.

    2. Set buffer’s bytes item to an empty byte sequence.

  6. Return result.

4.17.3. Read bytes

To read N bytes from a read buffer buffer, the UA MUST:

  1. Assert: This algorithm is running in parallel.

  2. Let bytes be the result of reading up to N bytes from buffer.

  3. If bytes is a failure, return it.

  4. If bytes is exactly N bytes long, return it.

  5. Otherwise, return a failure.

4.17.4. Dump to another stream

To Dump a read buffer input to a ReadableStream output, the UA MUST:

  1. Assert: This algorithm is running in parallel.

  2. Let enqueue be the following steps, taking bytes:

    1. Enqueue a Uint8Array object wrapping an ArrayBuffer containing bytes to output. Both objects are created in output’s realm.

    2. If that threw an exception ex, error output with ex and cancel input’s stream with ex.

    3. Abort this algorithm.

  3. enqueue input’s bytes.

  4. While input’s stream is readable:

    1. Wait until output is closed, errored, or needs more data.

    2. If output is closed or errored, cancel input’s stream and abort these steps.

    3. Let chunk be the result of reading a chunk with input’s reader.

    4. Wait for chunk to settle.

    5. If chunk is:

      Fulfilled with an object whose done property is false and whose value property is a Uint8Array object bytes:

      enqueue bytes.

      Fulfilled with an object whose done property is true:

      Close output.

      Rejected with e:

      Error output with reason e.

5. Security Considerations

The Security Considerations of [draft-yasskin-http-origin-signed-responses] apply.

5.1. Certificate Transparency

To identify off-path attackers, § 4.9 Establishing trust in a certificate encourages UAs to implement Certificate Transparency, which requires that, in order for a certificate to be trusted, it must be logged publicly. This means that an off-path attacker who has managed to get a mis-issued certificate has to at least announce that certificate in a place the legitimate domain owner has a chance to notice. Once they notice, they can revoke the certificate, which will stop the UA from trusting it no more than 7 days later.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DRAFT-IETF-HTTPBIS-VARIANTS]
Mark Nottingham. HTTP Representation Variants. WD. URL: https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html
[DRAFT-THOMSON-HTTP-MICE]
Martin Thomson. Merkle Integrity Content Encoding. ED. URL: https://tools.ietf.org/html/draft-thomson-http-mice-03
[DRAFT-YASSKIN-HTTP-ORIGIN-SIGNED-RESPONSES]
Jeffrey Yasskin. Signed HTTP Exchanges. ED. URL: https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html
[DRAFT-YASSKIN-HTTPBIS-ORIGIN-SIGNED-EXCHANGES-IMPL-02]
Jeffrey Yasskin; Kouhei Ueno. Signed HTTP Exchanges Implementation Checkpoints. ED. URL: https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-02
[DRAFT-YASSKIN-WPACK-BUNDLED-EXCHANGES]
Jeffrey Yasskin. Web Bundles. ED. URL: https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing Standard. Living Standard. URL: https://mimesniff.spec.whatwg.org/
[NETWORK-ERROR-LOGGING-1]
Douglas Creager; et al. Network Error Logging. 25 September 2018. WD. URL: https://www.w3.org/TR/network-error-logging-1/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[RFC5280]
D. Cooper; et al. Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile. May 2008. Proposed Standard. URL: https://tools.ietf.org/html/rfc5280
[RFC5480]
S. Turner; et al. Elliptic Curve Cryptography Subject Public Key Information. March 2009. Proposed Standard. URL: https://tools.ietf.org/html/rfc5480
[RFC6960]
S. Santesson; et al. X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP. June 2013. Proposed Standard. URL: https://tools.ietf.org/html/rfc6960
[RFC6962]
B. Laurie; A. Langley; E. Kasper. Certificate Transparency. June 2013. Experimental. URL: https://tools.ietf.org/html/rfc6962
[RFC7231]
R. Fielding, Ed.; J. Reschke, Ed.. Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content. June 2014. Proposed Standard. URL: https://httpwg.org/specs/rfc7231.html
[RFC8446]
E. Rescorla. The Transport Layer Security (TLS) Protocol Version 1.3. WD. URL: https://tools.ietf.org/html/draft-ietf-tls-tls13
[SECURE-CONTEXTS]
Mike West. Secure Contexts. 15 September 2016. CR. URL: https://www.w3.org/TR/secure-contexts/
[SHA2]
FIPS PUB 180-4, Secure Hash Standard. URL: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
[STREAMS]
Adam Rice; Domenic Denicola; 吉野剛史 (Takeshi Yoshino). Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Boris Zbarsky. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

Informative References

[DRAFT-YASSKIN-HTTPBIS-ORIGIN-SIGNED-EXCHANGES-IMPL-03]
Jeffrey Yasskin; Kouhei Ueno. Signed HTTP Exchanges Implementation Checkpoints. ED. URL: https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-03
[HTTP-DIG-ALG]
Hypertext Transfer Protocol (HTTP) Digest Algorithm Values. LS. URL: https://www.iana.org/assignments/http-dig-alg/http-dig-alg.xhtml
[RFC3230]
J. Mogul; A. Van Hoff. Instance Digests in HTTP. January 2002. Proposed Standard. URL: https://tools.ietf.org/html/rfc3230
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1. 19 November 2019. CR. URL: https://www.w3.org/TR/service-workers-1/

Issues Index

Our security reviewers aren’t sure we should allow UAs to take clock skew into account. <https://github.com/WICG/webpackage/issues/141>
This depends on the Variants Cache Behavior returning a list of lists. <https://github.com/httpwg/http-extensions/issues/744>
The algorithms in this section create and operate over ECMAScript objects like ReadableStream despite not having a Realm to attach them to. <https://github.com/whatwg/fetch/issues/730>