Skip to content

Instantly share code, notes, and snippets.

@emlun
Created October 22, 2018 08:17
Show Gist options
  • Save emlun/74a4d8bf53fd760a5c5408b418875e2b to your computer and use it in GitHub Desktop.
Save emlun/74a4d8bf53fd760a5c5408b418875e2b to your computer and use it in GitHub Desktop.
Draft: WebAuthn recovery extension

DRAFT: Recovery Credentials Extension (recovery)

This extension allows for recovery credentials to be registered with an RP, which can be used for account recovery in the case of a lost/destroyed main authenticator. This is done by associating one or more backup authenticators with the main authenticator, the latter of which is then able to provide additional credentials for account recovery to the RP without involving the backup authenticators. The mechanism of setting this up is outside of the scope of this extension, however a state counter is defined as follows:

Let state be initialized to 0. Performing a device reset re-initializes state to 0. When the set of registered backup authenticators for the device changes (eg. on adding/removing a backup authenticator, including adding the first backup authenticator) state is incremented by one.

The state counter is stored by the main authenticator, and allows the RP to automatically detect when the set of registered recovery credentials needs to be updated.

NOTE: The choice to make registration of recovery credentials explicit is deliberate, in an attempt to ensure that the user deliberately intends to do so and understands the implications.

Extension identifier

recovery

Operation applicability

Registration and Authentication

Client extension input

partial dictionary AuthenticationExtensionsClientInputs {
  RecoveryExtensionInput recovery;
}

dictionary RecoveryExtensionInput {
  required RecoveryExtensionAction action;
  sequence<PublicKeyCredentialDescriptor> allowCredentials;
}

enum RecoveryExtensionAction {
  "state",
  "generate",
  "recover"
}

The values of action have the following meanings. X indicates that the value is applicable for the given WebAuthn operation:

Value create() get() Description
state X X Get the state counter value from the main authenticator.
generate X Regenerate recovery credentials from the main authenticator.
recover X Get a recovery signature from a backup authenticator, to replace the main credential with a new one.

Client extension processing

None required, except creating the authenticator extension input from the client extension input.

If the client implements support for this extension, then when action is "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

If action is

  • "state",

    set the extension output to the CBOR encoding of {"action": "state", "state": <state counter>}.

  • "generate",

    generate one recovery credential for each associated backup authenticator, formatting these as a CBOR array of attested credential data byte arrays. Set the extension output to the CBOR encoding of {"action": "generate", "state": <state counter>, "creds": <list of recovery credentials>}.

  • "recover",

    locate a usable recovery credential from the credential IDs in allowCredentials in the extension input.

    • If no usable credential is found, do not include the extension in the output.

    • If a usable credential is found,

      1. Let cred be the found credential.

      2. Let authenticatorDataWithoutExtensions be the authenticator data that will be returned from this registration operation, but without the extensions part. The ED flag in authenticatorDataWithoutExtensions MUST be set even though authenticatorDataWithoutExtensions does not include extension data.

      3. Let sig be a signature over authenticatorDataWithoutExtensions || clientDataHash using cred.

      4. Set the extension output to the CBOR encoding of {"action": "recover", "credId": <credential ID of cred>, "sig": <sig>, "state": <state counter>}.

Authenticator extension output

A CBOR map with contents as defined above.

dictionary RecoveryExtensionOutput {
  required RecoveryExtensionAction action;
  required int state;
  sequence<ArrayBuffer> creds;
  ArrayBuffer credId;
  ArrayBuffer sig;
}

Recovery credential considerations

  • The RP MUST be very explicit in notifying the user when recovery credentials are registered, and how many, to avoid any credentials being registed without the user's knowledge. If possible, the client SHOULD also display the number of backup authenticators associated with the main authenticator.

  • The RP SHOULD clearly display information about registered recovery credentials, just as it does with standard credentials.

  • The same security considerations apply to recovery credentials as to standard credentials.

  • Although recovery credentials are issued by the main authenticator, they can only ever be used by the backup authenticator.

  • Recovery credentials are scoped to a specific RP ID, and the RP SHOULD also associate them with a specific main credential.

  • Recovery credentials can only be used in registration ceremonies where the recovery extension is present, with action == "recover".

  • A main authenticator should ensure that the recovery credentials it issues on behalf of a backup authenticator are authentic.

RP support

An RP supporting this extension SHOULD include the extension the action = "state" value whenever performing standard registration or authentication ceremony. There are two cases where the response indicates that the RP should initiate recovery credential registration (action "generate"), which are:

  • Upon successful create(), if state > 0.
  • Upon successful get(), if state > old_state, where old_state is the previous value for state that the RP has seen for the used credential.

To initiate recovery credential registration, the RP performs a get() operation with action = "generate". Upon a successful response, the returned list of recovery credentials is stored, associated with the main credential. Any prior recovery credentials for that main credential are replaced.

If the user initiates device recovery, the RP performs the following procedure:

  1. Ask the user which credential to recover. Let mainCred be the chosen credential.

  2. Let allowCredentials be a list of the credential descriptors of the recovery credentials associated with mainCred. If allowCredentials is empty, abort this procedure with an error.

  3. Initiate a create() operation with the extension input:

    "recovery": {
      "action": "recover",
      "allowCredentials": <allowCredentials as computed above>
    }
    
  4. Wait for the response from the client. If the operation fails, abort this procedure with an error.

  5. Let publicKey be the public key for the recovery credential identified by the credential ID credId in the extension output.

  6. Let authenticatorDataWithoutExtensions be the authenticator data in the PublicKeyCredential response, but without the extensions part. The ED flag in authenticatorDataWithoutExtensions MUST be set even though authenticatorDataWithoutExtensions does not include the extension outputs.

  7. Using publicKey, verify that sig in the extension output is a valid signature over authenticatorDataWithoutExtensions || clientDataHash.

  8. Finish the registration ceremony as usual. This means a new credential has now been registered using the backup authenticator.

  9. Revoke mainCred and all recovery credentials associated with it.

  10. If state in the extension output is greater than 0, the RP SHOULD initiate recovery credential registration (action = "generate") for the newly registered credential.

As an alternative to proceeding to register a new credential for the backup authenticato, the RP MAY choose to not replace the lost credential with the new one, and instead disable 2FA or provide some other means for the user to access their account. In either case, the associated main credential SHOULD be revoked and no longer usable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment