Security Keys

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.

Life of a request

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::GetAssertion.

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.

The 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 FidoAuthenticator interface.

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 AuthenticatorCommon.


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 chrome://settings/securityKeys. In this case, calls from the Javascript that implements the Settings UI end up in SecurityKeysHandler, which then operates in the same way as AuthenticatorCommon, albeit without creating any native UI.


libFuzzer tests are in * files. They test for bad input from devices, e.g. when parsing responses to register or sign operations.