Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APIs to support wrapped keys #50

Open
ndevillard opened this issue Jan 31, 2023 · 13 comments · May be fixed by #215 or #224
Open

Add APIs to support wrapped keys #50

ndevillard opened this issue Jan 31, 2023 · 13 comments · May be fixed by #215 or #224
Labels
API design Related the design of the API Crypto API Issue or PR related to the Cryptography API enhancement New feature or request proposal An RFC, or proposal for discussion

Comments

@ndevillard
Copy link
Contributor

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.

@ndevillard ndevillard added enhancement New feature or request Crypto API Issue or PR related to the Cryptography API labels Jan 31, 2023
@gilles-peskine-arm
Copy link
Contributor

An old proposal, which I don't have time to work on right now: ARMmbed/mbed-crypto#364

@cneveux
Copy link

cneveux commented Feb 1, 2023

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
Copy link
Contributor

@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
Copy link
Contributor

Wrapping with a device-specific secret key

An API proposed in Mbed-TLS/mbedtls#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 athoelke added API design Related the design of the API proposal An RFC, or proposal for discussion labels Oct 20, 2023
@athoelke
Copy link
Contributor

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:

  • The NIST AES Key Wrap (AES-KW) and AES Key Wrap With Padding (AES-KWP) algorithms are currently provided by the Mbed TLS API (see mbedtls/nist_kw.h). Having a Crypto API interface for these algorithms would be valuable when the MbedTLS legacy interface is withdrawn in future.
  • With the introduction of additional supported import formats (see Work on psa_import_key_ext() Mbed-TLS/mbedtls#7910), we might have a mechanism to specify the format of the plaintext data that is wrapped, where the wrapping algorithm itself does not mandate a specific format.

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 Mbed-TLS/mbedtls#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
Copy link
Contributor

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
Copy link

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
Copy link
Contributor

athoelke commented Sep 6, 2024

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);

@athoelke athoelke linked a pull request Sep 10, 2024 that will close this issue
@athoelke
Copy link
Contributor

athoelke commented Oct 9, 2024

After thinking about the issues discussed in the PR here, I'm thinking about a change of plan for this API. In the process of trying to handle less simple scenarios via small adjustments and generalizations of the proposed API, we end with a hard to use and implement API for the obvious and straight-forward use cases.

Unwrapping

