aboutsummaryrefslogtreecommitdiff
path: root/docs/architecture/psa-migration/strategy.md
blob: 0ad5fa0a536a9c86239499a7fc56915e26f65f5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
This document explains the strategy that was used so far in starting the
migration to PSA Crypto and mentions future perspectives and open questions.

Goals
=====

Several benefits are expected from migrating to PSA Crypto:

G1. Use PSA Crypto drivers when available.
G2. Allow isolation of long-term secrets (for example, private keys).
G3. Allow isolation of short-term secrets (for example, TLS session keys).
G4. Have a clean, unified API for Crypto (retire the legacy API).
G5. Code size: compile out our implementation when a driver is available.

As of Mbed TLS 3.2, most of (G1) and all of (G2) is implemented when
`MBEDTLS_USE_PSA_CRYPTO` is enabled. For (G2) to take effect, the application
needs to be changed to use new APIs. For a more detailed account of what's
implemented, see `docs/use-psa-crypto.md`, where new APIs are about (G2), and
internal changes implement (G1).

Generally speaking, the numbering above doesn't mean that each goal requires
the preceding ones to be completed.

Compile-time options
====================

We currently have two compile-time options that are relevant to the migration:

- `MBEDTLS_PSA_CRYPTO_C` - enabled by default, controls the presence of the PSA
  Crypto APIs.
- `MBEDTLS_USE_PSA_CRYPTO` - disabled by default (enabled in "full" config),
  controls usage of PSA Crypto APIs to perform operations in X.509 and TLS
(G1 above), as well as the availability of some new APIs (G2 above).
- `PSA_CRYPTO_CONFIG` - disabled by default, supports builds with drivers and
  without the corresponding software implementation (G5 above).

The reasons why `MBEDTLS_USE_PSA_CRYPTO` is optional and disabled by default
are:
- it's incompatible with `MBEDTLS_ECP_RESTARTABLE`;
- to avoid a hard/default dependency of TLS, X.509 and PK on
  `MBEDTLS_PSA_CRYPTO_C`, for backward compatibility reasons:
  - When `MBEDTLS_PSA_CRYPTO_C` is enabled and used, applications need to call
    `psa_crypto_init()` before TLS/X.509 uses PSA functions. (This prevents us
from even enabling the option by default.)
  - `MBEDTLS_PSA_CRYPTO_C` has a hard dependency on `MBEDTLS_ENTROPY_C ||
    MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` but it's
    currently possible to compile TLS and X.509 without any of the options.
    Also, we can't just auto-enable `MBEDTLS_ENTROPY_C` as it doesn't build
    out of the box on all platforms, and even less
    `MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` as it requires a user-provided RNG
    function.

The downside of this approach is that until we are able to make
`MBDEDTLS_USE_PSA_CRYPTO` non-optional (always enabled), we have to maintain
two versions of some parts of the code: one using PSA, the other using the
legacy APIs. However, see next section for strategies that can lower that
cost. The rest of this section explains the reasons for the
incompatibilities mentioned above.

At the time of writing (early 2022) it is unclear what could be done about the
backward compatibility issues, and in particular if the cost of implementing
solutions to these problems would be higher or lower than the cost of
maintaining dual code paths until the next major version. (Note: these
solutions would probably also solve other problems at the same time.)

### `MBEDTLS_ECP_RESTARTABLE`

Currently this option controls not only the presence of restartable APIs in
the crypto library, but also their use in the TLS and X.509 layers. Since PSA
Crypto does not support restartable operations, there's a clear conflict: the
TLS and X.509 layers can't both use only PSA APIs and get restartable
behaviour.

Supporting this in PSA is on our roadmap and currently planned for end of
2022, see <https://github.com/orgs/Mbed-TLS/projects/1#column-18883250>.

It will then require follow-up work to make use of the new PSA API in
PK/X.509/TLS in all places where we currently allow restartable operations.

### Backward compatibility issues with making `MBEDTLS_USE_PSA_CRYPTO` always on

1. Existing applications may not be calling `psa_crypto_init()` before using
   TLS, X.509 or PK. We can try to work around that by calling (the relevant
part of) it ourselves under the hood as needed, but that would likely require
splitting init between the parts that can fail and the parts that can't (see
<https://github.com/ARM-software/psa-crypto-api/pull/536> for that).
2. It's currently not possible to enable `MBEDTLS_PSA_CRYPTO_C` in
   configurations that don't have `MBEDTLS_ENTROPY_C`, and we can't just
auto-enable the latter, as it won't build or work out of the box on all
platforms. There are two kinds of things we'd need to do if we want to work
around that:
   1. Make it possible to enable the parts of PSA Crypto that don't require an
      RNG (typically, public key operations, symmetric crypto, some key
management functions (destroy etc)) in configurations that don't have
`ENTROPY_C`. This requires going through the PSA code base to adjust
dependencies. Risk: there may be annoying dependencies, some of which may be
surprising.
   2. For operations that require an RNG, provide an alternative function
      accepting an explicit `f_rng` parameter (see #5238), that would be
available in entropy-less builds. (Then code using those functions still needs
to have one version using it, for entropy-less builds, and one version using
the standard function, for driver support in build with entropy.)

See <https://github.com/Mbed-TLS/mbedtls/issues/5156>.

Taking advantage of the existing abstractions layers - or not
=============================================================

The Crypto library in Mbed TLS currently has 3 abstraction layers that offer
algorithm-agnostic APIs for a class of algorithms:

- MD for messages digests aka hashes (including HMAC)
- Cipher for symmetric ciphers (included AEAD)
- PK for asymmetric (aka public-key) cryptography (excluding key exchange)

Note: key exchange (FFDH, ECDH) is not covered by an abstraction layer.

These abstraction layers typically provide, in addition to the API for crypto
operations, types and numerical identifiers for algorithms (for
example `mbedtls_cipher_mode_t` and its values). The
current strategy is to keep using those identifiers in most of the code, in
particular in existing structures and public APIs, even when
`MBEDTLS_USE_PSA_CRYPTO` is enabled. (This is not an issue for G1, G2, G3
above, and is only potentially relevant for G4.)

The are multiple strategies that can be used regarding the place of those
layers in the migration to PSA.

Silently call to PSA from the abstraction layer
-----------------------------------------------

- Provide a new definition (conditionally on `USE_PSA_CRYPTO`) of wrapper
  functions in the abstraction layer, that calls PSA instead of the legacy
crypto API.
- Upside: changes contained to a single place, no need to change TLS or X.509
  code anywhere.
- Downside: tricky to implement if the PSA implementation is currently done on
  top of that layer (dependency loop).

This strategy is currently (early 2022) used for all operations in the PK
layer.

This strategy is not very well suited to the Cipher layer, as the PSA
implementation is currently done on top of that layer.

This strategy will probably be used for some time for the PK layer, while we
figure out what the future of that layer is: parts of it (parse/write, ECDSA
signatures in the format that X.509 & TLS want) are not covered by PSA, so
they will need to keep existing in some way. (Also, the PK layer is a good
place for dispatching to either PSA or `mbedtls_xxx_restartable` while that
part is not covered by PSA yet, if we decide to do that.)

Replace calls for each operation
--------------------------------

- For every operation that's done through this layer in TLS or X.509, just
  replace function call with calls to PSA (conditionally on `USE_PSA_CRYPTO`)
- Upside: conceptually simple, and if the PSA implementation is currently done
  on top of that layer, avoids concerns about dependency loops.
- Upside: opens the door to building TLS/X.509 without that layer, saving some
  code size.
- Downside: TLS/X.509 code has to be done for each operation.

This strategy is currently (early 2022) used for the MD layer and the Cipher
layer.

Opt-in use of PSA from the abstraction layer
--------------------------------------------

- Provide a new way to set up a context that causes operations on that context
  to be done via PSA.
- Upside: changes mostly contained in one place, TLS/X.509 code only needs to
  be changed when setting up the context, but not when using it. In
  particular, no changes to/duplication of existing public APIs that expect a
  key to be passed as a context of this layer (eg, `mbedtls_pk_context`).
- Upside: avoids dependency loop when PSA implemented on top of that layer.
- Downside: when the context is typically set up by the application, requires
  changes in application code.

This strategy is not useful when no context is used, for example with the
one-shot function `mbedtls_md()`.

There are two variants of this strategy: one where using the new setup
function also allows for key isolation (the key is only held by PSA,
supporting both G1 and G2 in that area), and one without isolation (the key is
still stored outside of PSA most of the time, supporting only G1).

This strategy, with support for key isolation, is currently (early 2022) used for
private-key operations in the PK layer - see `mbedtls_pk_setup_opaque()`. This
allows use of PSA-held private ECDSA keys in TLS and X.509 with no change to
the TLS/X.509 code, but a contained change in the application.

This strategy, without key isolation, was also previously used (until 3.1
included) in the Cipher layer - see `mbedtls_cipher_setup_psa()`. This allowed
use of PSA for cipher operations in TLS with no change to the application
code, and a contained change in TLS code. (It only supported a subset of
ciphers.)

Note: for private key operations in the PK layer, both the "silent" and the
"opt-in" strategy can apply, and can complement each other, as one provides
support for key isolation, but at the (unavoidable) code of change in
application code, while the other requires no application change to get
support for drivers, but fails to provide isolation support.

Summary
-------

Strategies currently (early 2022) used with each abstraction layer:

- PK (for G1): silently call PSA
- PK (for G2): opt-in use of PSA (new key type)
- Cipher (G1): replace calls at each call site
- MD (G1): replace calls at each call site


Supporting builds with drivers without the software implementation
==================================================================

This section presents a plan towards G5: save code size by compiling out our
software implementation when a driver is available.

Additionally, we want to save code size by compiling out the
abstractions layers that we are not using when `MBEDTLS_USE_PSA_CRYPTO` is
enabled (see previous section): MD and Cipher.

Let's expand a bit on the definition of the goal: in such a configuration
(driver used, software implementation and abstraction layer compiled out),
we want:

a. the library to build in a reasonably-complete configuration,
b. with all tests passing,
c. and no more tests skipped than the same configuration with software
   implementation.

Criterion (c) ensures not only test coverage, but that driver-based builds are
at feature parity with software-based builds.

We can roughly divide the work needed to get there in the following steps:

0. Have a working driver interface for the algorithms we want to replace.
1. Have users of these algorithms call to PSA, not the legacy API, for all
   operations. (This is G1, and for PK, X.509 and TLS this is controlled by
   `MBEDTLS_USE_PSA_CRYPTO`.) This needs to be done in the library and tests.
2. Have users of these algorithms not depend on the legacy API for information
   management (getting a size for a given algorithm, etc.)
3. Adapt compile-time guards used to query availability of a given algorithm;
   this needs to be done in the library (for crypto operations and data) and
tests.

Note: the first two steps enable use of drivers, but not by themselves removal
of the software implementation.

Note: the fact that step 1 is not achieved for all of libmbedcrypto (see
below) is the reason why criterion (a) has "a reasonably-complete
configuration", to allow working around internal crypto dependencies when
working on other parts such as X.509 and TLS - for example, a configuration
without RSA PKCS#1 v2.1 still allows reasonable use of X.509 and TLS.

Note: this is a conceptual division that will sometimes translate to how the
work is divided into PRs, sometimes not. For example, in situations where it's
not possible to achieve good test coverage at the end of step 1 or step 2, it
is preferable to group with the next step(s) in the same PR until good test
coverage can be reached.

**Status as of Mbed TLS 3.2:**

- Step 0 is achieved for most algorithms, with only a few gaps remaining.
- Step 1 is achieved for most of PK, X.509, and TLS when
  `MBEDTLS_USE_PSA_CRYPTO` is enabled with only a few gaps remaining (see
  docs/use-psa-crypto.md).
- Step 1 is not achieved for a lot of the crypto library including the PSA
  core. For example, `entropy.c` calls the legacy API
  `mbedtls_sha256` (or `mbedtls_sha512` optionally); `hmac_drbg.c` calls the
  legacy API `mbedtls_md` and `ctr_drbg.c` calls the legacy API `mbedtls_aes`;
  the PSA core depends on the entropy module and at least one of the DRBG
  modules (unless `MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` is used). Further, several
  crypto modules have similar issues, for example RSA PKCS#1 v2.1 calls
  `mbedtls_md` directly.
- Step 2 is achieved for most of X.509 and TLS (same gaps as step 1) when
  `MBEDTLS_USE_PSA_CRYPTO` is enabled - this was tasks like #5795, #5796,
  #5797. It is being done in PK and RSA PKCS#1 v1.5 by PR #6065.
- Step 3 was mostly not started at all before 3.2; it is being done for PK by
  PR #6065.

**Strategy for step 1:**

Regarding PK, X.509, and TLS, this is mostly achieved with only a few gaps.
(The strategy was outlined in the previous section.)

Regarding libmbedcrypto, outside of the RNG subsystem, for modules that
currently depend on other legacy crypto modules, this can be achieved without
backwards compatibility issues, by using the software implementation if
available, and "falling back" to PSA only if it's not. The compile-time
dependency changes from the current one (say, `MD_C` or `AES_C`) to "the
previous dependency OR PSA Crypto with needed algorithms". When building
without software implementation, users need to call `psa_crypto_init()` before
calling any function from these modules. This condition does not constitute a
break of backwards compatibility, as it was previously impossible to build in
those configurations, and in configurations were the build was possible,
application code keeps working unchanged. An work-in-progress example of
applying this strategy, for RSA PKCS#1 v2.1, is here:
<https://github.com/Mbed-TLS/mbedtls/pull/6141>

There is a problem with the modules used for the PSA RNG, as currently the RNG
is initialized before drivers and the key store. This part will need further
study, but in the meantime we can proceed with everything that's not the
entropy module of one of the DRBG modules, and that does not depend on one of
those modules.

**Strategy for step 2:**

The most satisfying situation here is when we can just use the PSA Crypto API
for information management as well. However sometimes it may not be
convenient, for example in parts of the code that accept old-style identifiers
(such as `mbedtls_md_type_t`) in their API and can't assume PSA to be
compiled in (such as `rsa.c`).

It is suggested that, as a temporary solution until we clean this up
later when removing the legacy API including its identifiers (G4), we may
occasionally use ad-hoc internal functions, such as the ones introduced by PR
6065 in `library/hash_info.[ch]`.

An alternative would be to have two different code paths depending on whether
`MBEDTLS_PSA_CRYPTO_C` is defined or not. However this is not great for
readability or testability.

**Strategy for step 3:**

There are currently two (complementary) ways for crypto-using code to check if a
particular algorithm is supported: using `MBEDTLS_xxx` macros, and using
`PSA_WANT_xxx` macros. For example, PSA-based code that want to use SHA-256
will check for `PSA_WANT_ALG_SHA_256`, while legacy-based code that wants to
use SHA-256 will check for `MBEDTLS_SHA256_C` if using the `mbedtls_sha256`
API, or for `MBEDTLS_MD_C && MBEDTLS_SHA256_C` if using the `mbedtls_md` API.

Code that obeys `MBEDTLS_USE_PSA_CRYPTO` will want to use one of the two
dependencies above depending on whether `MBEDTLS_USE_PSA_CRYPTO` is defined:
if it is, the code want the algorithm available in PSA, otherwise, it wants it
available via the legacy API(s) is it using (MD and/or low-level).

The strategy for steps 1 and 2 above will introduce new situations: code that
currently compute hashes using MD (resp. a low-level hash module) will gain
the ability to "fall back" to using PSA if the legacy dependency isn't
available. Data related to a certain hash (OID, sizes, translations) should
only be included in the build if it is possible to use that hash in some way.

In order to cater to these new needs, new families of macros are introduced in
`legacy_or_psa.h`, see its documentation for details.

It should be noted that there are currently:
- too many different ways of computing a hash (low-level, MD, PSA);
- too many different ways to configure the library that influence which of
  these ways is available and will be used (`MBEDTLS_USE_PSA_CRYPTO`,
  `MBEDTLS_PSA_CRYPTO_CONFIG`, `mbedtls_config.h` + `psa/crypto_config.h`).

As a result, we need more families of dependency macros than we'd like to.
This is a temporary situation until we move to a place where everything is
based on PSA Crypto. In the meantime, long and explicit names where chosen for
the new macros in the hope of avoiding confusion.

Note: the new macros supplement but do not replace the existing macros:
- code that always uses PSA Crypto (for example, code specific to TLS 1.3)
  should use `PSA_WANT_xxx`;
- code that always uses the legacy API (for example, crypto modules that have
  not undergone step 1 yet) should use `MBEDTLS_xxx_C`;
- code that may use one of the two APIs, either based on
  `MBEDTLS_USE_PSA_CRYPTO` (X.509, TLS 1.2, shared between TLS 1.2 and 1.3),
  or based on availability (crypto modules after step 1), should use one of
  the new macros from `legacy_or_psa.h`.

Executing step 3 will mostly consist of using the right dependency macros in
the right places (once the previous steps are done).

**Note on testing**

Since supporting driver-only builds is not about adding features, but about
supporting existing features in new types of builds, testing will not involve
adding cases to the test suites, but instead adding new components in `all.sh`
that build and run tests in newly-supported configurations. For example, if
we're making some part of the library work with hashes provided only by
drivers when `MBEDTLS_USE_PSA_CRYPTO` is defined, there should be a place in
`all.sh` that builds and run tests in such a configuration.

There is however a risk, especially in step 3 where we change how dependencies
are expressed (sometimes in bulk), to get things wrong in a way that would
result in more tests being skipped, which is easy to miss. Care must be
taken to ensure this does not happen. The following criteria can be used:

- the sets of tests skipped in the default config and the full config must be
  the same before and after the PR that implements step 3;
- the set of tests skipped in the driver-only build is the same as in an
  equivalent software-based configuration, or the difference is small enough,
  justified, and a github issue is created to track it.

Note that the favourable case is when the number of tests skipped is 0 in the
driver-only build. In other cases, analysis of the outcome files is needed,
see the example script `outcome-analysis.sh` in the same directory.


Migrating away from the legacy API
==================================

This section briefly introduces questions and possible plans towards G4,
mainly as they relate to choices in previous stages.

The role of the PK/Cipher/MD APIs in user migration
---------------------------------------------------

We're currently taking advantage of the existing PK layer in order
to reduce the number of places where library code needs to be changed. It's
only natural to consider using the same strategy (with the PK, MD and Cipher
layers) for facilitating migration of application code.

Note: a necessary first step for that would be to make sure PSA is no longer
implemented of top of the concerned layers

### Zero-cost compatibility layer?

The most favourable case is if we can have a zero-cost abstraction (no
runtime, RAM usage or code size penalty), for example just a bunch of
`#define`s, essentially mapping `mbedtls_` APIs to their `psa_` equivalent.

Unfortunately that's unlikely to fully work. For example, the MD layer uses the
same context type for hashes and HMACs, while the PSA API (rightfully) has
distinct operation types. Similarly, the Cipher layer uses the same context
type for unauthenticated and AEAD ciphers, which again the PSA API
distinguishes.

It is unclear how much value, if any, a zero-cost compatibility layer that's
incomplete (for example, for MD covering only hashes, or for Cipher covering
only AEAD) or differs significantly from the existing API (for example,
introducing new context types) would provide to users.

### Low-cost compatibility layers?

Another possibility is to keep most or all of the existing API for the PK, MD
and Cipher layers, implemented on top of PSA, aiming for the lowest possible
cost. For example, `mbedtls_md_context_t` would be defined as a (tagged) union
of `psa_hash_operation_t` and `psa_mac_operation_t`, then `mbedtls_md_setup()`
would initialize the correct part, and the rest of the functions be simple
wrappers around PSA functions. This would vastly reduce the complexity of the
layers compared to the existing (no need to dispatch through function
pointers, just call the corresponding PSA API).

Since this would still represent a non-zero cost, not only in terms of code
size, but also in terms of maintenance (testing, etc.) this would probably
be a temporary solution: for example keep the compatibility layers in 4.0 (and
make them optional), but remove them in 5.0.

Again, this provides the most value to users if we can manage to keep the
existing API unchanged. Their might be conflicts between this goal and that of
reducing the cost, and judgment calls may need to be made.

Note: when it comes to holding public keys in the PK layer, depending on how
the rest of the code is structured, it may be worth holding the key data in
memory controlled by the PK layer as opposed to a PSA key slot, moving it to a
slot only when needed (see current `ecdsa_verify_wrap` when
`MBEDTLS_USE_PSA_CRYPTO` is defined)  For example, when parsing a large
number, N, of X.509 certificates (for example the list of trusted roots), it
might be undesirable to use N PSA key slots for their public keys as long as
the certs are loaded. OTOH, this could also be addressed by merging the "X.509
parsing on-demand" (#2478), and then the public key data would be held as
bytes in the X.509 CRT structure, and only moved to a PK context / PSA slot
when it's actually used.

Note: the PK layer actually consists of two relatively distinct parts: crypto
operations, which will be covered by PSA, and parsing/writing (exporting)
from/to various formats, which is currently not fully covered by the PSA
Crypto API.

### Algorithm identifiers and other identifiers

It should be easy to provide the user with a bunch of `#define`s for algorithm
identifiers, for example `#define MBEDTLS_MD_SHA256 PSA_ALG_SHA_256`; most of
those would be in the MD, Cipher and PK compatibility layers mentioned above,
but there might be some in other modules that may be worth considering, for
example identifiers for elliptic curves.

### Lower layers

Generally speaking, we would retire all of the low-level, non-generic modules,
such as AES, SHA-256, RSA, DHM, ECDH, ECP, bignum, etc, without providing
compatibility APIs for them. People would be encouraged to switch to the PSA
API. (The compatibility implementation of the existing PK, MD, Cipher APIs
would mostly benefit people who already used those generic APis rather than
the low-level, alg-specific ones.)

### APIs in TLS and X.509

Public APIs in TLS and X.509 may be affected by the migration in at least two
ways:

1. APIs that rely on a legacy `mbedtls_` crypto type: for example
   `mbedtls_ssl_conf_own_cert()` to configure a (certificate and the
associated) private key. Currently the private key is passed as a
`mbedtls_pk_context` object, which would probably change to a `psa_key_id_t`.
Since some users would probably still be using the compatibility PK layer, it
would need a way to easily extract the PSA key ID from the PK context.

2. APIs the accept list of identifiers: for example
   `mbedtls_ssl_conf_curves()` taking a list of `mbedtls_ecp_group_id`s. This
could be changed to accept a list of pairs (`psa_ecc_family_t`, size) but we
should probably take this opportunity to move to a identifier independent from
the underlying crypto implementation and use TLS-specific identifiers instead
(based on IANA values or custom enums), as is currently done in the new
`mbedtls_ssl_conf_groups()` API, see #4859).

Testing
-------

An question that needs careful consideration when we come around to removing
the low-level crypto APIs and making PK, MD and Cipher optional compatibility
layers is to be sure to preserve testing quality. A lot of the existing test
cases use the low level crypto APIs; we would need to either keep using that
API for tests, or manually migrate tests to the PSA Crypto API. Perhaps a
combination of both, perhaps evolving gradually over time.