Add transition-guards.md

Signed-off-by: Manuel Pégourié-Gonnard <manuel.pegourie-gonnard@arm.com>
This commit is contained in:
Manuel Pégourié-Gonnard 2024-05-23 09:12:24 +02:00
parent 7237563d4b
commit 06adca465b

@ -0,0 +1,290 @@
This document explains feature guards macros to be used during the transition
from legacy to PSA in order to determine whether a given cryptographic
mechanism is available in the current build.
We currently (as of Mbed TLS 3.6) have three sets of feature macros:
- `PSA_WANT` macros;
- legacy `MBEDTLS_xxx` macros;
- transitional `MBEDTLS_xxx` macros that stem from the desire to be able to
use crypto mechanisms that are only provided by a driver (G5 in
`strategy.md`).
This document's goal is to shed some light on when to use which. It is mostly
intended for maintainers.
Since most transition macros come from driver-only work, it can be useful to
check `docs/driver-only-builds.md` as well for background. (Note: as
maintainers, for the best precision about what's supported of not with
drivers, check the relevant `component_test_psa_crypto_config_accel_xxx`'s
configuration, as well as the corresponding exclude list in
`analyze_outcomes.py`.)
General considerations
======================
This document only applies to Mbed TLS 3.6 TLS. By contrast:
- in 2.28 we have no driver-only support, so the legacy guards `MBEDTLS_XXX`
should be used everywhere;
- in 4.0 configuration will be purely based on PSA, so `PSA_WANT` macros
should be used everywhere.
It is useful to consider the following domains:
- The PSA domain: things declared in `include/psa/*.h`, implemented in
`library/psa_*.c` and tested in `tests/suites/test_suite_psa*`.
- The pure TLS 1.3 domain: the parts of TLS 1.3 that are not in the `USE_PSA`
domain (see below). Those use PSA APIs unconditionally.
- The `USE_PSA` domain: that's PK, X.509, most of TLS 1.2 and the parts of TLS
1.3 that are common with TLS 1.2 or are about public/private keys (see
`docs/use-psa-crypto.md` for details).
- The legacy crypto domain: a number of modules there will use crypto from
other modules, for example RSA and entropy will use hashes, PEM will use
hashes and ciphers (from encrypted PEM), etc.
The first two categories (PSA domain, pure TLS 1.3 domain) are simple: as a
general rule, use `PSA_WANT` macros. (With very few exceptions, see
`component_check_test_dependencies` in `all.sh`.) When it is necessary to
check whether a mechanism is built-in or provided by a driver,
`MBEDTLS_PSA_BUILTIN_xxx` and `MBEDTLS_PSA_ACCEL_xxx` macros should be used
(but not legacy `MBEDTLS_xxx` macros).
The other two categories (legacy and `USE_PSA` domains) tend to be more
complex. There are different rules for different families of mechanisms, as
detailed in the following sections.
However as a general rule, it should always be correct for code in the
`USE_PSA` domain to use expressions like `(!USE_PSA && MBEDTLS_xxx) ||
(USE_PSA && PSA_WANT_xxx)`.
Symmetric crypto
================
Hashes
------
**Hash vs HMAC:** Historically (since 2.0) we've had the generic hash
interface, and the implementation of HMAC, in the same file controlled by a
single feature macro: `MBEDTLS_MD_C`. This has now be split in two:
- `MBEDTLS_MD_LIGHT` is about the generic hash interface; we could think of it
as `MBEDTLS_HASH_C`.
- `MBEDTLS_MC_C` is about the HMAC implementation; we could think of it as
`MBEDTLS_HMAC_C` (auto-enabling `MBEDTLS_HASH_C`).
(In fact, this is not the whole story: `MD_LIGHT` is the _core_ of the generic
hash interface, excluding functions such as `mbedtls_md_list()` and
`mbedtls_md_info_from_string()`, `mbedtls_md_file()`, etc. But I think the
above should still provide a good intuition as first approximation.)
Note that all users of hashes use either the PSA Crypto API or the `md.h` API.
That is, no user, even in the legacy domain, uses the low-level hash APIs
(`mbedtls_sha256` etc).
**Helper macros:** in `config_adjust_legacy_crypto.h` we define a family of
macro `MBEDTLS_MD_CAN_xxx`. These macros are defined (for available hashes) as
soon as `MBEDTLS_MD_LIGHT` is enabled. This subset of `MD` is automatically
enabled as soon as something from the legacy domain, or from the `USE_PSA`
domain, needs a hash. (Note that this include `ENTROPY_C`, so in practice
`MD_LIGHT` is enabled in most builds.)
Note that there is a rule, enforced by `config_adjust_psa_superset_legacy.h`,
that all hashes that are enabled on the legacy side are also enabled on the
PSA side. So, in practice, when `MD_LIGHT` is enabled, `PSA_WANT_ALG_xxx` and
`MBEDTLS_MD_CAN_xxx` are equivalent.
**Legacy and `USE_PSA` domains:** for hashes, `MBEDTLS_MD_CAN_xxx` (where
`xxx` is the legacy name of the hash) can be used everywhere (except in the
PSA domain which should use `PSA_WANT` as usual). No special include is
required, `build_info.h` or `common.h` is enough.
**Pure TLS 1.3 domain:** it is not easy to know which uses of hashes fall in
this domain as opposed to the `USE_PSA` domain which looking at the code.
Fortunately, `MD_CAN` and `PSA_WANT` macros can be used interchangeably, as
per the note above.
HMAC
----
**Legacy domain:** the code is using the `md.h` API. For this domain,
availability of HMAC-xxx is determined by `MBEDTLS_MD_C && MBEDTLS_MD_CAN_xxx`
(see previous subsection about `MD_CAN`). Modules in this domain that may use
HMAC are PKCS5, PKCS7, HKDF, HMAC-DRBG and ECDSA deterministic.
**`USE_PSA` domain:** code will use either the `md.h` API or the `psa_mac`
API. It should check for the availability of HMAC-xxx with either:
```
((!MBEDTLS_USE_PSA_CRYPTO && MBEDTLS_MD_C) ||
(MBEDTLS_USE_PSA_CRYPTO && PSA_WANT_ALG_HMAC)) &&
MBEDTLS_MD_CAN_xxx
```
or
```
(!MBEDTLS_USE_PSA_CRYPTO && MBEDTLS_MD_C && MBEDTLS_xxx_C) ||
(MBEDTLS_USE_PSA_CRYPTO && PSA_WANT_ALG_HMAC && PSA_WANT_ALG_xxx)
```
or any equivalent condition (see note at the end of the previous section).
The only module in this case is TLS, which currently depends on
`USE_PSA_CRYPTO || MD_C`.
Note: while writing this, it occurs to me that TLS 1.2 does not seem to be
checking for `PSA_WANT_ALG_HMAC` before enabling CBC ciphersuites when
`USE_PSA` is enabled, which I think it should. Builds with `USE_PSA` enabled,
`PSA_WANT_ALG_HMAC` disabled and other requirements for CBC ciphersuites
enabled, are probably broken (perhaps only at runtime when a CBC ciphersuite
is negotiated).
**Pure TLS 1.3 domain:** HMAC is used for the Finished message via PSA Crypto
APIs. So, TLS 1.3 should depend on `PSA_WANT_ALG_HMAC` - doesn't seem to be
enforced by `check_config.h` at the moment.
Ciphers (AEAD and unauthenticated)
----------------------------------
**Overview of existing (internal) APIs:** we currently have 4 (families of)
APIs for ciphers in the library:
- Low-level API: `mbedtls_aes_xxx` etc. - used by `cipher.c` and some other
modules in the legacy domain.
- Internal abstraction layer `block_cipher` for AES, ARIA and Camellia
primitives - used only by `gcm.c` and `ccm.c`, only when `CIPHER_C` is not
enabled (for compatibility reasons).
- Cipher: used by some modules in the legacy domain, and by the built-in PSA
implementation.
- PSA: used by the `USE_PSA` domain when `MBEDTLS_USE_PSA_CRYPTO` is enabled.
**Legacy domain:** most code here is using either `cipher.h` or low-level APIs
like `aes.h`, and should use legacy macros like `MBEDTLS_AES_C` and
`MBEDTLS_CIPHER_MOD_CBC`. This includes NIST-KW, CMAC, PKCS5 en/decryption
functions, PEM decryption, PK parsing of encrypted keys. The only exceptions
are `GCM` and `CCM` which use the internal abstraction layer `block_cipher`
and check for availability of block ciphers using `MBEDTLS_CCM_GCM_CAN_xxx`
macros defined in `config_adjut_legacy_crypto.h`.
**`USE_PSA` domain:** here we should use conditions like the following in
order to test for availability of ciphers and associated modes.
```
// is AES available?
(!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_AES_C)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_AES))
// is CBC available?
(!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_CIPHER_MODE_CBC)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_CBC_NO_PADDING))
// is GCM available?
(!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_GCM_C)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_GCM))
```
Note: TLS is the only user of ciphers in the `USE_PSA` domain, and it defines
`MBEDTLS_SSL_HAVE_xxx` macros in `config_adjust_legacy_crypto.h` for the
ciphers and modes it needs to know about.
**Pure TLS 1.3 domain:** none. All from TLS 1.3 are in the `USE_PSA` domain
(common to TLS 1.2).
Key derivation
--------------
**Legacy and `USE_PSA` domains:** no users here.
**Pure TLS 1.3 domain:** TLS 1.3 is using HKDF via PSA Crypto APIs. We already
enforce in `check_config.h` that TLS 1.3 depends on the appropriate `PSA_WANT`
macros.
Asymmetric crypto
=================
RSA
---
**Legacy domain and `USE_PSA` domain:** use `RSA_C` everywhere. (Note: there's
no user of RSA in the legacy domain, and the only direct user in the `USE_PSA`
domain is PK - both X.509 and TLS will only RSA via PK.)
**Pure TLS 1.3 domain:** no use of RSA in this domain. All TLS 1.3 uses of RSA
go through PK, hence are in the `USE_PSA` domain.
FFDH
----
**Legacy domain and `USE_PSA` domain:** use `DHM_C`. The only user is TLS 1.2
which is actually in the legacy domain - this is an exception where `USE_PSA`
has no effect, because PSA doesn't cover the needs of TLS 1.2 here.
**Pure TLS 1.3 domain:** use `PSA_WANT`. The TLS 1.3 code for Diffie-Hellman
is common to ECDH and FFDH thanks to PSA Crypto APIs being generic enough. The
parts about FFDH are guarded with `PSA_WANT_ALG_FFDH` (with the reasoning that
this implies support for the corresponding key type).
ECC
---
**Curves:** in `config_adjut_psa_superset_legacy.h` we ensure that all
curves that are supported on the legacy side (`MBEDTLS_ECP_DP_xxx_ENABLED`)
are also supported on the PSA side (`PSA_WANT_ECC_xxx`).
In `config_adjust_legacy_crypto.h` we define macros `MBEDTLS_ECP_HAVE_xxx`.
These macros are useful for data and functions that have users in several
domains, such as `mbedtls_ecc_group_to_psa()`, or that have users only in the
`USE_PSA` domain but want a simpler (if sub-optimal) condition, such as
`mbedtls_oid_get_ec_grp()`.
Strictly speaking, code in the `USE_PSA` domain should not use the above
`MBEDTLS_ECP_HAVE_xxx` macros but conditions like
```
(!MBEDTLS_USE_PSA_CRYPTO && MBEDTLS_ECP_DP_xxx_ENABLED) ||
(MBEDTLS_USE_PSA_CRYPTO && PSA_WANT_ECC_xxx)
```
Note while writing: a lot of tests for things in the `USE_PSA` domain appear
to be using `MBEDTLS_ECP_HAVE_xxx`. IMO this is incorrect, but not caught by
the CI because I guess we don't run tests in configurations that have both
`USE_PSA_CRYPTO` disabled, and some curves enabled only on the PSA side. My
initial feeling is we don't care about such configurations as this point, and
can leave the dependencies as they are until they're replaced with `PSA_WANT`
macros in 4.0 anyway.
**Legacy domain:** use the legacy macros `ECP_C`, `ECDH_C`, `ECDSA_C`,
`ECJPAKE_C`, `MBEDTLS_ECP_DP_xxx_ENABLED`. (This is mostly just ECDH, ECDSA
and EC J-PAKE using ECP.)
**Key management, `USE_PSA` domain:** `MBEDTLS_PK_HAVE_ECC_KEYS` means that PK
supports ECC key parsing and writing (and storage). It does not imply support
for doing crypto operation with such keys - see `MBEDTLS_PK_CAN_ECDSA_xxx`
above for that.
**ECDH, `USE_PSA` domain:** this is just TLS 1.2. It's using the helper macro
`MBEDTLS_CAN_ECDH` defined in `config_adjust_legacy_crypto.h` (which should
probably be called `MBEDTLS_SSL_TLS1_2_CAN_ECDH` as it's only for TLS 1.2).
(Note: the macro is not used directly in the code, it's only used as a
dependency for relevant TLS 1.2 key exchanges. Then the code uses the guards
for the key exchanges.)
**ECDH, pure TLS 1.3 domain:** using `PSA_WANT_ALG_ECDH`.
**ECDSA, `USE_PSA` domain:** should use the macros
`MBEDTLS_PK_CAN_ECDSA_{SIGN,VERIFY,SOME}` that indicate support for signature
generation, verification, or at least one of those, respectively. To check for
support for signatures with a specific hash, combine
`MBEDTLS_PK_CAN_ECDSA_xxx` with `MBEDTLS_MD_CAN_xxx`.
**ECDSA, pure TLS 1.3 domain:** none - everything goes through PK.
**EC J-PAKE, `USE_PSA` domain:** only used by TLS 1.2. The code is guarded by
the corresponding `KEY_EXCHANGE` macro, which in `check_config.h` depends on
the appropriate macros depending on whether `USE_PSA` is on or off.
**EC J-PAKE, pure TLS 1.3 domain:** none - EC J-PAKE is TLS 1.2 (so far).
**Related internal macros:**
- `MBEDTLS_PK_USE_PSA_EC_DATA` is an internal switch of the PK module. When
it's not defined, PK stores ECC keys as a `struct mbedtls_ecxxx_keypair`;
when it's defined, PK stores in a PSA -friendly format instead (PSA key slot
for private keys, metadata + array of bytes with the PSA import/export format
for the public part). This macro is only defined when `ECP_C` is not and
`USE_PSA` is, see comments above its definition in `pk.h` for details.
- `MBEDTLS_ECP_LIGHT` enables only a subset of `ecp.c`. This subset is pretty
much ad hoc: it's basically everything that doesn't depend on scalar
multiplication (_the_ complex expensive operation in ECC arithmetic).
Basically, this subset gives access to curve data (constants), key storage,
basic parsing and writing. It is auto-enabled in some driver-only
configurations where the user has disabled `ECP_C` because they have drivers
for the crypto operations they use, but they've also asked for some things
that are not supported by drivers yet, such as deterministic key derivation,
or parsing of compressed keys - on those cases, `ECP_LIGHT` will support this
needs without bringing back the full `ECP_C`.