Security keys are physical devices that often connect via USB and have a button. They can generate public keys and sign with them to authenticate a user and are most often used as a second factor for security.
Websites interact with them via two APIs: the older U2F API and the modern W3C Webauthn API. In Chromium, the U2F API is not directly supported but it can be used by using
postMessage with an internal extension called cryptotoken. Webauthn is supported by Blink and is part of CredMan.
(Historically cryptotoken contained a complete stack that interacted with USB devices directly. Now, however, it's a wrapper layer over the Webauthn APIs.)
Several different types of security keys are supported. Older security keys implement the U2F protocol while more modern ones implement CTAP2. These devices can work over USB, Bluetooth Low Energy (BLE), or NFC (not supported). Additionally Chromium contains support for using Touch ID on macOS as a security key as well support for forwarding requests to the native libraries on modern versions of Windows.
This section provides a coarse roadmap for understanding the code involved in security key support by highlighting the path that a login request might take.
Firstly, the CredMan
get call ends up in
CredentialsContainer::get. CredMan supports several types of credentials but the code dealing with
publicKey relates to security key support.
The request is packaged into a Mojo call defined in authenticator.mojom. On Android, that Mojo request is handled by Android-specific code and is forwarded to support libraries in Google Play Services. Otherwise the Mojo interface will be bound to
AuthenticatorCommon; specifically it'll call
AuthenticatorCommon is part of Chromium‘s content layer and so calls into the embedder to get a delegate object that allows it to perform actions like showing UI. It also triggers the lower-level code to start the process of finding an authenticator to handle the request. For an assertion request it’ll create a
GetAssertionRequestHandler from this directory.
Handler classes manage a specific user action and their first job is to initiate discovery of possible security keys. The discovery process will find candidate USB, BLE, Touch ID, etc devices, each of which will be fed into
DispatchRequest. Different actions may be taken depending on features of the discovered authenticator. For example, an authenticator which cannot handle the request may be asked to wait for a touch so that the user can still select it, even though it'll cause the request to fail. These per-authenticator operations will be dispatched via the abstract
If a per-authenticator operation is complex and requires several steps it will be handled by a “task”. In this example, a
GetAssertionTask will likely be created by a
FidoDeviceAuthenticator, the implementation of
FidoAuthenticator used by physical devices.
The assertion task knows how to sequence a series of U2F or CTAP2 operations to implement an assertion request. In the case of U2F, there will be another layer of state machines in, e.g.,
U2fSignOperation because U2F has a historical authenticator model.
If interaction with UI is required, for example to prompt for a PIN, the handler will make calls via the
Observer interface, which is implemented by the embedder's UI objects that were created by
It's also possible for security key operations to be triggered by actions in the Settings UI: there are several security key actions that can be taken on
SecurityKeysHandler, which then operates in the same way as
AuthenticatorCommon, albeit without creating any native UI.
libFuzzer tests are in
*_fuzzer.cc files. They test for bad input from devices, e.g. when parsing responses to register or sign operations.