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

Importing and exporting keys that are in non-default formats #149

Open athoelke opened 7 months ago

athoelke commented 7 months ago

This issue is a broader set of use cases than the one defined in #44, which only considers the import of a key from a data format that specifies the key type and/or policy.

There are a number of uses cases where built-in support for additional key data formats are valuable for applications using the Crypto API:

For more general key-wrapping support, where the application can select the algorithm and key-wrapping key, see #50.

athoelke commented 7 months ago

Initial work drafting an API to support the first use case - "import keys in other formats" - has been carried out in the Mbed TLS project, here: https://github.com/Mbed-TLS/mbedtls/pull/7910.

I plan to extend that design to support key export, and implementation-defined formats, in order to support the other use cases.

athoelke commented 7 months ago

Key formats to support

Key format related requirements:

The API design in https://github.com/Mbed-TLS/mbedtls/pull/7910 only addresses key import, and the definition of key formats in the psa_key_data_format_t enumeration is imprecise: each of the five proposed key format values support both PEM and DER encoding. For export, the encoding has to be specified by the application.

Proposal: Split the format specifier into separate 'format structure' and 'data encoding' fields, so that it is possible to use a relaxed specifier on import (expecting the implementation to detect PEM or DER encoding), but use a precise specifier on export. This seems like a reasonable approach - it permits additional encoding mechanisms for the same structural data layout, but also permits implementation-specific formats to reuse one or both of the fields independently.

Question: how much future space do we need for the structure and encoding fields in the format specifier?

API proposal

The key data formats are defined as 16-bit integer values.

The format value zero is special, indicating the Crypto API default format and encoding for all key types.

For other values, the top bit (bit 15) is reserved for use by implementation-specific formats, eleven bits (bits [14:4]) define the structure, and four bits ([3:0]) the encoding.

An encoding value of zero, indicates 'any' encoding, permitted for use in key import. Key export must specify an encoding, if the structure support multiple types of data encoding.

typedef uint16_t psa_key_data_format_t;

#define PSA_KEY_FORMAT_DEFAULT ((psa_key_data_format_t) 0)

#define PSA_KEY_FORMAT_RSA_PUBLIC_KEY(encoding)          \
    ((psa_key_data_format_t) 0x0010 | (encoding))
#define PSA_KEY_FORMAT_SUBJECT_PUBLIC_KEY_INFO(encoding) \
    ((psa_key_data_format_t) 0x0020 | (encoding))
#define PSA_KEY_FORMAT_RSA_PRIVATE_KEY(encoding)         \
    ((psa_key_data_format_t) 0x0030 | (encoding))
#define PSA_KEY_FORMAT_EC_PRIVATE_KEY(encoding)          \
    ((psa_key_data_format_t) 0x0040 | (encoding))
#define PSA_KEY_FORMAT_ONE_ASYMMETRIC_KEY(encoding)      \
    ((psa_key_data_format_t) 0x0050 | (encoding))

#define PSA_KEY_ENCODING_ANY (0x0)
#define PSA_KEY_ENCODING_DER (0x1)
#define PSA_KEY_ENCODING_PEM (0x2)

The extended import and export APIs are just like the current functions, but take a format specifier. Note that if PSA_KEY_FORMAT_DEFAULT is used, then these functions act exactly like psa_import_key() and psa_export_key().

psa_status_t psa_import_key_ext(const psa_key_attributes_t *attributes,
                                psa_key_data_format_t format,
                                const uint8_t *data,
                                size_t data_length,
                                psa_key_id_t *key);

psa_status_t psa_export_key_ext(psa_key_id_t key,
                          psa_key_data_format_t format,
                          const uint8_t *data,
                          size_t data_size,
                          size_t *data_length);

When importing a key where the key data includes some of the key attributes, such as the key type or key policy, any values supplied in the key attributes object much match the values in the imported key data. Specific rules for combining the key policy attributes from the provided attributes and the key data will be defined for each supported key format.

athoelke commented 7 months ago

With only two encodings (plus the 'any' one), the API might be easier to use by just explicitly defining all of the formats (but retaining the field structure)? E.g.:

#define PSA_KEY_FORMAT_RSA_PUBLIC_KEY_ANY          ((psa_key_data_format_t) 0x0010)
#define PSA_KEY_FORMAT_RSA_PUBLIC_KEY_DER          ((psa_key_data_format_t) 0x0011)
#define PSA_KEY_FORMAT_RSA_PUBLIC_KEY_PEM          ((psa_key_data_format_t) 0x0012)
#define PSA_KEY_FORMAT_SUBJECT_PUBLIC_KEY_INFO_ANY ((psa_key_data_format_t) 0x0020)
#define PSA_KEY_FORMAT_SUBJECT_PUBLIC_KEY_INFO_DER ((psa_key_data_format_t) 0x0021)
#define PSA_KEY_FORMAT_SUBJECT_PUBLIC_KEY_INFO_PEM ((psa_key_data_format_t) 0x0022)
#define PSA_KEY_FORMAT_RSA_PRIVATE_KEY_ANY         ((psa_key_data_format_t) 0x0030)
...
michaelthomasj commented 5 months ago

@athoelke , when do you plant/expect to get this merged ?

athoelke commented 5 months ago

when do you plant/expect to get this merged ?

