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:
-
If
navigationPreload
is enabled, the signed response will be available in theFetchEvent
'spreloadResponse
. Note that this will also cause a network request for requests that aren’t served from a signed exchange. -
clone()
therequest
and set itscache
to"only-if-cached"
, to retrieve the matching response from either the signed exchange or the HTTP cache. Note thatfetch()
ing a newRequest
with the sameurl
will not retrieve the response from the signed exchange.
1.2. Other interesting details
-
Like with
<link rel="prefetch">
and<link rel="preload">
, the UA will use the stashed response even if its HTTP cache headers have expired. This makes it easier to specify that the contents of the signed exchange don’t wind up in the HTTP cache before our security reviewers have gotten comfortable with that idea. The publisher can still control resource expiration by setting the Signature header’s expiration time to the cache expiration time if that’s sooner than they’d otherwise have the signature expire.
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:
- Let newRequest be a copy of request, except for its body and stashed exchange .
- If request’s body is non-null, set newRequest’s body to the result of cloning request’s body.
- 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.
- Return newRequest.
2.3. Response date
A response response’s date is the result of:
-
Let date be the result of extracting header list values given
`Date`
and response’s header list. -
If date is a failure, return the point in time of the beginning of the universe.
-
Return the point in time represented by date, as interpreted for the Date header field.
2.4. Monkeypatch HTTP fetch
In HTTP fetch, before
If actualResponse’s status is a redirect status, then: ...
add the following steps:
-
If the signed exchange version of actualResponse is:
- undefined
-
Do nothing.
"b2"
or"b3"
-
-
Let report be the result of create a new signed exchange report with request and actualResponse.
-
Let parsedExchange be the result of parsing a signed exchange of version
b2
orb3
, respectively, from actualResponse in the context of request’s client, reporting to report. -
If parsedExchange is not an exchange, run queue a signed exchange report report with parsedExchange as the result, and return a network error.
-
In parallel, wait and queue a report for parsedExchange and report.
-
Set actualResponse’s status to
303
. -
Set actualResponse’s
`Location`
header to the ASCII encoding of the serialization of parsedExchange’s request URL. -
Set request’s stashed exchange to parsedExchange.
-
- Anything else
-
-
Let fallbackUrlBytes be the result of extracting the fallback URL from actualResponse.
-
If fallbackUrlBytes is a failure, return a network error.
-
Set actualResponse’s status to
303
. -
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:
-
If httpRequest’s stashed exchange isn’t null:
-
Let stashedExchange be httpRequest’s stashed exchange.
-
If
-
httpRequest matches the stored exchange stashedExchange and
-
response is null or response’s date is earlier than httpRequest’s stashed exchange's response's date
then set response to httpRequest’s stashed exchange's response.
-
-
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:
-
stream: A
ReadableStream
. -
reader: A
ReadableStreamDefaultReader
whose stream is stream. -
bytes: A byte sequence holding the bytes that have been read from stream but not returned to a calling algorithm yet.
3.3. Augmented Certificate
An augmented certificate is a tuple with the following items:
-
certificate, a byte sequence that’s expected to hold a DER-encoded X.509v3 certificate ([RFC5280]).
-
OCSP response, a byte sequence that’s expected to hold a DER-encoded OCSPResponse for the certificate.
-
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:
-
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. -
Let mimeType be the result of extracting a MIME type from response’s header list.
-
If mimeType is a failure, return undefined.
-
If mimeType’s essence is not
"application/signed-exchange"
, return undefined. -
Let params be mimeType’s parameters
-
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:
-
Assert: This algorithm is running in parallel.
-
Assert: The signed exchange version of response is not undefined.
-
If bodyStream is null, return failure.
-
Let stream be a new read buffer for bodyStream.
-
Let (magic, fallbackUrlBytes, fallbackUrl) be the result of parsing the invariant prefix from stream. If returns a failure, return that failure.
-
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
-
Assert: This algorithm is running in parallel.
-
Assert: The signed exchange version of response is, if version is
b2
-
"b2"
b3
-
"b3"
-
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.
-
If bodyStream is null, return "parse_error".
-
Let stream be a new read buffer for bodyStream.
-
Let (magic, requestUrlBytes, requestUrl) be the result of parsing the invariant prefix from stream. If returns a failure, return "parse_error".
-
Set report’s inner URL to requestUrl.
-
If magic is not the following value, depending on version, return "parse_error":
b2
-
`sxg1-b2\0`
b3
-
`sxg1-b3\0`
-
Assert: requestUrlBytes should match the result of extracting the fallback URL from response.
-
Let encodedSigLength be the result of reading 3 bytes from stream.
-
Let encodedHeaderLength be the result of reading 3 bytes from stream.
-
If encodedSigLength or encodedHeaderLength is a failure, return "parse_error".
-
Let sigLength be the result of decoding encodedSigLength as a big-endian integer.
-
Let headerLength be the result of decoding encodedHeaderLength as a big-endian integer.
-
If sigLength > 16384 or headerLength > 524288, return "parse_error".
-
Let signature be the result of reading sigLength bytes from stream.
-
If signature is a failure, return "parse_error".
-
Let parsedSignature be the result of parsing the Signature header field signature in the context of client reporting to with report.
-
If parsedSignature is not an exchange signature, return it.
-
Let headerBytes be the result of reading headerLength bytes from stream.
-
If headerBytes is a failure, return "parse_error".
-
If parsedSignature is not valid for headerBytes and requestUrlBytes, and signed exchange version version, return "signature_verification_error".
-
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.
-
If parsedSignature does not establish cross-origin trust for parsedExchange, return "cert_verification_error".
-
Set parsedExchange’s response's HTTPS state to either "
deprecated
" or "modern
".Note: See HTTP-network fetch for details of this choice.
-
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.
-
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.
-
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:
-
Assert: This algorithm is running in parallel.
-
Let magic be the result of reading 8 bytes from stream.
-
If magic is a failure, return it.
-
Let encodedFallbackUrlLength be the result of reading 2 bytes from stream.
-
If encodedFallbackUrlLength is a failure, return it.
-
Let fallbackUrlLength be the result of decoding encodedFallbackUrlLength as a big-endian integer.
-
Let fallbackUrlBytes be the result of reading fallbackUrlLength bytes from stream.
-
If fallbackUrlBytes is a failure, return it.
-
Let fallbackUrlString be the result of UTF-8 decode without BOM or fail on fallbackUrlBytes.
-
If fallbackUrlString is a failure, return it.
-
Let fallbackUrl be the result of running the URL parser on fallbackUrlString.
-
If fallbackUrl is a failure, if it has a non-null fragment, or if its scheme is something other than
"https"
, return a failure. -
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:
-
Assert: This algorithm is running in parallel.
-
If signatureString contains any bytes that aren’t ASCII bytes, return "parse_error".
-
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".
-
If parsed has more than one element, "parse_error".
Note: This limitation of current implementations will go away in the future.
-
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"
-
Let result be a new exchange signature struct.
-
Set result’s signature to the "sig" parameter of parsed[0].
-
Set result’s integrity header to the result of strictly splitting the "integrity" parameter of parsed[0] on U+002F (
/
). -
Let certUrl be the result of running the URL parser on the "cert-url" parameter of parsed[0].
-
Append certUrl to report’s cert URL list.
-
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". -
Set result’s certSha256 to the "cert-sha256" parameter of parsed[0].
-
Set result’s validityUrlBytes to the ASCII encoding of the "validity-url" parameter of parsed[0].
-
Let validityUrl be the result of running the URL parser on the "validity-url" parameter of parsed[0]..
-
If validityUrl is a failure, if it has a non-null fragment, or if its scheme is something other than
"https"
, return "parse_error". -
Set result’s validityUrl to validityUrl.
-
Set result’s date to the "date" parameter of parsed[0].
-
Set result’s expiration time to the "expires" parameter of parsed[0].
-
If result’s expiration time or result’s date is less than 0 or greater than 263-1, return "parse_error".
-
If result’s expiration time <= result’s date, return "parse_error".
-
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.
-
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:
-
Assert: This algorithm is running in parallel.
-
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
"
-
Let certResponse be the result of fetching certRequest.
-
Append the IP address of the server from which the user agent received the certResponse to report’s cert server IP list, if available.
-
If certResponse’s status is not
200
, return "cert_fetch_error". -
Let certMimeType be the result of extracting a MIME type from certResponse’s header list.
-
If certMimeType is a failure or its essence is not
"application/cert-chain+cbor"
, return "cert_fetch_error". -
If certResponse’s body is null or that body’s stream is null, return "cert_parse_error".
-
Let bytes be the result of reading all bytes from certResponse’s body's stream with a new reader over the same stream.
-
Wait for bytes to settle.
-
If bytes was rejected, return "cert_parse_error".
-
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".
-
Assert: chain has at least one item.
-
If the SHA-256 hash of chain’s leaf's certificate is not equal to certSha256, return "signature_verification_error".
-
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:
-
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.
-
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.
-
A single 0x00 byte which serves as a separator.
-
A single 0x20 (SP) byte, representing the length of the next field.
-
signature’s certSha256.
-
The 8-byte big-endian encoding of the length in bytes of signature’s validityUrlBytes.
-
signature’s validityUrlBytes.
-
The 8-byte big-endian encoding of signature’s date.
-
The 8-byte big-endian encoding of signature’s expiration time.
-
The 8-byte big-endian encoding of the length in bytes of requestUrlBytes.
-
requestUrlBytes.
-
The 8-byte big-endian encoding of the length in bytes of headerBytes.
-
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
:
-
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.
-
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>
-
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.
-
Let message be the signed message for version, signature, requestUrlBytes, and headerBytes.
-
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
. -
If publicKey’s algorithm is not id-ecPublicKey on the secp256r1 named curve, return
invalid
. -
If parsedSignature’s signature is not a valid signature of message by publicKey using the ecdsa_secp256r1_sha256 algorithm, return
invalid
. -
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":
-
Let requestUrl be exchange’s request URL.
-
If signature’s validityUrl's origin is not same origin with requestUrl’s origin, return "untrusted".
-
If exchange’s response's header list includes an uncached response header, return "untrusted".
-
If signature’s expiration time is more than 604800 seconds (7 days) after signature’s date, return "untrusted".
-
If signature’s certificate chain does not have a trusted leaf for requestUrl’s origin, return "untrusted".
-
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
:
-
Let leaf be chain’s leaf.
-
Attempt to build a trustworthy path from leaf’s certificate to a trusted root with
-
chain,
-
leaf’s OCSP response,
-
any SCTs from
-
leaf’s SCT.
-
An OCSP extension in leaf’s OCSP response.
-
An X.509 extension in leaf’s certificate.
-
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
. -
-
If leaf’s certificate is not trusted for origin’s host, return
untrusted
. -
If leaf’s extensions don’t map a CanSignHttpExchanges OID to the ASN.1/DER encoding of NULL (0x05 0x00), return
untrusted
. -
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:
-
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, } ]
-
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 not200
.
-
-
Let requestHeaders be the result of creating a header list from the CBOR map headers[0].
-
If requestHeaders is a failure, return it.
-
Let responseHeaders be the result of creating a header list from the CBOR map headers[1].
-
If responseHeaders is a failure, return it.
-
If responseHeaders does not contain
`Content-Type`
, return a failure. -
Set
`X-Content-Type-Options`
/`nosniff`
in responseHeaders. -
Let response be a new response with status headers[1][
`:status`
] and header list responseHeaders. -
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:
-
Let headers be the result of parsing a CBOR item from headerBytes, matching the following CDDL rule:
headers = { ':status': bstr, * bstr => bstr, }
-
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 not200
.
-
-
Let responseHeaders be the result of creating a header list from the CBOR map headers.
-
If responseHeaders is a failure, return it.
-
Let response be a new response with status headers[
`:status`
] and header list responseHeaders. -
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:
-
Let headers be a new empty header list.
-
For each key → value of map:
-
If key starts with
`:`
, continue. -
If the isomorphic decoding of key contains any ASCII upper alpha, return a failure.
-
If key doesn’t match the constraints on a name or value doesn’t match the constraints on a value, return a failure.
-
Assert: headers does not contain key.
-
Append key/value to headers.
-
-
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:
-
If signature’s integrity header is:
- «"
digest
", "mi-sha256-03
"» -
-
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>
. -
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". -
Let codings be the result of getting, decoding, and splitting
`content-encoding`
in response’s header list. -
If codings doesn’t include
"mi-sha256-03"
, return an error string "invalid_integrity_header". -
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".
- «"
-
Let body be a new body.
-
Let cancel be the following steps, taking reason as an argument:
-
Let outputStream be the result of constructing a ReadableStream with cancel.
-
Set body’s stream to outputStream.
-
Set response’s body to body.
-
-
Dump stream to outputStream.
-
4.13. Request matching
A request browserRequest matches the stored exchange storedExchange if the following steps return "match":
-
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. -
If browserRequest’s url is not equal to storedExchange’s request URL, return "mismatch".
-
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.
- Neither a
-
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". -
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.
-
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. -
If parsing variantKeys failed, return "mismatch".
-
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>
-
Return "match".
4.14. Create a new signed exchange report
To create a new signed exchange report with request and actualResponse, the UA MUST:-
Let report be a new signed exchange report struct.
-
Set report’s outer request to request.
-
Set report’s outer response to actualResponse.
-
Set report’s server IP to the IP address of the server from which the user agent received the actualResponse, if available.
-
Return report.
4.15. Wait and queue a report
To wait and queue a report for parsedExchange and report, the UA MUST:-
Wait until parsedExchange’s body's stream is closed or errored.
-
If parsedExchange’s body's stream is closed, run queue a signed exchange report report with "ok" as the result and abort these steps.
-
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:
-
Set report’s result to result.
-
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.
-
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.
-
Add a new property
"sxg"
to report body with a new ECMAScript object with the following properties:-
outer_url
: The serialization of report’s outer request's url. -
inner_url
: The serialization of report’s inner URL. -
cert_url
: The sequence type of the result of serialization of each element of report’s cert URL list.
-
-
Set report body’s
"phase"
to"sxg"
. -
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. -
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":-
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:
-
Set report body’s
"type"
to"sxg.failed"
. -
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. -
-
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" : "<IP_ADDRESS>" , // 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:
-
Assert: This algorithm is running in parallel.
-
Let done be
false
. -
While done is
false
and the length of buffer’s bytes item is less than N bytes:-
Let chunk be the result of reading a chunk with buffer’s reader.
-
Wait for chunk to settle.
-
If chunk is :
- Fulfilled with an object whose
done
property isfalse
and whosevalue
property is aUint8Array
object: -
-
Let bs be the byte sequence represented by the
Uint8Array
object. -
Set buffer to the concatenation of buffer and bs.
-
- Fulfilled with an object whose
done
property istrue
: -
Set done to
true
. - Rejected with e:
-
Return a failure with reason e.
- Fulfilled with an object whose
-
-
If buffer’s bytes item is at least N bytes long:
-
Let result be a byte sequence consisting of the first N bytes of buffer’s bytes item.
-
Set buffer’s bytes item to a byte sequence consisting of the bytes after the Nth from its old value.
-
-
Otherwise:
-
Let result be buffer’s bytes item.
-
Set buffer’s bytes item to an empty byte sequence.
-
-
Return result.
4.17.3. Read bytes
To read N bytes from a read buffer buffer, the UA MUST:
-
Assert: This algorithm is running in parallel.
-
Let bytes be the result of reading up to N bytes from buffer.
-
If bytes is a failure, return it.
-
If bytes is exactly N bytes long, return it.
-
Otherwise, return a failure.
4.17.4. Dump to another stream
To Dump a read buffer input to a ReadableStream
output, the UA MUST:
-
Assert: This algorithm is running in parallel.
-
Let enqueue be the following steps, taking bytes:
-
Enqueue a
Uint8Array
object wrapping anArrayBuffer
containing bytes to output. Both objects are created in output’s realm. -
If that threw an exception ex, error output with ex and cancel input’s stream with ex.
-
Abort this algorithm.
-
-
enqueue input’s bytes.
-
While input’s stream is readable:
-
Wait until output is closed, errored, or needs more data.
-
If output is closed or errored, cancel input’s stream and abort these steps.
-
Let chunk be the result of reading a chunk with input’s reader.
-
Wait for chunk to settle.
-
If chunk is:
- Fulfilled with an object whose
done
property isfalse
and whosevalue
property is aUint8Array
object bytes: -
enqueue bytes.
- Fulfilled with an object whose
done
property istrue
: -
Close output.
- Rejected with e:
-
Error output with reason e.
- Fulfilled with an object whose
-
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.


Psalm 101
A psalm of David.
1 I will sing of covenant faithfulness and justice;
to you, Yahweh, I will sing praises.
2 I will walk in the way of integrity.
Oh, when will you come to me?

Those who walk in the way of integrity may serve me.
7 Deceitful people will not remain within my house;
liars will not be welcome before my eyes.
8 Morning by morning I will destroy all the wicked from the land;
I will remove all evildoers from the city of Yahweh.