From d468a75b6a723867d24add0bd01bd7225acbcdbf Mon Sep 17 00:00:00 2001 From: =JeffH Date: Thu, 9 Nov 2017 07:49:23 -0800 Subject: [PATCH] fix #254: credman alignment: update #getAssertion section a la PR #498 (#665) * actually improve #254, and fix #661 * DiscoFrmExtSource(options) -> (origin, options) * make [[DiscoFrmExtSource]]'s exposition match [[Create]]'s * deal with yet another fix #254 straggler in [[Create]] * get rid of |global| in [[DiscoFrmExtSource]] * remove 'in parallel' and 'global' stuff from #discover-from-external-source alg * work on #discover-from-external-source alg to improve #254 * finish (one hopes) work on #discover-from-external-source alg to fix #254 * minor editorial * repair #createCredential intro parag, improves issue #671 * complete fix #671 --- index.bs | 272 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 167 insertions(+), 105 deletions(-) diff --git a/index.bs b/index.bs index 06e35fea5..0daa4b44b 100644 --- a/index.bs +++ b/index.bs @@ -58,7 +58,9 @@ spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/# for: JSON; text: stringify; url: sec-json.stringify type: dfn text: %ArrayBuffer%; url: sec-arraybuffer-constructor - text: internal slot; url: sec-object-internal-methods-and-internal-slots + url: sec-object-internal-methods-and-internal-slots + text: internal method + text: internal slot spec: HTML52; urlPrefix: https://w3c.github.io/html/ @@ -87,7 +89,8 @@ spec: credential-management-1; urlPrefix: https://w3c.github.io/webappsec-creden text: CredentialRequestOptions; url: dictdef-credentialrequestoptions for: Credential type: method - text: [[Create]](options) + text: [[Create]](origin, options) + text: [[DiscoverFromExternalSource]](origin, options) for: CredentialsContainer type: method text: create(); url: dom-credentialscontainer-create @@ -595,8 +598,8 @@ that are returned to the caller when a new credential is created, or a new asser {{PublicKeyCredential}}'s [=interface object=] inherits {{Credential}}'s implementation of {{Credential/[[CollectFromCredentialStore]](options)}} and {{Credential/[[Store]](credential)}}, and defines its own -implementation of {{PublicKeyCredential/[[DiscoverFromExternalSource]](options)}} and -{{PublicKeyCredential/[[Create]](options)}}. +implementation of {{PublicKeyCredential/[[DiscoverFromExternalSource]](origin, options)}} and +{{PublicKeyCredential/[[Create]](origin, options)}}. ### `CredentialCreationOptions` Extension ### {#credentialcreationoptions-extension} @@ -621,30 +624,35 @@ To support obtaining assertions via {{CredentialsContainer/get()|navigator.crede -### Create a new credential - PublicKeyCredential's `[[Create]](options)` method ### {#createCredential} +### Create a new credential - PublicKeyCredential's `[[Create]](origin, options)` method ### {#createCredential} -
+
{{PublicKeyCredential}}'s [=interface object=]'s implementation of the -\[[Create]](options) method allows scripts to call -{{CredentialsContainer/create()|navigator.credentials.create()}} to request the creation of a new [=credential key pair=] -and {{PublicKeyCredential}}, managed by an [=authenticator=]. -On success, the returned {{promise}} will be resolved with a {{PublicKeyCredential}} containing an -{{AuthenticatorAttestationResponse}} object. This {{CredentialsContainer/create()|navigator.credentials.create()}} -operation can be aborted by leveraging the {{AbortController}}; see -[[dom#abortcontroller-api-integration]] for detailed instructions. -Note: This algorithm is synchronous; the {{Promise}} resolution/rejection is handled by -{{CredentialsContainer/create()|navigator.credentials.create()}}. +\[[Create]](origin, options) [=internal method=] [[CREDENTIAL-MANAGEMENT-1]] allows +[=[RP]=] scripts to call {{CredentialsContainer/create()|navigator.credentials.create()}} to request the creation of a new +[=public key credential source=], bound to an [=authenticator=]. This +{{CredentialsContainer/create()|navigator.credentials.create()}} operation can be aborted by leveraging the {{AbortController}}; +see [[dom#abortcontroller-api-integration]] for detailed instructions. -This method accepts a single argument: -
+This method accepts two arguments: + +
+ + : origin + :: This argument is the [=relevant settings object=]'s [=environment settings object/origin=], as determined by the + calling {{CredentialsContainer/create()}} implementation. + : options :: This argument is a {{CredentialCreationOptions}} object whose - |options|.{{CredentialCreationOptions/publicKey}} member contains a {{MakePublicKeyCredentialOptions}} object - specifying the desired attributes of the to-be-created [=public key credential=]. + |options|.{{CredentialCreationOptions/publicKey}} member contains a {{MakePublicKeyCredentialOptions}} + object specifying the desired attributes of the to-be-created [=public key credential=].
+Note: This algorithm is synchronous: the {{Promise}} resolution/rejection is handled by +{{CredentialsContainer/create()|navigator.credentials.create()}}. + When this method is invoked, the user agent MUST execute the following algorithm: 1. Assert: |options|.{{CredentialCreationOptions/publicKey}} is [=present=]. @@ -656,11 +664,7 @@ When this method is invoked, the user agent MUST execute the following algorithm |lifetimeTimer| to this adjusted value. If the {{MakePublicKeyCredentialOptions/timeout}} member of |options| is [=present|not present=], then set |lifetimeTimer| to a platform-specific default. -1. Let |global| be the {{PublicKeyCredential}}'s [=interface object=]'s [=global object|environment settings object's global - object=]. - -1. Let |callerOrigin| be the [=environment settings object/origin=] specified by this {{PublicKeyCredential}} [=interface - object=]'s [=relevant settings object=]. If |callerOrigin| is an [=opaque origin=], return a {{DOMException}} whose name is +1. Let |callerOrigin| be {{PublicKeyCredential/[[Create]](origin, options)/origin}}. If |callerOrigin| is an [=opaque origin=], return a {{DOMException}} whose name is "{{NotAllowedError}}", and terminate this algorithm. 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. @@ -813,15 +817,15 @@ When this method is invoked, the user agent MUST execute the following algorithm 1. Let |credentialCreationData| be a [=struct=] whose [=items=] are: - : attestationObjectResult + : attestationObjectResult :: whose value is the bytes returned from the successful [=authenticatorMakeCredential=] operation. Note: this value is attObj, as defined in [[#generating-an-attestation-object]]. - : clientDataJSONResult + : clientDataJSONResult :: whose value is the bytes of |clientDataJSON|. - : clientExtensionResults + : clientExtensionResults :: whose value is an {{AuthenticationExtensions}} object containing [=extension identifier=] → [=client extension output=] entries. The entries are created by running each extension's [=client extension processing=] algorithm to create the [=client extension outputs=], for each @@ -845,14 +849,14 @@ When this method is invoked, the user agent MUST execute the following algorithm : {{AuthenticatorResponse/clientDataJSON}} :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of - |credentialCreationData|.[=clientDataJSONResult=]. + |credentialCreationData|.[=credentialCreationData/clientDataJSONResult=]. : {{AuthenticatorAttestationResponse/attestationObject}} :: |attestationObject| : {{PublicKeyCredential/[[clientExtensionsResults]]}} :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of - |credentialCreationData|.[=clientExtensionResults=]. + |credentialCreationData|.[=credentialCreationData/clientExtensionResults=]. 1. Return |pubKeyCred|. @@ -873,7 +877,7 @@ authorizing an authenticator. ### Use an existing credential to make an assertion ### {#getAssertion} [=[RPS]=] call navigator.credentials.get({publicKey:..., ...}) to -discover and use an existing [=public key credential=], with the user's consent. The script optionally specifies some criteria +discover and use an existing [=public key credential=], with the [=user consent|user's consent=]. [=[RP]=] script optionally specifies some criteria to indicate what [=credential sources=] are acceptable to it. The user agent and/or platform locates [=credential sources=] matching the specified criteria, and guides the user to pick one that the script will be allowed to use. The user may choose to decline the entire interaction even if a [=credential source=] is present, for example to maintain privacy. If the user picks a @@ -883,19 +887,37 @@ decline the entire interaction even if a [=credential source=] is present, for e The {{CredentialsContainer/get()}} implementation [[CREDENTIAL-MANAGEMENT-1]] calls PublicKeyCredential.{{PublicKeyCredential/[[CollectFromCredentialStore]]()}} to collect any [=credentials=] that -should be available without [=user mediation=] (roughly, this specification's [=authorization gesture=]), and if it doesn't find -exactly one of those, it calls PublicKeyCredential.{{PublicKeyCredential/[[DiscoverFromExternalSource]]()}} to have +should be available without [=user mediation=] (roughly, this specification's [=authorization gesture=]), and if it does not find +exactly one of those, it then calls PublicKeyCredential.{{PublicKeyCredential/[[DiscoverFromExternalSource]]()}} to have the user select a [=credential source=]. -Since this specification requires an [=authorization gesture=] to create any [=credentials=], PublicKeyCredential.\[[CollectFromCredentialStore]](options) inherits the default behavior of +Since this specification requires an [=authorization gesture=] to create any [=credentials=], the PublicKeyCredential.\[[CollectFromCredentialStore]](options) [=internal method=] inherits the default behavior of {{Credential/[[CollectFromCredentialStore]]()|Credential.[[CollectFromCredentialStore]]()}}, of returning an empty set. -
PublicKeyCredential's `[[DiscoverFromExternalSource]](options)` method
-
-When the PublicKeyCredential.\[[DiscoverFromExternalSource]](options) -method is invoked, the user agent MUST: +
PublicKeyCredential's `[[DiscoverFromExternalSource]](origin, options)` method
+ +
+ +The {{PublicKeyCredential}}.\[[DiscoverFromExternalSource]](origin, options) [=internal method=] accepts two arguments: + +
+ + : origin + :: This argument is the [=relevant settings object=]'s [=environment settings object/origin=], as determined by the + calling {{CredentialsContainer/get()}} implementation, i.e., {{CredentialsContainer}}'s Request a `Credential` abstract operation. + + : options + :: This argument is a {{CredentialRequestOptions}} object whose + |options|.{{CredentialRequestOptions/publicKey}} member contains a {{PublicKeyCredentialRequestOptions}} + object specifying the desired attributes of the [=public key credential=] to discover. +
+ +Note: This algorithm is synchronous: the {{Promise}} resolution/rejection is handled by +{{CredentialsContainer/get()|navigator.credentials.get()}}. + +When this method is invoked, the user agent MUST execute the following algorithm: 1. Assert: |options|.{{CredentialRequestOptions/publicKey}} is [=present=]. @@ -906,11 +928,8 @@ method is invoked, the user agent MUST: Set a timer |lifetimeTimer| to this adjusted value. If the {{PublicKeyCredentialRequestOptions/timeout}} member of |options| is [=present|not present=], then set |lifetimeTimer| to a platform-specific default. -1. Let |global| be the {{PublicKeyCredential}}'s [=interface object=]'s [=relevant global object=]. - -1. Let |callerOrigin| be the [=environment settings object/origin=] specified by this {{PublicKeyCredential}} [=interface - object=]'s [=relevant settings object=]. If |callerOrigin| is an [=opaque origin=], return a {{DOMException}} whose name is - "{{NotAllowedError}}", and terminate this algorithm. +1. Let |callerOrigin| be {{PublicKeyCredential/[[DiscoverFromExternalSource]](origin, options)/origin}}. If |callerOrigin| is + an [=opaque origin=], return a {{DOMException}} whose name is "{{NotAllowedError}}", and terminate this algorithm. 1. Let |effectiveDomain| be the |callerOrigin|'s [=effective domain=]. If [=effective domain=] is not a [=valid domain=], then return a @@ -1004,9 +1023,13 @@ method is invoked, the user agent MUST: : [=list/is not empty=] :: 1. Let |distinctTransports| be a new [=ordered set=]. - 1. If |allowCredentialDescriptorList| has exactly one value, let |savedCredentialId| be a new {{ArrayBuffer}}, - created using |global|'s [=%ArrayBuffer%=], and containing the bytes of - |allowCredentialDescriptorList|[0].id. + 1. If |allowCredentialDescriptorList| has exactly one value, let |savedCredentialId| be a new + {{PublicKeyCredentialDescriptor}}.{{PublicKeyCredentialDescriptor/id}} and set its value to |allowCredentialDescriptorList|[0].id's + value (see [here](#authenticatorGetAssertion-return-values) in [[#op-get-assertion]] for more information). + + Issue: The foregoing step _may_ be incorrect, in that we are attempting to create |savedCredentialId| + here and use it later below, and we do not have a global in which to allocate a place for it. Perhaps this + is good enough? addendum: [@jcjones feels the above step is likely good enough](https://github.com/w3c/webauthn/pull/665#discussion_r148130187). 1. [=list/For each=] credential descriptor |C| in |allowCredentialDescriptorList|, [=set/append=] each value, if any, of |C|.{{transports}} to |distinctTransports|. @@ -1021,19 +1044,19 @@ method is invoked, the user agent MUST: configuration knowledge of the appropriate transport to use with |authenticator| in making its selection. - Then, using |transport|, invoke [=in parallel=] the [=authenticatorGetAssertion=] operation on + Then, using |transport|, invoke the [=authenticatorGetAssertion=] operation on |authenticator|, with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, and |authenticatorExtensions| as parameters. : [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, - invoke [=in parallel=] the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, + invoke the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, |allowCredentialDescriptorList|, and |clientExtensions| as parameters.
: [=list/is empty=] :: Using local configuration knowledge of the appropriate transport to use with |authenticator|, invoke - [=in parallel=] the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, + the [=authenticatorGetAssertion=] operation on |authenticator| with |rpId|, |clientDataHash|, and |clientExtensions| as parameters. Note: In this case, the [=[RP]=] did not supply a list of acceptable credential descriptors. Thus the @@ -1043,62 +1066,97 @@ method is invoked, the user agent MUST: 1. [=set/Append=] |authenticator| to |issuedRequests|. -1. Execute the following steps [=in parallel=]. The [=task source=] for these [=tasks=] is the [=dom manipulation task source=]. +1. While |issuedRequests| [=list/is not empty=], perform the following actions depending upon |lifetimeTimer| + and responses from the authenticators: - 1. While |issuedRequests| [=list/is not empty=], perform the following actions depending upon |lifetimeTimer| - and responses from the authenticators: +
-
+ : If |lifetimeTimer| expires, + :: [=set/For each=] |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation on + |authenticator| and [=set/remove=] |authenticator| from |issuedRequests|. + + : If the {{CredentialRequestOptions/signal}} member is [=present=] and the [=AbortSignal/aborted flag=] is set to + true, + :: [=set/For each=] |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation on |authenticator| + and [=set/remove=] |authenticator| from |issuedRequests|. Then + return a {{DOMException}} whose name is "{{AbortError}}" and terminate this algorithm. + + : If any |authenticator| returns a status indicating that the user cancelled the operation, + :: 1. [=set/Remove=] |authenticator| from |issuedRequests|. + 1. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation + on |authenticator| and [=set/remove=] it from |issuedRequests|. + + : If any |authenticator| returns an error status, + :: [=set/Remove=] |authenticator| from |issuedRequests|. + + : If any |authenticator| indicates success, + :: 1. [=set/Remove=] |authenticator| from |issuedRequests|. + + 1. Let assertionCreationData be a [=struct=] whose [=items=] are: - : If |lifetimeTimer| expires, - :: [=set/For each=] |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation on - |authenticator| and [=set/remove=] |authenticator| from |issuedRequests|. + : credentialIdResult + :: If |savedCredentialId| exists, set the value of [=credentialIdResult=] to be the bytes of + |savedCredentialId|. Otherwise, set the value of [=credentialIdResult=] to be the bytes of the + [=credential ID=] returned from the successful [=authenticatorGetAssertion=] operation, as defined in + [[#op-get-assertion]]. + + : clientDataJSONResult + :: whose value is the bytes of |clientDataJSON|. - : If the {{CredentialRequestOptions/signal}} member is [=present=] and the [=AbortSignal/aborted flag=] is set to - true, - :: [=set/For each=] |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation on |authenticator| - and [=set/remove=] |authenticator| from |issuedRequests|. Then - return a {{DOMException}} whose name is "{{AbortError}}" and terminate this algorithm. + : authenticatorDataResult + :: whose value is the bytes of the [=authenticator data=] returned by the [=authenticator=]. - : If any |authenticator| returns a status indicating that the user cancelled the operation, - :: 1. [=set/Remove=] |authenticator| from |issuedRequests|. - 2. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation - on |authenticator| and [=set/remove=] it from |issuedRequests|. + : signatureResult + :: whose value is the bytes of the signature value returned by the [=authenticator=]. - : If any |authenticator| returns an error status, - :: [=set/Remove=] |authenticator| from |issuedRequests|. + : userHandleResult + :: whose value is the bytes of the [=user handle=] returned by the [=authenticator=]. - : If any |authenticator| indicates success, - :: 1. [=set/Remove=] |authenticator| from |issuedRequests|. - 2. Let |value| be a new {{PublicKeyCredential}} associated with |global| whose fields are: + : clientExtensionResults + :: whose value is an {{AuthenticationExtensions}} object containing [=extension identifier=] → + [=client extension output=] entries. The entries are created by running each extension's + [=client extension processing=] algorithm to create the [=client extension outputs=], for each + [=client extension=] in {{AuthenticatorResponse/clientDataJSON}}.clientExtensions. + + 1. Let |constructAssertionAlg| be an algorithm that takes a [=global object=] + |global|, and whose steps are: + + 1. Let |pubKeyCred| be a new {{PublicKeyCredential}} object associated with |global| whose fields are: : {{PublicKeyCredential/[[identifier]]}} - :: Create a new {{ArrayBuffer}}, using |global|'s [=%ArrayBuffer%=]. - If |savedCredentialId| exists, set the value of the new {{ArrayBuffer}} to be the bytes of - |savedCredentialId|. Otherwise, set the value of the new {{ArrayBuffer}} to be the bytes of the - [=credential ID=] returned from the successful [=authenticatorGetAssertion=] operation, as defined in - [[#op-get-assertion]]. + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=credentialIdResult=]. : {{PublicKeyCredential/response}} :: A new {{AuthenticatorAssertionResponse}} object associated with |global| whose fields are: - : {{AuthenticatorResponse/clientDataJSON}} - :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of - |clientDataJSON|. - : {{AuthenticatorAssertionResponse/authenticatorData}} - :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of the - returned {{authenticatorData}}. - : {{AuthenticatorAssertionResponse/signature}} - :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of the - returned {{signature}}. - : {{AuthenticatorAssertionResponse/userHandle}} - :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the [=user handle=] - returned from the successful [=authenticatorGetAssertion=] operation, as defined in - [[#op-get-assertion]]. - - 3. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation - on |authenticator| and [=set/remove=] it from |issuedRequests|. - 4. Return |value| and terminate this algorithm. -
+ + : {{AuthenticatorResponse/clientDataJSON}} + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/clientDataJSONResult=]. + + : {{AuthenticatorAssertionResponse/authenticatorData}} + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/authenticatorDataResult=]. + + : {{AuthenticatorAssertionResponse/signature}} + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/signatureResult=]. + + : {{AuthenticatorAssertionResponse/userHandle}} + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/userHandleResult=]. + + : {{PublicKeyCredential/[[clientExtensionsResults]]}} + :: A new {{ArrayBuffer}}, created using |global|'s [=%ArrayBuffer%=], containing the bytes of + |assertionCreationData|.[=assertionCreationData/clientExtensionResults=]. + + 1. Return |pubKeyCred|. + + 1. [=set/For each=] remaining |authenticator| in |issuedRequests| invoke the [=authenticatorCancel=] operation + on |authenticator| and [=set/remove=] it from |issuedRequests|. + + 1. Return |constructAssertionAlg| and terminate this algorithm. +
1. Return a {{DOMException}} whose name is "{{NotAllowedError}}". @@ -2011,16 +2069,19 @@ When this method is invoked, the [=authenticator=] must perform the following pr 1. If any error occurred while generating the [=assertion signature=], return an error code equivalent to "{{UnknownError}}" and terminate the operation. -1. Return to the user agent: - - |selectedCredential|'s [=credential ID=], if either a list of credentials of length 2 or greater was supplied by the client, - or no such list was supplied. +
  • + Return to the user agent: + - |selectedCredential|'s [=credential ID=], if either a list of credentials of length 2 or greater was supplied by the + client, or no such list was supplied. Otherwise, return only the below values. - Note: If the client supplies a list of exactly one credential and it was successfully employed, then its [=credential ID=] - is not returned since the client already knows it. + Note: If the client supplies a list of exactly one credential and it was successfully employed, then its + [=credential ID=] is not returned since the client already knows it. This saves transmitting these bytes over + what may be a constrained connection in what is likely a common case. - - |authenticatorData| - - |signature| - - The [=user handle=] associated with |selectedCredential|. + - |authenticatorData| + - |signature| + - The [=user handle=] associated with |selectedCredential|. +
  • If the authenticator cannot find any credential corresponding to the specified [=[RP]=] that matches the specified criteria, it terminates the operation and returns an error. @@ -2364,7 +2425,7 @@ When registering a new credential, represented by a {{AuthenticatorAttestationRe 13. If the attestation statement |attStmt| verified successfully and is found to be trustworthy, then register the new credential with the account that was denoted in the - {{PublicKeyCredential/[[Create]](options)/options}}.{{MakePublicKeyCredentialOptions/user}} passed to + {{PublicKeyCredential/[[Create]](origin, options)/options}}.{{MakePublicKeyCredentialOptions/user}} passed to {{CredentialsContainer/create()}}, by associating it with the [=credentialId=] and [=credentialPublicKey=] in the [=attestedCredentialData=] in |authData|, as appropriate for the [=[RP]=]'s system. @@ -3182,9 +3243,10 @@ error. :: A single JSON string specifying a FIDO |appId|. : Client extension processing -:: If {{PublicKeyCredentialRequestOptions/rpId}} is present, reject promise with a DOMException - whose name is "{{NotAllowedError}}", and terminate this algorithm. - Replace the calculation of |rpId| in Step 3 of [[#getAssertion]] with the +:: If {{PublicKeyCredentialRequestOptions/rpId}} is present, return a DOMException + whose name is "{{NotAllowedError}}", and terminate this algorithm ([[#discover-from-external-source]]). + + Otherwise, replace the calculation of |rpId| in Step 6 of [[#discover-from-external-source]] with the following procedure: The client uses the value of |appid| to perform the AppId validation procedure (as defined by [[FIDO-APPID]]). If valid, the value of |rpId| for all client processing should be replaced by the