Tweag and Mercury are happy to announce a server-side library for the the WebAuthn specification (part of the FIDO2 project), available as webauthn on Hackage!
This library has been developed by a team at Tweag, contracted by Mercury. Mercury felt that Webauthn support was a missing element in the Haskell ecosystem, and wanted to contribute an open-source library to fill this gap. The library builds upon two previous prototypes, in concertation with their authors: a hackathon project by Arian van Putten and an alternative implementation by Fumiaki Kinoshita (also known as webauthn-0 on Hackage).
In the rest of the blog post we will mostly focus on introducing WebAuthn itself.
The problems with passwords, TOTP and SMS 2FA
Passwords are the dominant way to log into web services, but they have problems, both for users and developers. Users have to choose a password that is not easy to guess and then remember it. A password manager solves this problem, but introduces another attack vector. Furthermore, passwords can be compromised with phishing attempts.
Developers have to handle passwords with care, using a good key derivation function involving hashing and salts, only transferring the resulting key for storage in a secure database. This can be very error prone, see the OWASP cheat sheet for password storage for the current best practices.
TOTP and SMS-based two-factor authentication methods provide additional security over standalone passwords. However, these also come with their respective downsides. TOTP is a symmetric algorithm, and therefore provides no additional security against database leaks; SMS is plaintext, unreliable, and subject to local law surrounding automated messages.
Enter WebAuthn
The FIDO2 project, of which WebAuthn is a part of, attempts to solve these issues by using public key cryptography instead of passwords for authentication. Public-private key pairs specific to each web service are generated and stored by authenticators like a YubiKey or SoloKey, or platform specific hardware like TPM or Apple’s TouchID1.
While the main motivation for WebAuthn is authentication without passwords, it can also be used to add second-factor authentication to password-based authentication.
Ceremonies
WebAuthn can be split into two ceremonies (a ceremony is like a network protocol except that it involves human actions). The first ceremony is one-time registration of a credential, in which a new key pair is generated on an authenticator and its public component transferred to and stored by the web service. The second ceremony is authentication using a previously registered credential, in which it is proven that the user is in possession of the authenticator with the private key corresponding to a given public key.
Since web services can only run sandboxed client-side code, connected authenticators cannot be used directly. Instead, the browser-provided WebAuthn API needs to be called via JavaScript to indirectly interact with authenticators. Both ceremonies also use the shared concept of a public key credential, which is something the user can present to the web service in order to be authenticated.
We will now look at the steps of these ceremonies in some detail.
Registration The image below depicts the steps of the registration ceremony, showing how the web server, the browser and the authenticator interact with each other through the WebAuthn API.
This figure depicting the registration component of WebAuthn by Mozilla Contributors is licensed under CC-BY-SA 2.5.
Step 0: The client informs the web server that a user wishes to register a credential. This initial message is implementation-specific, but typically it contains the desired username. For username-less login this message can be empty.
Step 1: The web server responds with detailed requirements of the to-be-created key pair and other information, of which the following are of particular interest:
-
challenge
: A server-generated cryptographic challenge, preventing replay attacks and proving to the web server that the key was attested for this specific session. -
pubKeyCredParams
: The signing algorithms that are allowed and their order of preference. For example, EdDSA, ECDSA with SHA-256 and RSA with SHA-256. -
authenticatorSelection
: Allows selecting authenticators with specific properties, see Authenticator Taxonomy for a more detailed discussion.-
authenticatorAttachment
: Whether the selected authenticator should be platform specific (like a TPM) or cross-platform (like a YubiKey). -
residentKey
: Whether a client-side discoverable credential should be created, having the effect that the user doesn’t need to enter a username for authentication. This is needed for username-less login. -
userVerification
: Whether user verification (in addition to user presence, which is always done) should be performed, making the result signify multi-factor authentication.
-
-
attestation
: Whether an attestation is desired. Enabling this makes it possible to know that the result comes from a trusted authenticator model.
Step 2: The client selects an authenticator based on the above requirements and relays the relevant information to it.
Step 3: From here, the authenticator verifies that a user is present (via a button for example) and generates a new public-private key pair scoped to the web service, optionally with a proof that it originates from a trusted and secure authenticator, which is relayed back to the client.
Step 4: The client combines this information with its own information and relays it back to the web server.
Step 5: The client takes the data created by the authenticator and constructs the PublicKeyCredential
, the interesting parts of which are:
-
identifier
: The unique identifier of the provided credential. -
response
: The response of the authenticator to the client’s request for a new credential.clientData
: Provides the context for which the credential was created. e.g. the challenge and perceived origin of the options.attestationObject
: In case attestation was requested this will contain the attestation information.
Step 6: The web server performs validation. This validation includes, but is not limited to:
- Checking if the challenge matches the challenge originally sent to the client.
- Checking that the origin is the expected origin. If this isn’t the case, the client might have been connected to another server.
In case the web server requested, and was provided with, an attestation object, it may also verify that the attestation is sufficient. Different authenticators provide different methods of attestation. Hence, the web server must be able to handle different formats. WebAuthn Level 2 (the specification used for the implementation of the library) defines 6 verifiable attestation statement formats.
The web server can also choose to lookup further information on attested authenticators in the FIDO Alliance Metadata Service (specification). This service provides up-to-date information on registered authenticators. These fields are of particular interest:
-
attestationRootCertificates
: The root certificates for many authenticators. For these authenticators, verifying the attestation using the metadata is essential to trust the attestation. For other authenticators (e.g. Apple), the root certificate is hardcoded in the library. Our library automatically handles looking up the authenticator and verifying the attestation if needed, if provided with the required data. -
AuthenticatorStatus
: The status of the authenticator. For instance, an authenticator might be compromised for physical attacks.
Authentication When a user wishes to authenticate themselves, this happens through the authentication ceremony. The goal of this ceremony is for the user to securely prove that they are in possession of the authenticator that holds the private key corresponding to a public key previously registered to the users account.
This figure depicting the authentication component of WebAuthn by Mozilla Contributors is licensed under CC-BY-SA 2.5.
Step 0: The client informs the web server that a user authenticate themselves. This initial message is implementation specific. For typical registration this message should contain the desired username. For username-less login, this message may be void of any information.
Step 1: The web server generates a new challenge and constructs the PublicKeyCredentialRequestOptions. For username-less login, the challenge is in fact the only field that has to be set. The two fields that are interesting are:
-
challenge
: The authenticator will sign this challenge in the following steps to prove the possession of the private key. -
allowCredentials
: For the given username (if any), the server selects the credentials it has on record and relays them as this field (in order of preference). This will allow the client to select the credential for which it knows an authenticator with the correct private key. For passwordless login, this field is empty, it is then up to the authenticator to select the correct credential based on their scope. -
userVerification
: Whether user verification (in addition to user presence, which is always done) should be performed, making the result signify multi-factor authentication.
Step 2-4: The client selects an authenticator and relays the relevant information to it.
From here, the authenticator verifies if the user is present (using a button for example), signs the challenge (and client data) with its private key, and returns the signature.
The authenticator also returns the authenticatorData
which contains information about the scope of the credential, the user interaction, and the authenticator itself.
Step 5: The client takes the data created by the authenticator and constructs the PublicKeyCredential
, the interesting parts of which are:
-
identifier
: The unique identifier of the provided credential. -
response
: The response of the authenticator to the client’s request.-
clientData
: Provides the context for which the signing took place. e.g. the challenge and perceived origin of the options. -
authenticatorData
: Contains information about the credential scope, user interaction with the authenticator for signing, and the signature counter. -
signature
: Contains the signature over theclientDataJSON
andauthenticatorData
.
-
Step 6: The server verifies that the client data and authenticatorData
are as expected, and that the signature is valid.
The Haskell library
The library implements almost the entire WebAuthn Level 2 specification, with extensions being the only major missing part. While the general design of the library isn’t expected to change very much, it should still be considered an alpha version for now. If you have a website with user accounts running on Haskell, we’d love for you to try it out and tell us what could be improved, contributions are welcome too!
To get started, here are our recommendations:
- Read the introduction to the WebAuthn specification
- Read the documentation of the main Crypto.WebAuthn module, which gives an overview of the library
- Check out and run the code of our demo server implementation, which shows an example of how the library might be used
- More specifically, the Secure Enclave, which TouchID allows access to.↩
About the authors
A software engineer with experience in creating web-based functional programs, maintaining a functional programming language and designing and implementing tools enhancing developer productivity. Their personal goal is to get as many people as possible working with functional programming languages.
Silvan is an active member of the Nix community, specializing in NixOS modules and deployment, Nix internals and state-of-the-art improvements. For writing actual programs he preferably uses Haskell, but can also use Bash if need be.
If you enjoyed this article, you might be interested in joining the Tweag team.