From 052280791ab70f8def2eae8c4dc5dac50f39fc96 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 12 Mar 2024 18:47:38 +0000 Subject: [PATCH 01/32] initial text --- index.bs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 80717951e..e23478566 100644 --- a/index.bs +++ b/index.bs @@ -1228,6 +1228,9 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Non-Discoverable Credential :: This is a [=credential=] whose [=credential ID=] must be provided in {{PublicKeyCredentialRequestOptions/allowCredentials}} when calling {{CredentialsContainer/get()|navigator.credentials.get()}} because it is not [=client-side discoverable credential|client-side discoverable=]. See also [=server-side credentials=]. +: Origin Label +:: The portion of a domain name preceding the effective top-level domain, as defined in the Public Suffix List [[PSL]]. This is often referred to as the eTLD+1. For example, the origin label for example.co.uk and example.de is `example`. + : Public Key Credential :: Generically, a *credential* is data one entity presents to another in order to *authenticate* the former to the latter [[RFC4949]]. The term [=public key credential=] refers to one of: a [=public key credential source=], the @@ -1824,11 +1827,37 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : is present :: If |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, throw a "{{SecurityError}}" {{DOMException}}. + registrable domain suffix of and is not equal to=] |effectiveDomain|, and if the client + +
+ : supports [[#sctn-related-origins|related origin requests]] + :: 1. let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} + + 1. let |maxLabels| be + + 1. fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). + + 1. if the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. + 1. if the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. + 1. if the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. + + 1. Let |originLabelsSeen| be an empty set + 1. [=set/For each=] string in |origins|: + 1. Let |url| be the result of parsing the string as a URL. If that fails, continue with the next element in the list. + 1. Let |domain| be the !!effective domain of |url|. If that is null, continue with the next element of the list. + 1. Remove any !!public suffix from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. + 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. + 1. If |label| is not in |labelsSeen| then: + 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. + 1. Otherwise, continue with the next element of the list. + 1. If |rpIdRequested| and |url| are !!same origin!!, then [=continue=]. + + : does not support [[#sctn-related-origins|related origin requests]] + :: throw a "{{SecurityError}}" {{DOMException}}. +
- : Is not present - :: Set |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} to - |effectiveDomain|. + : is not present + :: Set |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} to |effectiveDomain|. Note: |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} represents the @@ -4046,7 +4075,39 @@ To override this default policy and indicate that a cross-origin <{iframe}> is a [=[RPS]=] utilizing the WebAuthn API in an embedded context should review [[#sctn-seccons-visibility]] regarding [=UI redressing=] and its possible mitigations. +## Using Web Authentication across related origins ## {#sctn-related-origins} + +By default, + +[=[WRP]=] can opt in to allowing a [=WebAuthn Clients=] to select credentials created with a different, but related [=RP ID=] than the caller's [=origin=]. + +WebAuthn credential to be used across a limited set of [=origin|origins=] to support use cases such as country code TLDs(!add reference!), vanity or brand domains, and platform as a service providers supporting mobile apps. + +Relying Parties MUST choose a common Relying Party ID to use across all ceremonies from related origins. + +A JSON document MUST be hosted at the `webauthn-origins` well-known URL ([[!RFC8615]]) for the [=RP ID=]. + +The JSON document MUST be returned as follows: + + - the content type must be `application/json` + - JSON document MUST contain a single object named `origins` + - the `origins` object MUST contain an array of one or more web origins + +For example, for the RP ID `example.com`: + + +{ + "origins": [ + "https://example.co.uk", + "https://example.de", + "https://myexamplerewards.com" + ] +} + + +[=WebAuthn Clients=] SHOULD limit the number of related origins, with a recommended maximum of 5 [=origin labels=]. +[=WebAuthn Clients=] supporting this feature SHOULD include `related-origin-requests` in their response to getClientCapabilities() as defined in !XYZ!. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From d48a18c90abf22bb9f1b588c15ba4217fb62ada3 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 12 Mar 2024 21:13:27 +0000 Subject: [PATCH 02/32] remaining draft text --- index.bs | 94 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/index.bs b/index.bs index e23478566..72ef57674 100644 --- a/index.bs +++ b/index.bs @@ -1831,26 +1831,26 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o
: supports [[#sctn-related-origins|related origin requests]] - :: 1. let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} + :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}. - 1. let |maxLabels| be + 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. - 1. fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). + 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). - 1. if the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. - 1. if the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. - 1. if the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. - 1. Let |originLabelsSeen| be an empty set + 1. Let |originLabelsSeen| be an empty set. 1. [=set/For each=] string in |origins|: 1. Let |url| be the result of parsing the string as a URL. If that fails, continue with the next element in the list. - 1. Let |domain| be the !!effective domain of |url|. If that is null, continue with the next element of the list. - 1. Remove any !!public suffix from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. + 1. Let |domain| be the [=effective domain=] of |url|. If that is null, continue with the next element of the list. + 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. - 1. If |label| is not in |labelsSeen| then: + 1. If |label| is not in |labelsSeen| and: 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. 1. Otherwise, continue with the next element of the list. - 1. If |rpIdRequested| and |url| are !!same origin!!, then [=continue=]. + 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. @@ -2343,19 +2343,47 @@ When this method is invoked, the user agent MUST execute the following algorithm PKI-based security.
  • - If |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} is not present, then set |rpId| to - |effectiveDomain|. + If |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} +
    - Otherwise: + : is present + :: If |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, and if the client - 1. If |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} [=is not a registrable domain suffix of and is not - equal to=] |effectiveDomain|, throw a "{{SecurityError}}" {{DOMException}}. +
    + : supports [[#sctn-related-origins|related origin requests]] + :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} + + 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. + + 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). + + 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. + + 1. Let |originLabelsSeen| be an empty set. + 1. [=set/For each=] string in |origins|: + 1. Let |url| be the result of parsing the string as a URL. If that fails, continue with the next element in the list. + 1. Let |domain| be the [=effective domain=] of |url|. If that is null, continue with the next element of the list. + 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. + 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. + 1. If |label| is not in |labelsSeen| and: + 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. + 1. Otherwise, continue with the next element of the list. + 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. + + : does not support [[#sctn-related-origins|related origin requests]] + :: throw a "{{SecurityError}}" {{DOMException}}. +
    - 1. Set |rpId| to |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}}. + : is not present + :: Set |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} to |effectiveDomain|. +
    - Note: |rpId| represents the caller's [=RP ID=]. The [=RP ID=] defaults to being the caller's [=environment - settings object/origin=]'s [=effective domain=] unless the caller has explicitly set - |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} when calling {{CredentialsContainer/get()}}. + Note: |rpId| represents the caller's [=RP ID=]. The [=RP ID=] defaults to being the caller's [=environment + settings object/origin=]'s [=effective domain=] unless the caller has explicitly set + |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} when calling {{CredentialsContainer/get()}}.
  • 1. Let |clientExtensions| be a new [=map=] and let |authenticatorExtensions| be a new [=map=]. @@ -3992,6 +4020,7 @@ Note: The {{UserVerificationRequirement}} enumeration is deliberately not refere "hybridTransport", "passkeyPlatformAuthenticator", "userVerifyingPlatformAuthenticator", + "relatedOriginRequests" }; @@ -4014,6 +4043,9 @@ Note: The {{ClientCapability}} enumeration is deliberately not referenced, see [ : userVerifyingPlatformAuthenticator :: The [=WebAuthn Client=] supports usage of a [=user-verifying platform authenticator=]. + + : relatedOriginRequests + :: The [=WebAuthn Client=] supports [[#sctn-related-origins|Related Origin Requests]]. ### User-agent Hints Enumeration (enum PublicKeyCredentialHints) ### {#enum-hints} @@ -4077,17 +4109,15 @@ To override this default policy and indicate that a cross-origin <{iframe}> is a ## Using Web Authentication across related origins ## {#sctn-related-origins} -By default, +By default, Web Authentication requires that the [=RP ID=] be equal to the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=], or a [=is a registrable domain suffix of or is equal to|registrable domain suffix=] of the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=]. -[=[WRP]=] can opt in to allowing a [=WebAuthn Clients=] to select credentials created with a different, but related [=RP ID=] than the caller's [=origin=]. +This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where vanity or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. -WebAuthn credential to be used across a limited set of [=origin|origins=] to support use cases such as country code TLDs(!add reference!), vanity or brand domains, and platform as a service providers supporting mobile apps. +[=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=] -Relying Parties MUST choose a common Relying Party ID to use across all ceremonies from related origins. +Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from related origins. -A JSON document MUST be hosted at the `webauthn-origins` well-known URL ([[!RFC8615]]) for the [=RP ID=]. - -The JSON document MUST be returned as follows: +A JSON document MUST be hosted at the `webauthn-origins` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: - the content type must be `application/json` - JSON document MUST contain a single object named `origins` @@ -4100,14 +4130,15 @@ For example, for the RP ID `example.com`: "origins": [ "https://example.co.uk", "https://example.de", + "https://example.sg" "https://myexamplerewards.com" ] } -[=WebAuthn Clients=] SHOULD limit the number of related origins, with a recommended maximum of 5 [=origin labels=]. +To mitigate abuse, [=WebAuthn Clients=] SHOULD limit the number of related origins, with a recommended maximum of 5 [=origin labels=]. -[=WebAuthn Clients=] supporting this feature SHOULD include `related-origin-requests` in their response to getClientCapabilities() as defined in !XYZ!. +[=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOriginRequests}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. # WebAuthn Authenticator Model # {#sctn-authenticator-model} @@ -8677,6 +8708,11 @@ For example: {{CollectedClientData/origin}} to exactly equal some element of a list of allowed origins, for example the list ["https://example.org", "https://login.example.org"]. +- A web application leveraging [[#sctn-related-origins|related origin requests]] might also require + {{CollectedClientData/origin}} to exactly equal some element of a list of allowed origins, + for example the list ["https://example.co.uk", "https://example.de", "https://myexamplerewards.com"]. + This list will typically match the origins listed in the well-known URI for the [=RP ID=]. See [[#sctn-related-origins]]. + - A web application served at a large set of domains that changes often might parse {{CollectedClientData/origin}} structurally and require that the URL scheme is https and that the authority equals or is any subdomain of the [=RP ID=] - for example, From 2a18351fc7e0d56aa1976f5c02ffffe2e2c8a282 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 13 Mar 2024 19:24:55 +0000 Subject: [PATCH 03/32] s/webauthn-origins/webauthn --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 72ef57674..fcd46e5ca 100644 --- a/index.bs +++ b/index.bs @@ -1835,7 +1835,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. - 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). + 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. @@ -2356,7 +2356,7 @@ When this method is invoked, the user agent MUST execute the following algorithm 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. - 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn-origins). + 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. @@ -4117,7 +4117,7 @@ This can make deployment challenging for large environments where multiple count Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from related origins. -A JSON document MUST be hosted at the `webauthn-origins` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: +A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: - the content type must be `application/json` - JSON document MUST contain a single object named `origins` From 689647ca72d2438f8dd849b398f891779caf6a91 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 13 Mar 2024 19:41:57 +0000 Subject: [PATCH 04/32] MUST support 5 origin labels --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index fcd46e5ca..38fc7dc02 100644 --- a/index.bs +++ b/index.bs @@ -4136,7 +4136,7 @@ For example, for the RP ID `example.com`: } -To mitigate abuse, [=WebAuthn Clients=] SHOULD limit the number of related origins, with a recommended maximum of 5 [=origin labels=]. +[=WebAuthn Clients=] supporting this feature MUST support at least five [=origin labels=]. Client policy SHOULD define an upper limit to prevent abuse. [=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOriginRequests}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. From 3aad0542be39fdf0aabadbfb21fac9a729e75bfe Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 13 Mar 2024 20:41:17 +0000 Subject: [PATCH 05/32] s/member/property, s/list/array --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 38fc7dc02..d487ab473 100644 --- a/index.bs +++ b/index.bs @@ -1839,7 +1839,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. 1. Let |originLabelsSeen| be an empty set. 1. [=set/For each=] string in |origins|: @@ -2360,7 +2360,7 @@ When this method is invoked, the user agent MUST execute the following algorithm 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the value of the |origins| member of the JSON object is missing, or is not a list of strings, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. 1. Let |originLabelsSeen| be an empty set. 1. [=set/For each=] string in |origins|: From 5187e72ff47efdf7fe072e7896339a37385f33c2 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 16:35:28 -0400 Subject: [PATCH 06/32] use [=continue=] Co-authored-by: Emil Lundberg --- index.bs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index d487ab473..61036010f 100644 --- a/index.bs +++ b/index.bs @@ -1843,13 +1843,13 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o 1. Let |originLabelsSeen| be an empty set. 1. [=set/For each=] string in |origins|: - 1. Let |url| be the result of parsing the string as a URL. If that fails, continue with the next element in the list. - 1. Let |domain| be the [=effective domain=] of |url|. If that is null, continue with the next element of the list. - 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. + 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. + 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. + 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. 1. If |label| is not in |labelsSeen| and: 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. - 1. Otherwise, continue with the next element of the list. + 1. Otherwise, [=continue=]. 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. : does not support [[#sctn-related-origins|related origin requests]] From 881ac8f40e412951e8921182facd180b22d63444 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 20:38:57 +0000 Subject: [PATCH 07/32] collapse multiple steps into one --- index.bs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 61036010f..606e43470 100644 --- a/index.bs +++ b/index.bs @@ -1847,9 +1847,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. - 1. If |label| is not in |labelsSeen| and: - 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. - 1. Otherwise, [=continue=]. + 1. If |label| is not in |labelsSeen| and the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. Otherwise, [=continue=]. 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. : does not support [[#sctn-related-origins|related origin requests]] From 9062a155efe887534b7f957e4d2be3900511bf59 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 20:41:00 +0000 Subject: [PATCH 08/32] s/vanity/alternative --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 606e43470..84784a617 100644 --- a/index.bs +++ b/index.bs @@ -4109,7 +4109,7 @@ To override this default policy and indicate that a cross-origin <{iframe}> is a By default, Web Authentication requires that the [=RP ID=] be equal to the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=], or a [=is a registrable domain suffix of or is equal to|registrable domain suffix=] of the [=determines the set of origins on which the public key credential may be exercised|origin=]'s [=effective domain=]. -This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where vanity or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. +This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where alternative or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. [=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=] From 2315db8be394ba4c809476c365fecaba59e6c1da Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 16:42:15 -0400 Subject: [PATCH 09/32] capitalization Co-authored-by: Emil Lundberg --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 84784a617..40f1a00b1 100644 --- a/index.bs +++ b/index.bs @@ -4117,9 +4117,9 @@ Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: - - the content type must be `application/json` - - JSON document MUST contain a single object named `origins` - - the `origins` object MUST contain an array of one or more web origins + - The content type must be `application/json`. + - JSON document MUST contain a single object named `origins`. + - The `origins` object MUST contain an array of one or more web origins. For example, for the RP ID `example.com`: From 8ea0303f76a1fb269707c4654526fa067871135a Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 20:47:44 +0000 Subject: [PATCH 10/32] s/relatedOriginRequests/relatedOrigins --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 40f1a00b1..f29003a64 100644 --- a/index.bs +++ b/index.bs @@ -4018,7 +4018,7 @@ Note: The {{UserVerificationRequirement}} enumeration is deliberately not refere "hybridTransport", "passkeyPlatformAuthenticator", "userVerifyingPlatformAuthenticator", - "relatedOriginRequests" + "relatedOrigins" }; @@ -4042,7 +4042,7 @@ Note: The {{ClientCapability}} enumeration is deliberately not referenced, see [ : userVerifyingPlatformAuthenticator :: The [=WebAuthn Client=] supports usage of a [=user-verifying platform authenticator=]. - : relatedOriginRequests + : relatedOrigins :: The [=WebAuthn Client=] supports [[#sctn-related-origins|Related Origin Requests]]. @@ -4136,7 +4136,7 @@ For example, for the RP ID `example.com`: [=WebAuthn Clients=] supporting this feature MUST support at least five [=origin labels=]. Client policy SHOULD define an upper limit to prevent abuse. -[=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOriginRequests}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. +[=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOrigins}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From 28a2b0ff2db057b50414740677a4680bbaf0c04f Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 22:16:17 +0000 Subject: [PATCH 11/32] attempt at using a procedure --- index.bs | 70 +++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/index.bs b/index.bs index f29003a64..5748d2490 100644 --- a/index.bs +++ b/index.bs @@ -1833,27 +1833,17 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}. - 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. - - 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). - - 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. - - 1. Let |originLabelsSeen| be an empty set. - 1. [=set/For each=] string in |origins|: - 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. - 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. - 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. - 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. - 1. If |label| is not in |labelsSeen| and the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. Otherwise, [=continue=]. - 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. - + 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is +
    + : `false` + :: throw a "{{SecurityError}}" {{DOMException}}. + : `true` + :: [=break=]. +
    : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}.
    - + : is not present :: Set |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}} to |effectiveDomain|. @@ -2352,24 +2342,13 @@ When this method is invoked, the user agent MUST execute the following algorithm : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} - 1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. - - 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). - - 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. - 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. - - 1. Let |originLabelsSeen| be an empty set. - 1. [=set/For each=] string in |origins|: - 1. Let |url| be the result of parsing the string as a URL. If that fails, continue with the next element in the list. - 1. Let |domain| be the [=effective domain=] of |url|. If that is null, continue with the next element of the list. - 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, continue with the next element of the list. - 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. - 1. If |label| is not in |labelsSeen| and: - 1. If the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. - 1. Otherwise, continue with the next element of the list. - 1. If |rpIdRequested| and |url| are [=same origin=], then [=continue=]. + 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is +
    + : `false` + :: throw a "{{SecurityError}}" {{DOMException}}. + : `true` + :: [=break=]. +
    : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. @@ -4138,6 +4117,25 @@ For example, for the RP ID `example.com`: [=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOrigins}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. +### Validating Related Origins ### {#sctn-validating-relation-origin} + +To validate the calling origin is an authorized related origin for a given ceremony: + +1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. +1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). + 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. +1. Let |originLabelsSeen| be an empty set. +1. [=set/For each=] string in |origins|: + 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. + 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. + 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. + 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. + 1. If |label| is not in |labelsSeen| and the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. Otherwise, [=continue=]. + 1. If |rpIdRequested| and |url| are [=same origin=], return `true`. +1. Return `false`. + # WebAuthn Authenticator Model # {#sctn-authenticator-model} [[#sctn-api|The Web Authentication API]] implies a specific abstract functional model for a [=[WAA]=]. This section From b51bd165f830a32ae7dd8df38c60bedbfd3c9b8c Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Tue, 26 Mar 2024 22:18:17 +0000 Subject: [PATCH 12/32] s/must/MUST --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 5748d2490..4cb406a12 100644 --- a/index.bs +++ b/index.bs @@ -4096,7 +4096,7 @@ Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: - - The content type must be `application/json`. + - The content type MUST be `application/json`. - JSON document MUST contain a single object named `origins`. - The `origins` object MUST contain an array of one or more web origins. From a21babfe5a74ff9c5da5ab959f826128e252e951 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 18:57:56 +0000 Subject: [PATCH 13/32] remove break --- index.bs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 4cb406a12..36ece833f 100644 --- a/index.bs +++ b/index.bs @@ -1833,13 +1833,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}. - 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is -
    - : `false` - :: throw a "{{SecurityError}}" {{DOMException}}. - : `true` - :: [=break=]. -
    + 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. + : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. From 7466f32570b8ad4d90cf0b68d97d2fe5e1a9fd13 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 19:01:12 +0000 Subject: [PATCH 14/32] remove break for create --- index.bs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 36ece833f..b33cb5ef6 100644 --- a/index.bs +++ b/index.bs @@ -1834,7 +1834,7 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}. 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. - + : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. @@ -2337,13 +2337,7 @@ When this method is invoked, the user agent MUST execute the following algorithm : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} - 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is -
    - : `false` - :: throw a "{{SecurityError}}" {{DOMException}}. - : `true` - :: [=break=]. -
    + 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. From e7d0c7cdb39dbc5c76c971e468e3da8d25fd3da3 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 15:14:50 -0400 Subject: [PATCH 15/32] s/originLabelsSeen/labelsSeen and set def Co-authored-by: Emil Lundberg --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index b33cb5ef6..5af0fecc1 100644 --- a/index.bs +++ b/index.bs @@ -4115,7 +4115,7 @@ To validate the calling origin is an authorized related origin for a given cerem 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. -1. Let |originLabelsSeen| be an empty set. +1. Let |labelsSeen| be a new empty [=set=]. 1. [=set/For each=] string in |origins|: 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. From b3bf34c961435c15b3f55f0dbbe4e539d831d1d5 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 19:07:09 +0000 Subject: [PATCH 16/32] word order --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 5af0fecc1..8e618470b 100644 --- a/index.bs +++ b/index.bs @@ -4110,7 +4110,7 @@ For example, for the RP ID `example.com`: To validate the calling origin is an authorized related origin for a given ceremony: -1. Let |maxLabels| be the number of maximum [=origin labels=] allowed by client policy. +1. Let |maxLabels| be the maximum number of [=origin labels=] allowed by client policy. 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. From bdba7421d169bd4c203d86489c95aad95df8f1f3 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 15:20:28 -0400 Subject: [PATCH 17/32] Validation step optimization allows for exiting the loop early on match Co-authored-by: Emil Lundberg --- index.bs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 8e618470b..1047ecf4b 100644 --- a/index.bs +++ b/index.bs @@ -4121,8 +4121,10 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. - 1. If |label| is not in |labelsSeen| and the number of elements in |labelsSeen| is less than |maxLabels|, then insert |label| into |labelsSeen|. Otherwise, [=continue=]. + 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. 1. If |rpIdRequested| and |url| are [=same origin=], return `true`. + 1. [=set/Append=] |label| to |labelsSeen|. + 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. 1. Return `false`. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From de25c37b2686d171420e48c55a63f0e54f6dbd78 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Mon, 29 Apr 2024 19:23:03 +0000 Subject: [PATCH 18/32] use macros for true and false --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 1047ecf4b..36448287d 100644 --- a/index.bs +++ b/index.bs @@ -4122,10 +4122,10 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. - 1. If |rpIdRequested| and |url| are [=same origin=], return `true`. + 1. If |rpIdRequested| and |url| are [=same origin=], return [TRUE]. 1. [=set/Append=] |label| to |labelsSeen|. 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. -1. Return `false`. +1. Return [FALSE]. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From 71e4e80660e16606f143fe90e3f18b6b87aa63e8 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 13 Jun 2024 05:00:33 +0900 Subject: [PATCH 19/32] add period Co-authored-by: Emil Lundberg --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 36448287d..d11960043 100644 --- a/index.bs +++ b/index.bs @@ -4079,7 +4079,7 @@ By default, Web Authentication requires that the [=RP ID=] be equal to the [=det This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where alternative or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. -[=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=] +[=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=]. Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from related origins. From 26d350ca414b189c20d96adea60652c7f0ecb4aa Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 13 Jun 2024 05:36:28 +0900 Subject: [PATCH 20/32] text tweak Co-authored-by: Emil Lundberg --- index.bs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.bs b/index.bs index d11960043..2bf0fcfad 100644 --- a/index.bs +++ b/index.bs @@ -4080,8 +4080,7 @@ By default, Web Authentication requires that the [=RP ID=] be equal to the [=det This can make deployment challenging for large environments where multiple country-specific domains are in use (e.g. example.com vs example.co.uk vs example.sg), where alternative or brand domains are required (e.g. myexampletravel.com vs examplecruises.com), and/or where platform as a service providers are used to support mobile apps. [=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=]. - -Relying Parties MUST choose a common [=RP ID=] to use across all ceremonies from related origins. +Such [=[RPS]=] MUST choose a common [=RP ID=] to use across all ceremonies from related origins. A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: From a2ac319dcf0d29ff6c38eadb7f6db8086a630a7b Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 15:30:28 +0000 Subject: [PATCH 21/32] clean up well-known JSON definition --- index.bs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 2bf0fcfad..5f73247ca 100644 --- a/index.bs +++ b/index.bs @@ -4085,8 +4085,7 @@ Such [=[RPS]=] MUST choose a common [=RP ID=] to use across all ceremonies from A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: - The content type MUST be `application/json`. - - JSON document MUST contain a single object named `origins`. - - The `origins` object MUST contain an array of one or more web origins. + - The top-level JSON object MUST contain a key named `origins` whose value MUST be an array of one or more strings containing web origins. For example, for the RP ID `example.com`: From b120220e6c0eed44f3c1eeb08cad18526c701977 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 16:25:11 +0000 Subject: [PATCH 22/32] s/origin labels/labels --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 5f73247ca..03a97447e 100644 --- a/index.bs +++ b/index.bs @@ -4118,7 +4118,7 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. - 1. Split |domain| into [=origin labels|labels=] and let |label| be the right-most one. + 1. Split |domain| into |labels| and let |label| be the right-most one. 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. 1. If |rpIdRequested| and |url| are [=same origin=], return [TRUE]. 1. [=set/Append=] |label| to |labelsSeen|. From bcec7faef08c3f5128e88d0b009492f544745e6b Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 19:29:40 +0000 Subject: [PATCH 23/32] s/rpIdRequested/callerOrigin --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 03a97447e..56bdf5f26 100644 --- a/index.bs +++ b/index.bs @@ -4120,7 +4120,7 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into |labels| and let |label| be the right-most one. 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. - 1. If |rpIdRequested| and |url| are [=same origin=], return [TRUE]. + 1. If |callerOrigin| and |url| are [=same origin=], return [TRUE]. 1. [=set/Append=] |label| to |labelsSeen|. 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. 1. Return [FALSE]. From 3ba2ff3305c0bcb66bb4f3be943cb522b9bfe61d Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 19:48:22 +0000 Subject: [PATCH 24/32] move set contains label check after origin match check --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 56bdf5f26..a85484287 100644 --- a/index.bs +++ b/index.bs @@ -4119,8 +4119,8 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into |labels| and let |label| be the right-most one. - 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. 1. If |callerOrigin| and |url| are [=same origin=], return [TRUE]. + 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. 1. [=set/Append=] |label| to |labelsSeen|. 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. 1. Return [FALSE]. From a108a2c79e081e78d68646de08cf78dafd2d0f21 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 20:30:51 +0000 Subject: [PATCH 25/32] add RP guidance around adding all origins with the same origin label should bbe the first elements of the array --- index.bs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index a85484287..a908d60b3 100644 --- a/index.bs +++ b/index.bs @@ -4086,6 +4086,10 @@ A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) f - The content type MUST be `application/json`. - The top-level JSON object MUST contain a key named `origins` whose value MUST be an array of one or more strings containing web origins. + - Origins with the same [=origin label=] SHOULD be listed together as the first elements in the array, with unique origins as the last elements. + +:: Note: When adding additional origins matching an existing [=origin label=], add them after the existing elements with the same [=origin label=] instead of at the end of the array. + This will ensure their processing will not impact the [=origin label=] count. For example, for the RP ID `example.com`: @@ -4094,8 +4098,14 @@ For example, for the RP ID `example.com`: "origins": [ "https://example.co.uk", "https://example.de", - "https://example.sg" - "https://myexamplerewards.com" + "https://example.sg", + "https://example.net", + "https://exampledelivery.com", + "https://exampledelivery.co.uk", + "https://exampledelivery.de", + "https://exampledelivery.sg", + "https://myexamplerewards.com", + "https://examplecars.com" ] } From c0c40d1bfc7c14a799a166e58f1b6d79960843ef Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 20:40:30 +0000 Subject: [PATCH 26/32] remove check for label in labelsSeen, not necessary --- index.bs | 1 - 1 file changed, 1 deletion(-) diff --git a/index.bs b/index.bs index a908d60b3..42072f21f 100644 --- a/index.bs +++ b/index.bs @@ -4130,7 +4130,6 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into |labels| and let |label| be the right-most one. 1. If |callerOrigin| and |url| are [=same origin=], return [TRUE]. - 1. If |labelsSeen| [=set/contains=] |label|, [=continue=]. 1. [=set/Append=] |label| to |labelsSeen|. 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. 1. Return [FALSE]. From ebf03ebdd639280149b6f3671e54642d0308a546 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Fri, 14 Jun 2024 20:43:20 +0000 Subject: [PATCH 27/32] fix indents --- index.bs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 42072f21f..8849efe65 100644 --- a/index.bs +++ b/index.bs @@ -4088,8 +4088,9 @@ A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) f - The top-level JSON object MUST contain a key named `origins` whose value MUST be an array of one or more strings containing web origins. - Origins with the same [=origin label=] SHOULD be listed together as the first elements in the array, with unique origins as the last elements. -:: Note: When adding additional origins matching an existing [=origin label=], add them after the existing elements with the same [=origin label=] instead of at the end of the array. - This will ensure their processing will not impact the [=origin label=] count. + :: Note: When adding additional origins matching an existing [=origin label=], add them after the existing elements with the same [=origin label=] instead of at the end of the array. + This will ensure their processing will not impact the [=origin label=] count. + For example, for the RP ID `example.com`: From e4f24d9b3d1ec57f2dad79d6c210285699f4bda8 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 26 Jun 2024 17:47:50 +0000 Subject: [PATCH 28/32] update RWO algo to support additional origins with the same label --- index.bs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 8849efe65..57a2cec1b 100644 --- a/index.bs +++ b/index.bs @@ -4130,9 +4130,10 @@ To validate the calling origin is an authorized related origin for a given cerem 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. 1. Split |domain| into |labels| and let |label| be the right-most one. + 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels| and |labelsSeen|does not [=contains|contain=] |label|, [=continue=]. 1. If |callerOrigin| and |url| are [=same origin=], return [TRUE]. - 1. [=set/Append=] |label| to |labelsSeen|. - 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels|, [=break=]. + 1. If |labelsSeen| [=contains=] |label|, [=continue=]. + 1. If the [=set/size=] of |labelsSeen| is less than |maxLabels|, [=set/Append=] |label| to |labelsSeen|. 1. Return [FALSE]. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From 2e9bdcfb05503c617c09eb66fa249b21fd448946 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 26 Jun 2024 17:48:48 +0000 Subject: [PATCH 29/32] remove consideration text for RPs about RWO well-known ordering --- index.bs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/index.bs b/index.bs index 57a2cec1b..64c14212a 100644 --- a/index.bs +++ b/index.bs @@ -4086,11 +4086,6 @@ A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) f - The content type MUST be `application/json`. - The top-level JSON object MUST contain a key named `origins` whose value MUST be an array of one or more strings containing web origins. - - Origins with the same [=origin label=] SHOULD be listed together as the first elements in the array, with unique origins as the last elements. - - :: Note: When adding additional origins matching an existing [=origin label=], add them after the existing elements with the same [=origin label=] instead of at the end of the array. - This will ensure their processing will not impact the [=origin label=] count. - For example, for the RP ID `example.com`: From 2462fd87c10d7c038818e902e505d2bd676c4d2d Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 18 Jul 2024 00:00:03 +0900 Subject: [PATCH 30/32] Emil's feedback @emlun's feedback Co-authored-by: Emil Lundberg --- index.bs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index 64c14212a..e044085ed 100644 --- a/index.bs +++ b/index.bs @@ -1228,8 +1228,11 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S : Non-Discoverable Credential :: This is a [=credential=] whose [=credential ID=] must be provided in {{PublicKeyCredentialRequestOptions/allowCredentials}} when calling {{CredentialsContainer/get()|navigator.credentials.get()}} because it is not [=client-side discoverable credential|client-side discoverable=]. See also [=server-side credentials=]. -: Origin Label -:: The portion of a domain name preceding the effective top-level domain, as defined in the Public Suffix List [[PSL]]. This is often referred to as the eTLD+1. For example, the origin label for example.co.uk and example.de is `example`. +: Registrable Origin Label +:: The first [=domain label=] of the [=registrable domain=] of a [=domain=], + or null if the [=registrable domain=] is null. + For example, the [=registrable origin label=] of both `example.co.uk` and `www.example.de` is `example` + if both `co.uk` and `de` are [=public suffixes=]. : Public Key Credential :: Generically, a *credential* is data one entity presents to another in order to *authenticate* the former to the latter @@ -4106,7 +4109,7 @@ For example, for the RP ID `example.com`: } -[=WebAuthn Clients=] supporting this feature MUST support at least five [=origin labels=]. Client policy SHOULD define an upper limit to prevent abuse. +[=WebAuthn Clients=] supporting this feature MUST support at least five [=registrable origin labels=]. Client policy SHOULD define an upper limit to prevent abuse. [=WebAuthn Clients=] supporting this feature SHOULD include {{ClientCapability/relatedOrigins}} in their response to [[#sctn-getClientCapabilities|getClientCapabilities()]]. @@ -4114,21 +4117,20 @@ For example, for the RP ID `example.com`: To validate the calling origin is an authorized related origin for a given ceremony: -1. Let |maxLabels| be the maximum number of [=origin labels=] allowed by client policy. +1. Let |maxLabels| be the maximum number of [=registrable origin labels=] allowed by client policy. 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}. 1. Let |labelsSeen| be a new empty [=set=]. -1. [=set/For each=] string in |origins|: - 1. Let |url| be the result of parsing the string as a URL. If that fails, [=continue=]. +1. [=set/For each=] |originItem| of |origins|: + 1. Let |url| be the result of running the [=URL parser=] with |originItem| as the input. If that fails, [=continue=]. 1. Let |domain| be the [=effective domain=] of |url|. If that is null, [=continue=]. - 1. Remove any [=public suffix=] from the end of |domain|, including private registries and unknown registries. If |domain| is now empty, [=continue=]. - 1. Split |domain| into |labels| and let |label| be the right-most one. - 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels| and |labelsSeen|does not [=contains|contain=] |label|, [=continue=]. + 1. Let |label| be [=registrable origin label=] of |domain|. + 1. If |label| is empty or null, [=continue=]. + 1. If the [=set/size=] of |labelsSeen| is greater than or equal to |maxLabels| and |labelsSeen| does not [=set/contain=] |label|, [=continue=]. 1. If |callerOrigin| and |url| are [=same origin=], return [TRUE]. - 1. If |labelsSeen| [=contains=] |label|, [=continue=]. - 1. If the [=set/size=] of |labelsSeen| is less than |maxLabels|, [=set/Append=] |label| to |labelsSeen|. + 1. If the [=set/size=] of |labelsSeen| is less than |maxLabels|, [=set/append=] |label| to |labelsSeen|. 1. Return [FALSE]. # WebAuthn Authenticator Model # {#sctn-authenticator-model} From 9da2c4b4f55291376bf8da81ac9a4479e39c029b Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Wed, 17 Jul 2024 17:09:23 +0000 Subject: [PATCH 31/32] procedure dfn --- index.bs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index e044085ed..b8a5e1325 100644 --- a/index.bs +++ b/index.bs @@ -1836,7 +1836,8 @@ a numbered step. If outdented, it (today) is rendered as a bullet in the midst o : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialCreationOptions/rp}}.{{PublicKeyCredentialRpEntity/id}}. - 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. + 1. Run the [$related origins validation procedure$] with arguments |callerOrigin| and |rpIdRequested|. + If the result is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. @@ -2340,7 +2341,8 @@ When this method is invoked, the user agent MUST execute the following algorithm : supports [[#sctn-related-origins|related origin requests]] :: 1. Let |rpIdRequested| be the value of |pkOptions|.{{PublicKeyCredentialRequestOptions/rpId}} - 1. If the result of running the [[#sctn-validating-relation-origin|related origins validation procedure]] is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. + 1. Run the [$related origins validation procedure$] with arguments |callerOrigin| and |rpIdRequested|. + If the result is [FALSE], throw a "{{SecurityError}}" {{DOMException}}. : does not support [[#sctn-related-origins|related origin requests]] :: throw a "{{SecurityError}}" {{DOMException}}. @@ -4115,7 +4117,7 @@ For example, for the RP ID `example.com`: ### Validating Related Origins ### {#sctn-validating-relation-origin} -To validate the calling origin is an authorized related origin for a given ceremony: +The related origins validation procedure, given arguments |callerOrigin| and |rpIdRequested|, is as follows: 1. Let |maxLabels| be the maximum number of [=registrable origin labels=] allowed by client policy. 1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). From 017a5e3727c73ccf9a2011369622688c5b474444 Mon Sep 17 00:00:00 2001 From: Tim Cappalli Date: Thu, 18 Jul 2024 03:34:48 +0900 Subject: [PATCH 32/32] Editorial tweaks Co-authored-by: Emil Lundberg --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index b8a5e1325..6227bac49 100644 --- a/index.bs +++ b/index.bs @@ -4087,7 +4087,7 @@ This can make deployment challenging for large environments where multiple count [=[WRPS]=] can opt in to allowing [=WebAuthn Clients=] to enable a credential to be created and used across a limited set of related [=origin|origins=]. Such [=[RPS]=] MUST choose a common [=RP ID=] to use across all ceremonies from related origins. -A JSON document MUST be hosted at the `webauthn` well-known URL ([[!RFC8615]]) for the [=RP ID=]. The JSON document MUST be returned as follows: +A JSON document MUST be hosted at the `webauthn` well-known URL [[!RFC8615]] for the [=RP ID=]. The JSON document MUST be returned as follows: - The content type MUST be `application/json`. - The top-level JSON object MUST contain a key named `origins` whose value MUST be an array of one or more strings containing web origins. @@ -4120,7 +4120,7 @@ For example, for the RP ID `example.com`: The related origins validation procedure, given arguments |callerOrigin| and |rpIdRequested|, is as follows: 1. Let |maxLabels| be the maximum number of [=registrable origin labels=] allowed by client policy. -1. Fetch the well-known URL for the RP ID (https://|rpIdRequested|/.well-known/webauthn). +1. Fetch the `webauthn` well-known URL [[!RFC8615]] for the RP ID |rpIdRequested| (i.e., https://|rpIdRequested|/.well-known/webauthn). 1. If the fetch fails, the response does not have a content type of `application/json`, or does not have a status code (after following redirects) of 200, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the body of the resource is not a valid JSON object, then throw a "{{SecurityError}}" {{DOMException}}. 1. If the value of the |origins| property of the JSON object is missing, or is not an array of strings, then throw a "{{SecurityError}}" {{DOMException}}.