Cryptohome Architecture

Authors: kerrnel@chromium.org, apronin@chromium.org

Cryptohome is one of Chrome OS’s largest and oldest code bases. It is a gateway to user data on the system, authenticating user credentials, deriving those credentials into file encryption keys, and mounting the encrypted filesystem. This document describes the high level architectural principles of cryptohome.

Secret Wrapping on Disk

Users’ encryption secrets, such as filesystem encryption keys, are wrapped by a combination of user input (for example, a password) and a hardware security element (TPM, or GSC). These wrapped secrets must be persisted to disk, and there are two formats which do this (both of which are stored as protobufs).

SerializedVaultKeyset

SerializedVaultKeysets are the soon to be deprecated, as of March 2021, format used to hold file encryption keys and other necessary data (IVs, timestamps, etc). Some secrets stored there are repeated in each SerializedVaultKeyset file. The primary reason to deprecate SerializedVaultKeysets and replace them with UserSecretStash (defined below) is seamless management of multiple credentials. Currently there is a separate SerializedVaultKeyset file per credential, which has scaling issues and makes it impossible for a user to update shared data duplicated across all VaultKeyset files without knowing all credentials at once.

UserSecretStash

The UserSecretStash (USS) is a future container of the user’s file system keys. There is one USS per user, and one main key which encrypts the USS. Each credential wraps the main key, as a series of intermediate keys, which allows all credentials to modify the same filesystem keys and data.

D-Bus API Concepts

Cryptohome exposes a D-Bus interface which callers, mainly Chrome, use to interact with cryptohome. UserDataAuth is the name of the entire dbus interface that cryptohome exposes, with AuthSessions and AuthFactors being object oriented concepts implemented by the interface.

UserDataAuth

UserDataAuth (UDA) is the dbus interface exposed by cryptohome. UDA was rolled out in 2020 as a replacement of the old glib-dbus interface. UDA allows cryptohome to use modern dbus bindings and event loops, and uses protobufs for the message and reply data structures to simplify versioning. The dbus messages consume and output byte arrays, which are the serialized protobufs. In the past, changing the data fields passed in messages and responses was time consuming and brittle as it had to be changed in cryptohome daemon and all its clients, which have independent releases. Now the dbus method always stays the same, and the protobuf passed may change.

AuthSessions

AuthSession is an API exposed by Cryptohome via UserDataAuth. The session serves as a place for users to provide their credentials and request operations such as mounting, which require the credentials. The session will be established before any action that requires credential validation such as mount, updating keys, adding keys, etc. The AuthSession is controlled by states, and it starts by default in kAuthStatusFurtherFactorRequired. Each AuthSession is identified with a base::UnguessableToken. Authsession works in two steps to validate a set of credentials. These are:

StartAuthSession

StartAuthSession starts a new AuthSession with a given username. It also starts a timer, after which the AuthSession becomes invalid.The timer ensures that the credentials do not stay in the memory indefinitely.

AuthFactors

AuthFactors abstract credentials to represent the many credentials used for Chrome OS authentication. Historically passwords were used to sign in, but in the future users may use: security keys, smart cards, PINs, trusted phones, remotely stored escrow keys, etc.

AuthFactor is a purely virtual interface in C++. Each supported authentication form will have a concrete implementation of the AuthFactor class.

The default mapping from an authFactor to authBlock is determined by cryptohome and may differ from OS revision to OS revision or from board to board. E.g. a password authFactor can match to cryptohome_key-based authBlock for TPM1.2 devices and old OS revisions, but to Pinweaver authBlock for TPM2.0 devices and old OS revisions.

Once set, though, the authBlock used for a particular authFactor doesn't change, even if the default mapping for the authFactors of that type changes. Once set to a cryptohome_key authBlock, authMethod will continue verifying user credentials that way, even if the default mapping for “password” changes to Pinweaver in future OS versions. A migration to Pinweaver authBlock would require a specific request from upper layers - likely, creating a new authFactor and then deleting the old one.

