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

PSA drivers: specification for key derivation #5451

Merged
Merged
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a2b4159
Draft specification for key derivation
gilles-peskine-arm Jan 24, 2022
220bda7
Rename a function parameter to avoid confusion
gilles-peskine-arm Jan 25, 2022
c2e2910
Fix internal links
gilles-peskine-arm Jun 3, 2022
1a5b830
Fix typos and copypasta
gilles-peskine-arm Jun 3, 2022
3fc9e04
Be more consistent with raw/cooked key derivation terminology
gilles-peskine-arm Jun 3, 2022
54eb068
New function psa_crypto_driver_key_derivation_get_input_type
gilles-peskine-arm Jun 30, 2022
d9645c8
Fix naming confusion with opaque key derivation
gilles-peskine-arm Jun 30, 2022
eda71ce
Key derivation: improve overview of the problem space
gilles-peskine-arm Jan 12, 2023
4e346bd
Fix entry point name
gilles-peskine-arm Jan 12, 2023
635b779
Fix math character used in text mode
gilles-peskine-arm Jan 12, 2023
fd09408
Pass attributes alongside key buffer
gilles-peskine-arm Jan 20, 2023
66b96e2
Copyediting
gilles-peskine-arm May 30, 2023
4e94fea
Key derivation dispatch doesn't depend on the key type
gilles-peskine-arm May 30, 2023
d2fe1d5
Rationale on key derivation inputs and buffer ownership
gilles-peskine-arm May 30, 2023
f787879
Clarify sequencing of long inputs
gilles-peskine-arm May 30, 2023
b319ed6
State explicitly that cooked key derivation uses the export format
gilles-peskine-arm May 30, 2023
e52bff9
Note possible issue with derive_key: who should choose the input length?
gilles-peskine-arm May 30, 2023
24f5229
Key agreement needs an attribute structure for our key
gilles-peskine-arm May 30, 2023
1414bc3
Minor copyediting
gilles-peskine-arm Jun 2, 2023
f96a18e
Probably resolve concern about the input size for derive_key
gilles-peskine-arm Jun 2, 2023
dcaf104
Note that we may want to rename derive_key
gilles-peskine-arm Jun 2, 2023
7df8ba6
Rework the description of key derivation output/verify key
gilles-peskine-arm Jun 2, 2023
8dd1e62
Copyediting
gilles-peskine-arm Jun 5, 2023
f4ba001
Clarify when key derivation entry points are mandatory/permitted
gilles-peskine-arm Jun 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions docs/proposed/psa-driver-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,19 +301,20 @@ TODO

Key derivation is more complex than other multipart operations for several reasons:

* There are multiple of inputs and outputs.
* There are multiple inputs and outputs.
* Multiple drivers can be involved. This happens when an operation combines a key agreement and a subsequent symmetric key derivation, each of which can have independent drivers. This also happens when deriving an asymmetric key, where processing the secret input and generating the key output might involve different drivers.
* When multiple drivers are involved, they are not always independent: if the secret input is managed by an opaque driver, it might not allow the core to retrieve the intermediate output and pass it to another driver.
* The involvement of an opaque driver cannot be determined as soon as the operation is set up (since `psa_key_derivation_setup()` does not determine the key input).

#### Key derivation driver dispatch logic

The core decides whether to dispatch a key derivation operation to a driver based on the location of the input step `PSA_KEY_DERIVATION_INPUT_SECRET`.
The core decides whether to dispatch a key derivation operation to a driver based on the location associated with of the input step `PSA_KEY_DERIVATION_INPUT_SECRET`.

