PSA random generator driver interface

This document describes an interface for random generator drivers in the PSA cryptography API. This interface is part of the proposed PSA cryptoprocessor driver interface, which complements the PSA Cryptography API specification, which describes the interface between a PSA Cryptography implementation and an application.

Introduction

Purpose of the driver interface

The PSA Cryptography API defines an interface that allows applications to perform cryptographic operations in a uniform way regardless of how the operations are performed. Under the hood, different keys may be stored and used in different hardware or in different logical partitions, and different algorithms may involve different hardware or software components.

The driver interface allows implementations of the PSA Cryptography API to be built compositionally. An implementation of the PSA Cryptography API is composed of a core and zero or more drivers. The core handles key management, enforces key usage policies, and dispatches cryptographic operations either to the applicable driver or to built-in code.

This document covers drivers used for random generation. The core calls random generation drivers not only when an application requests random data, but also to implement key generation, random nonce generation, randomized algorithms, randomization-based countermeasures such as blinding, and anything else that requires random data.

System architecture overview

The PSA Cryptography API can be implemented either as a library running in the memory space of the calling application, or as a service running in its own, isolated memory space. When cryptography is implemented as a service, this service can store keys on behalf of multiple client applications.

Drivers run in the same memory space as the core. Drivers can allow cryptographic operations to be performed inside logically or physically separate components such as secure elements and secure enclaves. This specification only describes the functional interface between the core and drivers, and not the security boundary between protected system components.

Types of drivers

This document focuses on drivers related to random generation. There are two types of random generation drivers:

Overview of driver descriptions

A driver consists of some code as well as a driver description. The driver description is a machine-readable specification of the driver’s capabilities. It consists of a set of properties which have values, usually integers or names. The code must define a set of types and functions corresponding to the properties listed in the driver description. The core calls certain functions of the driver which this document refers to as entry points. The interface between the core and drivers is defined in terms of the C programming language.

Conventionally, the name of an entry point has the form prefix_name where prefix identifies the driver and name is the base name of the entry point. For example, if the ACME driver provides the "get_entropy" entry point, the function that implements this entry point is called acme_get_entropy by default.

For more details, please refer to the Overview of drivers in the PSA Cryptoprocessor Driver Interface document.

Return statuses

All driver entry point returns a value of type psa_status_t, either PSA_SUCCESS to indicate success or a PSA_ERROR_xxx value to indicate that an error occurred.

Entropy collection drivers

Entropy collection entry point

A driver can declare an entropy source by providing a "get_entropy" entry point. This entry point has the following prototype for a driver with the prefix "acme":

psa_status_t acme_get_entropy(uint32_t flags,
                              size_t *estimate_bits,
                              uint8_t *output,
                              size_t output_size);

The semantics of the parameters is as follows:

Note that there is no output parameter indicating how many bytes the driver wrote to the buffer. Such an output length indication is not necessary because the entropy may be located anywhere in the buffer, so the driver may write less than output_size bytes but the core does not need to know this. The output parameter estimate_bits contains the amount of entropy, expressed in bits, which may be significantly less than output_size * 8.

The entry point may return the following statuses:

If multiple drivers include a "get_entropy" point, the core will call all of them.

Entropy collection flags

Entropy collection and blocking

The intent of the BLOCK and KEEPALIVE flags is to support drivers for TRNG (True Random Number Generator, i.e. an entropy source peripheral) that have a long ramp-up time, especially on platforms with multiple entropy sources.

Here is a suggested call sequence for entropy collection that leverages these flags:

  1. The core makes a first round of calls to "get_entropy" on every source with the BLOCK flag clear and the KEEPALIVE flag set, so that drivers can prepare the TRNG peripheral.
  2. The core makes a second round of calls with the BLOCK flag set and the KEEPALIVE flag clear to gather needed entropy.
  3. If the second round does not collect enough entropy, the core makes more similar rounds, until the total amount of collected entropy is sufficient.

Random generation drivers

A driver may provide an operation family that can be used as a cryptographic random number generator. The random generation mechanism must obey the following requirements:

If no driver implements the random generation entry point family, the core provides an unspecified random generation mechanism.

