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

Key wrapping API proposal #364

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
133 changes: 132 additions & 1 deletion include/psa/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,9 @@ static size_t psa_get_key_bits(const psa_key_attributes_t *attributes);
* you must call psa_reset_key_attributes() to free these resources.
*
* \param[in] handle Handle to the key to query.
* \param[in,out] attributes On success, the attributes of the key.
* \param[in,out] attributes On entry, this structure must be in a
* valid state.
* On success, the attributes of the key.
* On failure, equivalent to a
* freshly-initialized structure.
*
Expand Down Expand Up @@ -852,6 +854,135 @@ psa_status_t psa_export_public_key(psa_key_handle_t handle,
size_t data_size,
size_t *data_length);

/**
* \brief Export a key and its metadata in wrapped form.
*
* A wrapped form of the key object preserves the confidentiality and
* authenticity of the key material and the authenticity of the key
* policy. In practical terms, the key material is encrypted, and
* the key data and metadata are authenticated together.
*
* The format of the wrapped data is implementation-dependent. It may depend
* both on the choice of wrapping key and on the type of key to wrap.
*
* The policy on the key must have the usage flag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to specify which key needs this policy?

* #PSA_KEY_USAGE_EXPORT_WRAPPED set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#PSA_KEY_USAGE_BACKUP for this API

*
* \param wrapping_key Handle to the key to wrap with.
* \param handle Handle to the key to export in wrapped form.
* \param[out] data Buffer where the wrapped key data is to be written.
* \param data_size Size of the \p data buffer in bytes.
* \param[out] data_length On success, the number of bytes
* that make up the wrapped key data.
*
* \retval #PSA_SUCCESS
* \retval #PSA_ERROR_INVALID_HANDLE
* One or both of \p handle and \p wrapping_key is not a valid
* handle to a key.
* \retval #PSA_ERROR_NOT_PERMITTED
* The key \p handle does not have the #PSA_KEY_USAGE_BACKUP flag.
* \retval #PSA_ERROR_INVALID_ARGUMENT
* \p wrapping_key does not support wrapping keys with metadata.
* \retval #PSA_ERROR_NOT_SUPPORTED
* \p wrapping_key does not support wrapping the key designated
* by \p handle.
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of the \p data buffer is too small. You can determine a
* sufficient buffer size by calling
* #PSA_WRAP_KEY_WITH_POLICY_OUTPUT_SIZE(\c type, \c bits)
* where \c type is the key type of \p handle
* and \c bits is the key size of \p handle in bits.
* \retval #PSA_ERROR_COMMUNICATION_FAILURE
* \retval #PSA_ERROR_HARDWARE_FAILURE
* \retval #PSA_ERROR_CORRUPTION_DETECTED
* \retval #PSA_ERROR_STORAGE_FAILURE
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* \retval #PSA_ERROR_BAD_STATE
* The library has not been previously initialized by psa_crypto_init().
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
psa_status_t psa_wrap_key_with_policy(psa_key_handle_t wrapping_key,
psa_key_handle_t handle,
uint8_t *data,
size_t data_size,
size_t *data_length);

/**
* \brief Import a wrapped key with its metadata.
*
* This function supports any output from psa_wrap_key_with_policy().
* For symmetric wrapping, you must unwrap with the same key that was
* used to wrap. For asymmetric wrapping where the wrapping operation uses
* a public key and the unwrapping operation uses the corresponding private
* key, you must unwrap with the corresponding unwrapping key.
*
* \param wrapping_key Handle to the key to unwrap with.
* \param[in] attributes The attributes for the new key.
* They are used as follows:
* - The key type and size may be 0. If either is
* nonzero, it must match the corresponding
* attribute of the wrapped key data.
* - The key location (the lifetime and, for
* persistent keys, the key identifier) is
* used directly.
* If the wrapped key does not have the usage
* flag #PSA_KEY_USAGE_COPY, then the location
* must match the location embedded in \p data.
* If the wrapped key has the usage
* flag #PSA_KEY_USAGE_COPY, then the location
* embedded in \p data is ignored.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that from this description the caller must set attributes -> location.
So the caller needs to call psa_get_wrapped_key_attributes

Is there a reason why they should not be able to leave it unset and use the one in the wrapped key?
And potentiall not offer psa_get_wrapped_key_attributes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to have psa_get_wrapped_key_attributes, but I added it because I couldn't find a better way.

“Leave unset” works for the key type and the key size because for those 0 is not a valid value, so it's interpreted as unset. But for lifetimes, 0 is a valid value, indicating a volatile key: keys are volatile by default, you only need to specify a lifetime explicitly if you want the key to live longer. If a wrapped key is non-copyable and persistent, attempting to unwrap it as volatile should fail.

Likewise, for the policy, 0 means “you can't do anything”. That's not the same thing as “use whatever is in the wrapped data”.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok - good answer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use cases. How about one or moe of these options as alternatives:

  • Permit attributes == NULL - this indicates that the key should be unwrapped exactly as it was wrapped. No override attributes.
  • Define a special lifetime value to mean ANY - i.e. "as wrapped"
  • Permit usage flags of MAX_UINT32 - and rely on the 'constrain to wrapped key policy' behaviour to replicate the wrapped key usage flags in the unwrapped key.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permit attributes == NULL

Doesn't help if you want to use the wrapped policy but specify a different lifetime or id, which I think is a very common use case.

Permit usage flags of MAX_UINT32

That does in fact work for the usage flags, but not for the algorithm. It could be added as a feature for the algorithm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second bullet implies that we define (within the new structured framework) a special lifetime value, e.g.

#define PSA_KEY_LIFETIME_UNSPECIFIED ((psa_key_lifetime_t) 0x0000007f)

This value is only valid in attributes passed to psa_unwrap_key_with_policy() to indicate that the key should be unwrapped into the same lifetime that it was wrapped from.

You could also support all of the above mechanisms:

  1. If you want to restore a backed up key, just pass attributes = NULL. (Volatile keys might have a different key handle/id after unwrapping)
  2. If you want to move the key then initialise the attributes, set the wildcard usage flags (PSA_KEY_USAGE_UNSPECIFIED - all bits 1, not all known bits one: only valid for unwrapping/copying), and set the lifetime/id for the desired location
  3. If you you want to limit the policy, but maintain the lifetime, then initialise the attributes, set the usage flags, and set the lifetime to PSA_KEY_LIFETIME_UNSPECIFIED (only valid for unwrap).

For (1), it would be possible to just use UNSPECIFIED for both the usage flags and the lifetime, but I suspect that the compact attributes = NULL form might be a common usage?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of *_UNSPECIFIED, other terms could be *_AS_SOURCE or *_ORIGINAL.

* - The policy constraints (usage flags and
* algorithm policy) are combined from
* the wrapped key data and \p attributes so that
* both sets of restrictions apply. The
* policy restrictions are calculated in the
* same way as in psa_copy_key().
* \param[in] data Buffer containing the wrapped key material.
* The expected format of this buffer depends
* on the wrapping key.
* \param data_length Size of the \p data buffer in bytes.
* \param[out] handle On success, a handle to the newly created key.
* \c 0 on failure.
*
* \retval #PSA_SUCCESS
* Success.
* If the unwrapped key is persistent, the key material and the
* key's metadata have been saved to persistent storage.
* \retval #PSA_ERROR_ALREADY_EXISTS
* This is an attempt to create a persistent key, and there is
* already a persistent key with the given identifier.
* \retval #PSA_ERROR_INVALID_ARGUMENT
* The key attributes, as a whole, are invalid.
* \retval #PSA_ERROR_INVALID_ARGUMENT
* The key data is not correctly formatted.
* \retval #PSA_ERROR_INVALID_ARGUMENT
* The size in \p attributes is nonzero and does not match the size
* of the key data.
* \retval #PSA_ERROR_INVALID_ARGUMENT
* \p wrapping_key does not support unwrapping keys with metadata.
* \retval #PSA_ERROR_INVALID_SIGNATURE
* \p data is not a valid wrapped key for \p wrapping_key.
* \retval #PSA_ERROR_NOT_SUPPORTED
* Some of the metadata in either \p attributes or \p data is
* not supported.
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* \retval #PSA_ERROR_INSUFFICIENT_STORAGE
* \retval #PSA_ERROR_COMMUNICATION_FAILURE
* \retval #PSA_ERROR_STORAGE_FAILURE
* \retval #PSA_ERROR_HARDWARE_FAILURE
* \retval #PSA_ERROR_CORRUPTION_DETECTED
* \retval #PSA_ERROR_BAD_STATE
* The library has not been previously initialized by psa_crypto_init().
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
psa_status_t psa_unwrap_key_with_policy(psa_key_handle_t wrapping_key,
const psa_key_attributes_t *attributes,
const uint8_t *data,
size_t data_length,
psa_key_handle_t *handle);



/**@}*/
Expand Down
29 changes: 27 additions & 2 deletions include/psa/crypto_sizes.h
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@
*
* \return If the parameters are valid and supported, return
* a buffer size in bytes that guarantees that
* psa_sign_hash() will not fail with
* #PSA_ERROR_BUFFER_TOO_SMALL.
* psa_export_key() (and psa_export_public_key() if applicable)
will not fail with #PSA_ERROR_BUFFER_TOO_SMALL.
* If the parameters are a valid combination that is not supported
* by the implementation, this macro shall return either a
* sensible size or 0.
Expand All @@ -659,4 +659,29 @@
PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(key_type) ? PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(key_bits) : \
0)

