[webauthn] Move PRF Extension into its own specification (#1462)

jcjones has just created a new issue for https://github.com/w3c/webauthn:

== Move PRF Extension into its own specification ==
The pseudo-random function extension does not belong in PublicKeyCredential or in Web Authentication’s specification, and should be moved into another document with its own methods extending the Web Cryptography API.

The mechanism is useful, but I don’t see a reason to make it carry the baggage of WebAuthn simply because it’s relies on the CTAP2 wire protocol. As we always postulated in the early days of this working group, WebAuthn could use additional authenticator interface mechanisms some day. Similarly, a mechanism for obtaining a hardware-backed origin-bound, rotatable secret key could be extended in the future independent of CTAP2.

## Example in the State of the Working Draft
Let’s first look at the extension as defined in the current working draft. My password manager, “Honest J.C.’s Recycled Passwords”, wants to do client-side encryption of its password database. Let’s assume that WebAuthn is already used for server-side enrollment and authentication.

The developer of the password database now seeks to add client-side hardware-backed encryption. Regardless of whether or not the user has already obtained a WebAuthn credential for their account, the developer, seeking a secret key, needs to construct a publicKey-type create request:

navigator.credentials.create({ publicKey: createRequest })

In that `createRequest` they must decide what public key type they want to use.

"pubKeyCredParams": [
      "alg": -7,
      "type": "public-key"

What does this have to do with the secret key? Will the signature be important for the symmetric encryption later? The developer of “Honest J.C.’s Recycled Passwords” is not a cryptographer. They want to get a 256-bit key they can pass into WebCrypto, but what are they supposed to do with the public key after the create? Perhaps they store it in the database alongside the hmac salt inputs because maybe it’s important?

Now they encrypt the database with the key they obtained. But they have this signature operation, too. Always good to have more crypto, they think.

But now we’re into bespoke verification mechanisms rather than something more like an AEAD, and in our best case the excess data leads the developer of “Honest J.C.’s Recycled Passwords” towards passing the ciphertext into subsequent calls to `navigator.credentials.get()` that seek to re-derive the secret key.

In the worst case, they design something too clever, perhaps utilizing the current database ciphertext as the challenge field for all login authentication, so they only have to call `navigator.credentials.get` once. That’s more efficient, right? The key won’t decrypt if the user isn’t authentic. What does it matter if the database didn’t change between invocations?

I think the WebAuthn pieces of this get very in-the-way, and we’ll probably be dealing with confusion around the extension for the foreseeable future if we continue down this path.

## Web Hardware-Backed Encryption
Let’s imagine this more focused on the application developer. The wants are:

- To get an encryption key that’s stored in hardware
  - Potentially only accessible after user verification
  - The encryption key should be origin-bound
  - To be able to rotate the encryption keys
- Use the existing [CTAP2 `hmac-secret` command](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html#sctn-hmac-secret-extension)

Let’s imagine a document that defines a new WebCrypto Algorithm, “Hardware-Backed Secret”, and some new methods:

partial interface SubtleCrypto {
    Promise<HardwareBackedSecretAttestationResponse> createHardwareSecret(...)
    Promise<HardwareBackedSecret> getHardwareSecret(...)

The resulting objects can omit the signature but still return the attestation and the attested client data:

[SecureContext, Exposed=Window]
interface HardwareBackedSecret {
    required ArrayBuffer first;
    ArrayBuffer second;
    [SameObject] readonly attribute ArrayBuffer      rawSecretKeyId;
    [SameObject] readonly attribute ArrayBuffer      clientDataJSON;
    SecretKeyExtensionsClientOutputs                 getClientExtensionResults();
    // Probably others, when we get to the full analysis... but the point is that
    // it is doable, and this capability is worthwhile enough to be worth doing
    // properly.

[SecureContext, Exposed=Window]
interface HardwareBackedSecretAttestationResponse : HardwareBackedSecret {
    [SameObject] readonly attribute ArrayBuffer      attestationObject;
    sequence<DOMString>                              getTransports();
    ArrayBuffer                                      getAuthenticatorData();

The input parameters would follow the same pattern, shaving off anything unnecessary to the secrets management. We can also craft them to inherit from Algorithm, to allow use via the existing `deriveBits` and `deriveKey` methods. For those methods, one might not be able to handle the optional Second buffer for key rollover operations, but the new get/create hardware methods would be compatible with credentials produced via the `derive...` methods, in the event a developer needed to get more sophisticated with time. 

dictionary HardwareBackedSecretParams : Algorithm {
    required SecretKeyRpEntity         rp;
    unsigned long                      timeout;
    AuthenticatorSelectionCriteria     authenticatorSelection;
    SecretKeyExtensionsClientInputs    extensions;

Creation of new secret key types uses a dictionary that includes the necessary WebAuthn types, but can forego things irrelevant to its use case, perhaps even the user account information:

dictionary HardwareBackedSecretCreateParams : HardwareBackedSecretParams {
    DOMString                          attestation = "none";
    sequence<SecretKeyDescriptor>      excludeCredentials = [];

dictionary HardwareBackedSecretGetParams : HardwareBackedSecretParams {
    sequence<SecretKeyDescriptor>      includeCredentials = [];

dictionary SecretKeyDescriptor {
    required BufferSource              secretKeyId;
    sequence<DOMString>                transports;

At the User Agent level, utilizing the `hmac-secret` CTAP2 feature would ingest data not supplied here (challenge, notably!), and similarly would produce signatures that would not be emitted. The specification would define how the user agent behaves with that data to make it safe.

But the resulting methods become more apparent to the application developer, and the extraneous bits won’t be there to confuse them into misuse, directly. 
## Future Extensibility
A powerful reason to make the hardware-backed encryption key mechanism stand on its own is letting it be further extended without dragging along the baggage of WebAuthn’s extensions mechanism.

Just from brief conversations about using authenticators as encryption sources, there’s desire for mechanisms like M-of-N secret sharing/splits, which would be very cumbersome to try and implement atop of an extension atop a WebAuthn extension, but could be added as their _own_ extensions into calls of `crypto.subtle.getHardwareSecret(...)`.
## Making It Work
I know we all want to get WebAuthn Level 2 finalized. I think we should remove this extension from the draft and introduce it as its own document, either inside the WebAuthn WG if we believe the charter supports it (and if not, perhaps that’s another argument against the extension!), in a new Web Crypto Community Group, or perhaps at the WICG. Then taking the above as a starting point, we construct a document that incorporates the Authenticator Model, Attestation Formats, fingerprinting and tracking mitigations, and other necessary sections by reference, and introduce that on its own publication schedule. And then we can do this right.

Please view or discuss this issue at https://github.com/w3c/webauthn/issues/1462 using your GitHub account

Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 30 July 2020 16:14:41 UTC