From 807a68901d53f67b6433205e2c56bb6b89b45489 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Wed, 27 May 2020 17:06:56 +0200 Subject: [PATCH 1/5] Add Yubico's proposed recovery extension See https://github.com/Yubico/webauthn-recovery-extension --- index.bs | 552 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) diff --git a/index.bs b/index.bs index d81b95765..ed3321437 100644 --- a/index.bs +++ b/index.bs @@ -292,6 +292,19 @@ spec: RFC5280; urlPrefix: https://tools.ietf.org/html/rfc5280 type: dfn text: SubjectPublicKeyInfo; url: section-4.1.2.7 +spec: RFC2104; urlPrefix: https://tools.ietf.org/html/rfc2104 + type: dfn + text: HMAC-SHA-256 + +spec: RFC5869; urlPrefix: https://tools.ietf.org/html/rfc5869 + type: dfn + text: HKDF-Extract; url: section-2.2 + text: HKDF-Expand; url: section-2.3 + +spec: RFC6090; urlPrefix: https://tools.ietf.org/html/rfc6090 + type: dfn + text: ECDH; url: section-4 + @@ -5639,6 +5652,545 @@ At this time, one [=credential property=] is defined: the [=resident key credent :: None. +## Recovery Credentials Extension (recovery) ## {#sctn-recovery-credentials-extension} + +This [=authenticator extension=] allows for registering [=recovery credentials=] with an RP, +which can be used for account recovery in the case of a lost or destroyed [=primary authenticator=]. + +This is done through a cryptographic association between a primary authenticator and a backup authenticator. +After the association is established, the [=primary authenticator=] can generate [=credential public keys=] +for which the [=backup authenticator=] can derive the corresponding [=credential private key=]. +These key pairs are referred to as recovery credentials +and are associated with the [=credential=] the [=primary authenticator=] used to create them, +referred to as the primary credential. +Each [=authenticator=] MAY be both a [=primary authenticator|primary=] and [=backup authenticator=] simultaneously, +and each [=primary authenticator=] MAY be associated with any number of [=backup authenticators=] simultaneously. + +This extension does not specify the association procedure, +but assumes that each [=backup authenticator=] has a recovery seed pair (s, S) +with private seed |s| and public seed |S|. +Only the [=backup authenticator=] has access to the private recovery seed |s|. +Each associated [=primary authenticator=] imports and stores the public recovery seed |S| +in a tuple (|alg|, |aaguid|, |S|), where |aaguid| is the [=AAGUID=] of the [=backup authenticator=]. +|alg| is an extension point identifying a key generation algorithm; +currently the only valid value is 0, +indicating the [=recovery seed pair=] (|s|, |S|) is an elliptic curve key pair on the P-256 curve. +Neither part of the [=recovery seed pair=] is exposed to the [=[RP]=], +and the key generation algorithm is opaque to the [=[RP]=]. +Each [=primary authenticator=] also has a recovery credentials state counter +which is initialized to zero and is incremented +whenever the [=primary authenticator=]'s set of imported [=public recovery seeds=] changes. +The [=[RP]=] uses this counter to detect when [=recovery credentials=] can be generated +or need to be regenerated. + +The user uses the [=primary authenticator=] to register [=recovery credentials=] as part of an [=authentication ceremony=]. +If the user loses the [=primary authenticator=], +the [=backup authenticator=] can [=authenticatorMakeCredential|create a new credential=] +and use a [=recovery credential=] to sign the new credential during the [=registration ceremony=]. +This creates a signature chain from the [=primary credential=], via the [=recovery credential=], +to the new credential created by the [=backup authenticator=], +and the [=backup authenticator=] can perform [=attestation=] for the new credential as usual. + +[=Recovery credentials=] by definition cannot be [=discoverable credentials=], +since they are created by the [=primary authenticator=] but used by the [=backup authenticator=], +but they can be used to "back up" both [=discoverable credentials=] and non-discoverable credentials, +since the recovery process replaces the [=recovery credential=] with a new ordinary credential. +Any {{PublicKeyCredentialCreationOptions/user}} information associated with +a [=discoverable credential|discoverable=] [=primary credential=] +is lost in the recovery process, but can be re-assigned when the new credential is created. + +This extension requires additional processing by the [=[RP]=] as detailed in [[#sctn-recovery-credentials-extension-rp-processing]]. + + +: Extension identifier +:: `recovery` + +: Operation applicability +:: [=registration extension|Registration=] and [=authentication extension|Authentication=] + +: Client extension input +:: A {{RecoveryExtensionInput}} object as defined below. + + + partial dictionary AuthenticationExtensionsClientInputs { + RecoveryExtensionInput recovery; + }; + + dictionary RecoveryExtensionInput { + required RecoveryExtensionAction action; + sequence<PublicKeyCredentialDescriptor> allowCredentials; + }; + + enum RecoveryExtensionAction { + "state", + "generate", + "recover" + }; + + +
+ : action + :: This member indicates the sub-operation to perform, + and thus whether the [=authenticator=] will be acting as a [=primary authenticator=] or a [=backup authenticator=]. + Its value is one of the following: + +
+ : state + :: Get the [=recovery credentials state counter=] value from the [=authenticator=]. + Applicable to both [=registration ceremony|registration=] and [=authentication ceremonies=]; + the [=authenticator=] acts as a [=primary authenticator=]. + + : generate + :: Generate new [=recovery credentials=]. + Applicable only to [=authentication ceremonies=]; the [=authenticator=] acts as a [=primary authenticator=]. + + : recover + :: Sign the newly created [=credential=] with a [=recovery credential=]. + Applicable only to [=registration ceremonies=]; + the [=authenticator=] is acting as a [=backup authenticator=] + which will assume the role of [=primary authenticator=] upon successful completion of the [=registration ceremony=]. +
+ + : allowCredentials + :: This member, indicates the [=recovery credentials=] that MAY be used in a [=registration ceremony=] + where {{RecoveryExtensionInput/action}} is set to {{RecoveryExtensionAction/recover}}. + Its function is analogous to that of + {{PublicKeyCredentialRequestOptions}}.{{PublicKeyCredentialRequestOptions/allowCredentials}}. +
+ +: Client extension processing +:: Create the authenticator extension input from the client extension input. + + If the client implements support for this extension, + then when {{RecoveryExtensionInput/action}} is {{RecoveryExtensionAction/generate}}, + the client SHOULD notify the user of the number of [=recovery credentials=] in the response. + +: Client extension output +:: None. + + +: Authenticator extension input +:: The client extension input encoded as a CBOR map. + +: Authenticator extension processing +:: The following definitions are used below: + + - `LEFT(X, n)` is the first n bytes of the byte array X. + - `DROP_LEFT(X, n)` is the byte array X without the first n bytes. + + If {{RecoveryExtensionInput/action}} is +
+ : {{RecoveryExtensionAction/state}} + :: Generate the [=authenticator extension output=] as described in the Authenticator extension output section. + + : {{RecoveryExtensionAction/generate}} + :: 1. If the current operation is not an [=authenticatorGetAssertion=] operation, + return an error code equivalent to "{{OperationError}}" and terminate the operation. + + 1. Let |creds| be a new [=list=]. + + 1. [=list/For each=] recovery seed tuple (|alg|, |aaguid|, |S|) stored in this authenticator: + + 1. If |alg|: + +
+ : equals 0, + :: Issue: The following procedure might be more suitable to extract to an external reference. + + 1. Generate an ephemeral P-256 elliptic curve key pair; + let |e| be the private key and |E| be the public key. + |E| MUST NOT be the point at infinity. + + 1. Let |ikm| = [=ECDH=](|e|, |S|) [[!RFC6090]]. + Let |ikm_x| be the X coordinate of |ikm|, + encoded as a byte string of length 32 as described in section 2.3.7 of [[!SEC1]]. + + 1. Let |prk| be the pseudorandom key output from [=HKDF-Extract=] [[!RFC5869]] with the arguments: + + - `Hash`: SHA-256. + - `salt`: Not set. + - `IKM`: |ikm_x|. + + 1. Let |okm| be 64 bytes of output keying material from [=HKDF-Expand=] [[!RFC5869]] with the arguments: + + - `Hash`: SHA-256. + - `PRK`: |prk|. + - `info`: Not set. + - `L`: 64. + + 1. Let |credKey| = LEFT(|okm|, 32) + and |macKey| = LEFT(DROP_LEFT(|okm|, 32), 32), + both parsed as big-endian unsigned 256-bit numbers. + + 1. If |credKey| >= n, where n is the order of the P-256 curve, start over from 1. + + 1. Let |P| = (|credKey| * G) + |S|, + where * and + are elliptic curve point multiplication and addition, + and G is the generator of the P-256 curve. + + 1. If |P| is the point at infinity, start over from 1. + + 1. Let |E_enc| be |E| encoded as described in section 2.3.3 of [[!SEC1]], without point compression. + + 1. Let |credentialId| = |alg| || |E_enc| || LEFT([=HMAC-SHA-256=](|macKey|, |alg| || |E_enc| || [=rpIdHash=]), 16) [[!RFC2104]]. + + : equals anything else, + :: Return an error code equivalent to "{{UnknownError}}" and terminate the operation. + + Note: This should never happen, + since the authenticator should never store a recovery seed with an unknown |alg| value. +
+ + 1. Let |attCredData| be a new [=attested credential data=] structure with the following member values: + + - [=aaguid=]: |aaguid|. + - [=credentialIdLength=]: The byte length of |credentialId|. + - [=credentialId=]: |credentialId|. + - [=credentialPublicKey=]: |P|. + + 1. Add |attCredData| to |creds|. + + 1. Generate the [=authenticator extension output=] as described + in the Authenticator extension output section, + with the {{RecoveryExtensionOutput/creds}} member set to |creds|. + + + : {{RecoveryExtensionAction/recover}} + :: 1. If the current operation is not an [=authenticatorMakeCredential=] operation, + return an error code equivalent to "{{OperationError}}" and terminate the operation. + + 1. If the [=recovery seed pair=] (|s|, |S|) has not been initialized, + return an error code equivalent to "{{ConstraintError}}" and terminate the operation. + + 1. Let |creds| be a new [=list=]. + + 1. [=list/For each=] |cred| in {{RecoveryExtensionInput/allowCredentials}}: + + 1. Let |alg| = LEFT(|cred|.{{PublicKeyCredentialDescriptor/id}}, 1). + + 1. If |alg|: + +
+ : equals 0, + :: Issue: The following procedure might be more suitable to extract to an external reference. + + 1. Let |E_enc| = LEFT(DROP_LEFT(|cred|.{{PublicKeyCredentialDescriptor/id}}, 1), 65). + + 1. Let |E| be the P-256 public key decoded from the uncompressed point |E_enc| + as described in section 2.3.4 of [[!SEC1]]. + If invalid, [=continue=]. + + 1. If |E| is the point at infinity, [=continue=]. + + 1. Let |ikm| = [=ECDH=](|s|, |E|). + Let |ikm_x| be the X coordinate of |ikm|, + encoded as a byte string of length 32 as described in section 2.3.7 of [[!SEC1]]. + + 1. Let |prk| be the pseudorandom key output from [=HKDF-Extract=] [[!RFC5869]] with the arguments: + + - `Hash`: SHA-256. + - `salt`: Not set. + - `IKM`: |ikm_x|. + + 1. Let |okm| be 64 bytes of output keying material from [=HKDF-Expand=] [[!RFC5869]] with the arguments: + + - `Hash`: SHA-256. + - `PRK`: |prk|. + - `info`: Not set. + - `L`: 64. + + 1. Let |credKey| = LEFT(|okm|, 32) + and |macKey| = LEFT(DROP_LEFT(|okm|, 32), 32), + both parsed as big-endian unsigned 256-bit numbers. + + 1. If |cred|.{{PublicKeyCredentialDescriptor/id}} is not exactly equal to + |alg| || |E| || LEFT([=HMAC-SHA-256=](|macKey|, |alg| || |E| || [=rpIdHash=]), 16) [[!RFC2104]], + [=continue=]. + + 1. Let |p| = |credKey| + |s| (mod n), where n is the order of the P-256 curve, + and interpret |p| as a P-256 elliptic curve private key. + + 1. Let |authDataWithoutExtensions| be the [=authenticator data=] + that will be returned from this [=authenticatorMakeCredential=] operation, + but without the [=authDataExtensions|extensions=] part. + The `ED` [=flag=] in |authDataWithoutExtensions| MUST be set to 1 + even though |authDataWithoutExtensions| does not include extension data. + + 1. Let |clientDataHash| be the |hash| argument to this [=authenticatorMakeCredential=] operation. + + 1. Let |sig| be a signature of the concatenation + |authDataWithoutExtensions| || |clientDataHash| + using the private key |p|. + |sig| is DER encoded as described in [[!RFC3279]]. + + : equals anything else, + :: [=Continue=]. +
+ + 1. Generate the [=authenticator extension output=] as described + in the Authenticator extension output section, + with the {{RecoveryExtensionOutput/credId}} member set to |cred|.{{PublicKeyCredentialDescriptor/id}} + and the {{RecoveryExtensionOutput/sig}} member set to |sig|, + and end extension processing. + + 1. Return an error code equivalent to "{{InvalidStateError}}". +
+ + +: Authenticator extension output +:: A CBOR map with the following structure: + + + dictionary RecoveryExtensionOutput { + required RecoveryExtensionAction action; + required int state; + sequence<ArrayBuffer> creds; + ArrayBuffer credId; + ArrayBuffer sig; + }; + + + ``` + $$recoveryExtOutput //= { + action: RecoveryExtensionAction, + state: int + creds: [ * (cred: bytes) ], + credId: bytes, + sig: bytes, + } + ``` + +
+ : action + :: The value of the extension input {{RecoveryExtensionInput/action}}. + + : state + :: The current value of the [=recovery credentials state counter=]. + + : creds + :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/generate}}. + Its value is a sequence of [=attested credential data=] structures representing newly created [=recovery credentials=]. + + : credId + :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/recover}}. + Its value is the [=credential ID=] of the [=recovery credential=] that was used to create the signature {{RecoveryExtensionOutput/sig}}. + + : sig + :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/recover}}. + Its value is a signature generated as described in the Authenticator extension processing section. +
+ + +### [=[RP]=] extension processing ### {#sctn-recovery-credentials-extension-rp-processing} + +A [=[RP]=] supporting this extension SHOULD include this extension +with {{RecoveryExtensionInput/action}} set to {{RecoveryExtensionAction/state}} +whenever performing a [=registration ceremony|registration=] or [=authentication ceremony=]. +There are two cases where the response indicates that +the [=[RP]=] SHOULD initiate [[#sctn-recovery-credentials-extension-rp-register-recovery-creds|recovery credential registration]], which are: + + - Upon successful {{CredentialsContainer/create()|navigator.credentials.create()}}, + if {{RecoveryExtensionOutput/state}} > 0. + - Upon successful {{CredentialsContainer/create()|navigator.credentials.create()}}, + if {{RecoveryExtensionOutput/state}} is greater than the previous value for {{RecoveryExtensionOutput/state}} + that the [=[RP]=] has seen for the used [=credential=]. + +The following operations assume that each user account contains a |recoveryStates| field, +which is a [=map=] with [=credential IDs=] as keys. +|recoveryStates| is initialized to an [=map/is empty|empty=] [=map=]. + + +#### Detecting changes to recovery seeds #### {#sctn-recovery-credentials-extension-rp-detect-changed-seeds} + +To detect when the user's [=primary authenticator=] has updated its set of [=public recovery seeds=], +the [=[RP]=] SHOULD add the following steps to all registration and authentication ceremonies: + + 1. When initiating any {{CredentialsContainer/create()}} or {{CredentialsContainer/get()}} operation, + set the extension input `"recovery": {"action": "state"}`. + + 1. Let |pkc| be the {{PublicKeyCredential}} response from the [=client=]. + + 1. In step 17 of the [=[RP]=] Operation to [[#sctn-registering-a-new-credential|register a new credential]], + or 18 of the RP Operation to [[#sctn-verifying-assertion|verify an authentication assertion]], + perform the following steps: + + 1. Let |extOutput| be the `recovery` extension output, or null if not present. + For {{CredentialsContainer/create()}} ceremonies, + this is |extOutput| = |pkc|.{{PublicKeyCredential/response}}.{{AuthenticatorAttestationResponse/attestationObject}}["authData"].[=authDataExtensions|extensions=]["recovery"]; + for {{CredentialsContainer/get()}} ceremonies it is |extOutput| = + |pkc|.{{PublicKeyCredential/response}}.{{AuthenticatorAssertionResponse/authenticatorData}}.[=authDataExtensions|extensions=]["recovery"]. + + 1. If |extOutput| is not null: + + 1. If |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/state}} + or |extOutput|.{{RecoveryExtensionOutput/state}} is not present, + abort this extension processing and OPTIONALLY show a user-visible warning. + + 1. If |extOutput|.{{RecoveryExtensionOutput/state}} > 0: + + 1. Let |recoveryState| = |recoveryStates|[|pkc|.{{Credential/id}}], + or null if |pkc|.{{Credential/id}} is not present in |recoveryStates|. + + 1. If |recoveryState| is null or |extOutput|.{{RecoveryExtensionOutput/state}} > |recoveryState|.state: + + 1. If the ceremony finishes successfully, + prompt the user that their [=recovery credentials=] need to be updated + and ask to initiate a [[#sctn-recovery-credentials-extension-rp-register-recovery-creds]] ceremony. + It is RECOMMENDED to set {{PublicKeyCredentialRequestOptions/allowCredentials}} + to contain only |pkc|.{{Credential/id}} in this authentication ceremony. + + 1. Continue with the remaining steps of the registration or authentication ceremony. + + +#### Registering recovery credentials #### {#sctn-recovery-credentials-extension-rp-register-recovery-creds} + +To register new [=recovery credentials=] for a given [=primary credential=], +or replace the existing recovery credentials with updated ones, +the RP performs the following procedure: + + 1. Initiate a {{CredentialsContainer/get()}} operation + and set the extension input `"recovery": {"action": "generate"}`. + + If this ceremony was triggered as described in [[#sctn-recovery-credentials-extension-rp-detect-changed-seeds]], + it is RECOMMENDED to set {{PublicKeyCredentialRequestOptions/allowCredentials}} + to contain only the [=credential ID=] that was used in that preceding ceremony. + + 1. Let |pkc| be the {{PublicKeyCredential}} response from the [=client=]. + If the operation fails, abort the ceremony with an error. + + 1. In step 18 of the [=[RP]=] Operation to [[#sctn-verifying-assertion|verify an authentication assertion]], + perform the following steps: + + 1. Let |extOutput| = |pkc|.{{PublicKeyCredential/response}}.{{AuthenticatorAssertionResponse/authenticatorData}}.[=authDataExtensions|extensions=]["recovery"], + or null if not present. + + 1. If |extOutput| is null, + |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/generate}}, + or |extOutput|.{{RecoveryExtensionOutput/creds}} is not present, + abort the ceremony with an error. + + 1. Let |acceptedCreds| be a new [=list=]. + + 1. Let |rejectedCreds| be a new [=list=]. + + 1. [=list/For each=] |cred| in |extOutput|.{{RecoveryExtensionOutput/creds}}: + + 1. If |cred|.[=aaguid=] identifies an [=authenticator=] model accepted by the [=[RP]=]'s policy, + [=list/append=] |cred| to |acceptedCreds|. + Otherwise, [=list/append=] |cred| to |rejectedCreds|. + + Note: This AAGUID is not yet verifiable by an [=attestation signature=], + but can be verified when [[#sctn-recovery-credentials-extension-rp-recover]]; + this step helps fail early if the [=[RP]=] would reject the [=backup authenticator=] at that point. + If an authenticator is dishonest about |cred|.[=aaguid=], + it could at worst cause a false-positive rejection of this [=recovery credential=]. + + 1. Set |recoveryStates|[|pkc|.{{Credential/id}}] = (|extOutput|.{{RecoveryExtensionOutput/state}}, |acceptedCreds|). + + Note: This intentionally overwrites any [=recovery credentials=] previously registered with this [=primary credential=]. + + 1. Show the user a confirmation message containing the length of |acceptedCreds|. + + 1. If |rejectedCreds| is not [=list/empty=], show the user a warning message. + The warning message SHOULD contain the length of |rejectedCreds| and, if possible, + descriptions of the AAGUIDs that were rejected. + + 1. Continue with the remaining steps of the authentication ceremony. + + +#### Using a [=recovery credential=] to replace a lost [=primary credential=] #### {#sctn-recovery-credentials-extension-rp-recover} + +To authenticate the user with a [=recovery credential=] and create a new [=primary credential=], +the [=[RP]=] performs the following procedure: + + 1. Identify the user, for example by asking for a username. + + 1. Let |allowCredentials| be a new [=list=]. + + 1. [=list/For each=] (|state|, |creds|) value in the |recoveryStates| map stored in the user's account: + + 1. [=list/For each=] |cred| in |creds|: + + 1. Let |credDesc| be a {{PublicKeyCredentialDescriptor}} structure with the following member values: + + - {{PublicKeyCredentialDescriptor/type}}: "public-key". + - {{PublicKeyCredentialDescriptor/id}}: |cred|.[=credentialId=]. + + 1. Add |credDesc| to |allowCredentials|. + + Note: The privacy considerations in [[#sctn-credential-id-privacy-leak]] apply here also. + + 1. If |allowCredentials| is [=list/empty=], abort this procedure with an error. + + 1. Initiate a {{CredentialsContainer/create()}} operation with the {{AuthenticationExtensionsClientInputs/recovery}} extension input members: + + - {{RecoveryExtensionInput/action}}: {{RecoveryExtensionAction/recover}} + - {{RecoveryExtensionInput/allowCredentials}}: |allowCredentials| + + 1. Let |pkc| be the {{PublicKeyCredential}} response from the [=client=]. + If the operation fails, abort the ceremony with an error. + + 1. In step 17 of the [=[RP]=] Operation to [[#sctn-registering-a-new-credential|register a new credential]], + perform the following steps: + + 1. Let |extOutput| = |pkc|.{{PublicKeyCredential/response}}.{{AuthenticatorAttestationResponse/attestationObject}}["authData"].[=authDataExtensions|extensions=]["recovery"], + or null if not present. + + 1. If |extOutput| is null, + |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/recover}}, + or any of + |extOutput|.{{RecoveryExtensionOutput/state}}, + |extOutput|.{{RecoveryExtensionOutput/credId}}, + or |extOutput|.{{RecoveryExtensionOutput/sig}} + is not present, abort the ceremony with an error. + + 1. If |extOutput|.{{RecoveryExtensionOutput/credId}} does not equal + the {{PublicKeyCredentialDescriptor/id}} member of some element of |allowCredentials|, + abort the ceremony with an error. + + 1. Let |revokedCredId| be null. + + 1. [=map/For each=] |primaryCredId| → (|state|, |creds|) of |recoveryStates|: + + 1. [=list/For each=] |cred| in |creds|: + + 1. If |cred|.[=credentialId=] equals |extOutput|.{{RecoveryExtensionOutput/credId}}: + + 1. Let |publicKey| be the decoded public key |cred|.[=credentialPublicKey=]. + + 1. Let |authDataWithoutExtensions| = |pkc|.{{PublicKeyCredential/response}}.{{AuthenticatorAttestationResponse/attestationObject}}["authData"], + but without the [=authDataExtensions|extensions=] part. + The `ED` [=flag=] in |authDataWithoutExtensions| MUST be set to 1 + even though |authDataWithoutExtensions| does not include extension data. + + 1. Let |clientDataHash| be the [=hash of the serialized client data=] for this registration ceremony. + + 1. Using |publicKey|, verify that |extOutput|.{{RecoveryExtensionOutput/sig}} + is a valid signature over |authDataWithoutExtensions| || |clientDataHash|. + If the signature is invalid, fail the registration ceremony. + + 1. Set |revokedCredId| = |primaryCredId|. + + 1. [=Break=]. + + 1. If |revokedCredId| is not null, [=break=]. + + 1. If |revokedCredId| is null, abort the ceremony with an error. + + 1. Continue with the remaining steps of the registration ceremony. + This means a new [=credential=] has now been registered using the [=backup authenticator=]. + + 1. Invalidate the credential identified by |revokedCredId| and all [=recovery credentials=] associated with it + (i.e., delete |recoveryStates|[|revokedCredId|]). + This step and the registration of the new credential SHOULD be performed as an atomic operation. + + 1. It is RECOMMENDED to send the user an e-mail or similar notification about this change to their account. + + 1. If |extOutput|.{{RecoveryExtensionOutput/state}} is greater than 0, + it is RECOMMENDED to treat this as equivalent to detecting this as described in [[#sctn-recovery-credentials-extension-rp-detect-changed-seeds]], + and initiate [[#sctn-recovery-credentials-extension-rp-register-recovery-creds]] for the newly registered credential. + + # User Agent Automation # {#sctn-automation} For the purposes of user agent automation and [=web application=] testing, this document defines a number of [[WebDriver]] [=extension commands=]. From 333938dee8b7c78611713fff7c5881fc3cb9e1f5 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Wed, 27 May 2020 20:53:01 +0200 Subject: [PATCH 2/5] Fix description of RecoveryExtensionInput.allowCredentials --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index ed3321437..8741bf99a 100644 --- a/index.bs +++ b/index.bs @@ -5752,8 +5752,8 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# : allowCredentials - :: This member, indicates the [=recovery credentials=] that MAY be used in a [=registration ceremony=] - where {{RecoveryExtensionInput/action}} is set to {{RecoveryExtensionAction/recover}}. + :: This member, REQUIRED when {{action}} is {{RecoveryExtensionAction/recover}}, + indicates the [=recovery credentials=] that MAY be used in the [=registration ceremony=]. Its function is analogous to that of {{PublicKeyCredentialRequestOptions}}.{{PublicKeyCredentialRequestOptions/allowCredentials}}. From 51a18a0b5f956d7efdb3e3b069484cf583a0e0db Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Wed, 27 May 2020 21:07:02 +0200 Subject: [PATCH 3/5] Use info parameter in HKDF similar to CTAP2 PIN protocol --- index.bs | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/index.bs b/index.bs index 8741bf99a..49f7cc10d 100644 --- a/index.bs +++ b/index.bs @@ -298,8 +298,7 @@ spec: RFC2104; urlPrefix: https://tools.ietf.org/html/rfc2104 spec: RFC5869; urlPrefix: https://tools.ietf.org/html/rfc5869 type: dfn - text: HKDF-Extract; url: section-2.2 - text: HKDF-Expand; url: section-2.3 + text: HKDF-SHA-256; url: section-2 spec: RFC6090; urlPrefix: https://tools.ietf.org/html/rfc6090 type: dfn @@ -5805,22 +5804,23 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# Let |ikm_x| be the X coordinate of |ikm|, encoded as a byte string of length 32 as described in section 2.3.7 of [[!SEC1]]. - 1. Let |prk| be the pseudorandom key output from [=HKDF-Extract=] [[!RFC5869]] with the arguments: + 1. Let |credKey| be the 32 bytes of output keying material from [=HKDF-SHA-256=] [[!RFC5869]] with the arguments: - - `Hash`: SHA-256. - - `salt`: Not set. - - `IKM`: |ikm_x|. + - `salt`: Not set (equivalent to 32 zero bytes). + - `IKM`: |ikm_x| + - `info`: the byte string `0x776562617574686e2e7265636f766572792e637265645f6b6579` + (the [=UTF-8 encoded=] string `webauthn.recovery.cred_key`). + - `L`: 32. - 1. Let |okm| be 64 bytes of output keying material from [=HKDF-Expand=] [[!RFC5869]] with the arguments: + Parse |credKey| as a big-endian unsigned 256-bit number. - - `Hash`: SHA-256. - - `PRK`: |prk|. - - `info`: Not set. - - `L`: 64. + 1. Let |macKey| be the 32 bytes of output keying material from [=HKDF-SHA-256=] [[!RFC5869]] with the arguments: - 1. Let |credKey| = LEFT(|okm|, 32) - and |macKey| = LEFT(DROP_LEFT(|okm|, 32), 32), - both parsed as big-endian unsigned 256-bit numbers. + - `salt`: Not set (equivalent to 32 zero bytes). + - `IKM`: |ikm_x| + - `info`: the byte string `0x776562617574686e2e7265636f766572792e6d61635f6b6579` + (the [=UTF-8 encoded=] string `webauthn.recovery.mac_key`). + - `L`: 32. 1. If |credKey| >= n, where n is the order of the P-256 curve, start over from 1. @@ -5886,22 +5886,23 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# Let |ikm_x| be the X coordinate of |ikm|, encoded as a byte string of length 32 as described in section 2.3.7 of [[!SEC1]]. - 1. Let |prk| be the pseudorandom key output from [=HKDF-Extract=] [[!RFC5869]] with the arguments: + 1. Let |credKey| be the 32 bytes of output keying material from [=HKDF-SHA-256=] [[!RFC5869]] with the arguments: - - `Hash`: SHA-256. - - `salt`: Not set. - - `IKM`: |ikm_x|. + - `salt`: Not set (equivalent to 32 zero bytes). + - `IKM`: |ikm_x| + - `info`: the byte string `0x776562617574686e2e7265636f766572792e637265645f6b6579` + (the [=UTF-8 encoded=] string `webauthn.recovery.cred_key`). + - `L`: 32. - 1. Let |okm| be 64 bytes of output keying material from [=HKDF-Expand=] [[!RFC5869]] with the arguments: + Parse |credKey| as a big-endian unsigned 256-bit number. - - `Hash`: SHA-256. - - `PRK`: |prk|. - - `info`: Not set. - - `L`: 64. + 1. Let |macKey| be the 32 bytes of output keying material from [=HKDF-SHA-256=] [[!RFC5869]] with the arguments: - 1. Let |credKey| = LEFT(|okm|, 32) - and |macKey| = LEFT(DROP_LEFT(|okm|, 32), 32), - both parsed as big-endian unsigned 256-bit numbers. + - `salt`: Not set (equivalent to 32 zero bytes). + - `IKM`: |ikm_x| + - `info`: the byte string `0x776562617574686e2e7265636f766572792e6d61635f6b6579` + (the [=UTF-8 encoded=] string `webauthn.recovery.mac_key`). + - `L`: 32. 1. If |cred|.{{PublicKeyCredentialDescriptor/id}} is not exactly equal to |alg| || |E| || LEFT([=HMAC-SHA-256=](|macKey|, |alg| || |E| || [=rpIdHash=]), 16) [[!RFC2104]], From a6814a2cd3feb23123f85ed3c8d24c70320e4c6f Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Fri, 5 Jun 2020 23:49:10 +0200 Subject: [PATCH 4/5] Use CDDL for authenticator extension in/outputs --- index.bs | 160 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 65 deletions(-) diff --git a/index.bs b/index.bs index 49f7cc10d..57bafcd52 100644 --- a/index.bs +++ b/index.bs @@ -5751,7 +5751,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# : allowCredentials - :: This member, REQUIRED when {{action}} is {{RecoveryExtensionAction/recover}}, + :: This member, REQUIRED when {{action}} is {{RecoveryExtensionAction/recover}} and ignored otherwise, indicates the [=recovery credentials=] that MAY be used in the [=registration ceremony=]. Its function is analogous to that of {{PublicKeyCredentialRequestOptions}}.{{PublicKeyCredentialRequestOptions/allowCredentials}}. @@ -5769,7 +5769,33 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# : Authenticator extension input -:: The client extension input encoded as a CBOR map. +:: A CBOR map with the following structure: + + ``` + recoveryExtInput = { + action: "state" / "generate", + // + + action: "recover", + allowCredentials: [* publicKeyCredentialDescriptor], + } + + publicKeyCredentialDescriptor = { + type: "public-key", + id: bytes, + ? transports: [* tstr], + } + ``` + + The semantics of the above fields are as follows: + + : action + :: The value of the [=client extension input=] {{RecoveryExtensionInput/action}}. + + : allowCredentials + :: A list identifying [=recovery credentials=] that MAY be used in a [=registration ceremony=]. + MUST be present if and only if [=recoveryExtInput-action|action=] is {{RecoveryExtensionAction/recover}}. + : Authenticator extension processing :: The following definitions are used below: @@ -5777,7 +5803,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# - `LEFT(X, n)` is the first n bytes of the byte array X. - `DROP_LEFT(X, n)` is the byte array X without the first n bytes. - If {{RecoveryExtensionInput/action}} is + If [=recoveryExtInput-action|action=] is
: {{RecoveryExtensionAction/state}} :: Generate the [=authenticator extension output=] as described in the Authenticator extension output section. @@ -5808,8 +5834,8 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# - `salt`: Not set (equivalent to 32 zero bytes). - `IKM`: |ikm_x| - - `info`: the byte string `0x776562617574686e2e7265636f766572792e637265645f6b6579` - (the [=UTF-8 encoded=] string `webauthn.recovery.cred_key`). + - `info`: the [=UTF-8 encoded=] string `webauthn.recovery.cred_key` + (CDDL: `h'776562617574686e2e7265636f766572792e637265645f6b6579'`). - `L`: 32. Parse |credKey| as a big-endian unsigned 256-bit number. @@ -5818,8 +5844,8 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# - `salt`: Not set (equivalent to 32 zero bytes). - `IKM`: |ikm_x| - - `info`: the byte string `0x776562617574686e2e7265636f766572792e6d61635f6b6579` - (the [=UTF-8 encoded=] string `webauthn.recovery.mac_key`). + - `info`: the [=UTF-8 encoded=] string `webauthn.recovery.mac_key` + (CDDL: `h'776562617574686e2e7265636f766572792e6d61635f6b6579'`). - `L`: 32. 1. If |credKey| >= n, where n is the order of the P-256 curve, start over from 1. @@ -5852,7 +5878,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# 1. Generate the [=authenticator extension output=] as described in the Authenticator extension output section, - with the {{RecoveryExtensionOutput/creds}} member set to |creds|. + with the [=recoveryExtOutput-creds|creds=] member set to |creds|. : {{RecoveryExtensionAction/recover}} @@ -5864,7 +5890,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# 1. Let |creds| be a new [=list=]. - 1. [=list/For each=] |cred| in {{RecoveryExtensionInput/allowCredentials}}: + 1. [=list/For each=] |cred| in [=recoveryExtInput-allowCredentials|allowCredentials=]: 1. Let |alg| = LEFT(|cred|.{{PublicKeyCredentialDescriptor/id}}, 1). @@ -5890,8 +5916,8 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# - `salt`: Not set (equivalent to 32 zero bytes). - `IKM`: |ikm_x| - - `info`: the byte string `0x776562617574686e2e7265636f766572792e637265645f6b6579` - (the [=UTF-8 encoded=] string `webauthn.recovery.cred_key`). + - `info`: the [=UTF-8 encoded=] string `webauthn.recovery.cred_key` + (CDDL: `h'776562617574686e2e7265636f766572792e637265645f6b6579'`). - `L`: 32. Parse |credKey| as a big-endian unsigned 256-bit number. @@ -5900,8 +5926,8 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# - `salt`: Not set (equivalent to 32 zero bytes). - `IKM`: |ikm_x| - - `info`: the byte string `0x776562617574686e2e7265636f766572792e6d61635f6b6579` - (the [=UTF-8 encoded=] string `webauthn.recovery.mac_key`). + - `info`: the [=UTF-8 encoded=] string `webauthn.recovery.mac_key` + (CDDL: `h'776562617574686e2e7265636f766572792e6d61635f6b6579'`). - `L`: 32. 1. If |cred|.{{PublicKeyCredentialDescriptor/id}} is not exactly equal to @@ -5930,8 +5956,9 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# 1. Generate the [=authenticator extension output=] as described in the Authenticator extension output section, - with the {{RecoveryExtensionOutput/credId}} member set to |cred|.{{PublicKeyCredentialDescriptor/id}} - and the {{RecoveryExtensionOutput/sig}} member set to |sig|, + with the [=recoveryExtOutput-credId|credId=] member set to + |cred|.{{PublicKeyCredentialDescriptor/id}} + and the [=recoveryExtOutput-sig|sig=] member set to |sig|, and end extension processing. 1. Return an error code equivalent to "{{InvalidStateError}}". @@ -5941,45 +5968,47 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# : Authenticator extension output :: A CBOR map with the following structure: - - dictionary RecoveryExtensionOutput { - required RecoveryExtensionAction action; - required int state; - sequence<ArrayBuffer> creds; - ArrayBuffer credId; - ArrayBuffer sig; - }; - - ``` - $$recoveryExtOutput //= { - action: RecoveryExtensionAction, - state: int - creds: [ * (cred: bytes) ], - credId: bytes, - sig: bytes, - } + recoveryExtOutput = { + action: "state", + state: uint, + // + + action: "generate", + state: uint, + creds: [* bytes], + // + + action: "recover", + state: uint, + credId: bytes, + sig: bytes, + } ``` -
- : action - :: The value of the extension input {{RecoveryExtensionInput/action}}. + The semantics of the above fields are as follows: - : state - :: The current value of the [=recovery credentials state counter=]. + : action + :: The value of the extension input [=recoveryExtInput-action|action=]. - : creds - :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/generate}}. - Its value is a sequence of [=attested credential data=] structures representing newly created [=recovery credentials=]. + : state + :: The current value of the [=recovery credentials state counter=]. - : credId - :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/recover}}. - Its value is the [=credential ID=] of the [=recovery credential=] that was used to create the signature {{RecoveryExtensionOutput/sig}}. + : creds + :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] + is {{RecoveryExtensionAction/generate}}. + Its value is a sequence of [=attested credential data=] structures conveying newly created [=recovery credentials=]. - : sig - :: This member MUST be present if and only if the output {{RecoveryExtensionOutput/action}} is {{RecoveryExtensionAction/recover}}. - Its value is a signature generated as described in the Authenticator extension processing section. -
+ : credId + :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] + is {{RecoveryExtensionAction/recover}}. + Its value is the [=credential ID=] of the [=recovery credential=] that was used + to create the signature [=recoveryExtOutput-sig|sig=]. + + : sig + :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] + is {{RecoveryExtensionAction/recover}}. + Its value is a signature generated as described in the Authenticator extension processing section. ### [=[RP]=] extension processing ### {#sctn-recovery-credentials-extension-rp-processing} @@ -5991,9 +6020,10 @@ There are two cases where the response indicates that the [=[RP]=] SHOULD initiate [[#sctn-recovery-credentials-extension-rp-register-recovery-creds|recovery credential registration]], which are: - Upon successful {{CredentialsContainer/create()|navigator.credentials.create()}}, - if {{RecoveryExtensionOutput/state}} > 0. + if [=recoveryExtOutput-state|state=] > 0. - Upon successful {{CredentialsContainer/create()|navigator.credentials.create()}}, - if {{RecoveryExtensionOutput/state}} is greater than the previous value for {{RecoveryExtensionOutput/state}} + if [=recoveryExtOutput-state|state=] is greater than the previous value + for [=recoveryExtOutput-state|state=] that the [=[RP]=] has seen for the used [=credential=]. The following operations assume that each user account contains a |recoveryStates| field, @@ -6023,16 +6053,16 @@ the [=[RP]=] SHOULD add the following steps to all registration and authenticati 1. If |extOutput| is not null: - 1. If |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/state}} - or |extOutput|.{{RecoveryExtensionOutput/state}} is not present, + 1. If |extOutput|.[=recoveryExtOutput-action|action=] does not equal {{RecoveryExtensionAction/state}} + or |extOutput|.[=recoveryExtOutput-state|state=] is not present, abort this extension processing and OPTIONALLY show a user-visible warning. - 1. If |extOutput|.{{RecoveryExtensionOutput/state}} > 0: + 1. If |extOutput|.[=recoveryExtOutput-state|state=] > 0: 1. Let |recoveryState| = |recoveryStates|[|pkc|.{{Credential/id}}], or null if |pkc|.{{Credential/id}} is not present in |recoveryStates|. - 1. If |recoveryState| is null or |extOutput|.{{RecoveryExtensionOutput/state}} > |recoveryState|.state: + 1. If |recoveryState| is null or |extOutput|.[=recoveryExtOutput-state|state=] > |recoveryState|.state: 1. If the ceremony finishes successfully, prompt the user that their [=recovery credentials=] need to be updated @@ -6066,15 +6096,15 @@ the RP performs the following procedure: or null if not present. 1. If |extOutput| is null, - |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/generate}}, - or |extOutput|.{{RecoveryExtensionOutput/creds}} is not present, + |extOutput|.[=recoveryExtOutput-action|action=] does not equal {{RecoveryExtensionAction/generate}}, + or |extOutput|.[=recoveryExtOutput-creds|creds=] is not present, abort the ceremony with an error. 1. Let |acceptedCreds| be a new [=list=]. 1. Let |rejectedCreds| be a new [=list=]. - 1. [=list/For each=] |cred| in |extOutput|.{{RecoveryExtensionOutput/creds}}: + 1. [=list/For each=] |cred| in |extOutput|.[=recoveryExtOutput-creds|creds=]: 1. If |cred|.[=aaguid=] identifies an [=authenticator=] model accepted by the [=[RP]=]'s policy, [=list/append=] |cred| to |acceptedCreds|. @@ -6086,7 +6116,7 @@ the RP performs the following procedure: If an authenticator is dishonest about |cred|.[=aaguid=], it could at worst cause a false-positive rejection of this [=recovery credential=]. - 1. Set |recoveryStates|[|pkc|.{{Credential/id}}] = (|extOutput|.{{RecoveryExtensionOutput/state}}, |acceptedCreds|). + 1. Set |recoveryStates|[|pkc|.{{Credential/id}}] = (|extOutput|.[=recoveryExtOutput-state|state=], |acceptedCreds|). Note: This intentionally overwrites any [=recovery credentials=] previously registered with this [=primary credential=]. @@ -6138,14 +6168,14 @@ the [=[RP]=] performs the following procedure: or null if not present. 1. If |extOutput| is null, - |extOutput|.{{RecoveryExtensionOutput/action}} does not equal {{RecoveryExtensionAction/recover}}, + |extOutput|.[=recoveryExtOutput-action|action=] does not equal {{RecoveryExtensionAction/recover}}, or any of - |extOutput|.{{RecoveryExtensionOutput/state}}, - |extOutput|.{{RecoveryExtensionOutput/credId}}, - or |extOutput|.{{RecoveryExtensionOutput/sig}} + |extOutput|.[=recoveryExtOutput-state|state=], + |extOutput|.[=recoveryExtOutput-credId|credId=], + or |extOutput|.[=recoveryExtOutput-sig|sig=] is not present, abort the ceremony with an error. - 1. If |extOutput|.{{RecoveryExtensionOutput/credId}} does not equal + 1. If |extOutput|.[=recoveryExtOutput-credId|credId=] does not equal the {{PublicKeyCredentialDescriptor/id}} member of some element of |allowCredentials|, abort the ceremony with an error. @@ -6155,7 +6185,7 @@ the [=[RP]=] performs the following procedure: 1. [=list/For each=] |cred| in |creds|: - 1. If |cred|.[=credentialId=] equals |extOutput|.{{RecoveryExtensionOutput/credId}}: + 1. If |cred|.[=credentialId=] equals |extOutput|.[=recoveryExtOutput-credId|credId=]: 1. Let |publicKey| be the decoded public key |cred|.[=credentialPublicKey=]. @@ -6166,7 +6196,7 @@ the [=[RP]=] performs the following procedure: 1. Let |clientDataHash| be the [=hash of the serialized client data=] for this registration ceremony. - 1. Using |publicKey|, verify that |extOutput|.{{RecoveryExtensionOutput/sig}} + 1. Using |publicKey|, verify that |extOutput|.[=recoveryExtOutput-sig|sig=] is a valid signature over |authDataWithoutExtensions| || |clientDataHash|. If the signature is invalid, fail the registration ceremony. @@ -6187,7 +6217,7 @@ the [=[RP]=] performs the following procedure: 1. It is RECOMMENDED to send the user an e-mail or similar notification about this change to their account. - 1. If |extOutput|.{{RecoveryExtensionOutput/state}} is greater than 0, + 1. If |extOutput|.[=recoveryExtOutput-state|state=] is greater than 0, it is RECOMMENDED to treat this as equivalent to detecting this as described in [[#sctn-recovery-credentials-extension-rp-detect-changed-seeds]], and initiate [[#sctn-recovery-credentials-extension-rp-register-recovery-creds]] for the newly registered credential. From de0b91685de61deb011f1d16951412d9157b8ff5 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Sat, 6 Jun 2020 00:34:53 +0200 Subject: [PATCH 5/5] Add attestation parameter to recovery extension input --- index.bs | 91 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/index.bs b/index.bs index 57bafcd52..4e42bd3dc 100644 --- a/index.bs +++ b/index.bs @@ -5717,6 +5717,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# dictionary RecoveryExtensionInput { required RecoveryExtensionAction action; + DOMString attestation = "none"; sequence allowCredentials; }; @@ -5750,6 +5751,19 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# which will assume the role of [=primary authenticator=] upon successful completion of the [=registration ceremony=]. + : attestation + :: This member, which is ignored unless {{action}} is {{RecoveryExtensionAction/generate}}, + is intended for use by [=[RPS]=] that wish to express their preference for [=attestation conveyance=]. + Its value SHOULD be a member of {{AttestationConveyancePreference}}. + [=Client platforms=] MUST ignore unknown values. + + If set to {{AttestationConveyancePreference/direct}} or {{AttestationConveyancePreference/enterprise}}, + the [=authenticator=] will emit the [=backup authenticator=]'s [=AAGUID=] with each generated [=recovery credential=]. + If absent or set to anything else, the [=AAGUID=]s will be zeroed. + + The [=client=] SHOULD apply the same restrictions to this member + as it does to the {{PublicKeyCredentialCreationOptions/attestation}} member of {{PublicKeyCredentialCreationOptions}}. + : allowCredentials :: This member, REQUIRED when {{action}} is {{RecoveryExtensionAction/recover}} and ignored otherwise, indicates the [=recovery credentials=] that MAY be used in the [=registration ceremony=]. @@ -5758,10 +5772,18 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# : Client extension processing -:: Create the authenticator extension input from the client extension input. - - If the client implements support for this extension, - then when {{RecoveryExtensionInput/action}} is {{RecoveryExtensionAction/generate}}, +:: Create the authenticator extension input from the client extension input: + + - [=recoveryExtInput-action|action=]: {{RecoveryExtensionInput/action}}. + - [=recoveryExtInput-aaguid|aaguid=]: + If {{RecoveryExtensionInput/attestation}} is present + and set to {{AttestationConveyancePreference/direct}} or {{AttestationConveyancePreference/enterprise}}, + and the [=client=] allows emitting attestation information, [TRUE]. + Otherwise, [FALSE]. + - [=recoveryExtInput-allowList|allowList=]: + The {{PublicKeyCredentialDescriptor/id}} member of each [=list/item=] in {{RecoveryExtensionInput/allowCredentials}}, in order. + + When {{RecoveryExtensionInput/action}} is {{RecoveryExtensionAction/generate}}, the client SHOULD notify the user of the number of [=recovery credentials=] in the response. : Client extension output @@ -5773,11 +5795,15 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# ``` recoveryExtInput = { - action: "state" / "generate", + 0: "state", + // + + 0: "generate", + 1: bool, // - action: "recover", - allowCredentials: [* publicKeyCredentialDescriptor], + 0: "recover", + 2: [* publicKeyCredentialDescriptor], } publicKeyCredentialDescriptor = { @@ -5789,10 +5815,15 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# The semantics of the above fields are as follows: - : action + : 0 (action) :: The value of the [=client extension input=] {{RecoveryExtensionInput/action}}. - : allowCredentials + : 1 (aaguid) + :: [TRUE] if the [=authenticator=] should emit AAGUIDs for generated [=recovery credentials=], + [FALSE] otherwise. + MUST be present if and only if [=recoveryExtInput-action|action=] is {{RecoveryExtensionAction/generate}}. + + : 2 (allowList) :: A list identifying [=recovery credentials=] that MAY be used in a [=registration ceremony=]. MUST be present if and only if [=recoveryExtInput-action|action=] is {{RecoveryExtensionAction/recover}}. @@ -5867,9 +5898,19 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# since the authenticator should never store a recovery seed with an unknown |alg| value.
+ 1. Let |emittedAaguid| be a byte string computed as follows: + +
+ : If [=recoveryExtInput-aaguid|aaguid=] is [TRUE], + :: let |emittedAaguid| be |aaguid|. + + : Otherwise, + :: let |emittedAaguid| be 16 zero bytes. +
+ 1. Let |attCredData| be a new [=attested credential data=] structure with the following member values: - - [=aaguid=]: |aaguid|. + - [=aaguid=]: |emittedAaguid|. - [=credentialIdLength=]: The byte length of |credentialId|. - [=credentialId=]: |credentialId|. - [=credentialPublicKey=]: |P|. @@ -5890,7 +5931,7 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# 1. Let |creds| be a new [=list=]. - 1. [=list/For each=] |cred| in [=recoveryExtInput-allowCredentials|allowCredentials=]: + 1. [=list/For each=] |cred| in [=recoveryExtInput-allowList|allowList=]: 1. Let |alg| = LEFT(|cred|.{{PublicKeyCredentialDescriptor/id}}, 1). @@ -5970,42 +6011,42 @@ This extension requires additional processing by the [=[RP]=] as detailed in [[# ``` recoveryExtOutput = { - action: "state", - state: uint, + 0: "state", + 1: uint, // - action: "generate", - state: uint, - creds: [* bytes], + 0: "generate", + 1: uint, + 2: [* bytes], // - action: "recover", - state: uint, - credId: bytes, - sig: bytes, + 0: "recover", + 1: uint, + 3: bytes, + 4: bytes, } ``` The semantics of the above fields are as follows: - : action + : 0 (action) :: The value of the extension input [=recoveryExtInput-action|action=]. - : state + : 1 (state) :: The current value of the [=recovery credentials state counter=]. - : creds + : 2 (creds) :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] is {{RecoveryExtensionAction/generate}}. Its value is a sequence of [=attested credential data=] structures conveying newly created [=recovery credentials=]. - : credId + : 3 (credId) :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] is {{RecoveryExtensionAction/recover}}. Its value is the [=credential ID=] of the [=recovery credential=] that was used to create the signature [=recoveryExtOutput-sig|sig=]. - : sig + : 4 (sig) :: This member MUST be present if and only if the output [=recoveryExtOutput-action|action=] is {{RecoveryExtensionAction/recover}}. Its value is a signature generated as described in the Authenticator extension processing section.