This operation family requires the following type, entry points and parameters (TODO: where exactly are the parameters in the JSON structure?):

Random generation is not parametrized by an algorithm. The choice of algorithm is up to the driver.

Random generator initialization

The "init_random" entry point has the following prototype for a driver with the prefix "acme":

psa_status_t acme_init_random(acme_random_context_t *context);

The core calls this entry point once after allocating a random generation context. Initially, the context object is all-bits-zero.

If a driver does not have an "init_random" entry point, the context object passed to the first call to "add_entropy" or "get_random" will be all-bits-zero.

Entropy injection

The "add_entropy" entry point has the following prototype for a driver with the prefix "acme":

psa_status_t acme_add_entropy(acme_random_context_t *context,
                              const uint8_t *entropy,
                              size_t entropy_size);

The semantics of the parameters is as follows:

The core calls this function to supply entropy to the driver. The driver must mix this entropy into its internal state. The driver must mix the whole supplied entropy, even if there is more than what the driver requires, to ensure that all entropy sources are mixed into the random generator state. The driver may mix additional entropy of its own.

The core may call this function at any time. For example, to enforce prediction resistance, the core can call "add_entropy" immediately after each call to "get_random". The core must call this function in two circumstances:

When the driver requires entropy, the core can supply it with one or more successive calls to the "add_entropy" entry point. If the required entropy size is zero, the core does not need to call "add_entropy".

Combining entropy sources with a random generation driver

This section provides guidance on combining one or more entropy sources (each having a "get_entropy" entry point) with a random generation driver (with an "add_entropy" entry point).

Note that "get_entropy" returns data with an estimated amount of entropy that is in general less than the buffer size. The core must apply a mixing algorithm to the output of "get_entropy" to obtain full-entropy data.

For example, the core may use a simple mixing scheme based on a pseudorandom function family (Fk) with an E-bit output where E = 8 ⋅ entropysize and entropysize is the desired amount of entropy in bytes (typically the random driver’s "initial_entropy_size" property for the initial seeding and the "reseed_entropy_size" property for subsequent reseeding). The core calls the "get_entropy" points of the available entropy drivers, outputting a string si and an entropy estimate ei on the ith call. It does so until the total entropy estimate e1 + e2 + … + en is at least E. The core then calculates Fk(0) where k = s1||s2||…||sn. This value is a string of entropysize, and since (Fk) is a pseudorandom function family, Fk(0) is uniformly distributed over strings of entropysize bytes. Therefore Fk(0) is a suitable value to pass to "add_entropy".

Note that the mechanism above is only given as an example. Implementations may choose a different mechanism, for example involving multiple pools or intermediate compression functions.

Random generator drivers without entropy injection

Random generator drivers should have the capability to inject additional entropy through the "add_entropy" entry point. This ensures that the random generator depends on all the entropy sources that are available on the platform. A driver where a call to "add_entropy" does not affect the state of the random generator is not compliant with this specification.

However, a driver may omit the "add_entropy" entry point. This limits the driver’s portability: implementations of the PSA Cryptography specification may reject drivers without an "add_entropy" entry point, or only accept such drivers in certain configurations. In particular, the "add_entropy" entry point is required if:

The "get_random" entry point

The "get_random" entry point has the following prototype for a driver with the prefix "acme":

psa_status_t acme_get_random(acme_random_context_t *context,
                             uint8_t *output,
                             size_t output_size,
                             size_t *output_length);

The semantics of the parameters is as follows:

The driver may return the following status codes:

Open questions

Input to "add_entropy"

Should the input to the "add_entropy" entry point be a full-entropy buffer (with data from all entropy sources already mixed), raw entropy direct from the entropy sources, or give the core a choice?

Flags for "get_entropy"

Are the entropy collection flags well-chosen?

Random generator instantiations

May the core instantiate a random generation context more than once? In other words, can there be multiple objects of type acme_random_context_t?

Functionally, one RNG is as good as any. If the core wants some parts of the system to use a deterministic generator for reproducibility, it can’t use this interface anyway, since the RNG is not necessarily deterministic. However, for performance on multiprocessor systems, a multithreaded core could prefer to use one RNG instance per thread.