ARM-software / psa-api

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

Add APIs to support wrapped keys #50

Open ndevillard opened 1 year ago

ndevillard commented 1 year ago

Many secure elements and crypto accelerators require the use of wrapped keys and will not accept importing clear-text keys to their key store. The existing psa_import_key function could be augmented to support wrapped keys, but that puts the burden of identifying wrapping and associated parameters on the underlying implementation. A new function should be proposed to support wrapped key imports. As there is no standard for key wrapping data formats and associated algorithms, this should be made generic enough to be adaptable to any such key stores. Same need for exporting wrapped keys.

gilles-peskine-arm commented 1 year ago

Some related internal discussions on the topic:

gilles-peskine-arm commented 1 year ago

An old proposal, which I don't have time to work on right now: https://github.com/ARMmbed/mbed-crypto/pull/364

cneveux commented 1 year ago

Some related internal discussions on the topic:

* https://github.com/ARM-software/psa-crypto-api/issues/32

* https://github.com/ARM-software/psa-crypto-api/issues/39

* https://github.com/ARM-software/psa-crypto-api/issues/77

* https://github.com/ARM-software/psa-crypto-api/issues/90

* https://github.com/ARMmbed/mbedtls-psa/issues/123

@gilles-peskine-arm

This link above are not working. They are probably to old and no more accessible for the public.

gilles-peskine-arm commented 1 year ago

@cneveux Indeed the psa-crypto-api and mbedtls-psa projects are private. If someone from outside Arm wants to work on the topic, let us know and we'll summarise the internal discussions.

athoelke commented 11 months ago

Wrapping with a device-specific secret key

An API proposed in https://github.com/Mbed-TLS/mbedtls/pull/7910, enables the import of keys in other formats. As currently proposed, this only covers formats where the key material is in cleartext.

However, if we add the equivalent psa_export_key_ext(), and permit implementation-defined formats for the API; then this would enable an implementation to provide import and export of wrapped keys, using a device-secret key wrapping key and implementation-specific algorithm.

This is sufficient for use cases that do not involve an application choice of algorithm or key-wrapping key.

athoelke commented 11 months ago

Wrapping using a standard algorithm and application-selected key-wrapping key

Defining an API for this use case is more challenging, as there are a lack of standards defining key-wrapping algorithms and formats for the wrapped material.

However:

API proposal

A simple API design could just implement the wrapping algorithm on data provided by the application. This requires that the application export the key material to the application and then encrypt it, exposing the key material within the application memory. In keeping with the design goals of the Crypto API, I would prefer to define an API that enabled the cleartext key material to be kept within a cryptoprocessor implementation, providing better assurance of the key confidentiality and integrity for application developers. Note that this improvement is a reduction in risk: the application is capable of decrypting the wrapped key as it knows the key and algorithm used to wrap it; but an attacker requires more than application-memory-read capability to disclose the key.

Building on the format specifier additions for psa_import_key_ext() in https://github.com/Mbed-TLS/mbedtls/pull/7910, we could define the following functions:

psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes,
                            psa_key_id_t wrapping_key,
                            psa_algorithm_t alg,
                            psa_key_data_format_t format,
                            const uint8_t *data,
                            size_t data_length,
                            psa_key_id_t *key);

psa_status_t psa_wrap_key(psa_key_id_t key,
                          psa_key_id_t wrapping_key,
                          psa_algorithm_t alg,
                          psa_key_data_format_t format,
                          const uint8_t *data,
                          size_t data_size,
                          size_t *data_length);

Conceptually, psa_unwrap_key(att, format, wrapping_key, alg, data, length, &key) would be equivalent to:

uint8_t cleartext[32];
size_t cleartext_len;
psa_unwrap_keydata(wrapping_key, alg, data, data_length, &cleartext, sizeof(cleartext), &cleartext_len);
psa_import_key_ext(att, format, &cleartext, cleartext_len, &key);

.. except that the cleartext is not visible to the application, and the format might be dictated by the wrapping algorithm.

For psa_unwrap_key(), the wrapping_key must specify the wrapping algorithm as the permitted-algorithm, and have the the [new] usage flag PSA_KEY_USAGE_UNWRAP. The key attributes will be interpreted as for psa_import_key_ext(), and must not conflict with any key attributes and policy that are encoded in the wrapped key data.