Internal Architecture

Cryptohome organzies code internally into object oriented interfaces to support the myriad of code paths in a scalable manner. Two key interfaces are AuthMethods and AuthBlocks.

AuthMethods

AuthMethod is a specific combination of authFactors that may authenticate a given user account.

As an example, based on enterprise policies or personal preferences, a user account may be set up to allow authentication through either of the following methods: (a) entering a password (1FA authMethod with password-based authFactor); (b) inserting a smart card and verifying the fingerprint (2FA authMethod with smartcard and FP authFactors); (c) entering a PIN and verifying the fingerprint (2FA authMethod with PIN and FP authFactors, where FP authFactor is the same as for method (b)).

When authMethod is created, specific authFactor(s) are selected for it. I.e. even if multiple “password” authFactors exist for a user, an authMethod will allow only one specific password from this known list. And when an authMethod is included in a set (see below), only that specific password will be allowed for authentication for the purposes covered by the set. To avoid requiring the user to authenticate all factors when credentials are created, the AuthFactor secrets must be stored in the USS.

Sets of authMethods

There are two sets of authMethods associated with a user account: LoginMethodsSet (methods accepted during user login), and UnlockMethodsSet (methods accepted for screen unlock for an already signed-in user). An account may allow the same sets of authMethods for login and unlock, or have different sets for them - e.g. requiring 2FA including password/PIN and FP for login, but allowing any of the factors as a single factor for unlock: LoginMethodsSet = {Password+FP, PIN+FP}, UnlockMethodsSet = {Password, PIN, FP}. In practice, all authMethods are registered in UnlockMethodsSet.

AuthBlocks

AuthBlock is a pure virtual C++ interface found in the file auth_block.h. Each authentication method (password, security key, 2FA with password and security key), has a concrete AuthBlock implementation. The AuthBlock has two methods: Create() and Derive().

AuthBlockState

AuthBlockState contains per auth block metadata which is used to derive the keys produced by the AuthBlock. An example of this metadata is the IVs. The metadata is stored to disk in plaintext, output by the AuthBlock’s Create() method, and loaded by the Derive() method.

Create()

Create() consumes an AuthFactor, which will reveal its underlying secret to the AuthBlock. For example, SecurityKeyAuthFactor will talk to the security key over DBUS, and ultimately present the AuthBlock with a high entropy secret that can only be reconstructed with the physical security key present. In a simpler case, PasswordAuthFactor will reveal the hashed password sent from Chrome.

Create() then derives a high entropy key from the AuthFactor's secret, and uses the derived key to wrap either the USS main key, or the VaultKeyset. Create() outputs the high entropy keys in the KeyBlobs object, which is secret and should never be persisted. Create() also outputs the public metadata to re-create those keys in the AuthBlockState. AuthBlockState is serialized to disk as a protobuf.

Derive()

Derive consumes an AuthBlockState instance, and generates the keys previously created in Create(). For example, derive will run the user’s password through scrypt, and then give it to the TPM to unseal the USS main wrapping key.

Secret Storage in Memory

Cryptohome should store user specific secrets, such as key material derived from the password, in memory only when the user authenticates. As soon as possible key material should be overwritten in memory and deallocated.

brillo::SecureBlob

To support this property, cryptohome uses brillo::SecureBlob instead of std::string or std::vector. SecureBlob uses an allocator that pins the pages to physical memory, and overwrites the memory on deallocation, among other properties.

Note on Serialization Methods

Cryptohome must serialize objects to a byte array before encrypting them and persisting to storage. Many serialization libraries, such as protobuf or JSON parsers, will make their own heap allocations, copying class members to intermediate objects. This will cancel any benefit of SecureBlob, so cryptohome must use a serialization library that allows a custom allocator to be specified. The current plan is to use Flatbuffers for future serialization format, such as UserSecretStash and AuthBlockState. protobuf is currently used for SerializedVaultKeyset and will remain in cryptohome until SerializedVaultKeyset is removed.