FIDOCRYPT(3) Library Functions Manual FIDOCRYPT(3)

fidocrypt, fido_assert_decrypt, fido_cred_encrypt
encrypt a secret with U2F/FIDO

U2F/FIDO encryption library (libfidocrypt, -lfidocrypt)

#include <fido.h>
#include <fidocrypt.h>

int
fido_assert_decrypt(const fido_assert_t *assert, size_t idx, const unsigned char *payload, size_t payloadlen, unsigned char **ciphertextp, size_t *ciphertextlenp);

int
fido_cred_encrypt(const fido_cred_t *cred, const fido_assert_t *assert, size_t idx, const unsigned char *ciphertext, size_t ciphertextlen, unsigned char **payloadp, size_t payloadlen);

The fidocrypt functions encrypt and decrypt a secret using information obtained through U2F/FIDO/Webauthn credential registration and assertion, such as (a share of) an encryption key for a user's password vault.

Registration
  1. Make a credential with a U2F/FIDO device, e.g. using navigator.credential.create in webauthn, or using fido_dev_make_cred(3) on a client.
  2. Optionally, get an assertion from the device with the hmac-secret extension enabled, e.g. using navigator.credential.get in webauthn, or using fido_dev_get_assert(3) on a client.
  3. Call fido_cred_encrypt() with the credential, optionally the assertion, and the payload you wish to encrypt so that it cannot be decrypted except with the same U2F/FIDO device later on. You will be returned a ciphertext, which includes enough information to verify an assertion of the credential and recover the plaintext later on.
  4. Store the ciphertext alongside the credential id so you can retireve it later during authentication. Do not store the credential's public key — the ciphertext contains enough information to verify an assertion during authentication, as if the public key had been stored.

Typically the payload will be the same for every credential of a single user. For example, you might store a share of the encryption key for the user's password vault, and store the same key for every credential registered by the user. This way, any one of the user's U2F/FIDO keys can be used (along with a share derived from another factor such as a master password) to decrypt the password vault.

You may also wish to verify any device attestation in the credential separately with fido_cred_verify(3)fido_cred_encrypt() does not verify device attestations.

Authentication

  1. Get an assertion from a U2F/FIDO device, e.g. using navigator.credential.get in webauthn, or using fido_dev_get_assert(3) on a client. If hmac-secret was enabled during registration, it must be enabled during authentication too.
  2. Retrieve the ciphertext stored for the alleged credential id in the assertion.
  3. Call fido_assert_decrypt() to simultaneously verify the assertion of the credential and decrypt the ciphertext.

fido_assert_decrypt(assert, idx, ciphertext, ciphertextlen, payloadp, payloadlenp)
Verify the signature in statement index idx of assert against the ciphertext ciphertext of ciphertextlen bytes. If the signature matches, decrypt the ciphertext and return its payload in a newly allocated buffer in *payloadp with length in bytes stored in *payloadlenp. The buffer is allocated with malloc(3) or equivalent and must be freed with free(3) when done. If the signature does not match or anything else about the assertion statement is invalid, return one of the FIDO_ERR_* constants to indicate failure.

fido_assert_decrypt() implies the same authentication guarantees as fido_assert_verify() normally does without fidocrypt, but with fidocrypt, you must use fido_assert_decrypt() instead of fido_assert_verify().

If the ciphertext was created with an assertion made using the hmac-secret extension during credential registration, then assert must also have the hmac-secret extension, and conversely.

fido_cred_encrypt(cred, assert, idx, payload, payloadlen, ciphertextp, ciphertextlenp)
Encrypt a message payload of payloadlen bytes using the credential in cred, and, if provided, the hmac-secret in the statement index idx of assert. Return a newly allocated buffer in *ciphertextp with a length in bytes in *ciphertextlenp. The buffer is allocated with malloc(3) or equivalent and must be freed with free(3) when done.

You should store the ciphertext, and only the ciphertext, to later verify an assertion of the credential with fido_assert_decrypt(). You must not store the credential's public key — the ciphertext contains enough information to verify an assertion during authentication, as if the public key had been stored.

assert may be a null pointer, in which case no hmac-secret is incorporated, and hmac-secret must be disabled when getting an assertion from this device to retrieve the secret.

The fido_assert_decrypt() and fido_cred_encrypt() functions return FIDO_OK on success or one of the FIDO_ERR_* constants on error (see fido_strerr(3)).

fidocrypt(1), fido_assert_new(3), fido_cred_new(3), fido_init(3)

Joseph Birr-Pixton, Abusing U2F to 'store' keys, https://jbp.io/2015/11/23/abusing-u2f-to-store-keys.html, 2015-11-23.

Rolf Lindemann, Vijay Bharadwaj, Alexei Czeskis, Michael B. Jones, Jeff Hodges, Akshay Kumar, Christiaan Brand, Johan Verrept, and Jakob Ehrensvärd, Client To Authenticator Protocol, https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html, FIDO Alliance, 2017-09-27.

Dirk Balfanz, Alexei Czeskis, Jeff Hodges, J.C. Jones, Michael B. Jones, Akshay Kumar, Angelo Liao, Rolf Lindemann, and Emil Lundberg, Web Authentication: An API for accessing Public Key Credentials Level 1, https://www.w3.org/TR/webauthn-1/, World Wide Web Consortium, 2019-03-04.

fidocrypt works only with U2F devices, and with FIDO2 devices that either (a) support ECDSA over NIST P-256, or (b) support the hmac-secret extension. fidocrypt also only supports ECDSA over NIST P-256 and Ed25519 to date. (Fortunately, essentially all U2F/FIDO devices on the market as of 2020 support ECDSA over NIST P-256 — and it is even hard to find ones that support any other credential types such as RS256.)
December 27, 2020 NetBSD 9.1_STABLE