For psa_wrap_key(), , the wrapping_key must specify the wrapping algorithm as the permitted-algorithm, and have the the [new] usage flag PSA_KEY_USAGE_WRAP. The key to be wrapped must have the usage flag PSA_KEY_USAGE_EXPORT.

For both functions, the format of the key material must be compatible with the algorithm - for example, AES-KW requires cleartext that is $8n, n>=2$ bytes in size.

An 'unspecified' key format, perhaps PSA_KEY_FORMAT_DEFAULT, requests to use the format associated with the algorithm, if there is one, otherwise the standard PSA Crypto API key format as expected by psa_import_key().

athoelke commented 1 month ago

I now plan to progress this to a draft PR, given interest in finalizing this API for some implementors.

Reviewing the proposal, I have a question about the parameter ordering for psa_wrap_key(). For other cryptographic operations, the key and algorithm that are used for the operation are typically the first parameters (except when the attributes for an output key might precede them). The data inputs and outputs then follow. For this API, the key to be wrapped is a data input, and the wrapping key is the operational input. This would suggest that the API should be:

psa_status_t psa_wrap_key(psa_key_id_t wrapping_key,
                          psa_algorithm_t alg,
                          psa_key_data_format_t format,
                          psa_key_id_t key,
                          const uint8_t *data,
                          size_t data_size,
                          size_t *data_length);

Where the wrapping-key, wrap-algorithm, and intermediate-format-specifier are the control/operational inputs. This is the first true 2-key-input functions (compare with key agreement, which takes the peer key as a data buffer)

However, this makes it look/read differently to APIs such as psa_export_key(), or psa_export_formatted_key() (see #149), where the key to export is the initial parameter.

Is there a preference for one parameter order over the other?

oberon-sk commented 1 month ago

Thanks for promoting this PR!

Regarding the signature: the parameter ordering of the original proposal above is consistent with the ordering in #149 for psa_import_formatted_key() and psa_export_formatted_key() which is consistent with the existing API for psa_import_key() and psa_export_key(). Hence I would vote to keep it that way, i.e.:

psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes,
                            psa_key_id_t wrapping_key,
                            psa_algorithm_t alg,
                            psa_key_data_format_t format,
                            const uint8_t *data,
                            size_t data_length,
                            psa_key_id_t *key);

psa_status_t psa_wrap_key(psa_key_id_t key,
                          psa_key_id_t wrapping_key,
                          psa_algorithm_t alg,
                          psa_key_data_format_t format,
                          const uint8_t *data,
                          size_t data_size,
                          size_t *data_length);

(copied from above)

athoelke commented 2 weeks ago

Thank you for the feedback @oberon-sk. After some discussion with @gilles-peskine-arm, we think that we want to use the alternate ordering because (a) it treats the key-to-be-wrapped as an input parameter, and (b) it does not have two identically typed parameters next to each other which probably has a higher risk of key mis-ordering in application code.

We could also tweak the function names to be a bit more like other cryptographic operations (and less like the key management functions), by using psa_key_wrap() and psa_key_unwrap()? - but I'm not convinced.

Also, following the development of the formatted key import and export functions, we probably (to be discussed) need to add export format options to key wrapping, and import policy options to key unwrapping. Might we also need the interactive policy construction described in this comment?

I don't think we need to be able to wrap the public-key part of a key-pair, so we would not need a wrapping equivalent of psa_export_formatted_public_key().

So the updated proposed API would be something like:

psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes,
                            psa_key_id_t wrapping_key,
                            psa_algorithm_t alg,
                            psa_key_data_format_t format,
                            psa_key_import_options_t options,
                            const uint8_t *data,
                            size_t data_length,
                            psa_key_id_t *key);

psa_status_t psa_wrap_key(psa_key_id_t wrapping_key,
                          psa_algorithm_t alg,
                          psa_key_data_format_t format,
                          psa_key_format_option_t options,
                          psa_key_id_t key,
                          const uint8_t *data,
                          size_t data_size,
                          size_t *data_length);