From a936a3abecb351ecdaacb346c2138396dab2106f Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Mon, 8 Jul 2024 16:26:10 -0400 Subject: [PATCH 01/21] Add Signal API This commit adds a `PublicKeyCredential.signal` method that relying parties can call to notify authenticators of changes on the applicability or metadata of credentials. Closes #1967 --- index.bs | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/index.bs b/index.bs index d70ec37cf..43bc504ff 100644 --- a/index.bs +++ b/index.bs @@ -2953,6 +2953,182 @@ value and terminate the operation. +### Signal Credential Changes to the Authenticator - PublicKeyCredential's `signal()` Method {#sctn-signal} + + + partial interface PublicKeyCredential { + static Promise<undefined> signal(PublicKeyCredentialSignalOptions options); + }; + + dictionary PublicKeyCredentialSignalOptions { + USVString rpId; + Base64URLString unknownCredentialId; + PublicKeyCurrentCredentialDetails currentCredentials; + }; + + dictionary PublicKeyCurrentCredentialDetails { + required Base64URLString userId; + PublicKeyCurrentCredentialUserDetails user; + sequence<Base64URLString> allAcceptedCredentialIds; + }; + + dictionary PublicKeyCurrentCredentialUserDetails { + required DOMString name; + required DOMString displayName; + }; + + +[=[WRPS]=] may use {{PublicKeyCredential/signal()}} to signal [=authenticators=] +the state of [=public key credentials=], so that incorrect or revoked +credentials may be updated or removed. [=User agents=] provide this +functionality opportunistically, since an authenticator may not support updating +its [=credentials map=] or it may not be attached at the time the request is +made. Furthermore, in order to avoid revealing information about a user's +credentials without [=user consent=], the {{PublicKeyCredential/signal()}} +operation does indicate whether the operation succeeded. A successfully resolved +promise only indicates that the {{PublicKeyCredentialSignalOptions}} object was +well formed and accepted. + +A number of credential reports can be signalled by [=[WRPS]=]. Each +[=credential report=] declares a validation algorithm and +authenticator actions. The input for both these algorithms is the +{{PublicKeyCredentialSignalOptions}} |options|. + +#### unknownCredentialId report #### {#sctn-unknownCredentialIdReport} + +Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. +because it was deleted by the user. + + : [=unknownCredentialId=] [=validation algorithm=]: + :: If the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}} + is an error, then throw a {{TypeError}}. + + : [=unknownCredentialId=] [=authenticator actions=]: + :: [=map/For each=] [=public key credential source=] |credential| in the + [=authenticator=]'s [=credential map=]: + * If the |credential|'s [=public key credential source/rpId=] does + not equal + |options|.{{PublicKeyCredentialSignalOptions/rpId}}, + [=continue=]. + * If the |credential|'s [=public key credential source/id=] does + not equal the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}}, + [=continue=]. + * [=map/Remove=] |credential| from the [=credentials map=] or hide it + from future [=authentication ceremonies=]. + +
+ +A user deletes a [=credential=] on a [=[WRP]=] provided UI. Later, when trying +to authenticate to the [=[WRP]=] with an [=list/empty=] +{{PublicKeyCredentialRequestOptions/allowCredentials}}, the [=authenticator=] UI +offers them the [=credential=] they previously deleted. If the user selects that +[=credential=], after rejecting the sign-in attempt, the [=[WRP]=] can run: + +``` +PublicKeyCredential.signal({ + rpId: "example.com", + unknownCredentialId: , +}); +``` + +The [=authenticator=] then deletes or hides the [=credential=] from future +[=authentication ceremonies=]. + +
+ +#### currentCredentials report #### {#sctn-currentCredentialsReport} + +Signals the complete list of [=credential ids=] for a given user, as well as the +user's current {{PublicKeyCredentialEntity/name}} and +{{PublicKeyCredentialUserEntity/displayName}}. + + : [=currentCredentials=] [=validation algorithm=]: + :: If the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}} + is an error, then throw a {{TypeError}}. + If + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}} + is present: + * [=list/For each=] |credentialId| in + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}: + * If the result of [=base64url encoding | base64url decoding=] + |credentialId| is an error, then throw a {{TypeError}}. + + : [=currentCredentials=] [=authenticator actions=]: + :: Let |rpId| be |options|.{{PublicKeyCredentialSignalOptions/rpId}}. + Let |userId| be result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}}. + Assert: |userId| is not an error. + Let |credential| be [=credentials map=]\[|rpId|, |userId|]. + If |credential| does not exist, abort these steps. + * Let |userDetails| be + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/user}}. + * If |userDetails| is present, update the |credential|'s + [=public key credential source/otherUI=] to match + |userDetails|.{{PublicKeyCurrentCredentialUserDetails/name}} + and + |userDetails|.{{PublicKeyCurrentCredentialUserDetails/displayName}} + * Let |allAcceptedCredentialIds| be + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}. + * If |allAcceptedCredentialIds| is present and + |allAcceptedCredentialIds| does NOT [=list/contain=] the result of + [=base64url encoding=] the |credential|'s [=public key credential + source/id=], then [=map/remove=] |credential| from the + [=credentials map=] or hide it from future [=authentication + ceremonies=]. + +
+ +A user updates their name on a [=[WRP]=] provided UI. The [=[WRP]=] runs: + +``` +PublicKeyCredential.signal({ + rpId: "example.com", + currentCredentials: { + userId: + user: { + name: , + displayName: , + } + } +}); +``` + +The [=authenticator=] then updates the [=public key credential source/otherUI=] +of the matching credential. + +
+ +
+ +A user has two credentials with [=credential ids=] that [=base64url encode=] to +`aa` and `bb`. The user deletes the credential `aa` on a [=[WRP]=] provided UI. +The [=[WRP]=] runs: + +``` +PublicKeyCredential.signal({ + rpId: "example.com", + currentCredentials: { + userId: + allAcceptedCredentialIds: [ + "bb", + ] + } +}); +``` + +If the [=authenticator=] is attached at the time of the report, it deletes or +hides the [=credential=] corresponding to `aa` from future [=authentication +ceremonies=]. + +
+ +Note: [=authenticators=] may not be attached at the time a report is signalled. +Therefore, [=[WRPS]=] may choose to signal [=currentCredentials=] periodically, +e.g. on every sign in. + ## Authenticator Responses (interface AuthenticatorResponse) ## {#iface-authenticatorresponse} [=Authenticators=] respond to [=[RP]=] requests by returning an object derived from the From e60e2a8c231401d80d50a3a1463d158e9f7e82f5 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Mon, 8 Jul 2024 17:18:33 -0400 Subject: [PATCH 02/21] Add first version of the algo and format --- index.bs | 142 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 55 deletions(-) diff --git a/index.bs b/index.bs index 43bc504ff..8ab1fc079 100644 --- a/index.bs +++ b/index.bs @@ -2999,32 +2999,32 @@ authenticator actions. The input for both these algorithms is the Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. because it was deleted by the user. - : [=unknownCredentialId=] [=validation algorithm=]: - :: If the result of [=base64url encoding | base64url decoding=] - |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}} - is an error, then throw a {{TypeError}}. - - : [=unknownCredentialId=] [=authenticator actions=]: - :: [=map/For each=] [=public key credential source=] |credential| in the - [=authenticator=]'s [=credential map=]: - * If the |credential|'s [=public key credential source/rpId=] does - not equal - |options|.{{PublicKeyCredentialSignalOptions/rpId}}, - [=continue=]. - * If the |credential|'s [=public key credential source/id=] does - not equal the result of [=base64url encoding | base64url decoding=] - |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}}, - [=continue=]. - * [=map/Remove=] |credential| from the [=credentials map=] or hide it - from future [=authentication ceremonies=]. +The [=unknownCredentialId=] [=validation algorithm=] is as follows: +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}} + is an error, then throw a {{TypeError}}. + +The [=unknownCredentialId=] [=authenticator actions=] are as follows: +1. [=map/For each=] [=public key credential source=] |credential| in the + [=authenticator=]'s [=credential map=]: + 1. If the |credential|'s [=public key credential source/rpId=] does + not equal + |options|.{{PublicKeyCredentialSignalOptions/rpId}}, + [=continue=]. + 1. If the |credential|'s [=public key credential source/id=] does + not equal the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}}, + [=continue=]. + 1. [=map/Remove=] |credential| from the [=credentials map=] or hide it + from future [=authentication ceremonies=].
A user deletes a [=credential=] on a [=[WRP]=] provided UI. Later, when trying to authenticate to the [=[WRP]=] with an [=list/empty=] {{PublicKeyCredentialRequestOptions/allowCredentials}}, the [=authenticator=] UI -offers them the [=credential=] they previously deleted. If the user selects that -[=credential=], after rejecting the sign-in attempt, the [=[WRP]=] can run: +offers them the [=credential=] they previously deleted. The user selects that +[=credential=]. After rejecting the sign-in attempt, the [=[WRP]=] runs: ``` PublicKeyCredential.signal({ @@ -3044,40 +3044,39 @@ Signals the complete list of [=credential ids=] for a given user, as well as the user's current {{PublicKeyCredentialEntity/name}} and {{PublicKeyCredentialUserEntity/displayName}}. - : [=currentCredentials=] [=validation algorithm=]: - :: If the result of [=base64url encoding | base64url decoding=] - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}} - is an error, then throw a {{TypeError}}. - If - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}} - is present: - * [=list/For each=] |credentialId| in - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}: - * If the result of [=base64url encoding | base64url decoding=] - |credentialId| is an error, then throw a {{TypeError}}. - - : [=currentCredentials=] [=authenticator actions=]: - :: Let |rpId| be |options|.{{PublicKeyCredentialSignalOptions/rpId}}. - Let |userId| be result of [=base64url encoding | base64url decoding=] - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}}. - Assert: |userId| is not an error. - Let |credential| be [=credentials map=]\[|rpId|, |userId|]. - If |credential| does not exist, abort these steps. - * Let |userDetails| be - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/user}}. - * If |userDetails| is present, update the |credential|'s - [=public key credential source/otherUI=] to match - |userDetails|.{{PublicKeyCurrentCredentialUserDetails/name}} - and - |userDetails|.{{PublicKeyCurrentCredentialUserDetails/displayName}} - * Let |allAcceptedCredentialIds| be - |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}. - * If |allAcceptedCredentialIds| is present and - |allAcceptedCredentialIds| does NOT [=list/contain=] the result of - [=base64url encoding=] the |credential|'s [=public key credential - source/id=], then [=map/remove=] |credential| from the - [=credentials map=] or hide it from future [=authentication - ceremonies=]. +The [=currentCredentials=] [=validation algorithm=] is as follows: +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}} + is an error, then throw a {{TypeError}}. +1. If + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}} + is present: + 1. [=list/For each=] |credentialId| in + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}: + 1. If the result of [=base64url encoding | base64url decoding=] + |credentialId| is an error, then throw a {{TypeError}}. + +The [=currentCredentials=] [=authenticator actions=] are as follows: +1. Let |rpId| be |options|.{{PublicKeyCredentialSignalOptions/rpId}}. +1. Let |userId| be result of [=base64url encoding | base64url decoding=] + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}}. +1. Assert: |userId| is not an error. +1. Let |credential| be [=credentials map=][|rpId|, |userId|]. +1. If |credential| does not exist, abort these steps. +1. Let |userDetails| be + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/user}}. +1. If |userDetails| is present, update the |credential|'s [=public key + credential source/otherUI=] to match + |userDetails|.{{PublicKeyCurrentCredentialUserDetails/name}} + and + |userDetails|.{{PublicKeyCurrentCredentialUserDetails/displayName}} +1. Let |allAcceptedCredentialIds| be + |options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}. +1. If |allAcceptedCredentialIds| is present and + |allAcceptedCredentialIds| does NOT [=list/contain=] the result of + [=base64url encoding=] the |credential|'s [=public key credential + source/id=], then [=map/remove=] |credential| from the [=credentials map=] + or hide it from future [=authentication ceremonies=].
@@ -3125,10 +3124,43 @@ ceremonies=].
-Note: [=authenticators=] may not be attached at the time a report is signalled. +Note: [=Authenticators=] may not be attached at the time a report is signalled. Therefore, [=[WRPS]=] may choose to signal [=currentCredentials=] periodically, e.g. on every sign in. +#### {{PublicKeyCredential/signal()}} algorithm #### {#sctn-signalAlgorithm} + +Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: + +1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If + [=effective domain=] is not a [=valid domain=], then return [=a promise + rejected with=] "{{SecurityError}}" {{DOMException}}. +1. If |options|.{{PublicKeyCredentialSignalOptions/rpId}} [=is not + a registrable domain suffix of and is not equal to=] |effectiveDomain|, + then return [=a promise rejected with=] "{{SecurityError}}" + {{DOMException}}. + + TODO: this may require updating once #2040 lands. +1. For every [=credential report=] |report| defined by this specification: + 1. If |options|.|report| does NOT exist, [=continue=]. + 1. Run the |report|'s [=validation algorithm=]. + 1. If that threw an [=exception=], then return [=a promise rejected with=] + that exception and abort these steps. + +1. Run the following steps [=in parallel=]: + + 1. For every [=credential report=] |report| defined by this specification: + 1. If |options|.|report| does NOT exist, [=continue=]. + 1. For every [=authenticator=] presently available on this [=client + platform=], invoke the the |report|'s [=authenticator actions=]. + +1. Return [=a promise resolved with=] `undefined`. + +Note: This algorithm intentionally does not wait for the [=authenticators=] to +finish executing the [=credential report=] [=authenticator actions=] to protect +users from [=[WRPS]=] gaining information about availability of their +credentials without [=user consent=] from the timing of the request. + ## Authenticator Responses (interface AuthenticatorResponse) ## {#iface-authenticatorresponse} [=Authenticators=] respond to [=[RP]=] requests by returning an object derived from the From eb75cc8761d9ded7565f10f501a2e28ca9aae70f Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Tue, 9 Jul 2024 11:29:35 -0400 Subject: [PATCH 03/21] Update interface. --- index.bs | 141 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/index.bs b/index.bs index 8ab1fc079..9795802ce 100644 --- a/index.bs +++ b/index.bs @@ -2953,26 +2953,31 @@ value and terminate the operation.
-### Signal Credential Changes to the Authenticator - PublicKeyCredential's `signal()` Method {#sctn-signal} +### Signal Credential Changes to the Authenticator - PublicKeyCredential's `signal()` Method ### {#sctn-signal} partial interface PublicKeyCredential { - static Promise<undefined> signal(PublicKeyCredentialSignalOptions options); + static Promise<undefined> signal(optional PublicKeyCredentialSignalOptions options = {}); }; dictionary PublicKeyCredentialSignalOptions { + UnknownCredentialIdReport unknownCredential; + CurrentCredentialsReport currentCredentials; + }; + + dictionary UnknownCredentialIdReport { USVString rpId; - Base64URLString unknownCredentialId; - PublicKeyCurrentCredentialDetails currentCredentials; + required Base64URLString credentialId; }; - dictionary PublicKeyCurrentCredentialDetails { + dictionary CurrentCredentialsReport { + USVString rpId; required Base64URLString userId; - PublicKeyCurrentCredentialUserDetails user; + CurrentCredentialUserDetails user; sequence<Base64URLString> allAcceptedCredentialIds; }; - dictionary PublicKeyCurrentCredentialUserDetails { + dictionary CurrentCredentialUserDetails { required DOMString name; required DOMString displayName; }; @@ -2990,30 +2995,43 @@ promise only indicates that the {{PublicKeyCredentialSignalOptions}} object was well formed and accepted. A number of <dfn>credential reports</dfn> can be signalled by [=[WRPS]=]. Each -[=credential report=] declares a <dfn>validation algorithm</dfn> and <dfn> -authenticator actions</dfn>. The input for both these algorithms is the -{{PublicKeyCredentialSignalOptions}} |options|. +[=credential report=] declares a <dfn for="credential report">validation +algorithm</dfn> and <dfn for="credential report">authenticator actions</dfn>. -#### <dfn>unknownCredentialId</dfn> report #### {#sctn-unknownCredentialIdReport} +Note: In cases where an [=authenticator=] does not have the capability to +process [=credential report/authenticator actions=], [=clients=] may choose to +use existing interfaces such as [[!FIDO-CTAP]]'s +`authenticatorCredentialManagement` command. + +#### <dfn>unknownCredential</dfn> report #### {#sctn-unknownCredentialIdReport} Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. because it was deleted by the user. -The [=unknownCredentialId=] [=validation algorithm=] is as follows: +The [=unknownCredential=] [=validation algorithm=] takes an +{{UnknownCredentialIdReport}} |options| and is as follows: +1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If + [=effective domain=] is not a [=valid domain=], then return [=a promise + rejected with=] "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, then + return [=a promise rejected with=] "{{SecurityError}}" {{DOMException}}. + + TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}}</code> - is an error, then throw a {{TypeError}}. + <code>|options|.{{UnknownCredentialIdReport/credentialId}}</code> is an + error, then throw a {{TypeError}}. -The [=unknownCredentialId=] [=authenticator actions=] are as follows: +The [=unknownCredential=] [=authenticator actions=] take an +{{UnknownCredentialIdReport}} |options| and are as follows: 1. [=map/For each=] [=public key credential source=] |credential| in the [=authenticator=]'s [=credential map=]: - 1. If the |credential|'s [=public key credential source/rpId=] does - not equal - <code>|options|.{{PublicKeyCredentialSignalOptions/rpId}}</code>, + 1. If the |credential|'s [=public key credential source/rpId=] does not + equal <code>|options|.{{UnknownCredentialIdReport/rpId}}</code>, [=continue=]. 1. If the |credential|'s [=public key credential source/id=] does not equal the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{PublicKeyCredentialSignalOptions/unknownCredentialId}}</code>, + <code>|options|.{{UnknownCredentialIdReport/credentialId}}</code>, [=continue=]. 1. [=map/Remove=] |credential| from the [=credentials map=] or hide it from future [=authentication ceremonies=]. @@ -3028,8 +3046,10 @@ offers them the [=credential=] they previously deleted. The user selects that ``` PublicKeyCredential.signal({ - rpId: "example.com", - unknownCredentialId: <id of the credential the user just tried, base64url>, + unknownCredential: { + rpId: "example.com", + credentialId: <id of the credential the user just tried, base64url>, + } }); ``` @@ -3044,39 +3064,48 @@ Signals the complete list of [=credential ids=] for a given user, as well as the user's current {{PublicKeyCredentialEntity/name}} and {{PublicKeyCredentialUserEntity/displayName}}. -The [=currentCredentials=] [=validation algorithm=] is as follows: +The [=currentCredentials=] [=validation algorithm=] takes a +{{CurrentCredentialsReport}} |options| and is as follows: +1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If + [=effective domain=] is not a [=valid domain=], then return [=a promise + rejected with=] "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{CurrentCredentialsReport/rpId}}</code> [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, then + return [=a promise rejected with=] "{{SecurityError}}" {{DOMException}}. + + TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}}</code> - is an error, then throw a {{TypeError}}. + <code>|options|.{{CurrentCredentialsReport/userId}}</code> is an error, then + throw a {{TypeError}}. 1. If - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}</code> + <code>|options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}</code> is present: 1. [=list/For each=] |credentialId| in - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}</code>: + <code>|options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}</code>: 1. If the result of [=base64url encoding | base64url decoding=] |credentialId| is an error, then throw a {{TypeError}}. -The [=currentCredentials=] [=authenticator actions=] are as follows: -1. Let |rpId| be |options|.{{PublicKeyCredentialSignalOptions/rpId}}. +The [=currentCredentials=] [=credential report/authenticator actions=] take a +{{CurrentCredentialsReport}} |options| and are as follows: 1. Let |userId| be result of [=base64url encoding | base64url decoding=] - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/userId}}</code>. + <code>|options|.{{CurrentCredentialsReport/userId}}</code>. 1. Assert: |userId| is not an error. -1. Let |credential| be <code>[=credentials map=][|rpId|, |userId|]</code>. +1. Let |credential| be + <code>[=credentials map=][|options|.{{CurrentCredentialsReport/rpId}}, |userId|]</code>. 1. If |credential| does not exist, abort these steps. -1. Let |userDetails| be - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/user}}</code>. -1. If |userDetails| is present, update the |credential|'s [=public key - credential source/otherUI=] to match - <code>|userDetails|.{{PublicKeyCurrentCredentialUserDetails/name}}</code> +1. If <code>|options|.{{CurrentCredentialsReport/user}}</code> is present, + update the |credential|'s [=public key credential source/otherUI=] to match + <code>|options|.{{CurrentCredentialsReport/user}}.{{CurrentCredentialUserDetails/name}}</code> and - <code>|userDetails|.{{PublicKeyCurrentCredentialUserDetails/displayName}}</code> -1. Let |allAcceptedCredentialIds| be - <code>|options|.{{PublicKeyCredentialSignalOptions/currentCredentials}}.{{PublicKeyCurrentCredentialDetails/allAcceptedCredentialIds}}</code>. -1. If |allAcceptedCredentialIds| is present and - |allAcceptedCredentialIds| does NOT [=list/contain=] the result of - [=base64url encoding=] the |credential|'s [=public key credential - source/id=], then [=map/remove=] |credential| from the [=credentials map=] - or hide it from future [=authentication ceremonies=]. + <code>|options|.{{CurrentCredentialsReport/user}}.{{CurrentCredentialUserDetails/displayName}}</code> +1. If + <code>|options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}</code> + is present and + <code>|options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}</code> + does NOT [=list/contain=] the result of [=base64url encoding=] the + |credential|'s [=public key credential source/id=], then [=map/remove=] + |credential| from the [=credentials map=] or hide it from future + [=authentication ceremonies=]. <div class="example"> @@ -3084,8 +3113,8 @@ A user updates their name on a [=[WRP]=] provided UI. The [=[WRP]=] runs: ``` PublicKeyCredential.signal({ - rpId: "example.com", currentCredentials: { + rpId: "example.com", userId: <user handle, base64url> user: { name: <new user name>, @@ -3108,8 +3137,8 @@ The [=[WRP]=] runs: ``` PublicKeyCredential.signal({ - rpId: "example.com", currentCredentials: { + rpId: "example.com", userId: <user handle, base64url> allAcceptedCredentialIds: [ "bb", @@ -3132,18 +3161,10 @@ e.g. on every sign in. Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: -1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then return [=a promise - rejected with=] "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{PublicKeyCredentialSignalOptions/rpId}}</code> [=is not - a registrable domain suffix of and is not equal to=] |effectiveDomain|, - then return [=a promise rejected with=] "{{SecurityError}}" - {{DOMException}}. - - TODO: this may require updating once #2040 lands. 1. For every [=credential report=] |report| defined by this specification: 1. If |options|.|report| does NOT exist, [=continue=]. - 1. Run the |report|'s [=validation algorithm=]. + 1. Run the |report|'s [=credential report/validation algorithm=] with + |options|.|report| as input. 1. If that threw an [=exception=], then return [=a promise rejected with=] that exception and abort these steps. @@ -3152,14 +3173,16 @@ Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: 1. For every [=credential report=] |report| defined by this specification: 1. If |options|.|report| does NOT exist, [=continue=]. 1. For every [=authenticator=] presently available on this [=client - platform=], invoke the the |report|'s [=authenticator actions=]. + platform=], invoke the the |report|'s [=credential + report/authenticator actions=]. 1. Return [=a promise resolved with=] `undefined`. Note: This algorithm intentionally does not wait for the [=authenticators=] to -finish executing the [=credential report=] [=authenticator actions=] to protect -users from [=[WRPS]=] gaining information about availability of their -credentials without [=user consent=] from the timing of the request. +finish executing the [=credential report=] [=credential report/authenticator +actions=] to protect users from [=[WRPS]=] gaining information about +availability of their credentials without [=user consent=] from the timing of +the request. ## Authenticator Responses (interface <dfn interface>AuthenticatorResponse</dfn>) ## {#iface-authenticatorresponse} From 0b85a2746e2433e0b015012741ae117b9e495105 Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Tue, 9 Jul 2024 14:05:30 -0400 Subject: [PATCH 04/21] Have more flexibility for authenticators. --- index.bs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 9795802ce..f82728638 100644 --- a/index.bs +++ b/index.bs @@ -2998,10 +2998,16 @@ A number of <dfn>credential reports</dfn> can be signalled by [=[WRPS]=]. Each [=credential report=] declares a <dfn for="credential report">validation algorithm</dfn> and <dfn for="credential report">authenticator actions</dfn>. +[=Authenticators=] may choose to deviate in their [=credential +report/authenticator actions=] from the present specification, e.g. to ignore an +update they have a reasonable belief would be contrary to the user's wish, or to +ask the user before making any changes. [=credential report/Authenticator +actions=] are provided as the recommended way to handle [=credential reports=]. + Note: In cases where an [=authenticator=] does not have the capability to process [=credential report/authenticator actions=], [=clients=] may choose to use existing interfaces such as [[!FIDO-CTAP]]'s -`authenticatorCredentialManagement` command. +`authenticatorCredentialManagement` command to achieve an equivalent effect. #### <dfn>unknownCredential</dfn> report #### {#sctn-unknownCredentialIdReport} @@ -3033,8 +3039,9 @@ The [=unknownCredential=] [=authenticator actions=] take an not equal the result of [=base64url encoding | base64url decoding=] <code>|options|.{{UnknownCredentialIdReport/credentialId}}</code>, [=continue=]. - 1. [=map/Remove=] |credential| from the [=credentials map=] or hide it - from future [=authentication ceremonies=]. + 1. [=map/Remove=] |credential| from the [=credentials map=] or employ an + [=authenticator=]-specific procedure to hide it from future + [=authentication ceremonies=]. <div class="example"> @@ -3104,8 +3111,9 @@ The [=currentCredentials=] [=credential report/authenticator actions=] take a <code>|options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}</code> does NOT [=list/contain=] the result of [=base64url encoding=] the |credential|'s [=public key credential source/id=], then [=map/remove=] - |credential| from the [=credentials map=] or hide it from future - [=authentication ceremonies=]. + |credential| from the [=credentials map=] or employ an + [=authenticator=]-specific procedure to hide it from future [=authentication + ceremonies=]. <div class="example"> @@ -3169,7 +3177,6 @@ Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: that exception and abort these steps. 1. Run the following steps [=in parallel=]: - 1. For every [=credential report=] |report| defined by this specification: 1. If |options|.|report| does NOT exist, [=continue=]. 1. For every [=authenticator=] presently available on this [=client From 6c3507f01bbfe8a03d7785eda7729752862242bb Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Tue, 9 Jul 2024 17:53:25 -0400 Subject: [PATCH 05/21] Polish --- index.bs | 78 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/index.bs b/index.bs index f82728638..1852c3a08 100644 --- a/index.bs +++ b/index.bs @@ -2985,28 +2985,28 @@ value and terminate the operation. [=[WRPS]=] may use {{PublicKeyCredential/signal()}} to signal [=authenticators=] the state of [=public key credentials=], so that incorrect or revoked -credentials may be updated or removed. [=User agents=] provide this +credentials may be updated, removed, or hidden. [=Clients=] provide this functionality opportunistically, since an authenticator may not support updating its [=credentials map=] or it may not be attached at the time the request is made. Furthermore, in order to avoid revealing information about a user's -credentials without [=user consent=], the {{PublicKeyCredential/signal()}} -operation does indicate whether the operation succeeded. A successfully resolved -promise only indicates that the {{PublicKeyCredentialSignalOptions}} object was -well formed and accepted. +credentials without [=user consent=], {{PublicKeyCredential/signal()}} does not +indicate whether the operation succeeded. A successfully resolved promise only +means that the {{PublicKeyCredentialSignalOptions}} object was well formed. A number of <dfn>credential reports</dfn> can be signalled by [=[WRPS]=]. Each [=credential report=] declares a <dfn for="credential report">validation algorithm</dfn> and <dfn for="credential report">authenticator actions</dfn>. [=Authenticators=] may choose to deviate in their [=credential -report/authenticator actions=] from the present specification, e.g. to ignore an -update they have a reasonable belief would be contrary to the user's wish, or to -ask the user before making any changes. [=credential report/Authenticator -actions=] are provided as the recommended way to handle [=credential reports=]. +report/authenticator actions=] from the present specification, e.g. to ignore a +change they have a reasonable belief would be contrary to the user's wish, or to +ask the user before making some change. [=credential report/Authenticator +actions=] are thus provided as the recommended way to handle [=credential +reports=]. Note: In cases where an [=authenticator=] does not have the capability to process [=credential report/authenticator actions=], [=clients=] may choose to -use existing interfaces such as [[!FIDO-CTAP]]'s +use existing infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` command to achieve an equivalent effect. #### <dfn>unknownCredential</dfn> report #### {#sctn-unknownCredentialIdReport} @@ -3015,13 +3015,17 @@ Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. because it was deleted by the user. The [=unknownCredential=] [=validation algorithm=] takes an -{{UnknownCredentialIdReport}} |options| and is as follows: +{{UnknownCredentialIdReport}} |options| and executes these steps: + 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then return [=a promise - rejected with=] "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, then - return [=a promise rejected with=] "{{SecurityError}}" {{DOMException}}. + [=effective domain=] is not a [=valid domain=], then throw a + "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> is not present, + then set <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> to + |effectiveDomain|. +1. Otherwise, if <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> [=is + not a registrable domain suffix of and is not equal to=] |effectiveDomain|, + then throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] @@ -3051,11 +3055,11 @@ to authenticate to the [=[WRP]=] with an [=list/empty=] offers them the [=credential=] they previously deleted. The user selects that [=credential=]. After rejecting the sign-in attempt, the [=[WRP]=] runs: -``` +```javascript PublicKeyCredential.signal({ unknownCredential: { rpId: "example.com", - credentialId: <id of the credential the user just tried, base64url>, + credentialId: "aabbcc" // credential id the user just tried, base64url } }); ``` @@ -3072,13 +3076,16 @@ user's current {{PublicKeyCredentialEntity/name}} and {{PublicKeyCredentialUserEntity/displayName}}. The [=currentCredentials=] [=validation algorithm=] takes a -{{CurrentCredentialsReport}} |options| and is as follows: +{{CurrentCredentialsReport}} |options| and executes these steps: 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then return [=a promise - rejected with=] "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{CurrentCredentialsReport/rpId}}</code> [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, then - return [=a promise rejected with=] "{{SecurityError}}" {{DOMException}}. + [=effective domain=] is not a [=valid domain=], then throw a + "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{CurrentCredentialsReport/rpId}}</code> is not present, + then set <code>|options|.{{CurrentCredentialsReport/rpId}}</code> to + |effectiveDomain|. +1. Otherwise, if <code>|options|.{{CurrentCredentialsReport/rpId}}</code> [=is + not a registrable domain suffix of and is not equal to=] |effectiveDomain|, + then throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] @@ -3096,7 +3103,7 @@ The [=currentCredentials=] [=credential report/authenticator actions=] take a {{CurrentCredentialsReport}} |options| and are as follows: 1. Let |userId| be result of [=base64url encoding | base64url decoding=] <code>|options|.{{CurrentCredentialsReport/userId}}</code>. -1. Assert: |userId| is not an error. +1. Assertion: |userId| is not an error. 1. Let |credential| be <code>[=credentials map=][|options|.{{CurrentCredentialsReport/rpId}}, |userId|]</code>. 1. If |credential| does not exist, abort these steps. @@ -3119,14 +3126,14 @@ The [=currentCredentials=] [=credential report/authenticator actions=] take a A user updates their name on a [=[WRP]=] provided UI. The [=[WRP]=] runs: -``` +```javascript PublicKeyCredential.signal({ currentCredentials: { rpId: "example.com", - userId: <user handle, base64url> + userId: "aabbcc", // user handle, base64url. user: { - name: <new user name>, - displayName: <new user display name>, + name: "New user name", + displayName: "New display name" } } }); @@ -3143,11 +3150,11 @@ A user has two credentials with [=credential ids=] that [=base64url encode=] to `aa` and `bb`. The user deletes the credential `aa` on a [=[WRP]=] provided UI. The [=[WRP]=] runs: -``` +```javascript PublicKeyCredential.signal({ currentCredentials: { rpId: "example.com", - userId: <user handle, base64url> + userId: "aabbcc", // user handle, base64url. allAcceptedCredentialIds: [ "bb", ] @@ -3170,18 +3177,19 @@ e.g. on every sign in. Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: 1. For every [=credential report=] |report| defined by this specification: - 1. If |options|.|report| does NOT exist, [=continue=]. + 1. If <code>|options|[|report|]</code> does NOT exist, [=continue=]. 1. Run the |report|'s [=credential report/validation algorithm=] with - |options|.|report| as input. + <code>|options|[|report|]</code> as input. 1. If that threw an [=exception=], then return [=a promise rejected with=] that exception and abort these steps. 1. Run the following steps [=in parallel=]: 1. For every [=credential report=] |report| defined by this specification: - 1. If |options|.|report| does NOT exist, [=continue=]. + 1. If <code>|options|[|report|]</code> does NOT exist, [=continue=]. 1. For every [=authenticator=] presently available on this [=client platform=], invoke the the |report|'s [=credential - report/authenticator actions=]. + report/authenticator actions=] with <code>|options|[|report|]</code> + as input. 1. Return [=a promise resolved with=] `undefined`. From d9545f0aada109cd79dcff41dc3f070c49e2301d Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Thu, 11 Jul 2024 11:55:09 -0400 Subject: [PATCH 06/21] make rpid required --- index.bs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index 1852c3a08..8e4bad54b 100644 --- a/index.bs +++ b/index.bs @@ -2966,12 +2966,12 @@ value and terminate the operation. }; dictionary UnknownCredentialIdReport { - USVString rpId; + required USVString rpId; required Base64URLString credentialId; }; dictionary CurrentCredentialsReport { - USVString rpId; + required USVString rpId; required Base64URLString userId; CurrentCredentialUserDetails user; sequence<Base64URLString> allAcceptedCredentialIds; @@ -3020,12 +3020,9 @@ The [=unknownCredential=] [=validation algorithm=] takes an 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If [=effective domain=] is not a [=valid domain=], then throw a "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> is not present, - then set <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> to - |effectiveDomain|. -1. Otherwise, if <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> [=is - not a registrable domain suffix of and is not equal to=] |effectiveDomain|, - then throw a "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{UnknownCredentialIdReport/rpId}}</code> [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, then + throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] @@ -3080,12 +3077,9 @@ The [=currentCredentials=] [=validation algorithm=] takes a 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If [=effective domain=] is not a [=valid domain=], then throw a "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{CurrentCredentialsReport/rpId}}</code> is not present, - then set <code>|options|.{{CurrentCredentialsReport/rpId}}</code> to - |effectiveDomain|. -1. Otherwise, if <code>|options|.{{CurrentCredentialsReport/rpId}}</code> [=is - not a registrable domain suffix of and is not equal to=] |effectiveDomain|, - then throw a "{{SecurityError}}" {{DOMException}}. +1. If <code>|options|.{{CurrentCredentialsReport/rpId}}</code> [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, then + throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] From b8d6e716377d5bfaf0aae788b57113acebf694c6 Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Wed, 24 Jul 2024 15:48:22 -0400 Subject: [PATCH 07/21] Split signal API into three methods. --- index.bs | 254 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 135 insertions(+), 119 deletions(-) diff --git a/index.bs b/index.bs index 8e4bad54b..4f4e49882 100644 --- a/index.bs +++ b/index.bs @@ -2953,92 +2953,100 @@ value and terminate the operation. </div> -### Signal Credential Changes to the Authenticator - PublicKeyCredential's `signal()` Method ### {#sctn-signal} +### Signal Credential Changes to the Authenticator - PublicKeyCredential's [=signal methods=] ### {#sctn-signalMethods} <xmp class="idl"> partial interface PublicKeyCredential { - static Promise<undefined> signal(optional PublicKeyCredentialSignalOptions options = {}); + static Promise<undefined> signalUnknownCredentialId(UnknownCredentialIdOptions options); + static Promise<undefined> signalAllAcceptedCredentialIds(AllAcceptedCredentialIdsOptions options); + static Promise<undefined> signalCurrentUserDetails(CurrentUserDetailsOptions options); }; - dictionary PublicKeyCredentialSignalOptions { - UnknownCredentialIdReport unknownCredential; - CurrentCredentialsReport currentCredentials; - }; - - dictionary UnknownCredentialIdReport { + dictionary UnknownCredentialIdOptions { required USVString rpId; required Base64URLString credentialId; }; - dictionary CurrentCredentialsReport { + dictionary AllAcceptedCredentialIdsOptions { required USVString rpId; required Base64URLString userId; - CurrentCredentialUserDetails user; - sequence<Base64URLString> allAcceptedCredentialIds; + required sequence<Base64URLString> allAcceptedCredentialIds; }; - dictionary CurrentCredentialUserDetails { + dictionary CurrentUserDetailsOptions { + required USVString rpId; + required Base64URLString userId; required DOMString name; required DOMString displayName; }; -[=[WRPS]=] may use {{PublicKeyCredential/signal()}} to signal [=authenticators=] +[=[WRPS]=] may use these signal methods to signal [=authenticators=] the state of [=public key credentials=], so that incorrect or revoked credentials may be updated, removed, or hidden. [=Clients=] provide this functionality opportunistically, since an authenticator may not support updating its [=credentials map=] or it may not be attached at the time the request is made. Furthermore, in order to avoid revealing information about a user's -credentials without [=user consent=], {{PublicKeyCredential/signal()}} does not -indicate whether the operation succeeded. A successfully resolved promise only -means that the {{PublicKeyCredentialSignalOptions}} object was well formed. - -A number of credential reports can be signalled by [=[WRPS]=]. Each -[=credential report=] declares a validation -algorithm and authenticator actions. +credentials without [=user consent=], [=signal methods=] do not indicate whether +the operation succeeded. A successfully resolved promise only means that the +options object was well formed. -[=Authenticators=] may choose to deviate in their [=credential -report/authenticator actions=] from the present specification, e.g. to ignore a +Each [=signal method=] includes authenticator +actions. [=Authenticators=] may choose to deviate in their [=signal +method/authenticator actions=] from the present specification, e.g. to ignore a change they have a reasonable belief would be contrary to the user's wish, or to -ask the user before making some change. [=credential report/Authenticator -actions=] are thus provided as the recommended way to handle [=credential -reports=]. +ask the user before making some change. [=signal method/Authenticator actions=] +are thus provided as the recommended way to handle [=signal methods=]. Note: In cases where an [=authenticator=] does not have the capability to -process [=credential report/authenticator actions=], [=clients=] may choose to -use existing infrastructure such as [[!FIDO-CTAP]]'s +process a [=signal method/authenticator action=], [=clients=] may choose to use +existing infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` command to achieve an equivalent effect. -#### unknownCredential report #### {#sctn-unknownCredentialIdReport} +Note: [=signal methods] intentionally do not wait for the [=authenticators=] to +finish executing the [=signal method/authenticator actions=] to protect users +from [=[WRPS]=] gaining information about availability of their credentials +without [=user consent=] from the timing of the request. + +#### {{PublicKeyCredential/signalUnknownCredentialId(options)}} #### {#sctn-signalUnknownCredentialId} Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. because it was deleted by the user. -The [=unknownCredential=] [=validation algorithm=] takes an -{{UnknownCredentialIdReport}} |options| and executes these steps: +Upon execution of {{PublicKeyCredential/signalUnknownCredentialId(options)}}, +the [=client=] executes these steps: 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If [=effective domain=] is not a [=valid domain=], then throw a "{{SecurityError}}" {{DOMException}}. -1. If |options|.{{UnknownCredentialIdReport/rpId}} [=is not a +1. If |options|.{{UnknownCredentialIdOptions/rpId}} [=is not a registrable domain suffix of and is not equal to=] |effectiveDomain|, then throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] - |options|.{{UnknownCredentialIdReport/credentialId}} is an + |options|.{{UnknownCredentialIdOptions/credentialId}} is an error, then throw a {{TypeError}}. + +1. Run the following steps [=in parallel=]: + 1. For every [=authenticator=] presently available on this [=client + platform=], invoke the [=signal method/authenticator + action/unknownCredentialId=] [=authenticator action=] with |options| + as input. + +1. Return [=a promise resolved with=] `undefined`. -The [=unknownCredential=] [=authenticator actions=] take an -{{UnknownCredentialIdReport}} |options| and are as follows: +The unknownCredentialId +[=signal method/authenticator action=] takes an {{UnknownCredentialIdOptions}} +|options| and is as follows: 1. [=map/For each=] [=public key credential source=] |credential| in the [=authenticator=]'s [=credential map=]: 1. If the |credential|'s [=public key credential source/rpId=] does not - equal |options|.{{UnknownCredentialIdReport/rpId}}, + equal |options|.{{UnknownCredentialIdOptions/rpId}}, [=continue=]. 1. If the |credential|'s [=public key credential source/id=] does not equal the result of [=base64url encoding | base64url decoding=] - |options|.{{UnknownCredentialIdReport/credentialId}}, + |options|.{{UnknownCredentialIdOptions/credentialId}}, [=continue=]. 1. [=map/Remove=] |credential| from the [=credentials map=] or employ an [=authenticator=]-specific procedure to hide it from future @@ -3053,11 +3061,9 @@ offers them the [=credential=] they previously deleted. The user selects that [=credential=]. After rejecting the sign-in attempt, the [=[WRP]=] runs: ```javascript -PublicKeyCredential.signal({ - unknownCredential: { - rpId: "example.com", - credentialId: "aabbcc" // credential id the user just tried, base64url - } +PublicKeyCredential.signalUnknownCredentialId({ + rpId: "example.com", + credentialId: "aabbcc" // credential id the user just tried, base64url }); ``` @@ -3066,50 +3072,48 @@ The [=authenticator=] then deletes or hides the [=credential=] from future -#### currentCredentials report #### {#sctn-currentCredentialsReport} +#### {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} #### {#sctn-signalAllAcceptedCredentialIds} -Signals the complete list of [=credential ids=] for a given user, as well as the -user's current {{PublicKeyCredentialEntity/name}} and -{{PublicKeyCredentialUserEntity/displayName}}. +Signals the complete list of [=credential ids=] for a given user. + +Upon execution of +{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}}, the [=client=] +executes these steps: -The [=currentCredentials=] [=validation algorithm=] takes a -{{CurrentCredentialsReport}} |options| and executes these steps: 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If [=effective domain=] is not a [=valid domain=], then throw a "{{SecurityError}}" {{DOMException}}. -1. If |options|.{{CurrentCredentialsReport/rpId}} [=is not a +1. If |options|.{{AllAcceptedCredentialIdsOptions/rpId}} [=is not a registrable domain suffix of and is not equal to=] |effectiveDomain|, then throw a "{{SecurityError}}" {{DOMException}}. TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] - |options|.{{CurrentCredentialsReport/userId}} is an error, then - throw a {{TypeError}}. -1. If - |options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}} - is present: - 1. [=list/For each=] |credentialId| in - |options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}}: - 1. If the result of [=base64url encoding | base64url decoding=] - |credentialId| is an error, then throw a {{TypeError}}. - -The [=currentCredentials=] [=credential report/authenticator actions=] take a -{{CurrentCredentialsReport}} |options| and are as follows: + |options|.{{AllAcceptedCredentialIdsOptions/userId}} is an + error, then throw a {{TypeError}}. +1. [=list/For each=] |credentialId| in + |options|.{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}: + 1. If the result of [=base64url encoding | base64url decoding=] + |credentialId| is an error, then throw a {{TypeError}}. + +1. Run the following steps [=in parallel=]: + 1. For every [=authenticator=] presently available on this [=client + platform=], invoke the [=signal method/authenticator + actions/allAcceptedCredentialIds=] [=authenticator action=] with + |options| as input. + +The allAcceptedCredentialIds [=signal method/authenticator +actions=] take an {{AllAcceptedCredentialIdsOptions}} |options| and are as +follows: 1. Let |userId| be result of [=base64url encoding | base64url decoding=] - |options|.{{CurrentCredentialsReport/userId}}. + |options|.{{AllAcceptedCredentialIdsOptions/userId}}. 1. Assertion: |userId| is not an error. 1. Let |credential| be - [=credentials map=][|options|.{{CurrentCredentialsReport/rpId}}, |userId|]. + [=credentials map=][|options|.{{AllAcceptedCredentialIdsOptions/rpId}}, |userId|]. 1. If |credential| does not exist, abort these steps. -1. If |options|.{{CurrentCredentialsReport/user}} is present, - update the |credential|'s [=public key credential source/otherUI=] to match - |options|.{{CurrentCredentialsReport/user}}.{{CurrentCredentialUserDetails/name}} - and - |options|.{{CurrentCredentialsReport/user}}.{{CurrentCredentialUserDetails/displayName}} 1. If - |options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}} - is present and - |options|.{{CurrentCredentialsReport/allAcceptedCredentialIds}} + |options|.{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}} does NOT [=list/contain=] the result of [=base64url encoding=] the |credential|'s [=public key credential source/id=], then [=map/remove=] |credential| from the [=credentials map=] or employ an @@ -3118,41 +3122,17 @@ The [=currentCredentials=] [=credential report/authenticator actions=] take a
-A user updates their name on a [=[WRP]=] provided UI. The [=[WRP]=] runs: - -```javascript -PublicKeyCredential.signal({ - currentCredentials: { - rpId: "example.com", - userId: "aabbcc", // user handle, base64url. - user: { - name: "New user name", - displayName: "New display name" - } - } -}); -``` - -The [=authenticator=] then updates the [=public key credential source/otherUI=] -of the matching credential. - -
- -
- A user has two credentials with [=credential ids=] that [=base64url encode=] to `aa` and `bb`. The user deletes the credential `aa` on a [=[WRP]=] provided UI. The [=[WRP]=] runs: ```javascript -PublicKeyCredential.signal({ - currentCredentials: { - rpId: "example.com", - userId: "aabbcc", // user handle, base64url. - allAcceptedCredentialIds: [ - "bb", - ] - } +PublicKeyCredential.signalAllAcceptedCredentialIds({ + rpId: "example.com", + userId: "aabbcc", // user handle, base64url. + allAcceptedCredentialIds: [ + "bb", + ] }); ``` @@ -3163,35 +3143,71 @@ ceremonies=].
Note: [=Authenticators=] may not be attached at the time a report is signalled. -Therefore, [=[WRPS]=] may choose to signal [=currentCredentials=] periodically, +Therefore, [=[WRPS]=] may choose to run +{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} periodically, e.g. on every sign in. -#### {{PublicKeyCredential/signal()}} algorithm #### {#sctn-signalAlgorithm} +#### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} -Upon invocation of {{PublicKeyCredential/signal(options)}}, the [=client=] will: +Signals the user's current {{PublicKeyCredentialEntity/name}} and +{{PublicKeyCredentialUserEntity/displayName}}. -1. For every [=credential report=] |report| defined by this specification: - 1. If |options|[|report|] does NOT exist, [=continue=]. - 1. Run the |report|'s [=credential report/validation algorithm=] with - |options|[|report|] as input. - 1. If that threw an [=exception=], then return [=a promise rejected with=] - that exception and abort these steps. +Upon execution of {{PublicKeyCredential/signalCurrentUserDetails(options)}}, the +[=client=] executes these steps: + +1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If + [=effective domain=] is not a [=valid domain=], then throw a + "{{SecurityError}}" {{DOMException}}. +1. If |options|.{{CurrentUserDetailsOptions/rpId}} [=is not a + registrable domain suffix of and is not equal to=] |effectiveDomain|, then + throw a "{{SecurityError}}" {{DOMException}}. + + TODO: this may require updating once #2040 lands. +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{CurrentUserDetailsOptions/userId}} is an error, then + throw a {{TypeError}}. 1. Run the following steps [=in parallel=]: - 1. For every [=credential report=] |report| defined by this specification: - 1. If |options|[|report|] does NOT exist, [=continue=]. 1. For every [=authenticator=] presently available on this [=client - platform=], invoke the the |report|'s [=credential - report/authenticator actions=] with |options|[|report|] + platform=], invoke the [=signal method/authenticator + actions/currentUserDetails=] [=authenticator action=] with |options| as input. -1. Return [=a promise resolved with=] `undefined`. +The currentUserDetails +[=signal method/authenticator action=] takes a {{CurrentUserDetailsOptions}} +|options| and is as follows: +1. Let |userId| be result of [=base64url encoding | base64url decoding=] + |options|.{{CurrentUserDetailsOptions/userId}}. +1. Assertion: |userId| is not an error. +1. Let |credential| be + [=credentials map=][|options|.{{CurrentUserDetailsOptions/rpId}}, |userId|]. +1. If |credential| does not exist, abort these steps. +1. Update the |credential|'s [=public key credential source/otherUI=] to match + |options|.{{CurrentUserDetailsOptions/name}} and + |options|.{{CurrentUserDetailsOptions/displayName}}. + +
-Note: This algorithm intentionally does not wait for the [=authenticators=] to -finish executing the [=credential report=] [=credential report/authenticator -actions=] to protect users from [=[WRPS]=] gaining information about -availability of their credentials without [=user consent=] from the timing of -the request. +A user updates their name on a [=[WRP]=] provided UI. The [=[WRP]=] runs: + +```javascript +PublicKeyCredential.signalCurrentUserDetails({ + rpId: "example.com", + userId: "aabbcc", // user handle, base64url. + name: "New user name", + displayName: "New display name" +}); +``` + +The [=authenticator=] then updates the [=public key credential source/otherUI=] +of the matching credential. + +
+ +Note: [=Authenticators=] may not be attached at the time a report is signalled. +Therefore, [=[WRPS]=] may choose to run +{{PublicKeyCredential/signalCurrentUserDetails(options)}} periodically, e.g. on +every sign in. ## Authenticator Responses (interface AuthenticatorResponse) ## {#iface-authenticatorresponse} From 33e61c629e65a3ee4e578029463aec2cd6eb4c02 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 24 Jul 2024 15:58:45 -0400 Subject: [PATCH 08/21] emlun's comment --- index.bs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 4f4e49882..8a894a868 100644 --- a/index.bs +++ b/index.bs @@ -3041,14 +3041,12 @@ The unknownCredentialId |options| and is as follows: 1. [=map/For each=] [=public key credential source=] |credential| in the [=authenticator=]'s [=credential map=]: - 1. If the |credential|'s [=public key credential source/rpId=] does not - equal |options|.{{UnknownCredentialIdOptions/rpId}}, - [=continue=]. - 1. If the |credential|'s [=public key credential source/id=] does - not equal the result of [=base64url encoding | base64url decoding=] + 1. If the |credential|'s [=public key credential source/rpId=] equals + |options|.{{UnknownCredentialIdOptions/rpId}} and the + |credential|'s [=public key credential source/id=] equals the result of + [=base64url encoding | base64url decoding=] |options|.{{UnknownCredentialIdOptions/credentialId}}, - [=continue=]. - 1. [=map/Remove=] |credential| from the [=credentials map=] or employ an + [=map/remove=] |credential| from the [=credentials map=] or employ an [=authenticator=]-specific procedure to hide it from future [=authentication ceremonies=]. From d3bcd3eada6d50fc4ca4fca8cf4c578a6dba8dca Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 24 Jul 2024 16:28:18 -0400 Subject: [PATCH 09/21] Extra privacy considerations. --- index.bs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 8a894a868..d6925e628 100644 --- a/index.bs +++ b/index.bs @@ -3003,7 +3003,7 @@ process a [=signal method/authenticator action=], [=clients=] may choose to use existing infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` command to achieve an equivalent effect. -Note: [=signal methods] intentionally do not wait for the [=authenticators=] to +Note: [=Signal methods=] intentionally do not wait for the [=authenticators=] to finish executing the [=signal method/authenticator actions=] to protect users from [=[WRPS]=] gaining information about availability of their credentials without [=user consent=] from the timing of the request. @@ -3011,7 +3011,11 @@ without [=user consent=] from the timing of the request. #### {{PublicKeyCredential/signalUnknownCredentialId(options)}} #### {#sctn-signalUnknownCredentialId} Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. -because it was deleted by the user. +because it was deleted by the user. Unlike +{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}}, this +method does not require passing the entire list of accepted [=credential IDs=] +and the [=userHandle=], avoiding a privacy leak to an unauthenticated caller +(see [[#sctn-credential-id-privacy-leak]]). Upon execution of {{PublicKeyCredential/signalUnknownCredentialId(options)}}, the [=client=] executes these steps: @@ -3134,17 +3138,23 @@ PublicKeyCredential.signalAllAcceptedCredentialIds({ }); ``` -If the [=authenticator=] is attached at the time of the report, it deletes or +If the [=authenticator=] is attached at the time of execution, it deletes or hides the [=credential=] corresponding to `aa` from future [=authentication ceremonies=]. -Note: [=Authenticators=] may not be attached at the time a report is signalled. +Note: [=Authenticators=] may not be attached at the time +{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} is executed. Therefore, [=[WRPS]=] may choose to run {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} periodically, e.g. on every sign in. +Note: Credentials not present in +{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}} will be removed or +hidden, potentially irreversibly. [=Relying parties=] must exercise care that +valid credential IDs are never omitted from the list. + #### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} Signals the user's current {{PublicKeyCredentialEntity/name}} and @@ -3202,7 +3212,8 @@ of the matching credential. -Note: [=Authenticators=] may not be attached at the time a report is signalled. +Note: [=Authenticators=] may not be attached at the time +{{PublicKeyCredential/signalCurrentUserDetails(options)}} is executed. Therefore, [=[WRPS]=] may choose to run {{PublicKeyCredential/signalCurrentUserDetails(options)}} periodically, e.g. on every sign in. @@ -9293,6 +9304,11 @@ i.e., if {{PublicKeyCredentialRequestOptions/allowCredentials}} needs to be expo the [=[RP]=] could mitigate the privacy leak using the same approach of returning imaginary [=credential IDs=] as discussed in [[#sctn-username-enumeration]]. +When [=signal methods|signalling=] that a [=credential id=] was not recognized, +the [=[WRP]=] should use the +{{PublicKeyCredential/signalUnknownCredentialId(options)}} method instead of the +{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} method to avoid +exposing [=credential IDs=] to an unauthenticated caller. # Accessibility Considerations # {#sctn-accessiblility-considerations} From 134c4c2a7b51b19845269e0d1f3bb03c49461164 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 24 Jul 2024 16:34:21 -0400 Subject: [PATCH 10/21] Allow unhiding credentials. --- index.bs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index d6925e628..f66be4027 100644 --- a/index.bs +++ b/index.bs @@ -3121,6 +3121,9 @@ follows: |credential| from the [=credentials map=] or employ an [=authenticator=]-specific procedure to hide it from future [=authentication ceremonies=]. +1. Else, if |credential| has been hidden by an [=authenticator=]-specific + procecure, reverse the action so that |credential| is present in future + [=authentication ceremonies=].
@@ -3153,7 +3156,10 @@ e.g. on every sign in. Note: Credentials not present in {{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}} will be removed or hidden, potentially irreversibly. [=Relying parties=] must exercise care that -valid credential IDs are never omitted from the list. +valid credential IDs are never omitted from the list. If a valid [=credential +ID=] were accidentally omitted, [=relying parties=] should immediately include +them in {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} as soon +as possible to "unhide" them, if supported by the [=authenticator=]. #### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} From 2b6558dcbee2fc94b67285d5cec1505e9a73aa06 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 24 Jul 2024 18:21:55 -0400 Subject: [PATCH 11/21] Update RPID validation --- index.bs | 95 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/index.bs b/index.bs index f66be4027..8d9db0172 100644 --- a/index.bs +++ b/index.bs @@ -2953,7 +2953,7 @@ value and terminate the operation.
-### Signal Credential Changes to the Authenticator - PublicKeyCredential's [=signal methods=] ### {#sctn-signalMethods} +### Signal Credential Changes to the Authenticator - PublicKeyCredential's [=signal methods=] ### {#sctn-signal-methods} partial interface PublicKeyCredential { @@ -2963,18 +2963,18 @@ value and terminate the operation. }; dictionary UnknownCredentialIdOptions { - required USVString rpId; + required DOMString rpId; required Base64URLString credentialId; }; dictionary AllAcceptedCredentialIdsOptions { - required USVString rpId; + required DOMString rpId; required Base64URLString userId; required sequence<Base64URLString> allAcceptedCredentialIds; }; dictionary CurrentUserDetailsOptions { - required USVString rpId; + required DOMString rpId; required Base64URLString userId; required DOMString name; required DOMString displayName; @@ -3008,6 +3008,28 @@ finish executing the [=signal method/authenticator actions=] to protect users from [=[WRPS]=] gaining information about availability of their credentials without [=user consent=] from the timing of the request. +#### <dfn abstract-op>Asynchronous RP ID validation algorithm</dfn> #### {#sctn-signal-methods-async-rp-id-validation} + +The [$Asynchronous RP ID validation algorithm$] lets [=signal methods=] validate +[=RP IDs=] [=in parallel=]. The algorithm takes a {{DOMString}} |rpId| as input +and returns a promise that rejects if the validation fails. The steps are: + +1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If + [=effective domain=] is not a [=valid domain=], then return [=a promise + rejected with=] "{{SecurityError}}" {{DOMException}}. +1. If |rpId| [=is a registrable domain suffix of or is equal to=] + |effectiveDomain|, return [=a promise resolved with=] undefined. +1. If the client does not support [[#sctn-related-origins|related origin + requests]], return [=a promise rejected with=] a "{{SecurityError}}" + {{DOMException}}. +1. Let |p| be [=a new promise=]. +1. Execute the following steps [=in parallel=]: + 1. If the result of running the [$related origins validation procedure$] + with arguments |callerOrigin| and |rpId| is [TRUE], then [=resolve=] + |p|. + 1. Otherwise, [=reject=] |p| with a "{{SecurityError}}" {{DOMException}}. +1. Return |p|. + #### {{PublicKeyCredential/signalUnknownCredentialId(options)}} #### {#sctn-signalUnknownCredentialId} Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. @@ -3020,26 +3042,18 @@ and the [=userHandle=], avoiding a privacy leak to an unauthenticated caller Upon execution of {{PublicKeyCredential/signalUnknownCredentialId(options)}}, the [=client=] executes these steps: -1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then throw a - "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{UnknownCredentialIdOptions/rpId}}</code> [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, then - throw a "{{SecurityError}}" {{DOMException}}. - - TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] <code>|options|.{{UnknownCredentialIdOptions/credentialId}}</code> is an - error, then throw a {{TypeError}}. + error, then return [=a promise rejected with=] a {{TypeError}}. +1. Let |p| be the result of executing the [$Asynchronous RP ID validation + algorithm$] with <code>|options|.{{UnknownCredentialIdOptions/rpId}}</code>. +1. [=Upon fulfillment=] of |p|, run the following steps [=in parallel=]: + 1. For every [=authenticator=] presently available on this [=client + platform=], invoke the [=signal method/authenticator + action/unknownCredentialId=] [=authenticator action=] with |options| as + input. +1. Return |p|. -1. Run the following steps [=in parallel=]: - 1. For every [=authenticator=] presently available on this [=client - platform=], invoke the [=signal method/authenticator - action/unknownCredentialId=] [=authenticator action=] with |options| - as input. - -1. Return [=a promise resolved with=] `undefined`. - The <dfn for="signal method/authenticator action">unknownCredentialId</dfn> [=signal method/authenticator action=] takes an {{UnknownCredentialIdOptions}} |options| and is as follows: @@ -3082,27 +3096,23 @@ Upon execution of {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}}, the [=client=] executes these steps: -1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then throw a - "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{AllAcceptedCredentialIdsOptions/rpId}}</code> [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, then - throw a "{{SecurityError}}" {{DOMException}}. - - TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] <code>|options|.{{AllAcceptedCredentialIdsOptions/userId}}</code> is an - error, then throw a {{TypeError}}. + error, then return [=a promise rejected with=] a {{TypeError}}. 1. [=list/For each=] |credentialId| in <code>|options|.{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}</code>: 1. If the result of [=base64url encoding | base64url decoding=] - |credentialId| is an error, then throw a {{TypeError}}. - -1. Run the following steps [=in parallel=]: + |credentialId| is an error, then return [=a promise rejected with=] a + {{TypeError}}. +1. Let |p| be the result of executing the [$Asynchronous RP ID validation + algorithm$] with + <code>|options|.{{AllAcceptedCredentialIdsOptions/rpId}}</code>. +1. [=Upon fulfillment=] of |p|, run the following steps [=in parallel=]: 1. For every [=authenticator=] presently available on this [=client platform=], invoke the [=signal method/authenticator actions/allAcceptedCredentialIds=] [=authenticator action=] with |options| as input. +1. Return |p|. The <dfn for="signal method/authenticator actions">allAcceptedCredentialIds</dfn> [=signal method/authenticator @@ -3169,23 +3179,18 @@ Signals the user's current {{PublicKeyCredentialEntity/name}} and Upon execution of {{PublicKeyCredential/signalCurrentUserDetails(options)}}, the [=client=] executes these steps: -1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then throw a - "{{SecurityError}}" {{DOMException}}. -1. If <code>|options|.{{CurrentUserDetailsOptions/rpId}}</code> [=is not a - registrable domain suffix of and is not equal to=] |effectiveDomain|, then - throw a "{{SecurityError}}" {{DOMException}}. - - TODO: this may require updating once #2040 lands. 1. If the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{CurrentUserDetailsOptions/userId}}</code> is an error, then - throw a {{TypeError}}. - -1. Run the following steps [=in parallel=]: + <code>|options|.{{CurrentUserDetailsOptions/userId}}</code> is an error, + then return [=a promise rejected with=] a {{TypeError}}. +1. Let |p| be the result of executing the [$Asynchronous RP ID validation + algorithm$] with + <code>|options|.{{CurrentUserDetailsOptions/rpId}}</code>. +1. [=Upon fulfillment=] of |p|, run the following steps [=in parallel=]: 1. For every [=authenticator=] presently available on this [=client platform=], invoke the [=signal method/authenticator actions/currentUserDetails=] [=authenticator action=] with |options| as input. +1. Return |p|. The <dfn for="signal method/authenticator actions">currentUserDetails</dfn> [=signal method/authenticator action=] takes a {{CurrentUserDetailsOptions}} From 1ebe49c1eae836997cfca1d89f72a0bba49a6e1a Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Fri, 26 Jul 2024 11:20:05 -0400 Subject: [PATCH 12/21] Hiding > Deleting --- index.bs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 8d9db0172..6eb5af42e 100644 --- a/index.bs +++ b/index.bs @@ -3159,7 +3159,7 @@ ceremonies=]. Note: [=Authenticators=] may not be attached at the time {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} is executed. -Therefore, [=[WRPS]=] may choose to run +Therefore, [=[WRPS]=] MAY choose to run {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} periodically, e.g. on every sign in. @@ -3167,10 +3167,15 @@ Note: Credentials not present in {{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}} will be removed or hidden, potentially irreversibly. [=Relying parties=] must exercise care that valid credential IDs are never omitted from the list. If a valid [=credential -ID=] were accidentally omitted, [=relying parties=] should immediately include +ID=] were accidentally omitted, [=relying parties=] SHOULD immediately include them in {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} as soon as possible to "unhide" them, if supported by the [=authenticator=]. +Note: [=Authenticators=] SHOULD prefer hiding [=public key credentials=] instead +of permanently removing them whenever possible to aid recovery if a [=[WRP]=] +accidentally omits valid [=credential IDs=] from +{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}. + #### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} Signals the user's current {{PublicKeyCredentialEntity/name}} and From 3f0f2a2c3b71c49f1ed36f5c18010167620d2835 Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Tue, 30 Jul 2024 10:51:15 -0400 Subject: [PATCH 13/21] Drop the `id` --- index.bs | 60 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/index.bs b/index.bs index 6eb5af42e..35e794d5a 100644 --- a/index.bs +++ b/index.bs @@ -2957,17 +2957,17 @@ value and terminate the operation. <xmp class="idl"> partial interface PublicKeyCredential { - static Promise<undefined> signalUnknownCredentialId(UnknownCredentialIdOptions options); - static Promise<undefined> signalAllAcceptedCredentialIds(AllAcceptedCredentialIdsOptions options); + static Promise<undefined> signalUnknownCredential(UnknownCredentialOptions options); + static Promise<undefined> signalAllAcceptedCredentials(AllAcceptedCredentialsOptions options); static Promise<undefined> signalCurrentUserDetails(CurrentUserDetailsOptions options); }; - dictionary UnknownCredentialIdOptions { + dictionary UnknownCredentialOptions { required DOMString rpId; required Base64URLString credentialId; }; - dictionary AllAcceptedCredentialIdsOptions { + dictionary AllAcceptedCredentialsOptions { required DOMString rpId; required Base64URLString userId; required sequence<Base64URLString> allAcceptedCredentialIds; @@ -3030,23 +3030,23 @@ and returns a promise that rejects if the validation fails. The steps are: 1. Otherwise, [=reject=] |p| with a "{{SecurityError}}" {{DOMException}}. 1. Return |p|. -#### {{PublicKeyCredential/signalUnknownCredentialId(options)}} #### {#sctn-signalUnknownCredentialId} +#### {{PublicKeyCredential/signalUnknownCredential(options)}} #### {#sctn-signalUnknownCredential} Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. because it was deleted by the user. Unlike -{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}}, this +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, this method does not require passing the entire list of accepted [=credential IDs=] and the [=userHandle=], avoiding a privacy leak to an unauthenticated caller (see [[#sctn-credential-id-privacy-leak]]). -Upon execution of {{PublicKeyCredential/signalUnknownCredentialId(options)}}, +Upon execution of {{PublicKeyCredential/signalUnknownCredential(options)}}, the [=client=] executes these steps: 1. If the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{UnknownCredentialIdOptions/credentialId}}</code> is an + <code>|options|.{{UnknownCredentialOptions/credentialId}}</code> is an error, then return [=a promise rejected with=] a {{TypeError}}. 1. Let |p| be the result of executing the [$Asynchronous RP ID validation - algorithm$] with <code>|options|.{{UnknownCredentialIdOptions/rpId}}</code>. + algorithm$] with <code>|options|.{{UnknownCredentialOptions/rpId}}</code>. 1. [=Upon fulfillment=] of |p|, run the following steps [=in parallel=]: 1. For every [=authenticator=] presently available on this [=client platform=], invoke the [=signal method/authenticator @@ -3055,15 +3055,15 @@ the [=client=] executes these steps: 1. Return |p|. The <dfn for="signal method/authenticator action">unknownCredentialId</dfn> -[=signal method/authenticator action=] takes an {{UnknownCredentialIdOptions}} +[=signal method/authenticator action=] takes an {{UnknownCredentialOptions}} |options| and is as follows: 1. [=map/For each=] [=public key credential source=] |credential| in the [=authenticator=]'s [=credential map=]: 1. If the |credential|'s [=public key credential source/rpId=] equals - <code>|options|.{{UnknownCredentialIdOptions/rpId}}</code> and the + <code>|options|.{{UnknownCredentialOptions/rpId}}</code> and the |credential|'s [=public key credential source/id=] equals the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{UnknownCredentialIdOptions/credentialId}}</code>, + <code>|options|.{{UnknownCredentialOptions/credentialId}}</code>, [=map/remove=] |credential| from the [=credentials map=] or employ an [=authenticator=]-specific procedure to hide it from future [=authentication ceremonies=]. @@ -3077,7 +3077,7 @@ offers them the [=credential=] they previously deleted. The user selects that [=credential=]. After rejecting the sign-in attempt, the [=[WRP]=] runs: ```javascript -PublicKeyCredential.signalUnknownCredentialId({ +PublicKeyCredential.signalUnknownCredential({ rpId: "example.com", credentialId: "aabbcc" // credential id the user just tried, base64url }); @@ -3088,25 +3088,25 @@ The [=authenticator=] then deletes or hides the [=credential=] from future </div> -#### {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} #### {#sctn-signalAllAcceptedCredentialIds} +#### {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} #### {#sctn-signalAllAcceptedCredentials} Signals the complete list of [=credential ids=] for a given user. Upon execution of -{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}}, the [=client=] +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, the [=client=] executes these steps: 1. If the result of [=base64url encoding | base64url decoding=] - <code>|options|.{{AllAcceptedCredentialIdsOptions/userId}}</code> is an + <code>|options|.{{AllAcceptedCredentialsOptions/userId}}</code> is an error, then return [=a promise rejected with=] a {{TypeError}}. 1. [=list/For each=] |credentialId| in - <code>|options|.{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}</code>: + <code>|options|.{{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}</code>: 1. If the result of [=base64url encoding | base64url decoding=] |credentialId| is an error, then return [=a promise rejected with=] a {{TypeError}}. 1. Let |p| be the result of executing the [$Asynchronous RP ID validation algorithm$] with - <code>|options|.{{AllAcceptedCredentialIdsOptions/rpId}}</code>. + <code>|options|.{{AllAcceptedCredentialsOptions/rpId}}</code>. 1. [=Upon fulfillment=] of |p|, run the following steps [=in parallel=]: 1. For every [=authenticator=] presently available on this [=client platform=], invoke the [=signal method/authenticator @@ -3116,16 +3116,16 @@ executes these steps: The <dfn for="signal method/authenticator actions">allAcceptedCredentialIds</dfn> [=signal method/authenticator -actions=] take an {{AllAcceptedCredentialIdsOptions}} |options| and are as +actions=] take an {{AllAcceptedCredentialsOptions}} |options| and are as follows: 1. Let |userId| be result of [=base64url encoding | base64url decoding=] - <code>|options|.{{AllAcceptedCredentialIdsOptions/userId}}</code>. + <code>|options|.{{AllAcceptedCredentialsOptions/userId}}</code>. 1. Assertion: |userId| is not an error. 1. Let |credential| be - <code>[=credentials map=][|options|.{{AllAcceptedCredentialIdsOptions/rpId}}, |userId|]</code>. + <code>[=credentials map=][|options|.{{AllAcceptedCredentialsOptions/rpId}}, |userId|]</code>. 1. If |credential| does not exist, abort these steps. 1. If - <code>|options|.{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}</code> + <code>|options|.{{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}</code> does NOT [=list/contain=] the result of [=base64url encoding=] the |credential|'s [=public key credential source/id=], then [=map/remove=] |credential| from the [=credentials map=] or employ an @@ -3142,7 +3142,7 @@ A user has two credentials with [=credential ids=] that [=base64url encode=] to The [=[WRP]=] runs: ```javascript -PublicKeyCredential.signalAllAcceptedCredentialIds({ +PublicKeyCredential.signalAllAcceptedCredentials({ rpId: "example.com", userId: "aabbcc", // user handle, base64url. allAcceptedCredentialIds: [ @@ -3158,23 +3158,23 @@ ceremonies=]. </div> Note: [=Authenticators=] may not be attached at the time -{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} is executed. +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} is executed. Therefore, [=[WRPS]=] MAY choose to run -{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} periodically, +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} periodically, e.g. on every sign in. Note: Credentials not present in -{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}} will be removed or +{{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}} will be removed or hidden, potentially irreversibly. [=Relying parties=] must exercise care that valid credential IDs are never omitted from the list. If a valid [=credential ID=] were accidentally omitted, [=relying parties=] SHOULD immediately include -them in {{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} as soon +them in {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} as soon as possible to "unhide" them, if supported by the [=authenticator=]. Note: [=Authenticators=] SHOULD prefer hiding [=public key credentials=] instead of permanently removing them whenever possible to aid recovery if a [=[WRP]=] accidentally omits valid [=credential IDs=] from -{{AllAcceptedCredentialIdsOptions/allAcceptedCredentialIds}}. +{{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}. #### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} @@ -9322,8 +9322,8 @@ as discussed in [[#sctn-username-enumeration]]. When [=signal methods|signalling=] that a [=credential id=] was not recognized, the [=[WRP]=] should use the -{{PublicKeyCredential/signalUnknownCredentialId(options)}} method instead of the -{{PublicKeyCredential/signalAllAcceptedCredentialIds(options)}} method to avoid +{{PublicKeyCredential/signalUnknownCredential(options)}} method instead of the +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} method to avoid exposing [=credential IDs=] to an unauthenticated caller. # Accessibility Considerations # {#sctn-accessiblility-considerations} From c6d634ab822787d4f1152072f738b60af24335cc Mon Sep 17 00:00:00 2001 From: Nina Satragno <nsatragno@gmail.com> Date: Tue, 30 Jul 2024 11:05:13 -0400 Subject: [PATCH 14/21] add to client capabilities --- index.bs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 35e794d5a..684bfb691 100644 --- a/index.bs +++ b/index.bs @@ -4317,7 +4317,10 @@ Note: The {{UserVerificationRequirement}} enumeration is deliberately not refere "hybridTransport", "passkeyPlatformAuthenticator", "userVerifyingPlatformAuthenticator", - "relatedOrigins" + "relatedOrigins", + "signalAllAcceptedCredentials", + "signalCurrentUserDetails", + "signalUnknownCredential" }; @@ -4352,6 +4355,15 @@ Note: The {{ClientCapability}} enumeration is deliberately not referenced, see [ : relatedOrigins :: The [=WebAuthn Client=] supports [[#sctn-related-origins|Related Origin Requests]]. + + : signalAllAcceptedCredentials + :: The [=WebAuthn Client=] supports {{PublicKeyCredential/signalAllAcceptedCredentials()}}. + + : signalCurrentUserDetails, + :: The [=WebAuthn Client=] supports {{PublicKeyCredential/signalCurrentUserDetails()}}. + + : signalUnknownCredential + :: The [=WebAuthn Client=] supports {{PublicKeyCredential/signalUnknownCredential()}}. ### User-agent Hints Enumeration (enum PublicKeyCredentialHints) ### {#enum-hints} From 7c971ffe38e31d192188107936753cb1f1e2112c Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Tue, 30 Jul 2024 11:13:45 -0400 Subject: [PATCH 15/21] Add note comparing signal types --- index.bs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 684bfb691..803f1fa12 100644 --- a/index.bs +++ b/index.bs @@ -3090,7 +3090,12 @@ The [=authenticator=] then deletes or hides the [=credential=] from future #### {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} #### {#sctn-signalAllAcceptedCredentials} -Signals the complete list of [=credential ids=] for a given user. +Signals the complete list of [=credential ids=] for a given user. [=[WRPS]=] +SHOULD prefer this method over {{PublicKeyCredential/signalUnknownCredential()}} +when the user is authenticated and therefore there is no privacy leak risk (see +[[#sctn-credential-id-privacy-leak]]), since the list offers a full snapshot of +a user's [=public key credentials=] and might reflect changes that haven't yet +been reported to currently attached authenticators. Upon execution of {{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, the [=client=] From 9bd42546c6d5eaa2375ae7c7c1d43ab9a605752a Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Tue, 30 Jul 2024 16:31:11 -0400 Subject: [PATCH 16/21] Use CAPS for normative requirements Co-authored-by: Emil Lundberg --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 803f1fa12..367701026 100644 --- a/index.bs +++ b/index.bs @@ -2992,7 +2992,7 @@ the operation succeeded. A successfully resolved promise only means that the options object was well formed. Each [=signal method=] includes authenticator -actions. [=Authenticators=] may choose to deviate in their [=signal +actions. [=Authenticators=] MAY choose to deviate in their [=signal method/authenticator actions=] from the present specification, e.g. to ignore a change they have a reasonable belief would be contrary to the user's wish, or to ask the user before making some change. [=signal method/Authenticator actions=] @@ -9338,7 +9338,7 @@ the [=[RP]=] could mitigate the privacy leak using the same approach of returnin as discussed in [[#sctn-username-enumeration]]. When [=signal methods|signalling=] that a [=credential id=] was not recognized, -the [=[WRP]=] should use the +the [=[WRP]=] SHOULD use the {{PublicKeyCredential/signalUnknownCredential(options)}} method instead of the {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} method to avoid exposing [=credential IDs=] to an unauthenticated caller. From 02f1086ed70428679dec4fe84ac1713c9128d9e3 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Tue, 30 Jul 2024 16:37:40 -0400 Subject: [PATCH 17/21] Apply suggestions from code review Notes are not normative. Co-authored-by: Emil Lundberg --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 367701026..1e53d27a0 100644 --- a/index.bs +++ b/index.bs @@ -3164,7 +3164,7 @@ ceremonies=]. Note: [=Authenticators=] may not be attached at the time {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} is executed. -Therefore, [=[WRPS]=] MAY choose to run +Therefore, [=[WRPS]=] may choose to run {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} periodically, e.g. on every sign in. @@ -3172,11 +3172,11 @@ Note: Credentials not present in {{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}} will be removed or hidden, potentially irreversibly. [=Relying parties=] must exercise care that valid credential IDs are never omitted from the list. If a valid [=credential -ID=] were accidentally omitted, [=relying parties=] SHOULD immediately include +ID=] were accidentally omitted, [=relying parties=] should immediately include them in {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} as soon as possible to "unhide" them, if supported by the [=authenticator=]. -Note: [=Authenticators=] SHOULD prefer hiding [=public key credentials=] instead +Note: [=Authenticators=] should prefer hiding [=public key credentials=] instead of permanently removing them whenever possible to aid recovery if a [=[WRP]=] accidentally omits valid [=credential IDs=] from {{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}. From ccbaad2f87514e736a18f5f18ffd8996a3526751 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Tue, 30 Jul 2024 16:38:22 -0400 Subject: [PATCH 18/21] Make alternative methods normative. --- index.bs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index 1e53d27a0..feeccac2c 100644 --- a/index.bs +++ b/index.bs @@ -2998,10 +2998,10 @@ change they have a reasonable belief would be contrary to the user's wish, or to ask the user before making some change. [=signal method/Authenticator actions=] are thus provided as the recommended way to handle [=signal methods=]. -Note: In cases where an [=authenticator=] does not have the capability to -process a [=signal method/authenticator action=], [=clients=] may choose to use -existing infrastructure such as [[!FIDO-CTAP]]'s -`authenticatorCredentialManagement` command to achieve an equivalent effect. +In cases where an [=authenticator=] does not have the capability to process a +[=signal method/authenticator action=], [=clients=] MAY choose to use existing +infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` +command to achieve an equivalent effect. Note: [=Signal methods=] intentionally do not wait for the [=authenticators=] to finish executing the [=signal method/authenticator actions=] to protect users From 76d68197aa2d1ee58ed90fb1ce0b231a9404f3cf Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 31 Jul 2024 10:38:31 -0400 Subject: [PATCH 19/21] Update index.bs Co-authored-by: Emil Lundberg --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index feeccac2c..3715bb858 100644 --- a/index.bs +++ b/index.bs @@ -2998,7 +2998,7 @@ change they have a reasonable belief would be contrary to the user's wish, or to ask the user before making some change. [=signal method/Authenticator actions=] are thus provided as the recommended way to handle [=signal methods=]. -In cases where an [=authenticator=] does not have the capability to process a +In cases where an [=authenticator=] does not have the capability to process an [=signal method/authenticator action=], [=clients=] MAY choose to use existing infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` command to achieve an equivalent effect. From 43e8e998160d9cfc9b757ec5dab8f63749d37311 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 31 Jul 2024 12:21:56 -0400 Subject: [PATCH 20/21] Address emlun's comments. --- index.bs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/index.bs b/index.bs index 3715bb858..3af2bb70e 100644 --- a/index.bs +++ b/index.bs @@ -3014,9 +3014,10 @@ The [$Asynchronous RP ID validation algorithm$] lets [=signal methods=] validate [=RP IDs=] [=in parallel=]. The algorithm takes a {{DOMString}} |rpId| as input and returns a promise that rejects if the validation fails. The steps are: -1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If - [=effective domain=] is not a [=valid domain=], then return [=a promise - rejected with=] "{{SecurityError}}" {{DOMException}}. +1. Let |effectiveDomain| be the [=relevant settings object=]'s [=environment + settings object/origin=]'s [=effective domain=]. If |effective domain| is + not a [=valid domain=], then return [=a promise rejected with=] + "{{SecurityError}}" {{DOMException}}. 1. If |rpId| [=is a registrable domain suffix of or is equal to=] |effectiveDomain|, return [=a promise resolved with=] undefined. 1. If the client does not support [[#sctn-related-origins|related origin @@ -3039,7 +3040,7 @@ method does not require passing the entire list of accepted [=credential IDs=] and the [=userHandle=], avoiding a privacy leak to an unauthenticated caller (see [[#sctn-credential-id-privacy-leak]]). -Upon execution of {{PublicKeyCredential/signalUnknownCredential(options)}}, +Upon invocation of {{PublicKeyCredential/signalUnknownCredential(options)}}, the [=client=] executes these steps: 1. If the result of [=base64url encoding | base64url decoding=] @@ -3097,7 +3098,7 @@ when the user is authenticated and therefore there is no privacy leak risk (see a user's [=public key credentials=] and might reflect changes that haven't yet been reported to currently attached authenticators. -Upon execution of +Upon invocation of {{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, the [=client=] executes these steps: @@ -3172,9 +3173,9 @@ Note: Credentials not present in {{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}} will be removed or hidden, potentially irreversibly. [=Relying parties=] must exercise care that valid credential IDs are never omitted from the list. If a valid [=credential -ID=] were accidentally omitted, [=relying parties=] should immediately include -them in {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} as soon -as possible to "unhide" them, if supported by the [=authenticator=]. +ID=] were accidentally omitted, the [=relying party=] should immediately include +it in {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} as soon as +possible to "unhide" it, if supported by the [=authenticator=]. Note: [=Authenticators=] should prefer hiding [=public key credentials=] instead of permanently removing them whenever possible to aid recovery if a [=[WRP]=] @@ -3186,7 +3187,7 @@ accidentally omits valid [=credential IDs=] from Signals the user's current {{PublicKeyCredentialEntity/name}} and {{PublicKeyCredentialUserEntity/displayName}}. -Upon execution of {{PublicKeyCredential/signalCurrentUserDetails(options)}}, the +Upon invocation of {{PublicKeyCredential/signalCurrentUserDetails(options)}}, the [=client=] executes these steps: 1. If the result of [=base64url encoding | base64url decoding=] From f08e11576da74da856d265a62bd57d01dfb725bd Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Wed, 14 Aug 2024 16:26:01 -0400 Subject: [PATCH 21/21] Apply suggestions from code review Co-authored-by: Tim Cappalli --- index.bs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/index.bs b/index.bs index 3af2bb70e..9ee7f33d5 100644 --- a/index.bs +++ b/index.bs @@ -2981,11 +2981,11 @@ value and terminate the operation. }; -[=[WRPS]=] may use these signal methods to signal [=authenticators=] -the state of [=public key credentials=], so that incorrect or revoked +[=[WRPS]=] may use these signal methods to inform [=authenticators=] +of the state of [=public key credentials=], so that incorrect or revoked credentials may be updated, removed, or hidden. [=Clients=] provide this functionality opportunistically, since an authenticator may not support updating -its [=credentials map=] or it may not be attached at the time the request is +its [=credentials map=] or may not be attached at the time the request is made. Furthermore, in order to avoid revealing information about a user's credentials without [=user consent=], [=signal methods=] do not indicate whether the operation succeeded. A successfully resolved promise only means that the @@ -3003,10 +3003,10 @@ In cases where an [=authenticator=] does not have the capability to process an infrastructure such as [[!FIDO-CTAP]]'s `authenticatorCredentialManagement` command to achieve an equivalent effect. -Note: [=Signal methods=] intentionally do not wait for the [=authenticators=] to -finish executing the [=signal method/authenticator actions=] to protect users +Note: [=Signal methods=] intentionally avoid waiting for [=authenticators=] to +complete executing the [=signal method/authenticator actions=]. This measure protects users from [=[WRPS]=] gaining information about availability of their credentials -without [=user consent=] from the timing of the request. +without [=user consent=] based on the timing of the request. #### Asynchronous RP ID validation algorithm #### {#sctn-signal-methods-async-rp-id-validation} @@ -3033,9 +3033,8 @@ and returns a promise that rejects if the validation fails. The steps are: #### {{PublicKeyCredential/signalUnknownCredential(options)}} #### {#sctn-signalUnknownCredential} -Signals that a [=credential id=] was not recognized by the [=[WRP]=], e.g. -because it was deleted by the user. Unlike -{{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, this +The {{PublicKeyCredential/signalUnknownCredential(options)|signalUnknownCredential}} method signals that a [=credential id=] was not recognized by the [=[WRP]=], +e.g. because it was deleted by the user. Unlike {{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, this method does not require passing the entire list of accepted [=credential IDs=] and the [=userHandle=], avoiding a privacy leak to an unauthenticated caller (see [[#sctn-credential-id-privacy-leak]]). @@ -3177,15 +3176,15 @@ ID=] were accidentally omitted, the [=relying party=] should immediately include it in {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} as soon as possible to "unhide" it, if supported by the [=authenticator=]. -Note: [=Authenticators=] should prefer hiding [=public key credentials=] instead +Note: [=Authenticators=] should prefer hiding [=public key credentials=] for a period of time instead of permanently removing them whenever possible to aid recovery if a [=[WRP]=] accidentally omits valid [=credential IDs=] from {{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}. #### {{PublicKeyCredential/signalCurrentUserDetails(options)}} #### {#sctn-signalCurrentUserDetails} -Signals the user's current {{PublicKeyCredentialEntity/name}} and -{{PublicKeyCredentialUserEntity/displayName}}. +The {{PublicKeyCredential/signalCurrentUserDetails(options)|signalCurrentUserDetails}} method signals the user's +current {{PublicKeyCredentialEntity/name}} and {{PublicKeyCredentialUserEntity/displayName}}. Upon invocation of {{PublicKeyCredential/signalCurrentUserDetails(options)}}, the [=client=] executes these steps: