ARM-software / psa-api

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

Support for interruptible key agreement/exchange #198

Open athoelke opened 1 month ago

athoelke commented 1 month ago

Issue #23 considers interruptible API design, and specifically looks at an API for asymmetric signature. There is a PR for an interruptible API for asymmetric signature creation and verification in #107.

Another use case that requires an interruptible operation is key agreement, in particular ECDH. (See https://github.com/Mbed-TLS/mbedtls/issues/9044)

For ECDH, the complex computation occurs at the following points:

  1. Computing the public key from an ECC private key.
  2. Validation of the public key provided by the other participant.
  3. Computing the shared secret from the two input keys.

In the current PSA Crypto API, steps (2) and (3) occur as part of the key agreement function. Step (1) might occur during key generation, or during public key export, depending on the implementation's approach to storing ECC keys.

For full coverage of the key agreement use case we would need interruptible operations for all three APIs (key generation, key export, and key agreement).

As each of these functions have simple (bounded) inputs, the interruptible operations can be much simpler that the one designed for asymmetric signature. In particular, each operation only requires a single setup function taking all the inputs (called once) and a single completion function providing the outputs (called until finished).

athoelke commented 1 month ago

Following the patterns in #107, the obvious pattern for these interruptible operations would be something like the following:

typedef /* implementation-defined */ psa_generate_key_interruptible_operation_t;
#define PSA_GENERATE_KEY_INTERRUPTIBLE_OPERATION_INIT /* implementation-defined */
psa_generate_key_interruptible_operation_t psa_generate_key_interruptible_operation_init();

psa_status_t psa_generate_key_interruptible_setup(psa_generate_key_interruptible_operation_t *op,
                                                  const psa_key_attributes_t * attr);
psa_status_t psa_generate_key_interruptible_complete(psa_generate_key_interruptible_operation_t *op,
                                                     psa_key_id_t *key);
psa_status_t psa_generate_key_interruptible_abort(psa_generate_key_interruptible_operation_t *op);

One concern I have is that these names are getting rather long (and psa_export_public_key_interruptible_operation_t gets longer). As psa_generate_key_interruptible_ is already 31 characters, all of the identifiers are indistinguishable in the first 31 characters: and thus a C99 implementation is not required to distinguish these identifiers.

Key Question

Do we need to devise a shorter format for naming these type of operations and their functions, so that code remains both readable and unambiguous?

And if we do, we should retro-fit the same pattern to #23 & #107.

I'll propose a strawman idea, to provoke a reaction... how about using intop in the structure and function names to indicate that this is part of an interruptible operation? - it isn't a valid abbreviation for anything, so would not be misunderstood even if unknown to a reader... i.e. psa_generate_key_intop_t and psa_generate_key_intop_setup(), etc.

Note that to achieve disambiguation in 31 characters for public key export, we need a suffix that is 7 characters or less as psa_export_public_key_1234567_ is 30 characters long.

athoelke commented 1 month ago

Perhaps, given that these functions will not require multi-part operations, we could elide the 'interruptible' entirely in the identifier names? - the use of the interruptible form of the API would still be evident in the code from the use of xxx_setup(), xxx_complete() and PSA_OPERATION_INCOMPLETE. i.e.

typedef /* implementation-defined */ psa_generate_key_operation_t;
#define PSA_GENERATE_KEY_OPERATION_INIT /* implementation-defined */
psa_generate_key_operation_t psa_generate_key_operation_init();

psa_status_t psa_generate_key_setup(psa_generate_key_operation_t *op,
                                    const psa_key_attributes_t * attr);
psa_status_t psa_generate_key_complete(psa_generate_key_operation_t *op,
                                       psa_key_id_t *key);
psa_status_t psa_generate_key_abort(psa_generate_key_operation_t *op);
athoelke commented 1 month ago

Maybe _start() is better than _setup() to distinguish from multi-part operations? - the challenge is the two-phases of completion for the interruptible signature API (see #98).

athoelke commented 3 weeks ago

See the discussion of API naming options in #98, which concluded with the following proposal for key generation and key agreement:

/* Key Generation ---------------------------------------*/

typedef /* implementation-defined */ psa_generate_key_iop_t;

#define PSA_GENERATE_KEY_IOP_INIT /* implementation-defined */

psa_generate_key_iop_t psa_generate_key_iop_init();

psa_status_t psa_generate_key_iop_setup(psa_generate_key_iop_t *iop,
                                        const psa_key_attributes_t * attr);
psa_status_t psa_generate_key_iop_complete(psa_generate_key_iop_t *iop,
                                           psa_key_id_t *key);
psa_status_t psa_generate_key_iop_abort(psa_generate_key_iop_t *iop);

/* Key Agreement ----------------------------------------*/

typedef /* implementation-defined */ psa_key_agreement_iop_t;

#define PSA_GENERATE_KEY_IOP_INIT /* implementation-defined */

psa_generate_key_iop_t psa_key_agreement_iop_init();

psa_status_t psa_key_agreement_iop_setup(psa_key_agreement_iop_t *iop,
                                         const psa_key_attributes_t *attr,
                                         psa_key_id_t private_key,
                                         const uint8_t *peer_key,
                                         size_t peer_key_length);
psa_status_t psa_key_agrement_iop_complete(psa_key_agreement_iop_t *iop,
                                           psa_key_id_t *key);
psa_status_t psa_key_agreement_iop_abort(psa_key_agreement_iop_t *iop);

The extension to this pattern for public key export would be:

/* Public key export ----------------------------------------*/

typedef /* implementation-defined */ psa_export_public_key_iop_t;

#define PSA_EXPORT_PUBLIC_KEY_IOP_INIT /* implementation-defined */

psa_export_public_key_iop_t psa_export_public_key_iop_init();

psa_status_t psa_export_public_key_iop_setup(psa_export_public_key_iop_t *iop,
                                             psa_key_id_t key);
psa_status_t psa_export_public_key_iop_complete(psa_export_public_key_iop_t *iop,
                                                uint8_t * data,
                                                size_t data_size,
                                                size_t * data_length);
psa_status_t psa_export_public_key_iop_abort(psa_export_public_key_iop_t *iop);
athoelke commented 3 weeks ago

In light of #194, should the interruptible key generation support additional production parameters, or only the default production parameters?

athoelke commented 3 weeks ago

Similarly, in light of #149, should an interruptible public key export operation support alternative output formats?

gilles-peskine-arm commented 3 weeks ago

We should have a way to have both custom production parameters and interruptibility. We aren't actually going to implement those variants in Mbed TLS since we only do custom production parameters for RSA and we only do interruptibility for ECC. But if someone needs them, they should exist in the API design so that implementers and users agree on naming, order of parameters, etc.

athoelke commented 2 weeks ago

In light of #194, should the interruptible key generation support additional production parameters, or only the default production parameters?

Similarly, in light of #149, should an interruptible public key export operation support alternative output formats?

The current opinion is that the default API should just cater for the default use cases (standard key generation/key format), and we can add additional setup or completion functions for interruptible operations that do need extended behavior. This also avoids trying to pre-empt a decision on API naming for things such as formatted key export.