/** Sufficient output buffer size for psa_wrap_key_with_policy().
*
* This macro returns a compile-time constant if its arguments are
* compile-time constants.
*
* \warning This function may call its arguments multiple times or
* zero times, so you should not pass arguments that contain
* side effects.
*
* \param key_type A supported key type.
* \param key_bits The size of the key in bits.
*
* \return If the parameters are valid and supported, return
* a buffer size in bytes that guarantees that
* psa_wrap_key_with_policy() will not fail with
* #PSA_ERROR_BUFFER_TOO_SMALL.
* If the parameters are a valid combination that is not supported
* by the implementation, this macro shall return either a
* sensible size or 0.
* If the parameters are not valid, the
* return value is unspecified.
*/
#define PSA_WRAP_KEY_WITH_POLICY_OUTPUT_SIZE(key_type, key_bits) \
0u /*not implemented yet*/

#endif /* PSA_CRYPTO_SIZES_H */
13 changes: 13 additions & 0 deletions include/psa/crypto_values.h
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,19 @@
*/
#define PSA_KEY_USAGE_COPY ((psa_key_usage_t)0x00000002)

/** Whether the key object may be saved outside the device in wrapped form.
* This is also known as key binding.
*
* This flag allows the use of psa_wrap_key_with_policy() on the given key,
* with any suitable wrapping key.
*
* A wrapped form of the key object preserves the confidentiality and
* authenticity of the key material and the authenticity of the key
* policy. In practical terms, the key material is encrypted, and
* the key data and metadata are authenticated together.
*/
#define PSA_KEY_USAGE_BACKUP ((psa_key_usage_t)0x00000020)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we used PSA_KEY_ID_WRAP_BOUND then I would be inclined to make this PSA_KEY_USAGE_EXPORT_BOUND or perhaps PSA_KEY_USAGE_WRAP_BOUND.

BACKUP is quite cute, but then we should use 'backup' in the wrap-with-policy API name and the built-in wrap-with-policy binding key to strengthen the association of these API elements.


/** Whether the key may be used to encrypt a message.
*
* This flag allows the key to be used for a symmetric encryption operation,
Expand Down