diff --git a/index.bs b/index.bs index d70ec37cf..9ee7f33d5 100644 --- a/index.bs +++ b/index.bs @@ -2953,6 +2953,292 @@ value and terminate the operation. +### Signal Credential Changes to the Authenticator - PublicKeyCredential's [=signal methods=] ### {#sctn-signal-methods} + + + partial interface PublicKeyCredential { + static Promise<undefined> signalUnknownCredential(UnknownCredentialOptions options); + static Promise<undefined> signalAllAcceptedCredentials(AllAcceptedCredentialsOptions options); + static Promise<undefined> signalCurrentUserDetails(CurrentUserDetailsOptions options); + }; + + dictionary UnknownCredentialOptions { + required DOMString rpId; + required Base64URLString credentialId; + }; + + dictionary AllAcceptedCredentialsOptions { + required DOMString rpId; + required Base64URLString userId; + required sequence<Base64URLString> allAcceptedCredentialIds; + }; + + dictionary CurrentUserDetailsOptions { + required DOMString rpId; + required Base64URLString userId; + required DOMString name; + required DOMString displayName; + }; + + +[=[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 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 +options object was well formed. + +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. [=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 an +[=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 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=] based on the timing of the request. + +#### Asynchronous RP ID validation algorithm #### {#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 [=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 + 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/signalUnknownCredential(options)}} #### {#sctn-signalUnknownCredential} + +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]]). + +Upon invocation of {{PublicKeyCredential/signalUnknownCredential(options)}}, +the [=client=] executes these steps: + +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{UnknownCredentialOptions/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 |options|.{{UnknownCredentialOptions/rpId}}. +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|. + +The unknownCredentialId +[=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 + |options|.{{UnknownCredentialOptions/rpId}} and the + |credential|'s [=public key credential source/id=] equals the result of + [=base64url encoding | base64url decoding=] + |options|.{{UnknownCredentialOptions/credentialId}}, + [=map/remove=] |credential| from the [=credentials map=] or employ an + [=authenticator=]-specific procedure to 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. The user selects that +[=credential=]. After rejecting the sign-in attempt, the [=[WRP]=] runs: + +```javascript +PublicKeyCredential.signalUnknownCredential({ + rpId: "example.com", + credentialId: "aabbcc" // credential id the user just tried, base64url +}); +``` + +The [=authenticator=] then deletes or hides the [=credential=] from future +[=authentication ceremonies=]. + +
+ +#### {{PublicKeyCredential/signalAllAcceptedCredentials(options)}} #### {#sctn-signalAllAcceptedCredentials} + +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 invocation of +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}}, the [=client=] +executes these steps: + +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{AllAcceptedCredentialsOptions/userId}} is an + error, then return [=a promise rejected with=] a {{TypeError}}. +1. [=list/For each=] |credentialId| in + |options|.{{AllAcceptedCredentialsOptions/allAcceptedCredentialIds}}: + 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 + |options|.{{AllAcceptedCredentialsOptions/rpId}}. +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 allAcceptedCredentialIds [=signal method/authenticator +actions=] take an {{AllAcceptedCredentialsOptions}} |options| and are as +follows: +1. Let |userId| be result of [=base64url encoding | base64url decoding=] + |options|.{{AllAcceptedCredentialsOptions/userId}}. +1. Assertion: |userId| is not an error. +1. Let |credential| be + [=credentials map=][|options|.{{AllAcceptedCredentialsOptions/rpId}}, |userId|]. +1. If |credential| does not exist, abort these steps. +1. If + |options|.{{AllAcceptedCredentialsOptions/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 + [=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=]. + +
+ +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.signalAllAcceptedCredentials({ + rpId: "example.com", + userId: "aabbcc", // user handle, base64url. + allAcceptedCredentialIds: [ + "bb", + ] +}); +``` + +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 +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} is executed. +Therefore, [=[WRPS]=] may choose to run +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} periodically, +e.g. on every sign in. + +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, 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=] 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} + +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: + +1. If the result of [=base64url encoding | base64url decoding=] + |options|.{{CurrentUserDetailsOptions/userId}} 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 + |options|.{{CurrentUserDetailsOptions/rpId}}. +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 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}}. + +
+ +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 +{{PublicKeyCredential/signalCurrentUserDetails(options)}} is executed. +Therefore, [=[WRPS]=] may choose to run +{{PublicKeyCredential/signalCurrentUserDetails(options)}} 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 @@ -4036,7 +4322,10 @@ Note: The {{UserVerificationRequirement}} enumeration is deliberately not refere "hybridTransport", "passkeyPlatformAuthenticator", "userVerifyingPlatformAuthenticator", - "relatedOrigins" + "relatedOrigins", + "signalAllAcceptedCredentials", + "signalCurrentUserDetails", + "signalUnknownCredential" }; @@ -4071,6 +4360,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} @@ -9039,6 +9337,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/signalUnknownCredential(options)}} method instead of the +{{PublicKeyCredential/signalAllAcceptedCredentials(options)}} method to avoid +exposing [=credential IDs=] to an unauthenticated caller. # Accessibility Considerations # {#sctn-accessiblility-considerations}