ARM-software / psa-api

Documentation source and development of the PSA Certified API
https://arm-software.github.io/psa-api/
Other
55 stars 25 forks source link

Incremental/partial initialization of a Crypto implementation #16

Open athoelke opened 1 year ago

athoelke commented 1 year ago

psa_crypto_init() is allowed to, and even encouraged to, initialize the RNG. This allows many implementations to provide a RNG that will never fail. (Implementations that comply to some security standards will still need to fail RNG calls if the entropy source fails, but a CSPRNG that has been seeded once can be good forever under reasonable security requirements.)

This is a problem on systems that don't have an entropy source available, or where the entropy source is not yet available at the time the application wants to call psa_crypto_init(). The typical scenario would be a device or an application that only wants to calculate hashes and verify signatures, for example a bootloader.

An application that only wants to calculate hashes might get away with insisting that the implementation accepts psa_hash_xxx() calls before psa_crypto_init(), although this is strict non-portable. An application that wants to verify signatures needs the keystore to be available, and it's less reasonable to require this to happen without psa_crypto_init(). And even hash drivers may need some initialization. And client-server implementations may need psa_crypto_init() to establish the communication between the client and the server.

There are use cases for doing a partial initialization of the PSA crypto subsystem: initialize basic functionality, initialize drivers, maybe initialize the key store, but do not initialize the RNG. This behavior is currently implementation-specific, but it makes sense to standardize it.

The following API definition is based on the current PR proposal for Mbed-TLS: https://github.com/Mbed-TLS/mbedtls/pull/6636.

Proposed API

psa_crypto_init (function)

Amend the description to also refer to the fine-grained initialization control provided by psa_crypto_init_subsystem().

psa_crypto_subsystem_t (type)

The designation of a subsystem of the PSA Crypto implementation.

typedef uint32_t psa_crypto_subsystem_t;

Value of this type are masks of PSA_CRYPTO_SUBSYSTEM_xxx constants.

PSA_CRYPTO_SUBSYSTEM_COMMUNICATION (macro)

Crypto subsystem identifier for the communication with the server, if this is a client that communicates with a server where the key store is located.

#define PSA_CRYPTO_SUBSYSTEM_COMMUNICATION /* implementation-defined value */

In a client-server implementation, this subsystem is necessary before any API function other than library initialization, deinitialization and functions accessing local data structures such as key attributes.

In a library implementation, initializing this subsystem does nothing and succeeds.

PSA_CRYPTO_SUBSYSTEM_KEYS (macro)

Crypto subsystem identifier for the key store in memory.

#define PSA_CRYPTO_SUBSYSTEM_KEYS /* implementation-defined value */

Initializing this subsystem allows creating, accessing and destroying volatile keys in the default location, i.e. keys with the lifetime PSA_KEY_LIFETIME_VOLATILE.

Persistent keys also require PSA_CRYPTO_SUBSYSTEM_STORAGE. Keys in other locations also require PSA_CRYPTO_SUBSYSTEM_SECURE_ELEMENTS.

PSA_CRYPTO_SUBSYSTEM_STORAGE (macro)

Crypto subsystem identifier for access to keys in storage.

#define PSA_CRYPTO_SUBSYSTEM_STORAGE /* implementation-defined value */

Initializing this subsystem as well as PSA_CRYPTO_SUBSYSTEM_KEYS allows creating, accessing and destroying persistent keys.

Persistent keys in secure elements also require PSA_CRYPTO_SUBSYSTEM_SECURE_ELEMENTS.

PSA_CRYPTO_SUBSYSTEM_ACCELERATORS (macro)

Crypto subsystem identifier for accelerator drivers.

#define PSA_CRYPTO_SUBSYSTEM_ACCELERATORS /* implementation-defined value */

Initializing this subsystem calls the initialization entry points of all registered accelerator drivers.

Initializing this subsystem allows cryptographic operations that are implemented via an accelerator driver.

PSA_CRYPTO_SUBSYSTEM_SECURE_ELEMENTS (macro)

Crypto subsystem identifier for secure element drivers.

#define PSA_CRYPTO_SUBSYSTEM_SECURE_ELEMENTS /* implementation-defined value */

Initializing this subsystem calls the initialization entry points of all registered secure element drivers.

Initializing this subsystem as well as PSA_CRYPTO_SUBSYSTEM_KEYS allows creating, accessing and destroying keys in a secure element (i.e. keys whose location is not PSA_KEY_LOCATION_LOCAL_STORAGE).

PSA_CRYPTO_SUBSYSTEM_RANDOM (macro)

Crypto subsystem identifier for the random generator.

#define PSA_CRYPTO_SUBSYSTEM_RANDOM /* implementation-defined value */

Initializing this subsystem initializes all registered entropy drivers and accesses the registered entropy sources.

Initializing this subsystem is necessary for psa_generate_random(), psa_generate_key(), as well as some operations using private or secret keys. Only the following operations are guaranteed not to require this subsystem:

Note

Currently, symmetric decryption (authenticated or not) and MAC operations do not require the random generator. This may change in future versions of the library or when the operations are performed by a driver.

PSA_CRYPTO_SUBSYSTEM_BUILTIN_KEYS (macro)

Crypto subsystem identifier for access to built-in keys.

#define PSA_CRYPTO_SUBSYSTEM_BUILTIN_KEYS /* implementation-defined value */

Initializing this subsystem as well as PSA_CRYPTO_SUBSYSTEM_KEYS allows access to built-in keys.

psa_crypto_init_subsystem (function)

Partial library initialization.

psa_status_t psa_crypto_init_subsystem(psa_crypto_subsystem_t subsystem);
Parameters
subsystem The subsystem, or set of subsystems, to initialize. This must be one of the PSA_CRYPTO_SUBSYSTEM_xxx values, or a bitwise-or of them.
Returns: psa_status_t

PSA_SUCCESS PSA_ERROR_INSUFFICIENT_MEMORY PSA_ERROR_INSUFFICIENT_STORAGE PSA_ERROR_COMMUNICATION_FAILURE PSA_ERROR_HARDWARE_FAILURE PSA_ERROR_CORRUPTION_DETECTED PSA_ERROR_INSUFFICIENT_ENTROPY PSA_ERROR_STORAGE_FAILURE PSA_ERROR_DATA_INVALID PSA_ERROR_DATA_CORRUPT

Description

Applications may call this function on the same subsystem more than once. Once a call succeeds, subsequent calls with the same subsystem are guaranteed to succeed.

Initializing a subsystem may initialize other subsystems if the implementations needs them internally. For example, in a typical client-server implementation, PSA_CRYPTO_SUBSYSTEM_COMMUNICATION is required for all other subsystems, and therefore initializing any other subsystem also initializes PSA_CRYPTO_SUBSYSTEM_COMMUNICATION.

Calling psa_crypto_init() is equivalent to calling psa_crypto_init_subsystem() on all the available subsystems.

Note
You can initialize multiple subsystems in the same call by passing a bitwise-or of PSA_CRYPTO_SUBSYSTEM_xxx values. If the initialization of one subsystem fails, it is unspecified whether other requested subsystems are initialized or not.
athoelke commented 1 month ago

Working through the proposal to construct a draft PR, I notice a handful of issues that are worth resolving:

  1. The statements about what functionality does, and does not, required PSA_CRYPTO_SUBSYSTEM_RANDOM seem particularly MbedTLS-based (not surprising, this is where the above text was taken from):

    Only the following operations are guaranteed not to require this subsystem:

    • Hash operations;
    • Signature verification operations.

    Currently, symmetric decryption for ciphers and AEAD, and MAC operations do not require the random generator. This may change in future versions of the library or when the operations are performed by a driver.

    Is there anything we could or should state categorically about this subsystem for the API specification?

  2. The definition of psa_crypto_init_subsystem() includes error codes that are not currently listed against psa_crypto_init() in the specification (although I see these are listed there in the mbedtls doxygen comments):

    • PSA_ERROR_INSUFFICIENT_STORAGE
    • PSA_ERROR_HARDWARE_FAILURE
    • PSA_ERROR_STORAGE_FAILURE
    • PSA_ERROR_DATA_INVALID
    • PSA_ERROR_DATA_CORRUPT

    Are these all useful? - if so, I presume they should be added to psa_crypto_init() in the spec?

  3. What behavior should be specified for calls to psa_crypto_init_subsystem() when the subsystem parameter includes bits that are not recognized/supported by the implementation? We could do either:

    a) Ignore unrecognized bits - in effect these unknown subsystems are 'already initialized', as they do not need further initialization. b) Report an INVALID_ARGUMENT or NOT_SUPPORTED error if unrecognized/unsupported bits are set.

    I think I prefer (a), as this fits a model of usage where a caller does not have to know implementation details (which subsystems are implemented) to use the API. But there might be a good argument for (b) that I haven't considered....

athoelke commented 1 month ago

Almost all function calls can return PSA_ERROR_BAD_STATE with the description:

The library requires initializing by a call to psa_crypto_init().

With the addition of psa_crypto_init_subsystem(), should we:

  1. Leave this text as is, even if a call to psa_crypto_init_subsystem() with a suitable parameter value would also have the required effect.

  2. Generalize the statement to something like the following with a section reference:

    The library requires initializing. See Library initialization.

  3. Enhance each instance to describe both psa_crypto_init() and psa_crypto_init_subsystem() (without providing subsystem details - as those may be implementation defined).

My current thinking is to do (2).

gilles-peskine-arm commented 1 month ago

The statements about what functionality does, and does not, required PSA_CRYPTO_SUBSYSTEM_RANDOM seem particularly MbedTLS-based

I think there are two plausible choices here. One is to make it entirely implementation-defined. The other is what I made a guarantee in Mbed TLS, which is that you can verify a signature before randomness is available. The second choice removes doubt that in a bootloader, you can verify the signature of the next bit (which may include drivers for other mechanisms).

The definition of psa_crypto_init_subsystem() includes error codes that are not currently listed against psa_crypto_init() in the specification

These are storage-related failures. They can happen if a driver stores persistent data which it reads during initialization, or if there is a secure element with key storage that requires transaction recovery during startup. I think they should be added to psa_crypto_init (but even if they aren't, implementations are allowed to use these error codes since these are implementation-specific concerns, as long as drivers aren't an official PSA feature).

What behavior should be specified for calls to psa_crypto_init_subsystem() when the subsystem parameter includes bits that are not recognized/supported by the implementation?

My thinking was: if you're requesting a specific feature, you really need the implementation to support it. But that's in terms of what's available to the application, not in terms of what the implementation does internally. For example, MBEDTLS_PSA_CRYPTO_SUBSYSTEM_ACCELERATORS_INDEX means “I can do operations with transparent keys”, and an implementation that doesn't support accelerator drivers would just make it a successful no-op.

To avoid requesting features that are not supported by the implementation, you can request one feature at a time and ignore failures (a failure of partial initialization leaves the previously-initialized subsystems available). Also, we could standardize MBEDTLS_PSA_CRYPTO_ALL_SUBSYSTEMS as a standard name having an implementation-defined value.

(TL,DR: leaning towards b.)

Almost all function calls can return PSA_ERROR_BAD_STATE with the description:

The library requires initializing by a call to psa_crypto_init().

We need to describe somewhere what you can do with various subsystems initialized. I don't have a strong opinion as to where. To some extent, this will be implementation-defined: for example only algorithms that require randomness are guaranteed to fail if RANDOM isn't initialized (and even then, an implementation may initialize RANDOM implicitly, for example a client in a client-server implementation will typically have everything available as soon as COMMUNICATION is established).

athoelke commented 1 month ago

The statements about what functionality does, and does not, required PSA_CRYPTO_SUBSYSTEM_RANDOM seem particularly MbedTLS-based

I think there are two plausible choices here. One is to make it entirely implementation-defined. The other is what I made a guarantee in Mbed TLS, which is that you can verify a signature before randomness is available. The second choice removes doubt that in a bootloader, you can verify the signature of the next bit (which may include drivers for other mechanisms).

I see that, and this is already reflected in the existing specification text in the 'Note' here regarding initialization of an RNG. I think this use case is important enough to keep a specification-level requirement for hashes and signature verification not requiring the RANDOM subsystem.

What behavior should be specified for calls to psa_crypto_init_subsystem() when the subsystem parameter includes bits that are not recognized/supported by the implementation?

... (TL,DR: leaning towards b.)

Do we want applications that want to use this facility to need to write code like this:

psa_status_t r = psa_crypto_init_subsystem(PSA_CRYPTO_SUBSYSTEM_COMMUNICATION);
if (r != PSA_SUCCESS && r !=PSA_ERROR_INVALID_ARGUMENT && r !=PSA_ERROR_NOT_SUPPORTED) {
    return ERR_INIT_FAILED;
}

... just in case they are compiled against a client-server implementation, and a non-client-server implementation is permitted to report any of SUCCESS, INVALID_ARGUMENT, or NOT_SUPPORTED in this situation?

I think I am leaning toward an in-between approach:

We need to describe somewhere what you can do with various subsystems initialized. I don't have a strong opinion as to where. To some extent, this will be implementation-defined: for example only algorithms that require randomness are guaranteed to fail if RANDOM isn't initialized (and even then, an implementation may initialize RANDOM implicitly, for example a client in a client-server implementation will typically have everything available as soon as COMMUNICATION is established).

I have been reworking the text that is currently in the description of psa_crypto_init() as an introduction to the Library initialization section, but expanded to describe both one-shot and partial initialization. This previously warned that what can be done prior to calling psa_crypto_init() is implementation-defined. I am adjusting that to describe the implementation-defined nature of the relationship between subsystem initialization and functionality.

gilles-peskine-arm commented 1 month ago
  • For defined (in the spec) subsystem values, the implementation returns SUCCESS if either (a) it has no such subsystem to initialize, or (b) it has successfully initialized that subsystem.
  • For undefined subsystem values, the implementation should return INVALID_ARGUMENT.

Yes, that was my intent. For example COMMUNICATION is effectively always enabled in a non-client-server implementation, and init(COMMUNICATION) should always succeed.