Unwrapping is easier to consider, and perhaps a higher priority API than wrapping, to support key provisioning scenarios. The two clear use cases with well defined (standardized) behavior are:

  1. Unwrap a ciphertext that contains a key wrapped using a standard Authenticated-Encryption algorithm/mode, such as AES-KWP. The key meta-data is supplied by the caller, as the ciphertext only includes the authenticated and encrypted key material.
  2. Unwrap an encrypted key object, such as a EncryptedPrivateKeyInfo (RFC 5958 - pkcs#12), which specifies the encryption algorithm to be used, and provides many of the key attributes.

For the first use case (1), the only key attributes that can typically be determined from the encrypted key data is the key size - assuming that the plain-text key material is formatted as per the default export format. For this kind of unwrapping API, the caller needs to provide

(a) The wrapping key and algorithm used to decrypt/authenticate the key material.
(b) The key attributes for the unwrapped key, containing everything that psa_import_key() requires.
(c) The encrypted key material.

This API could be:

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

For the second use case (2), an algorithm is not required, but the following inputs are needed:
.
(a) The wrapping key
(b) The key attributes for the unwrapped key, and options that determine how the final key policy is derived. I suspect that this should include the same type of flexible policy configuration that is agreed on for psa_import_formatted_key() (see #207). Although for wrapped keys, is it a good idea to enable the wrapped key policy to be over-ridden?
(c) The wrapped-key format of the data (e.g. EncryptedPrivateKeyInfo). Note that although all X.509 structures have self-describing DER and PEM encodings (i.e. the actual object type can be determined from the data), this API could be used to support COSE objects which is independently encoded from ASN.1 DER.
(d) The encrypted key object.

This is similar to the currently proposed API, but without an algorithm specified. I propose to call this psa_unwrap_formatted_key() - to follow the API naming convention suggested for import/export.

More discussion is needed to clarify the API for this use case. For some wrapped-key objects, such as OneAsymmetricKey (PrivateKeyInfo) is often wrapped into AuthEnvelopedData in CMS (pkcs#7), the inputs to the authenticated decryption are mixed with other content in the outer structure. It is not obvious how best to handle this for the API...

Wrapping

For wrapping, the algorithm will always need to be specified, even when using a key-wrapping format such as EncryptedPrivateKeyInfo/pkcs#12, as there are multiple permitted wrapping algorithms. However, it seems that it would be helpful to retain the separation of API for the simple 'wrap the key material in standard AE algorithm' from the 'wrap the key into a standard encrypted key format' use cases.

That would lead to:

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

and psa_wrap_formatted_key() similar to the initial proposal, with export format and export options.

However, although this idea can work for EncryptedPrivateKeyInfo, it is not ideal for another common structure. OneAsymmetricKey (PrivateKeyInfo) is often wrapped into AuthEnvelopedData in CMS (pkcs#7) to provide authenticated encryption of the wrapped key. AuthEnvelopedData has the ciphertext, authenticated attributes, and authentication tags in separate fields. However, the application will already have had to process the AuthEnvelopedData object to extract the corresponding RecipientrInfo field, and thus derived/decrypt the KEK to be used to decrypt the OneAsymmetricKey object.

Algorithms

For the key-wrapping algorithms APIs, I think we need to allocate a new category for 'key-wrapping algorithms' (as done in the current PR), even though these are often forms of AEAD algorithm. This is because, on its own, an AEAD algorithm takes a nonce, and can accept additional data, and outputs the authentication tag as a separate output - for key wrapping in the above API, the algorithm is authenticated-encryption (no AD), any IV is either deterministic, or perhaps random, and all of the output is in a single buffer. Although AEAD algorithms can be used to wrap keys, this requires the additional specification of how to handle these aspects - which suggests that a distinct algorithm identifier for the key-wrapping version of the algorithm.

Next steps

If we split this API between the use cases, then I think we can proceed with the simpler psa_wrap_key() and psa_unwrap_key() for AES-KW and AES-KWP immediately, as there are no dependencies on the remaining issues with formatted keys (in #207); and complete the design for supporting wrapped-key formats along with import/export of formatted keys in a later update/PR.

Is this a better approach?

@MarcusJGStreets
Copy link
Contributor

Are we sure there are no wrapping formats using AD?
If I were designing one now, the key's public identifier would go in the AD.

Unwrap should be a separate policy to decrypt.
Obviously, if the recipient can decrypt the wrapped blob outside the trusted system, they can ignore the policy.

The fact that I am sending you a blob with a policy, wrapped with a key that I trust can only unwrap into the secure state, means I expect you to honour the policy.

The problem case is where the blob contains a partial policy - I think the correct answer is that the caller can always specify extra constraints, but cannot overrule what is in the blob.

Is there actually a call for AES-KWP, I note that of the over 7400 CAVP certificates for AES, only 705 include AES-KWP.

@athoelke
Copy link
Contributor

Are we sure there are no wrapping formats using AD? If I were designing one now, the key's public identifier would go in the AD.

AuthEnvelopedData in CMS can use AEAD - it contains both authenticated-attributes and unauthenticated-attributes fields. But that is a wrapped-key format (use case (2)), not a key-wrapping algorithm.

I think as soon as the wrapping algorithm needs to include formatted content that separates key material (that requires encryption and authentication) from key attributes (that only require authentication), this is not a use case (1) scenario. If we extend the API to accept a key id (for encryption of key material) and a buffer of authenticated data, then there is the issue of how that extra data is formatted, and the API no longer binds the data to the key attributes within the key store. To achieve that [important] binding, the encoding the of the authenticated data has to be done within the keystore - which lead back to the psa_wrap_formatted_key() API and defining the encoding format as a 'key format'.

Unwrap should be a separate policy to decrypt. Obviously, if the recipient can decrypt the wrapped blob outside the trusted system, they can ignore the policy.

The fact that I am sending you a blob with a policy, wrapped with a key that I trust can only unwrap into the secure state, means I expect you to honour the policy.

If the key-wrapping key was not imported, is not exportable, and only permits USAGE_UNWRAP; then the caller cannot reproduce the decryption procedure for the encrypted-key blob - and so cannot extract and then import the key material with a different policy. So we should probably deny policy override - or at least permit an implementation to deny it.

The problem case is where the blob contains a partial policy - I think the correct answer is that the caller can always specify extra constraints, but cannot overrule what is in the blob.

Indeed.

Is there actually a call for AES-KWP, I note that of the over 7400 CAVP certificates for AES, only 705 include AES-KWP.

I think there are implementations that are using or wanting to make use of AES-KW/KWP.

@oberon-sk
Copy link

@athoelke

If we split this API between the use cases, then I think we can proceed with the simpler psa_wrap_key() and psa_unwrap_key() for AES-KW and AES-KWP immediately, as there are no dependencies on the remaining issues with formatted keys (in #207); and complete the design for supporting wrapped-key formats along with import/export of formatted keys in a later update/PR.

Is this a better approach?

We support this approach to splitting off the simple use case and handle further uses cases in a later update/PR.

@athoelke athoelke linked a pull request Oct 24, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API design Related the design of the API Crypto API Issue or PR related to the Cryptography API enhancement New feature or request proposal An RFC, or proposal for discussion
Projects
6 participants