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.
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.
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.
This document focuses on drivers related to random generation. There are two types of random generation drivers:
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.
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.
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:
flags
: a bit-mask of entropy collection flags.estimate_bits
: on success, an estimate of the amount of entropy that is present in the output
buffer, in bits. This must be at least 1
on success. The value is ignored on failure. Drivers should return a conservative estimate, even in circumstances where the quality of the entropy source is degraded due to environmental conditions (e.g. undervolting, low temperature, etc.).output
: on success, this buffer contains non-deterministic data with an estimated entropy of at least *estimate_bits
bits. When the entropy is coming from a hardware peripheral, this should preferably be raw or lightly conditioned measurements from a physical process, such that statistical tests run over a sufficiently large amount of output can confirm the entropy estimates. But this specification also permits entropy sources that are fully conditioned, for example when the PSA Cryptography system is running as an application in an operating system and "get_entropy"
returns data from the random generator in the operating system’s kernel.output_size
: the size of the output
buffer in bytes. This size should be large enough to allow a driver to pass unconditioned data with a low density of entropy; for example a peripheral that returns eight bytes of data with an estimated one bit of entropy cannot provide meaningful output in less than 8 bytes.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:
PSA_SUCCESS
: success. The output buffer contains some entropy.PSA_ERROR_INSUFFICIENT_ENTROPY
: no entropy is available without blocking. This is only permitted if the PSA_DRIVER_GET_ENTROPY_BLOCK
flag is clear. The core may call get_entropy
again later, giving time for entropy to be gathered or for adverse environmental conditions to be rectified.If multiple drivers include a "get_entropy"
point, the core will call all of them.
PSA_DRIVER_GET_ENTROPY_BLOCK
: If this flag is set, the driver should block until it has at least one bit of entropy. If this flag is clear, the driver should avoid blocking if no entropy is readily available.PSA_DRIVER_GET_ENTROPY_KEEPALIVE
: This flag is intended to help with energy management for entropy-generating peripherals. If this flag is set, the driver should expect another call to acme_get_entropy
after a short time. If this flag is clear, the core is not expecting to call the "get_entropy"
entry point again within a short amount of time (but it may do so nonetheless).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:
"get_entropy"
on every source with the BLOCK
flag clear and the KEEPALIVE
flag set, so that drivers can prepare the TRNG peripheral.BLOCK
flag set and the KEEPALIVE
flag clear to gather needed entropy.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_context_t"
: the type of a random generation context."init_random"
(entry point, optional): if this function is present, the core calls it once after allocating a "random_context_t"
object."add_entropy"
(entry point, optional): the core calls this function to inject entropy. This entry point is optional if the driver is for a peripheral that includes an entropy source of its own, however random generator drivers without entropy injection have limited portability since they can only be used on platforms with no other entropy source. This entry point is mandatory if "initial_entropy_size"
is nonzero."get_random"
(entry point, mandatory): the core calls this function whenever it needs to obtain random data."initial_entropy_size"
(integer, mandatory): the minimum number of bytes of entropy that the core must supply before the driver can output random data. This can be 0
if the driver is for a peripheral that includes an entropy source of its own."reseed_entropy_size"
(integer, optional): the minimum number of bytes of entropy that the core should supply via "add_entropy"
when the driver runs out of entropy. This value is also a hint for the size to supply if the core makes additional calls to "add_entropy"
, for example to enforce prediction resistance. If omitted, the core should pass an amount of entropy corresponding to the expected security strength of the device (for example, pass 32 bytes of entropy when reseeding to achieve a security strength of 256 bits). If specified, the core should pass the larger of "reseed_entropy_size"
and the amount corresponding to the security strength.Random generation is not parametrized by an algorithm. The choice of algorithm is up to the driver.
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.
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:
context
: a random generation context. On the first call to "add_entropy"
, this object has been initialized by a call to the driver’s "init_random"
entry point if one is present, and to all-bits-zero otherwise.entropy
: a buffer containing full-entropy data to seed the random generator. “Full-entropy” means that the data is uniformly distributed and independent of any other observable quantity.entropy_size
: the size of the entropy
buffer in bytes. It is guaranteed to be at least 1
, but it may be smaller than the amount of entropy that the driver needs to deliver random data, in which case the core will call the "add_entropy"
entry point again to supply more entropy.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:
"get_random"
entry point, to supply "initial_entropy_size"
bytes of entropy."get_random"
entry point returns less than the required amount of random data, to supply at least "reseed_entropy_size"
bytes of entropy.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"
.
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 ⋅ e
n
t
r
o
p
y
s
i
z
e
and e
n
t
r
o
p
y
s
i
z
e
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 e
n
t
r
o
p
y
s
i
z
e
, and since (Fk) is a pseudorandom function family, Fk(0) is uniformly distributed over strings of e
n
t
r
o
p
y
s
i
z
e
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 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:
"get_random"
entry pointThe "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:
context
: a random generation context. If the driver’s "initial_entropy_size"
property is nonzero, the core must have called "add_entropy"
at least once with a total of at least "initial_entropy_size"
bytes of entropy before it calls "get_random"
. Alternatively, if the driver’s "initial_entropy_size"
property is zero and the core did not call "add_entropy"
, or if the driver has no "add_entropy"
entry point, the core must have called "init_random"
if present, and otherwise the context is all-bits zero.output
: on success (including partial success), the first *output_length
bytes of this buffer contain cryptographic-quality random data. The output is not used on error.output_size
: the size of the output
buffer in bytes.*output_length
: on success (including partial success), the number of bytes of random data that the driver has written to the output
buffer. This is preferably output_size
, but the driver is allowed to return less data if it runs out of entropy as described below. The core sets this value to 0 on entry. The value is not used on error.The driver may return the following status codes:
PSA_SUCCESS
: the output
buffer contains *output_length
bytes of cryptographic-quality random data. Note that this may be less than output_size
; in this case the core should call the driver’s "add_entropy"
method to supply at least "reseed_entropy_size"
bytes of entropy before calling "get_random"
again.PSA_ERROR_INSUFFICIENT_ENTROPY
: the core must supply additional entropy by calling the "add_entropy"
entry point with at least "reseed_entropy_size"
bytes.PSA_ERROR_NOT_SUPPORTED
: the random generator is not available. This is only permitted if the driver specification for random generation has the fallback property enabled. In this case, the core tries the same entry point in another driver if there is one. If drivers providing this entry point return PSA_ERROR_NOT_SUPPORTED
, the core invokes its built-in random generator.PSA_ERROR_COMMUNICATION_FAILURE
or PSA_ERROR_HARDWARE_FAILURE
indicate a transient or permanent error."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?
"add_entropy"
needs an extra parameter to indicate the amount of entropy in the data. The core must not do any conditioning."add_entropy"
needs an extra parameter to indicate the amount of entropy in the data. The core may do conditioning if it wants, but doesn’t have to."get_entropy"
Are the entropy collection flags well-chosen?
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.