1. If this step is passed via `psa_key_derivation_input_key()` for a key in a secure element:
* If the driver for this secure element implements the `"key_derivation"` family for the specified key type and algorithm, the core calls that driver's `"key_derivation_setup"` and subsequent entry points.
* If the driver for this secure element implements the `"key_derivation"` family for the specified algorithm, the core calls that driver's `"key_derivation_setup"` and subsequent entry points.
Note that for all currently specified algorithms, the key type for the secret input does not matter.
* Otherwise the core calls the secure element driver's [`"export_key"`](#key-management-with-opaque-drivers) entry point.
2. Otherwise ([or on fallback?](#fallback-for-key-derivation-in-opaque-drivers)), if there is a transparent driver for the specified key type and algorithm, the core calls that driver's `"key_derivation_setup"` and subsequent entry points.
2. Otherwise ([or on fallback?](#fallback-for-key-derivation-in-opaque-drivers)), if there is a transparent driver for the specified algorithm, the core calls that driver's `"key_derivation_setup"` and subsequent entry points.
3. Otherwise, or on fallback, the core uses its built-in implementation.

#### Summary of entry points for the operation family `"key_derivation"`
Expand Down Expand Up @@ -341,7 +342,7 @@ The core conveys the initial inputs for a key derivation via an opaque data stru
typedef ... psa_crypto_driver_key_derivation_inputs_t; // implementation-specific type
```

A driver receiving an argument that points to a `psa_crypto_driver_key_derivation_inputs_t` can retrieve its contents by calling one of type-specific the functions below. To determine the correct function, the driver can call `psa_crypto_driver_key_derivation_get_input_type()`.
A driver receiving an argument that points to a `psa_crypto_driver_key_derivation_inputs_t` can retrieve its contents by calling one of the type-specific the functions below. To determine the correct function, the driver can call `psa_crypto_driver_key_derivation_get_input_type()`.

```
enum psa_crypto_driver_key_derivation_input_type_t {
Expand All @@ -363,8 +364,8 @@ The function `psa_crypto_driver_key_derivation_get_input_type()` determines whet

* `PSA_KEY_DERIVATION_INPUT_TYPE_INVALID`: the step is invalid for the algorithm of the operation that the inputs are for.
* `PSA_KEY_DERIVATION_INPUT_TYPE_OMITTED`: the step is optional for the algorithm of the operation that the inputs are for, and has been omitted.
* `PSA_KEY_DERIVATION_INPUT_TYPE_BYTES`: the step is valid and present and is a transparent byte string. Call `psa_crypto_driver_key_derivation_get_input_size()` to obtain the size of the input data. Call `psa_crypto_driver_key_derivation_get_input_bytes()` make a copy of the input data.
* `PSA_KEY_DERIVATION_INPUT_TYPE_KEY`: the step is valid and present and is a byte string passed via a key object. Call `psa_crypto_driver_key_derivation_get_input_key()` to obtain a pointer to the key data.
* `PSA_KEY_DERIVATION_INPUT_TYPE_BYTES`: the step is valid and present and is a transparent byte string. Call `psa_crypto_driver_key_derivation_get_input_size()` to obtain the size of the input data. Call `psa_crypto_driver_key_derivation_get_input_bytes()` to make a copy of the input data (design note: [why a copy?](#key-derivation-inputs-and-buffer-ownership)).
* `PSA_KEY_DERIVATION_INPUT_TYPE_KEY`: the step is valid and present and is a byte string passed via a key object. Call `psa_crypto_driver_key_derivation_get_input_key()` to obtain a pointer to the key context.
* `PSA_KEY_DERIVATION_INPUT_TYPE_INTEGER`: the step is valid and present and is an integer. Call `psa_crypto_driver_key_derivation_get_input_integer()` to retrieve the integer value.

```
Expand All @@ -379,6 +380,7 @@ psa_status_t psa_crypto_driver_key_derivation_get_input_bytes(
psa_status_t psa_crypto_driver_key_derivation_get_input_key(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

const psa_crypto_driver_key_derivation_inputs_t *inputs,
psa_key_derivation_step_t step,
const psa_key_attributes_t *attributes,
uint8_t** p_key_buffer, size_t *key_buffer_size);
psa_status_t psa_crypto_driver_key_derivation_get_input_integer(
const psa_crypto_driver_key_derivation_inputs_t *inputs,
Expand All @@ -390,14 +392,14 @@ The get-data functions take the following parameters:

* The first parameter `inputs` must be a pointer passed by the core to a key derivation driver setup entry point which has not returned yet.
* The `step` parameter indicates the input step whose content the driver wants to retrieve.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_size`, the core sets `*size` to the size of the desired input in bytes.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_bytes`, the core fills the first *N* bytes of `buffer` with the desired input and sets `*buffer_length` to *N*, where *N* is the length of the input in bytes. The value of `buffer_size` must be at least *N*, otherwise this function fails with the status `PSA_ERROR_BUFFER_TOO_SMALL`.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_size`, the core sets `*size` to the size of the specified input in bytes.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_bytes`, the core fills the first *N* bytes of `buffer` with the specified input and sets `*buffer_length` to *N*, where *N* is the length of the input in bytes. The value of `buffer_size` must be at least *N*, otherwise this function fails with the status `PSA_ERROR_BUFFER_TOO_SMALL`.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_key`, the core sets `*key_buffer` to a pointer to a buffer containing the key context and `*key_buffer_size` to the size of the key context in bytes. The key context buffer remains valid for the duration of the driver entry point. If the driver needs to access the key context after the current entry point returns, it must make a copy of the key context.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_integer`, the core sets `*value` to the value of the desired input.
* On a successful invocation of `psa_crypto_driver_key_derivation_get_input_integer`, the core sets `*value` to the value of the specified input.

These functions can return the following statuses:

* `PSA_SUCCESS`: the call succeeded and the desired value has been copied to the output parameter (`size`, `buffer`, `value` or `p_key_buffer`) and if applicable the size of the value has been writen to the applicable parameter (`buffer_length`, `key_buffer_size`).
* `PSA_SUCCESS`: the call succeeded and the requested value has been copied to the output parameter (`size`, `buffer`, `value` or `p_key_buffer`) and if applicable the size of the value has been written to the applicable parameter (`buffer_length`, `key_buffer_size`).
* `PSA_ERROR_DOES_NOT_EXIST`: the input step is valid for this particular algorithm, but it is not part of the initial inputs. This is not a fatal error. The driver will receive the input later as a [long input](#key-derivation-driver-long-inputs).
* `PSA_ERROR_INVALID_ARGUMENT`: the input type is not compatible with this function or was omitted. Call `psa_crypto_driver_key_derivation_get_input_type()` to find out the actual type of this input step. This is not a fatal error and the driver can, for example, subsequently call the appropriate function on the same step.
* `PSA_ERROR_BUFFER_TOO_SMALL` (`psa_crypto_driver_key_derivation_get_input_bytes` only): the output buffer is too small. This is not a fatal error and the driver can, for example, subsequently call the same function again with a larger buffer. Call `psa_crypto_driver_key_derivation_get_input_size` to obtain the required size.
Expand All @@ -419,7 +421,7 @@ psa_status_t acme_key_derivation_setup(

#### Key derivation driver long inputs

Some key derivation algorithms take long inputs which it would not be practical to pass in the [initial inputs](#key-derivation-driver-initial-inputs). A driver that implements a key derivation algorithm that takes such inputs must provide a `"key_derivation_input_step"` entry point. The core calls this input step for all the long inputs, in an unspecified order. Long input steps may be fragmented into multiple calls of `psa_key_derivation_input_bytes()`, and the core may reassemble or refragment those fragments before passing them to the driver.
Some key derivation algorithms take long inputs which it would not be practical to pass in the [initial inputs](#key-derivation-driver-initial-inputs). A driver that implements a key derivation algorithm that takes such inputs must provide a `"key_derivation_input_step"` entry point. The core calls this entry point for all the long inputs after calling `"acme_key_derivation_setup"`. A long input step may be fragmented into multiple calls of `psa_key_derivation_input_bytes()`, and the core may reassemble or refragment those fragments before passing them to the driver. Calls to this entry point for different step values occur in an unspecified order and may be interspersed.

```
psa_status_t acme_key_derivation_input_step(
Expand All @@ -440,7 +442,7 @@ psa_status_t acme_key_derivation_set_capacity(
acme_key_derivation_operation_t *operation,
size_t capacity);
```
`capacity` is guaranteed to be less or equal to any value previously set through this entry point, and is guaraneed not to be `PSA_KEY_DERIVATION_UNLIMITED_CAPACITY`.
`capacity` is guaranteed to be less or equal to any value previously set through this entry point, and is guaranteed not to be `PSA_KEY_DERIVATION_UNLIMITED_CAPACITY`.

If this entry point has not been called, the operation has an unlimited capacity.

Expand Down Expand Up @@ -477,7 +479,7 @@ If the key derivation's `PSA_KEY_DERIVATION_INPUT_SECRET` input is in a secure e
1. For a call to `psa_key_derivation_verify_key()` or `psa_key_derivation_verify_bytes()`, if the driver has a `"key_derivation_verify_bytes"` entry point, call the driver's `"export_key"` entry point on the key object that contains the expected value, call the `"key_derivation_verify_bytes"` entry point on the exported material, and stop.
1. Call the `"key_derivation_output_bytes"` entry point. The core may call this entry point multiple times to implement a single call from the application when deriving a cooked (non-raw) key as described below, or if the output size exceeds some implementation limit.
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, this call happens in case of a call to psa_key_derivation_output_key/verify_key/verify_bytes that has not stopped in the previous steps. This seems correct to me but maybe it would be easier to understand if the series of driver calls was described for each PSA API separately (longer spec with repetitions though).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Each step only applies if none of the previous steps had a “stop” instruction. Otherwise the preconditions on each step get increasingly complex and it's hard to see if they're exhaustive.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would be more comfortable with two series of steps, one for the psa_key_derivation_output_bytes() and psa_key_derivation_output_key() APIs and one for the psa_key_derivation_verify_bytes() or psa_key_derivation_verify_key() APIs. But this is not a blocker.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've split that part into one list for each function, because upon reflection the fallbacks between the various cases weren't quite correct. I'm not fully confident that the spec now handles all the cases it should, but this is the sort of thing that's best checked by implementing, so I'd rather not polish it further for now.


If the key derivation operation is not handled by an opaque driver as described above, the core calls the `"key_derivation_output_bytes"` from the applicable transparent driver (or multiple drivers in succession if fallback applies). In some cases, the driver then calls additional entry points in the same or another driver:
If the key derivation operation is not handled by an opaque driver as described above, the core calls the `"key_derivation_output_bytes"` from the applicable transparent driver (or multiple drivers in succession if fallback applies). In some cases, the core then calls additional entry points in the same or another driver:

* For a call to `psa_key_derivation_output_key()` for some key types, the core calls a transparent driver's `"derive_key"` entry point. See [“Transparent cooked key derivation”](#transparent-cooked-key-derivation).
* For a call to `psa_key_derivation_output_key()` where the derived key is in a secure element, call that secure element driver's `"import_key"` entry point.
Expand All @@ -490,7 +492,7 @@ A capability for cooked key derivation contains the following properties (this i

* `"entry_points"` (mandatory, list of strings). Must be `["derive_key"]`.
* `"derived_types"` (mandatory, list of strings). Each element is a [key type specification](#key-type-specifications). This capability only applies when deriving a key of the specified type.
* `"derived_sizes"` (optional, list of integers). Each element is a [key type specification](#key-type-specifications). This capability only applies when deriving a key of the specified sizes, in bits. If absent, this capability applies to all sizes for the specified types.
* `"derived_sizes"` (optional, list of integers). Each element is a size for the derived key, in bits. This capability only applies when deriving a key of the specified sizes. If absent, this capability applies to all sizes for the specified types.
* `"memory"` (optional, boolean). If present and true, the driver must define a type `"derive_key_memory_t"` and the core will allocate an object of that type as specified below.
Copy link
Contributor

Choose a reason for hiding this comment

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

From the following it seems it is an operation object. Shouldn't is name reflect that? What about to make it mandatory?

Copy link
Contributor Author

@gilles-peskine-arm gilles-peskine-arm May 30, 2023

Choose a reason for hiding this comment

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

Operation objects usually have a setup function and an abort function. Here there's just a single function, so I think it makes sense to use different terminology. But I have no strong feelings about it.

Re-reading this, I'm not sure the API works: the core chooses the amount of data that it passes to the driver, but wouldn't the driver know better? To be revised, perhaps in a follow-up.

* `"names"` (optional, object). A mapping from entry point names to C function and type names, as usual.
* `"fallback"` (optional, boolean). If present and true, the driver may return `PSA_ERROR_NOT_SUPPORTED` if it only partially supports the specified mechanism, as usual.
Expand All @@ -506,15 +508,16 @@ psa_status_t acme_derive_key(
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
```

* `attributes` contains the attributes of the desired key. Note that only the key type and the bit-size are guaranteed to be set.
* `attributes` contains the attributes of the specified key. Note that only the key type and the bit-size are guaranteed to be set.
* `input` is a buffer of `input_length` bytes which contains the raw key stream, i.e. the data that `psa_key_derivation_output_bytes()` would return.
TODO: how does the core choose `input_length`? Doesn't the driver know better? Should there be a driver entry point to determine the length, or should there be a callback that allows the driver to retrieve the input? (Note that for some algorithms, it's impossible to predict the amount of input in advance, because it depends on some complex calculation or even on random data, e.g. if doing a randomized pseudo-primality test.)
* If `"memory"` property in the driver capability is true, `memory` is a data structure that the driver may use to store data between successive calls of the `"derive_key"` entry point to derive the same key. If the `"memory"` property is false or absent, the `memory` parameter is a null pointer.
* `key_buffer` is a buffer for the output material. Its size is `key_buffer_size` bytes.
* `key_buffer` is a buffer for the output material, in the appropriate [export format](#key-format-for-transparent-drivers) for the key type. Its size is `key_buffer_size` bytes.
* On success, `*key_buffer_length` must contain the number of bytes written to `key_buffer`.

This entry point may return the following statuses:

* `PSA_SUCCESS`: a key was derived successfully. The driver has placed representation of the key is in `key_buffer`.
* `PSA_SUCCESS`: a key was derived successfully. The driver has placed the representation of the key in `key_buffer`.
* `PSA_ERROR_NOT_SUPPORTED` (for the first call only) (only if fallback is enabled): the driver cannot fulfill this request, but a fallback driver might.
* `PSA_ERROR_INSUFFICIENT_DATA`: the core must call the `"derive_key"` entry point again with the same `memory` object and with subsequent data from the key stream.
* Any other error is a fatal error.
Expand All @@ -524,7 +527,7 @@ The core calls the `"derive_key"` entry point in a loop until it returns a statu
For standard key types, the `"derive_key"` entry point is called with a certain input length as follows:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
For standard key types, the `"derive_key"` entry point is called with a certain input length as follows:
For standard key types, the `"derive_key"` entry point is called with a certain total input length as follows:

"total" as may be done in several calls?
Otherwise, does it mean that the core stop calling derive_key after passing the intended number of bytes and if deriver_key does not return SUCCESS on the last call, the derivation fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the length passed at each call. Though now I'm not sure if it's right that the core chooses the length.


* `PSA_KEY_TYPE_DES`: the length of the key.
* `PSA_KEY_TYPE_ECC_KEY_PAIR(…)`, `PSA_KEY_TYPE_DH_KEY_PAIR(…)`: $m$ bytes, where the bit-size of the key $n$ satisfies $m-1 < 8 n \le m$.
* `PSA_KEY_TYPE_ECC_KEY_PAIR(…)`, `PSA_KEY_TYPE_DH_KEY_PAIR(…)`: $m$ bytes, where the bit-size of the key $n$ satisfies $8 (m-1) < n \le 8 m$.
* `PSA_KEY_TYPE_RSA_KEY_PAIR`: an implementation-defined length. A future version of this specification may specify a length.
* Other key types: not applicable.

Expand All @@ -544,6 +547,7 @@ In other cases, the core treats `psa_key_derivation_key_agreement()` as if it wa
The entry points related to key agreement have the following prototypes for a driver with the prefix `"acme"`:
```
psa_status_t acme_key_agreement(psa_algorithm_t alg,
const psa_key_attributes_t *our_attributes,
const uint8_t *our_key_buffer,
size_t our_key_buffer_length,
const uint8_t *peer_key,
Expand All @@ -552,16 +556,19 @@ psa_status_t acme_key_agreement(psa_algorithm_t alg,
size_t output_size,
size_t *output_length);
psa_status_t acme_key_agreement_to_key(psa_algorithm_t alg,
const psa_key_attributes_t *attributes,
const psa_key_attributes_t *our_attributes,
const uint8_t *our_key_buffer,
size_t our_key_buffer_length,
const uint8_t *peer_key,
size_t peer_key_length,
const psa_key_attributes_t *shared_secret_attributes,
uint8_t *shared_secret_key_buffer,
size_t shared_secret_key_buffer_size,
size_t *shared_secret_key_buffer_length);
```

Note that unlike most other key creation entry points, in `"acme_key_agreement_to_key"`, the parameters for the shared secret are not placed near the beginning, but rather grouped with the other parameters at the end, to avoid confusion with the keys passed as inputs.

### Driver entry points for key management

The driver entry points for key management differ significantly between [transparent drivers](#key-management-with-transparent-drivers) and [opaque drivers](#key-management-with-opaque-drivers). This section describes common elements. Refer to the applicable section for each driver type for more information.
Expand Down Expand Up @@ -1177,6 +1184,12 @@ Should drivers really have to cope with overlap?

Should the core guarantee that the output buffer size has the size indicated by the applicable buffer size macro (which may be an overestimation)?

#### Key derivation inputs and buffer ownership

Why is `psa_crypto_driver_key_derivation_get_input_bytes` a copy, rather than giving a pointer?

The main reason is to avoid complex buffer ownership. A driver entry point does not own memory after the entry point return. This is generally necessary because an API function does not own memory after the entry point returns. In the case of key derivation inputs, this could be relaxed because the driver entry point is making callbacks to the core: these functions could return a pointer that is valid until the driver entry point, which would allow the driver to process the data immediately (e.g. hash it rather than copy it).

### Partial computations in drivers

#### Substitution points
Expand Down