I would be happy to include this in a 1.3 update (along with integration of the PAKE API).

However, I was hoping for some feedback on the proposed draft API (above), in particular regarding some of the perhaps-arbitrary choices relating to the design of the format specifier, and the balance between flexible and rigid interpretation of the format value on import and export.

I'd prefer to have a somewhat-agreed draft to base the specification text on, in order to reduce the risk that significant rewriting is needed if a reason to redesign the API only comes to light after creating the specification text.

athoelke commented 2 months ago

Do we also require a variant of psa_export_public_key() with a format specifier?

athoelke commented 2 months ago

In light of the API naming conclusion with the custom key production parameters, should this API also be named xxx_custom() instead of xxx_ext(), to maintain consistency?

gilles-peskine-arm commented 2 months ago

We'll definitely need a way to export a public key with a custom format. This might be a separate function or distinct format specifiers.

This is not related to custom production parameters. And in terms of naming, I'd like to find something more specific than “ext” or “custom”.

gilles-peskine-arm commented 1 month ago

A request for PKCS8-encrypted PEM keys: https://github.com/Mbed-TLS/mbedtls/issues/1372#issuecomment-2194538816

athoelke commented 1 month ago

I would like to suggest naming these functions psa_import_formatted_key() and psa_export_formatted_key(), instead of using a non-specific _ext suffix on the function names. This more clearly identifies what makes these functions distinct from the existing API.

athoelke commented 3 weeks ago

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

I am not confident about the proposed sizing of the key format encoding. There does not seem to be a strong case for optimizing the size of the key format specifier in the specification, so I am inclined to define the format specifier psa_key_data_format_t as a 32-bit value, which should remove the risk that the design is too constrained. Note that the format specifier is also used in the proposed definition for wrapping and unwrapping keys (#50).

For export, I am also debating an alternative parameter ordering:

psa_status_t psa_export_formatted_key(psa_key_data_format_t format,
                                      psa_key_id_t key,
                                      const uint8_t *data,
                                      size_t data_size,
                                      size_t *data_length);

The format specifier is an operational parameter (controlling what the function does), while the key is a data input.

Or is it better to place the format closer to the output data buffer parameters?

athoelke commented 3 weeks ago

Revisiting the public key export question...

psa_export_public_key() can be used to extract a public from a key-pair or a public-key object.

As psa_export_formatted_key() includes a format specifier, so we could provide other-format public key export by calling psa_export_formatted_key() with a public-key format (e.g. PSA_KEY_FORMAT_SUBJECT_PUBLIC_KEY_INFO) and a key-pair (or public-key) key.

However, the proposal above doesn't enable us to use this API to export an EC public key in the Crypto API default format from a EC key pair:

Proposal

  1. Observe that if the default format is required, the caller can use psa_export_public_key().
  2. Add a specific format specifier for EC points, with uncompressed/compressed encoding variants. This permits the current default format to be specifically requested.
  3. Specify that psa_export_formatted_key() when supplied with a public-key format, and a key-pair object, will export the public-key part of the key-pair in the requested format.

As a general follow-up - ensure that for any key type where we introduce additional key formats, the default key format has an explicit format specifier.

athoelke commented 3 weeks ago

Following discussion with the MbedTLS team, it might be useful to include an additional parameter when exporting. Many key formats have different ways of including elements of the key, or have optional content. For example:

Such options are not necessary to specify when importing a key, as they are unambiguously determined by the key data. Using the format specifier for such options becomes cumbersome for key import: should the implementation ignore them, or reject inputs that fail to match the specifier?

These options are, in general, specific to a particular key format (e.g. the compressed/uncompressed point). However, formats that embed other formats (SubjectPublicKeyInfo can include an EC curve point), may benefit from reusing some options.

So this might result in an export API like this:

psa_status_t psa_export_formatted_key(psa_key_data_format_t format,
                                      psa_key_data_format_options_t options,
                                      psa_key_id_t key,
                                      const uint8_t *data,
                                      size_t data_size,
                                      size_t *data_length);

Now a couple of further questions arise:

  1. Should PEM/DER be a format option, that is only specified on key export? - this relies on it being impossible to mistake one encoding for the other; and on there not being a use case for a caller to insist on parsing the input as a specific encoding.
  2. Could we have 'export the public key' as an option flag, that can be used with key pairs or public keys to cause the equivalent of psa_export_formatted_public_key()?
gilles-peskine-arm commented 3 weeks ago

Should PEM/DER be a format option, that is only specified on key export? - this relies on it being impossible to mistake one encoding for the other; and on there not being a use case for a caller to insist on parsing the input as a specific encoding.

DER encodings always begin with '0' = 0x30 (SEQUENCE tag) while PEM encodings always begin with ----- or, if being permissive on parsing, whitespace. So DER and PEM are unambiguous. In Mbed TLS, there is a single function to parse either (but there are separate functions for public keys and key pairs, which are also unambiguous). That being said, I could understand if some applications wanted to avoid going through an attempt at PEM parsing.

Could we have 'export the public key' as an option flag

I'm weakly in favor: it's one less API function and doesn't really add implementation complexity. The main argument I see against it is that it can make it harder to see where you might be exporting a private key (but then if applications that want to ensure they don't export private keys can do it by not setting the EXPORT usage flag).