From 948137be5923961acd6726f275d4e9bb3ddfbbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 10 Aug 2023 16:58:04 +0200 Subject: [PATCH 01/39] Add details on use of ciphers from other modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 355f5618dd..ba76f494b6 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -110,23 +110,45 @@ For the purposes of this work, three domains emerge: #### Non-use-PSA modules -The following modules in Mbed TLS call another module to perform cryptographic operations which, in the long term, will be provided through a PSA interface, but cannot make any PSA-related assumption: +The following modules in Mbed TLS call another module to perform cryptographic operations which, in the long term, will be provided through a PSA interface, but cannot make any PSA-related assumption. -* CCM (block cipher in ECB mode; interdependent with cipher) -* cipher (cipher and AEAD algorithms) -* CMAC (AES-ECB and DES-ECB, but could be extended to the other block ciphers; interdependent with cipher) -* CTR\_DRBG (AES-ECB, but could be extended to the other block ciphers) -* entropy (hashes via low-level) +Hashes and HMAC (after the work on MD-light): + +* entropy (hashes via MD-light) * ECDSA (HMAC\_DRBG; `md.h` exposed through API) -* ECJPAKE (hashes via md; `md.h` exposed through API) -* GCM (block cipher in ECB mode; interdependent with cipher) -* md (hashes and HMAC) -* NIST\_KW (AES-ECB; interdependent with cipher) +* ECJPAKE (hashes via MD-light; `md.h` exposed through API) +* MD (hashes and HMAC) * HMAC\_DRBG (hashes and HMAC via `md.h`; `md.h` exposed through API) -* PEM (AES and DES in CBC mode without padding; MD5 hash via low-level) -* PKCS12 (cipher, generically, selected from ASN.1 or function parameters; hashes via md; `cipher.h` exposed through API) -* PKCS5 (cipher, generically, selected from ASN.1; HMAC via `md.h`; `md.h` exposed through API) -* RSA (hash via md for PSS and OAEP; `md.h` exposed through API) +* PKCS12 (hashes via MD-light) +* PKCS5 (HMAC via `md.h`; `md.h` exposed through API) +* RSA (hash via MD-light for PSS and OAEP; `md.h` exposed through API) +* PEM (MD5 hash via MD-light) + +Symmetric ciphers and AEADs (before Cipher-light work): + +* PEM (AES and DES in CBC mode without padding) + AES and DES: setkey_dec + crypt_cbc + (look at test data for DES) +* PKCS12 (cipher, generically, selected from ASN.1 or function parameters; `cipher.h` exposed through API) + setup, setkey, set_iv, reset, update, finish (in sequence, once) + no documented restriction, block cipher in CBC mode in practice + (padding?) + (look at test cases) +* PKCS5 (cipher, generically, selected from ASN.1) + only DES-CBC or 3DES-CBC + (padding?) + setup, setkey, crypt +* CTR\_DRBG (AES-ECB, but could be extended to the other block ciphers) + setkey_enc + crypt_ecb +* CCM (block cipher in ECB mode; interdependent with cipher) + info, setup, setkey, update (several times), (never finish) +* CMAC (AES-ECB and DES-ECB, but could be extended to the other block ciphers; interdependent with cipher) + info, setup, setkey, update (several times), (never finish) +* GCM (block cipher in ECB mode; interdependent with cipher) + info, setup, setkey, update (several times), (never finish) +* NIST\_KW (AES-ECB; interdependent with cipher) + info, setup, setkey, update (several times), (never finish) +* cipher (cipher and AEAD algorithms) ### Difficulties From 36cd3f9f8ef6edbf9c7ba16a442117ddfa506748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 11 Aug 2023 10:06:42 +0200 Subject: [PATCH 02/39] Add tentative definition of Cipher light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index ba76f494b6..488cf20db9 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -499,3 +499,54 @@ The architecture can be extended to support `MBEDTLS_PSA_CRYPTO_CLIENT` with a l * Compile-time dependencies: instead of checking `defined(MBEDTLS_PSA_CRYPTO_C)`, check `defined(MBEDTLS_PSA_CRYPTO_C) || defined(MBEDTLS_PSA_CRYPTO_CLIENT)`. * Implementers of `MBEDTLS_PSA_CRYPTO_CLIENT` will need to provide `psa_can_do_hash()` (or a more general function `psa_can_do`) alongside `psa_crypto_init()`. Note that at this point, it will become a public interface, hence we won't be able to change it at a whim. + +### Cipher light + +#### Definition + +**Note:** this definition is tentative an may be refined when implementing and +testing, based and what's needed by internal users of Cipher light. + +Cipher light will be automatically enabled in `build_info.h` by modules that +need it. (Tentative list: PEM, PCKS12, PKCS5, CTR\_DRBG, CCM, CMAC, GCM, +NIS\_KW, PSA Crypto.) Note: some of these modules currently depend on the +full `CIPHER_C` (enforced by `check_config.h`); this hard dependency would be +replace by the above auto-enablement. + +Cipher light includes: +- info functions; +- support for block ciphers in ECB mode (to be confirmed: supporting one block + at a time could be enough); +- support for block ciphers in CBC mode with no padding (to be confirmed: do + we need a padding mode?); +- support for both the "one-shot" and "streaming" APIs for block ciphers. + +This excludes: +- the AEAD/KW API (both one-shot and streaming); +- support for stream ciphers; +- support for other modes of block ciphers (CTR, CFB, etc.); +- support for (other) padding modes of CBC. + +The following API functions, and supporting types, are candidates for +inclusion in the Cipher light API, with limited features as above: +``` +mbedtls_cipher_info_from_psa +mbedtls_cipher_info_from_type +mbedtls_cipher_info_from_values + +mbedtls_cipher_info_get_block_size +mbedtls_cipher_info_get_iv_size +mbedtls_cipher_info_get_key_bitlen + +mbedtls_cipher_init +mbedtls_cipher_setup +mbedtls_cipher_setkey +mbedtls_cipher_set_padding_mode +mbedtls_cipher_crypt +mbedtls_cipher_free + +mbedtls_cipher_set_iv +mbedtls_cipher_reset +mbedtls_cipher_update +mbedtls_cipher_finish +``` From 839d3580bd9eb1bba89887c515837a871c0078aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 15 Sep 2023 21:27:19 +0200 Subject: [PATCH 03/39] Update details of modules using cipher operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 82 +++++++++++++------ 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 488cf20db9..6bd0694c47 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -112,7 +112,7 @@ For the purposes of this work, three domains emerge: The following modules in Mbed TLS call another module to perform cryptographic operations which, in the long term, will be provided through a PSA interface, but cannot make any PSA-related assumption. -Hashes and HMAC (after the work on MD-light): +Hashes and HMAC (after the work on driver-only hashes): * entropy (hashes via MD-light) * ECDSA (HMAC\_DRBG; `md.h` exposed through API) @@ -124,31 +124,63 @@ Hashes and HMAC (after the work on MD-light): * RSA (hash via MD-light for PSS and OAEP; `md.h` exposed through API) * PEM (MD5 hash via MD-light) -Symmetric ciphers and AEADs (before Cipher-light work): +Symmetric ciphers and AEADs (before work on driver-only cipher): -* PEM (AES and DES in CBC mode without padding) - AES and DES: setkey_dec + crypt_cbc - (look at test data for DES) -* PKCS12 (cipher, generically, selected from ASN.1 or function parameters; `cipher.h` exposed through API) - setup, setkey, set_iv, reset, update, finish (in sequence, once) - no documented restriction, block cipher in CBC mode in practice - (padding?) - (look at test cases) -* PKCS5 (cipher, generically, selected from ASN.1) - only DES-CBC or 3DES-CBC - (padding?) - setup, setkey, crypt -* CTR\_DRBG (AES-ECB, but could be extended to the other block ciphers) - setkey_enc + crypt_ecb -* CCM (block cipher in ECB mode; interdependent with cipher) - info, setup, setkey, update (several times), (never finish) -* CMAC (AES-ECB and DES-ECB, but could be extended to the other block ciphers; interdependent with cipher) - info, setup, setkey, update (several times), (never finish) -* GCM (block cipher in ECB mode; interdependent with cipher) - info, setup, setkey, update (several times), (never finish) -* NIST\_KW (AES-ECB; interdependent with cipher) - info, setup, setkey, update (several times), (never finish) -* cipher (cipher and AEAD algorithms) +* PEM: + * AES, DES or 3DES in CBC mode without padding, decrypt only (!). + * Currently using low-level non-generic APIs. + * No hard dependency, features guarded by `AES_C` resp. `DES_C`. + * Functions called: `setkey_dec()` + `crypt_cbc()`. +* PKCS12: + * In practice: 2DES or 3DES in CBC mode with PKCS7 padding, decrypt only + (when called from pkparse). + * In principle: any cipher-mode (default padding), passed an + `mbedtls_cipher_type_t` as an argument, no documented restriction. + * Cipher, generically, selected from ASN.1 or function parameters; + no documented restriction but in practice TODO (inc. padding and + en/decrypt, look at standards and tests) + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Note: `cipher.h` exposed through API. + * Functions called: `setup`, `setkey`, `set_iv`, `reset`, `update`, `finish` (in sequence, once). +* PKCS5 (PBES2, `mbedtls_pkcs5_pbes2()`): + * 3DES or DES in CBC mode with PKCS7 padding, both encrypt and decrypt. + * Note: could also be AES in the future, see #7038. + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Functions called: `setup`, `setkey`, `crypt`. +* CTR\_DRBG: + * AES in ECB mode, encrypt only. + * Currently using low-level non-generic API (`aes.h`). + * Unconditional dependency on `AES_C` in `check_config.h`. + * Functions called: `setkey_enc`, `crypt_ecb`. +* CCM: + * AES, Camellia or Aria in ECB mode, encrypt only. + * Unconditional dependency on `AES_C || CAMELLIA_C || ARIA_C` in `check_config.h`. + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Note: also called by `cipher.c` if enabled. + * Functions called: `info`, `setup`, `setkey`, `update` (several times) - (never finish) +* CMAC: + * AES or DES in ECB mode, encrypt only. + * Unconditional dependency on `AES_C || DES_C` in `check_config.h`. + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Note: also called by `cipher.c` if enabled. + * Functions called: `info`, `setup`, `setkey`, `update` (several times) - (never finish) +* GCM: + * AES, Camellia or Aria in ECB mode, encrypt only. + * Unconditional dependency on `AES_C || CAMELLIA_C || ARIA_C` in `check_config.h`. + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Note: also called by `cipher.c` if enabled. + * Functions called: `info`, `setup`, `setkey`, `update` (several times) - (never finish) +* NIST\_KW: + * AES in ECB mode, both encryt and decrypt. + * Unconditional dependency on `AES_C || DES_C` in `check_config.h`. + * Unconditional dependency on `CIPHER_C` in `check_config.h`. + * Note: also called by `cipher.c` if enabled. + * Note: `cipher.h` exposed through API. + * Functions called: `info`, `setup`, `setkey`, `update` (several times) - (never finish) +* Cipher: + * potentially any cipher/AEAD in any mode and any direction + +Note: PSA cipher is built on Cipher, but PSA AEAD directly calls the underlying AEAD modules (GCM, CCM, ChachaPoly). ### Difficulties From ca18b7747e7738788df26c06c581e9c0f7c6a92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 10 Oct 2023 09:45:28 +0200 Subject: [PATCH 04/39] Update definition of Cipher light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 6bd0694c47..3feda1115f 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -537,48 +537,51 @@ The architecture can be extended to support `MBEDTLS_PSA_CRYPTO_CLIENT` with a l #### Definition **Note:** this definition is tentative an may be refined when implementing and -testing, based and what's needed by internal users of Cipher light. +testing, based and what's needed by internal users of Cipher light. The new +config symbol will not be considered public so its definition may change. Cipher light will be automatically enabled in `build_info.h` by modules that -need it. (Tentative list: PEM, PCKS12, PKCS5, CTR\_DRBG, CCM, CMAC, GCM, -NIS\_KW, PSA Crypto.) Note: some of these modules currently depend on the -full `CIPHER_C` (enforced by `check_config.h`); this hard dependency would be -replace by the above auto-enablement. +need it, namely: CTR\_DRBG, CCM, GCM. Note: CCM and GCM currently depend on +the full `CIPHER_C` (enforced by `check_config.h`); this hard dependency would +be replaced by the above auto-enablement. Cipher light includes: - info functions; -- support for block ciphers in ECB mode (to be confirmed: supporting one block - at a time could be enough); -- support for block ciphers in CBC mode with no padding (to be confirmed: do - we need a padding mode?); -- support for both the "one-shot" and "streaming" APIs for block ciphers. +- support for block ciphers in ECB mode, encrypt only (note: in Cipher, "ECB" + means just one block, contrary to PSA); +- the one-shot API as well as (part of) the streaming API; +- only AES, Aria and Camellia. This excludes: - the AEAD/KW API (both one-shot and streaming); - support for stream ciphers; -- support for other modes of block ciphers (CTR, CFB, etc.); -- support for (other) padding modes of CBC. +- support for other modes of block ciphers (CBC, CTR, CFB, etc.); +- DES and variants (3DES). The following API functions, and supporting types, are candidates for inclusion in the Cipher light API, with limited features as above: ``` -mbedtls_cipher_info_from_psa mbedtls_cipher_info_from_type -mbedtls_cipher_info_from_values - mbedtls_cipher_info_get_block_size -mbedtls_cipher_info_get_iv_size -mbedtls_cipher_info_get_key_bitlen mbedtls_cipher_init mbedtls_cipher_setup mbedtls_cipher_setkey -mbedtls_cipher_set_padding_mode mbedtls_cipher_crypt mbedtls_cipher_free -mbedtls_cipher_set_iv -mbedtls_cipher_reset mbedtls_cipher_update -mbedtls_cipher_finish +(mbedtls_cipher_finish) ``` + +Note: `mbedtls_cipher_info_get_block_size()` can be hard-coded to return 16, +as all three supported block ciphers have the same block size (DES was +excluded). + +Note: `mbedtls_cipher_finish()` is not required by any of the modules using +Cipher light, but it might be convenient to include it anyway as it's used in +the implementation of `mbedtls_cipher_crypt()`. + +#### Cipher light dual dispatch + +This is likely to come in the future, but has not been defined yet. From 2daee0410e725ef5ef5beb34d80ac35bbd88ac79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 10 Oct 2023 09:55:03 +0200 Subject: [PATCH 05/39] Update list of modules using hashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- docs/architecture/psa-migration/md-cipher-dispatch.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 3feda1115f..76081deef4 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -118,9 +118,11 @@ Hashes and HMAC (after the work on driver-only hashes): * ECDSA (HMAC\_DRBG; `md.h` exposed through API) * ECJPAKE (hashes via MD-light; `md.h` exposed through API) * MD (hashes and HMAC) +* HKDF (HMAC via `md.h`; `md.h` exposed through API) * HMAC\_DRBG (hashes and HMAC via `md.h`; `md.h` exposed through API) * PKCS12 (hashes via MD-light) * PKCS5 (HMAC via `md.h`; `md.h` exposed through API) +* PKCS7 (hashes via MD) * RSA (hash via MD-light for PSS and OAEP; `md.h` exposed through API) * PEM (MD5 hash via MD-light) From 301d2a29a72292d4654be1929007e33fe69334ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 10 Oct 2023 10:02:03 +0200 Subject: [PATCH 06/39] Update to MD light section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly to reflect this has been implemented, and remove references to temporary remains from the previous strategy (hash_info, legacy_or_psa) which would probably be more confusing than helpful at this point. Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 76081deef4..12b486a46a 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -323,8 +323,6 @@ These problems are easily solvable. ### MD light -https://github.com/Mbed-TLS/mbedtls/pull/6474 implements part of this specification, but it's based on Mbed TLS 3.2, so it needs to be rewritten for 3.3. - #### Definition of MD light MD light is a subset of `md.h` that implements the hash calculation interface described in ”[Designing an interface for hashes](#designing-an-interface-for-hashes)”. It is activated by `MBEDTLS_MD_LIGHT` in `mbedtls_config.h`. @@ -454,31 +452,7 @@ Note that this assumes that an operation that has been started via PSA can be co #### Error code conversion -After calling a PSA function, call `mbedtls_md_error_from_psa` to convert its status code. This function is currently defined in `hash_info.c`. - -### Migration to MD light - -#### Migration of modules that used to call MD and now do the legacy-or-PSA dance - -Get rid of the case where `MBEDTLS_MD_C` is undefined. Enable `MBEDTLS_MD_LIGHT` in `build_info.h`. - -#### Migration of modules that used to call a low-level hash module and now do the legacy-or-PSA dance - -Switch to calling MD (light) unconditionally. Enable `MBEDTLS_MD_LIGHT` in `build_info.h`. - -#### Migration of modules that call a low-level hash module - -Switch to calling MD (light). Enable `MBEDTLS_MD_LIGHT` in `build_info.h`. - -#### Migration of use-PSA mixed code - -Instead of calling `hash_info.h` functions to obtain metadata, get it from `md.h`. - -Optionally, code that currently tests on `MBEDTLS_USE_PSA_CRYPTO` just to determine whether to call MD or PSA to calculate hashes can switch to just having the MD variant. - -#### Remove `legacy_or_psa.h` - -It's no longer used. +After calling a PSA function, call `mbedtls_md_error_from_psa` to convert its status code. ### Support all legacy algorithms in PSA @@ -517,10 +491,6 @@ static inline psa_algorithm_t psa_alg_of_md_info( Work in progress on this conversion is at https://github.com/gilles-peskine-arm/mbedtls/tree/hash-unify-ids-wip-1 -#### Get rid of the hash_info module - -The hash_info module is redundant with MD light. Move `mbedtls_md_error_from_psa` to `md.c`, defined only when `MBEDTLS_MD_SOME_PSA` is defined. The rest is no longer used. - #### Unify HMAC with PSA PSA has its own HMAC implementation. In builds with both `MBEDTLS_MD_C` and `PSA_WANT_ALG_HMAC` not fully provided by drivers, we should have a single implementation. Replace the one in `md.h` by calls to the PSA driver interface. This will also give mixed-domain modules access to HMAC accelerated directly by a PSA driver (eliminating the need to a HMAC interface in software if all supported hashes have an accelerator that includes HMAC support). From f1878d89741ed968b55c2a10ce5d996073bd74bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Oct 2023 11:19:00 +0200 Subject: [PATCH 07/39] Update to only serve GCM and CCM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 12b486a46a..11c5f21fbd 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -513,9 +513,9 @@ testing, based and what's needed by internal users of Cipher light. The new config symbol will not be considered public so its definition may change. Cipher light will be automatically enabled in `build_info.h` by modules that -need it, namely: CTR\_DRBG, CCM, GCM. Note: CCM and GCM currently depend on -the full `CIPHER_C` (enforced by `check_config.h`); this hard dependency would -be replaced by the above auto-enablement. +need it, namely: CCM, GCM. Note: CCM and GCM currently depend on the full +`CIPHER_C` (enforced by `check_config.h`); this hard dependency would be +replaced by the above auto-enablement. Cipher light includes: - info functions; @@ -533,27 +533,21 @@ This excludes: The following API functions, and supporting types, are candidates for inclusion in the Cipher light API, with limited features as above: ``` -mbedtls_cipher_info_from_type +mbedtls_cipher_info_from_values mbedtls_cipher_info_get_block_size mbedtls_cipher_init mbedtls_cipher_setup mbedtls_cipher_setkey -mbedtls_cipher_crypt mbedtls_cipher_free mbedtls_cipher_update -(mbedtls_cipher_finish) ``` Note: `mbedtls_cipher_info_get_block_size()` can be hard-coded to return 16, as all three supported block ciphers have the same block size (DES was excluded). -Note: `mbedtls_cipher_finish()` is not required by any of the modules using -Cipher light, but it might be convenient to include it anyway as it's used in -the implementation of `mbedtls_cipher_crypt()`. - #### Cipher light dual dispatch This is likely to come in the future, but has not been defined yet. From 4fb1955b3184244ccd7e3c0066fb3b19457ca615 Mon Sep 17 00:00:00 2001 From: Paul Elliott Date: Wed, 18 Oct 2023 12:15:30 +0100 Subject: [PATCH 08/39] Remove NULL-ing of passed in SSL context in ssl_populate_transform() Remove a piece of code that was meant to ensure non-usage of the ssl context under conditions where it should not be used, as this now makes less sense and also triggers coverity. Signed-off-by: Paul Elliott --- library/ssl_tls.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 827b7fbcfc..0476a9f73e 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -8159,14 +8159,6 @@ static int ssl_tls12_populate_transform(mbedtls_ssl_transform *transform, psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; #endif -#if !defined(MBEDTLS_DEBUG_C) && \ - !defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) - if (ssl->f_export_keys == NULL) { - ssl = NULL; /* make sure we don't use it except for these cases */ - (void) ssl; - } -#endif - /* * Some data just needs copying into the structure */ @@ -8438,7 +8430,7 @@ static int ssl_tls12_populate_transform(mbedtls_ssl_transform *transform, goto end; } - if (ssl != NULL && ssl->f_export_keys != NULL) { + if (ssl->f_export_keys != NULL) { ssl->f_export_keys(ssl->p_export_keys, MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET, master, 48, From 3bcda449c08aea258029b2542e0e86dc745172c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 26 Oct 2023 10:03:49 +0200 Subject: [PATCH 09/39] Things forgotten in the previous commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- docs/architecture/psa-migration/md-cipher-dispatch.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index 11c5f21fbd..ca98a51077 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -518,13 +518,14 @@ need it, namely: CCM, GCM. Note: CCM and GCM currently depend on the full replaced by the above auto-enablement. Cipher light includes: -- info functions; +- some info functions; - support for block ciphers in ECB mode, encrypt only (note: in Cipher, "ECB" means just one block, contrary to PSA); -- the one-shot API as well as (part of) the streaming API; +- part of the streaming API for unauthenticated ciphers; - only AES, Aria and Camellia. This excludes: +- the one-shot API for unauthenticated ciphers; - the AEAD/KW API (both one-shot and streaming); - support for stream ciphers; - support for other modes of block ciphers (CBC, CTR, CFB, etc.); From 6b3643117b404767c246f019bc0d977c2bff220a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 26 Oct 2023 11:02:17 +0200 Subject: [PATCH 10/39] Document chosen goals and priorities for 3.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index ca98a51077..d75a4dcd40 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -99,8 +99,8 @@ We can classify code that implements or uses cryptographic mechanisms into sever * Software implementations of primitive cryptographic mechanisms. These are not expected to change. * Software implementations of constructed cryptographic mechanisms (e.g. HMAC, CTR_DRBG, RSA (calling a hash for PSS/OAEP, and needing to know the hash length in PKCS1v1.5 sign/verify), …). These need to keep working whenever a legacy implementation of the auxiliary mechanism is available, regardless of whether a PSA implementation is also available. * Code implementing the PSA crypto interface. This is not expected to change, except perhaps to expose some internal functionality to overhauled glue code. -* Code that's subject to `MBEDTLS_USE_PSA_CRYPTO`: `pk.h`, X.509, TLS (excluding TLS 1.3). -* Code that always uses PSA for crypto: TLS 1.3, LMS. +* Code that's subject to `MBEDTLS_USE_PSA_CRYPTO`: `pk.h`, X.509, TLS (excluding parts specific TLS 1.3). +* Code that always uses PSA for crypto: TLS 1.3 (except things common with 1.2), LMS. For the purposes of this work, three domains emerge: @@ -319,6 +319,35 @@ These problems are easily solvable. * We can make names and HMAC optional. The mixed-domain hash interface won't be the full `MBEDTLS_MD_C` but a subset. * We can optimize `md.c` without making API changes to `md.h`. +### Scope reductions and priorities for 3.x + +This section documents things that we chose to temporarily exclude from the scope in the 3.x branch (which will eventually be in scope again after 4.0) as well as things we chose to prioritize if we don't have time to support everything. + +#### Don't support PK, X.509 and TLS without `MBEDTLS_USE_PSA_CRYPTO` + +We do not need to support driver-only hashes and ciphers in PK. X.509 and TLS without `MBEDTLS_USE_PSA_CRYPTO`. Users who want to take full advantage of drivers will need to enabled this macro. + +Note that this applies to TLS 1.3 as well, as some uses of hashes and all uses of ciphers there are common with TLS 1.2, hence governed by `MBEDTLS_USE_PSA_CRYPTO`, see [this macro's extended documentation](../../docs/use-psa-crypto.html). + +This will go away naturally in 4.0 when this macros is not longer an option (because it's always on). + +#### Don't support for `MBEDTLS_PSA_CRYPTO_CLIENT` without `MBEDTLS_PSA_CRYPTO_C` + +We generally don't really support builds with `MBEDTLS_PSA_CRYPTO_CLIENT` without `MBEDTLS_PSA_CRYPTO_C`. For example, both `MBEDTLS_USE_PSA_CRYPTO` and `MBEDTLS_SSL_PROTO_TLS1_3` require `MBEDTLS_PSA_CRYPTO_C`, while in principle they should only require `MBEDTLS_PSA_CRYPTO_CLIENT`. + +Considering this existing restriction which we do not plan to lift before 4.0, it is acceptable driver-only hashes and cipher support to have the same restriction in 3.x. + +It is however desirable for the design to keep support for `MBEDTLS_PSA_CRYPTO_CLIENT` in mind, in order to avoid making it more difficult to add in the future. + +#### For cipher: prioritize constrained devices and modern TLS + +The primary target is a configuration like TF-M's medium profile, plus TLS with only AEAD ciphersuites. + +This excludes things like: +- Support for encrypted PEM, PKCS5 and PKCS12 encryption, and PKCS8 encrypted keys in PK parse. (Not widely used on highly constrained devices.) +- Support for NIST-KW. (Same justification.) +- Support for CBC ciphersuites in TLS. (They've been recommended against for a while now.) + ## Specification ### MD light From 4823d2c94e021994671fcb0f9208c1338253f782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 26 Oct 2023 12:56:39 +0200 Subject: [PATCH 11/39] Extend design discussion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index d75a4dcd40..a4c8fccf0f 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -346,8 +346,39 @@ The primary target is a configuration like TF-M's medium profile, plus TLS with This excludes things like: - Support for encrypted PEM, PKCS5 and PKCS12 encryption, and PKCS8 encrypted keys in PK parse. (Not widely used on highly constrained devices.) - Support for NIST-KW. (Same justification.) +- Support for CMAC. (Same justification, plus can be directly accelerated.) - Support for CBC ciphersuites in TLS. (They've been recommended against for a while now.) +### Dual-dispatch for block cipher primitives + +Considering the priorities stated above, initially we want to support GCM, CCM and CTR-DRBG. All trhee of them use the block cipher primitive only in the encrypt direction. Currently, GCM and CCM use the Cipher layer in order to work with AES, Aria and Camellia (DES is excluded by the standards due to its smaller block size) and CTR-DRBG directly uses the low-level API from `aes.h`. In all cases, access to the "block cipher primitive" is done by using "ECB mode" (which for both Cipher and `aes.h` only allows a single block, contrary to PSA which implements actual ECB mode). + +The two AEAD modes, GCM and CCM, have very similar needs and positions in the stack, strongly suggesting using the same design for both. On the other hand, there are a number of differences between CTR-DRBG and them. +- CTR-DRBG only uses AES (and there is no plan to extend it to other block ciphers at the moment), while GCM and CCM need to work with 3 block ciphers already. +- CTR-DRBG holds a special position in the stack: most users don't care about it per se, they only care about getting random numbers - in fact PSA users don't even need to know what DRBG is used. In particular, no part of the stack is asking questions like "is CTR-DRBG-AES available?" - an RNG needs to be available and that's it - contrary to similar questions about AES-GCM etc. which are asked for example by TLS. + +So, it makes sense to use different designs for CTR-DRBG on one hand, and GCM/CCM on the other hand: +- CTR-DRBG can just check if `AES_C` is present and "fall back" to PSA is not. +- GCM and CCM need an common abstraction layer that allows: + - Using AES, Aria or Camellia in a uniform way. + - Dispatching to built-in or driver. + +The abstraction layer used by GCM and CCM may either be a new internal module, or a subset of the existing Cipher API, extended with the ability to dispatch to a PSA driver. + +Reasons for making this layer's API a subset of the existing Cipher API: +- No need to design, implement and test a new module. (Will need to test the new subset though, as well as the extended behaviour.) +- No code change in GCM and CCM - only need to update dependencies. +- No risk for code duplication between a potential new module and Cipher: source-level, and in in particular in builds that still have `CIPHER_C` enabled. (Compiled-code duplication could be avoided by excluding the new module in such builds, though.) +- If want to support other users of Cipher later (such as NIST-KW, CMAC, PKCS5 and PKCS12), we can just extend dual-dispatch support to other modes/operations in Cipher and keep those extra modules unchanged as well. + +Possible costs of re-using (a subset of) the existing Cipher API instead of defining a new one: +- We carry over costs associated with `cipher_info_t` structures. (Currently the info structure is used for 3 things: (1) to check if the cipher is supported, (2) to check its block size, (3) because `setup()` requires it). +- We carry over questionable implementation decisions, like dynamic allocation of context. + +Those costs could be avoided by refactoring (parts of) Cipher, but that would probably mean either: +- significant differences in how the `cipher.h` API is implemented between builds with the full Cipher or only a subset; +- or more work to apply the simplifications to all of Cipher. + ## Specification ### MD light From 303121eb1682f3e4e18f2a7d9578c21a53f17f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 7 Dec 2023 12:05:07 +0100 Subject: [PATCH 12/39] Fix a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- docs/architecture/psa-migration/md-cipher-dispatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index a4c8fccf0f..f165b21e07 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -351,7 +351,7 @@ This excludes things like: ### Dual-dispatch for block cipher primitives -Considering the priorities stated above, initially we want to support GCM, CCM and CTR-DRBG. All trhee of them use the block cipher primitive only in the encrypt direction. Currently, GCM and CCM use the Cipher layer in order to work with AES, Aria and Camellia (DES is excluded by the standards due to its smaller block size) and CTR-DRBG directly uses the low-level API from `aes.h`. In all cases, access to the "block cipher primitive" is done by using "ECB mode" (which for both Cipher and `aes.h` only allows a single block, contrary to PSA which implements actual ECB mode). +Considering the priorities stated above, initially we want to support GCM, CCM and CTR-DRBG. All three of them use the block cipher primitive only in the encrypt direction. Currently, GCM and CCM use the Cipher layer in order to work with AES, Aria and Camellia (DES is excluded by the standards due to its smaller block size) and CTR-DRBG directly uses the low-level API from `aes.h`. In all cases, access to the "block cipher primitive" is done by using "ECB mode" (which for both Cipher and `aes.h` only allows a single block, contrary to PSA which implements actual ECB mode). The two AEAD modes, GCM and CCM, have very similar needs and positions in the stack, strongly suggesting using the same design for both. On the other hand, there are a number of differences between CTR-DRBG and them. - CTR-DRBG only uses AES (and there is no plan to extend it to other block ciphers at the moment), while GCM and CCM need to work with 3 block ciphers already. From 9f06681cb4d44ea31d9200909dd075f80729d91f Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 7 Dec 2023 11:02:48 +0000 Subject: [PATCH 13/39] Update psa-thread-safety.md Signed-off-by: Ryan Everett --- docs/architecture/psa-thread-safety.md | 34 ++++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/docs/architecture/psa-thread-safety.md b/docs/architecture/psa-thread-safety.md index 0d03e324d5..79881a624a 100644 --- a/docs/architecture/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety.md @@ -281,28 +281,24 @@ Note that a thread must hold the global mutex when it reads or changes a slot's #### Slot states -For concurrency purposes, a slot can be in one of three states: +For concurrency purposes, a slot can be in one of four states: -* UNUSED: no thread is currently accessing the slot. It may be occupied by a volatile key or a cached key. -* WRITING: a thread has exclusive access to the slot. This can only happen in specific circumstances as detailed below. -* READING: any thread may read from the slot. +* EMPTY: no thread is currently accessing the slot, and no information is stored in the slot. +* FILLING: one thread is currently loading or creating material to fill the slot, this thread is responsible for the next state transition. +* FULL: the slot contains a key, and any thread is able to use the key after registering as a reader. +* PENDING_DELETION: the key within the slot has been destroyed or marked for destruction, but at least one thread is still registered as a reader. No thread can register to read this slot. The slot must not be wiped until the last reader de-registers, wiping the slot by calling `psa_wipe_key_slot`. -A high-level view of state transitions: +To change `slot` to state `new_state`, a function must call `psa_slot_state_transition(slot, new_state)`. -* `psa_get_empty_key_slot`: UNUSED → WRITING. -* `psa_get_and_lock_key_slot_in_memory`: UNUSED or READING → READING. This function only accepts slots in the UNUSED or READING state. A slot with the correct id but in the WRITING state is considered free. -* `psa_unlock_key_slot`: READING → UNUSED or READING. -* `psa_finish_key_creation`: WRITING → READING. -* `psa_fail_key_creation`: WRITING → UNUSED. -* `psa_wipe_key_slot`: any → UNUSED. If the slot is READING or WRITING on entry, this function must wait until the writer or all readers have finished. (By the way, the WRITING state is possible if `mbedtls_psa_crypto_free` is called while a key creation is in progress.) See [“Destruction of a key in use”](#destruction-of-a-key-in-use). +A counter field within each slot keeps track of how many readers have registered. Library functions must call `psa_register_read` before reading the key data witin a slot, and `psa_unregister_read` after they have finished operating. -The current `state->lock_count` corresponds to the difference between UNUSED and READING: a slot is in use iff its lock count is nonzero, so `lock_count == 0` corresponds to UNUSED and `lock_count != 0` corresponds to READING. +Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. -There is currently no indication of when a slot is in the WRITING state. This only happens between a call to `psa_start_key_creation` and a call to one of `psa_finish_key_creation` or `psa_fail_key_creation`. This new state can be conveyed by a new boolean flag, or by setting `lock_count` to `~0`. +A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. This means that the linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. #### Destruction of a key in use -Problem: In [Key destruction long-term requirements](#key-destruction-long-term-requirements) we require that the key slot is destroyed (by `psa_wipe_key_slot`) even while it's in use (READING or WRITING). +Problem: In [Key destruction long-term requirements](#key-destruction-long-term-requirements) we require that the key slot is destroyed (by `psa_wipe_key_slot`) even while it's in use (FILLING or with at least one reader). How do we ensure that? This needs something more sophisticated than mutexes (concurrency number >2)! Even a per-slot mutex isn't enough (we'd need a reader-writer lock). @@ -310,11 +306,11 @@ Solution: after some team discussion, we've decided to rely on a new threading a ##### Mutex only -When calling `psa_wipe_key_slot` it is the callers responsibility to set the slot state to WRITING first. For most functions this is a clean UNUSED -> WRITING transition: psa_get_empty_key_slot, psa_get_and_lock_key_slot, psa_close_key, psa_purge_key. +When calling `psa_wipe_key_slot` it is the callers responsibility to set the slot state to PENDING_DELETION first. For most functions this is a clean {FULL, !has_readers} -> PENDING_DELETION transition: psa_get_empty_key_slot, psa_get_and_lock_key_slot, psa_close_key, psa_purge_key. `psa_wipe_all_key_slots` is only called from `mbedtls_psa_crypto_free`, here we will need to return an error as we won't be able to free the key store if a key is in use without compromising the state of the secure side. This is acceptable as an untrusted application cannot call `mbedtls_psa_crypto_free` in a crypto service. In a service integration, `mbedtls_psa_crypto_free` on the client cuts the communication with the crypto service. Also, this is the current behaviour. -`psa_destroy_key` marks the slot as deleted, deletes persistent keys and opaque keys and returns. This only works if drivers are protected by a mutex (and the persistent storage as well if needed). When the last reading operation finishes, it wipes the key slot. This will free the key ID, but the slot might be still in use. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. +`psa_destroy_key` marks the slot as deleted, deletes persistent keys and opaque keys and returns. This only works if drivers are protected by a mutex (and the persistent storage as well if needed).`psa_destroy_key` transfers to PENDING_DELETION as an intermediate state, then, when the last reading operation finishes, it wipes the key slot. This will free the key ID, but the slot might be still in use. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. These are serious limitations, but this can be implemented with mutexes only and arguably satisfies the [Key destruction short-term requirements](#key-destruction-short-term-requirements). @@ -329,9 +325,9 @@ We can't reuse the `lock_count` field to mark key slots deleted, as we still nee #### Condition variables -Clean UNUSED -> WRITING transition works as before. +Clean UNUSED -> PENDING_DELETION transition works as before. -`psa_wipe_all_key_slots` and `psa_destroy_key` mark the slot as deleted and go to sleep until the slot state becomes UNUSED. When waking up, they wipe the slot, and return. +`psa_wipe_all_key_slots` and `psa_destroy_key` mark the slot as deleted and go to sleep until the slot has no registered readers. When waking up, they wipe the slot, and return. If the slot is already marked as deleted the threads calling `psa_wipe_all_key_slots` and `psa_destroy_key` go to sleep until the deletion completes. To satisfy [Key destruction long-term requirements](#key-destruction-long-term-requirements) none of the threads may return from the call until the slot is deleted completely. This can be achieved by signalling them when the slot has already been wiped and ready for use, that is not marked for deletion anymore. To handle spurious wake-ups, these threads need to be able to tell whether the slot was already deleted. This is not trivial, because by the time the thread wakes up, theoretically the slot might be in any state. It might have been reused and maybe even marked for deletion again. @@ -354,7 +350,7 @@ Alternatively, protecting operation contexts can be left as the responsibility o #### Drivers -Each driver that hasn’t got the "thread_safe” property set has a dedicated mutex. +Each driver that hasn’t got the "thread_safe” property set has a dedicated mutex. Implementing "thread_safe” drivers depends on the condition variable protection in the key store, as we must guarantee that the core never starts the destruction of a key while there are operations in progress on it. From 1e9733c6a8d2505218801dd415065fdf34a8aa7c Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 7 Dec 2023 11:03:14 +0000 Subject: [PATCH 14/39] Add graph Signed-off-by: Ryan Everett --- .../key-slot-state-transitions.drawio | 183 ++++++++++++++++++ .../key-slot-state-transitions.jpg | Bin 0 -> 46583 bytes 2 files changed, 183 insertions(+) create mode 100644 docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio create mode 100644 docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg diff --git a/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio b/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio new file mode 100644 index 0000000000..5da2a7fcc9 --- /dev/null +++ b/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg b/docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebfadcb4963d39f59cc3f21d30a21a62ce1676c0 GIT binary patch literal 46583 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 literal 0 HcmV?d00001 From 204c8524422e3a2c9010b01f9427d3b8d1daf35d Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 7 Dec 2023 11:03:43 +0000 Subject: [PATCH 15/39] Move psa-thread-safety.md Signed-off-by: Ryan Everett --- docs/architecture/{ => psa-thread-safety}/psa-thread-safety.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/architecture/{ => psa-thread-safety}/psa-thread-safety.md (100%) diff --git a/docs/architecture/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md similarity index 100% rename from docs/architecture/psa-thread-safety.md rename to docs/architecture/psa-thread-safety/psa-thread-safety.md From b8c4254f449e263cac03c282f9a9b0124c54151c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 7 Dec 2023 12:12:39 +0100 Subject: [PATCH 16/39] Update cipher light -> block cipher definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../psa-migration/md-cipher-dispatch.md | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index f165b21e07..bc92d00b37 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -379,6 +379,8 @@ Those costs could be avoided by refactoring (parts of) Cipher, but that would pr - significant differences in how the `cipher.h` API is implemented between builds with the full Cipher or only a subset; - or more work to apply the simplifications to all of Cipher. +Prototyping both approaches showed better code size savings and cleaner code with a new internal module. + ## Specification ### MD light @@ -564,50 +566,32 @@ The architecture can be extended to support `MBEDTLS_PSA_CRYPTO_CLIENT` with a l * Compile-time dependencies: instead of checking `defined(MBEDTLS_PSA_CRYPTO_C)`, check `defined(MBEDTLS_PSA_CRYPTO_C) || defined(MBEDTLS_PSA_CRYPTO_CLIENT)`. * Implementers of `MBEDTLS_PSA_CRYPTO_CLIENT` will need to provide `psa_can_do_hash()` (or a more general function `psa_can_do`) alongside `psa_crypto_init()`. Note that at this point, it will become a public interface, hence we won't be able to change it at a whim. -### Cipher light +### Internal "block cipher" abstraction (Cipher light) #### Definition -**Note:** this definition is tentative an may be refined when implementing and -testing, based and what's needed by internal users of Cipher light. The new -config symbol will not be considered public so its definition may change. +The new module is automatically enabled in `build_info.h` by modules that need +it, namely: CCM, GCM, only when `CIPHER_C` is not available. Note: CCM and GCM +currently depend on the full `CIPHER_C` (enforced by `check_config.h`); this +hard dependency would be replaced by the above auto-enablement. -Cipher light will be automatically enabled in `build_info.h` by modules that -need it, namely: CCM, GCM. Note: CCM and GCM currently depend on the full -`CIPHER_C` (enforced by `check_config.h`); this hard dependency would be -replaced by the above auto-enablement. - -Cipher light includes: -- some info functions; -- support for block ciphers in ECB mode, encrypt only (note: in Cipher, "ECB" - means just one block, contrary to PSA); -- part of the streaming API for unauthenticated ciphers; -- only AES, Aria and Camellia. - -This excludes: -- the one-shot API for unauthenticated ciphers; -- the AEAD/KW API (both one-shot and streaming); -- support for stream ciphers; -- support for other modes of block ciphers (CBC, CTR, CFB, etc.); -- DES and variants (3DES). - -The following API functions, and supporting types, are candidates for -inclusion in the Cipher light API, with limited features as above: +The following API functions are offered: ``` -mbedtls_cipher_info_from_values -mbedtls_cipher_info_get_block_size - -mbedtls_cipher_init -mbedtls_cipher_setup -mbedtls_cipher_setkey -mbedtls_cipher_free - -mbedtls_cipher_update +void mbedtls_block_cipher_init(mbedtls_block_cipher_context_t *ctx); +void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx); +int mbedtls_block_cipher_setup(mbedtls_block_cipher_context_t *ctx, + mbedtls_cipher_id_t cipher_id); +int mbedtls_block_cipher_setkey(mbedtls_block_cipher_context_t *ctx, + const unsigned char *key, + unsigned key_bitlen); +int mbedtls_block_cipher_encrypt(mbedtls_block_cipher_context_t *ctx, + const unsigned char input[16], + unsigned char output[16]); ``` -Note: `mbedtls_cipher_info_get_block_size()` can be hard-coded to return 16, -as all three supported block ciphers have the same block size (DES was -excluded). +The only supported ciphers are AES, ARIA and Camellia. They are identified by +an `mbedtls_cipher_id_t` in the `setup()` function, because that's how they're +identifed by callers (GCM/CCM). #### Cipher light dual dispatch From 177a45f556f9a44f35c37a34926eefd45260def8 Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 7 Dec 2023 11:24:30 +0000 Subject: [PATCH 17/39] Small clarifications in documentation Signed-off-by: Ryan Everett --- docs/architecture/psa-thread-safety/psa-thread-safety.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 79881a624a..97273f3872 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -294,7 +294,7 @@ A counter field within each slot keeps track of how many readers have registered Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. -A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. This means that the linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. +A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. #### Destruction of a key in use @@ -310,7 +310,7 @@ When calling `psa_wipe_key_slot` it is the callers responsibility to set the slo `psa_wipe_all_key_slots` is only called from `mbedtls_psa_crypto_free`, here we will need to return an error as we won't be able to free the key store if a key is in use without compromising the state of the secure side. This is acceptable as an untrusted application cannot call `mbedtls_psa_crypto_free` in a crypto service. In a service integration, `mbedtls_psa_crypto_free` on the client cuts the communication with the crypto service. Also, this is the current behaviour. -`psa_destroy_key` marks the slot as deleted, deletes persistent keys and opaque keys and returns. This only works if drivers are protected by a mutex (and the persistent storage as well if needed).`psa_destroy_key` transfers to PENDING_DELETION as an intermediate state, then, when the last reading operation finishes, it wipes the key slot. This will free the key ID, but the slot might be still in use. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. +`psa_destroy_key` registers as a reader, marks the slot as deleted, deletes persistent keys and opaque keys and unregisters before returning. This will free the key ID, but the slot might be still in use. This only works if drivers are protected by a mutex (and the persistent storage as well if needed). `psa_destroy_key` transfers to PENDING_DELETION as an intermediate state. The last reading operation will wipe the key slot upon unregistering. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. These are serious limitations, but this can be implemented with mutexes only and arguably satisfies the [Key destruction short-term requirements](#key-destruction-short-term-requirements). From 4dde0b293cd84dd0eff77a1d6d5f9387a55f8a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 14 Dec 2023 12:09:38 +0100 Subject: [PATCH 18/39] md-cipher-dispatch: editorial improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a typo, add a reference. Signed-off-by: Manuel Pégourié-Gonnard --- docs/architecture/psa-migration/md-cipher-dispatch.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md index bc92d00b37..430b0caec9 100644 --- a/docs/architecture/psa-migration/md-cipher-dispatch.md +++ b/docs/architecture/psa-migration/md-cipher-dispatch.md @@ -358,7 +358,7 @@ The two AEAD modes, GCM and CCM, have very similar needs and positions in the st - CTR-DRBG holds a special position in the stack: most users don't care about it per se, they only care about getting random numbers - in fact PSA users don't even need to know what DRBG is used. In particular, no part of the stack is asking questions like "is CTR-DRBG-AES available?" - an RNG needs to be available and that's it - contrary to similar questions about AES-GCM etc. which are asked for example by TLS. So, it makes sense to use different designs for CTR-DRBG on one hand, and GCM/CCM on the other hand: -- CTR-DRBG can just check if `AES_C` is present and "fall back" to PSA is not. +- CTR-DRBG can just check if `AES_C` is present and "fall back" to PSA if not. - GCM and CCM need an common abstraction layer that allows: - Using AES, Aria or Camellia in a uniform way. - Dispatching to built-in or driver. @@ -379,7 +379,7 @@ Those costs could be avoided by refactoring (parts of) Cipher, but that would pr - significant differences in how the `cipher.h` API is implemented between builds with the full Cipher or only a subset; - or more work to apply the simplifications to all of Cipher. -Prototyping both approaches showed better code size savings and cleaner code with a new internal module. +Prototyping both approaches showed better code size savings and cleaner code with a new internal module (see section "Internal "block cipher" abstraction (Cipher light)" below). ## Specification From b461b8731c5549c5fb5e20f4c7f80ab8e40973bd Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 14 Dec 2023 14:40:36 +0000 Subject: [PATCH 19/39] Change how the state transition diagram is stored Store the source of the diagram as a url instead of an xml file. Signed-off-by: Ryan Everett --- .../key-slot-state-transitions.drawio | 183 ------------------ .../psa-thread-safety/psa-thread-safety.md | 6 + 2 files changed, 6 insertions(+), 183 deletions(-) delete mode 100644 docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio diff --git a/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio b/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio deleted file mode 100644 index 5da2a7fcc9..0000000000 --- a/docs/architecture/psa-thread-safety/key-slot-state-transitions.drawio +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 97273f3872..d8256b5c5a 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -296,6 +296,12 @@ Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if t A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. +#### Generating the state transition diagram from source + +To generate the state transition diagram in https://app.diagrams.net/, open the following url: + +https://viewer.diagrams.net/?tags=%7B%7D&highlight=FFFFFF&edit=_blank&layers=1&nav=1&title=key-slot-state-transitions#R5Vxbd5s4EP4t%2B%2BDH5iAJcXms4ySbrdtNT7qX9MWHgGyrxcABHNv59SsM2EhgDBhs3PVL0CANoBl9fDMaMkC3i%2FWDb3jzz65F7AGUrPUAjQYQAqBh9ieSbGKJIqFYMPOplXTaC57pO0mEUiJdUosEXMfQde2QerzQdB2HmCEnM3zfXfHdpq7NX9UzZiQneDYNOy%2F9h1rhPJZqUN3Lfyd0Nk%2BvDBQ9PrMw0s7JkwRzw3JXGRG6G6Bb33XD%2BGixviV2NHnpvMTj7g%2Bc3d2YT5ywyoDv4H08%2Ffvxj9VX3XGGw5cf3o9PHxJjvBn2MnngAVRspm9o0Td2OIsO7%2F8aj1Mx0585U9B5bgQTnxgW8YP07Ksv9he1bOcn3KSTzm6c2Zc1hqs5DcmzZ5jRmRVzsegK4cJmLcAOjcCLjT6la2LtVGUnJZmnN%2BKHZJ0RJZP0QNwFCf0N65KclbXEYDuPTdqrjP0T0Txj%2BlRmJB4322neG4UdJHapYSMACowkzphjfYy8nbVM2wgCavIT5btLx4pmaCSxFpscf%2FNvcmrbeMk2Rutsv9Emba1puBvEjl8y8v2QqJGOOGiNwF36Jjnul6Hhz0hY0k%2BO%2BxGLW8V522Zshwtsl8p8YhshfePXfpFBkys8uZQ92UHXwYrgE%2FFzJ6Oya1VUpOo3euancWplJKiNpymnduttu0k4wQFhzgGXjk9mNAiJv13seX9kBhkbr%2BxlwK9Xm86cyEeZQxCfCaJlSRnafkxOLKhlRTqGPgnou%2FG61Re5khc93PZx8XCAR4XOVb56RADYvTOSq3CwXAQM0g2UVJ2zxAd4mt%2BkaoAwxJ1OA9KNLasA%2Ft3np28v14nevQNvvXXwTmBYysAwKIXhHdxLWbiXjsB9c%2FCGFcEb9Au8ec%2FJgWxl7D7yDugYrFO6mXE4LzAmU4Pak59kMzEZXofUdfoM2ema6SNkJ5ohp1Qc3x1%2B51%2FF94%2Fj8eOXh17DMFIuDMNyldderTjnt18u0Lm4kXAVIz3dfRlt3b2inUZ347tvj39%2BuU4b9Y7PqF3RmepRZbPotTmdSdNOx%2BgM7BWdgRJ7%2BWkyVAGLJmWs8G9BLCs3KsAq1FTMGkhQX5XrAEUgTfJ5yY5WyHXYFSdk4YWbLeEJbDfsMdlJF1Qfuc5OjXwuegOKXtTt48sNbhIwxaMuGjL1K98VYYwkpRijMDjg0QBEWawUZJAmqc1QRpYElGG%2BjgSX7DoFVow0U%2BrQYH41cVW6uE7Gmg%2FM7rKu8mCDWvEpRSvUegboKaKfgi3Npf%2B2RZaYbZwv51492dMcg6rm3FGvMEhWMecwitowb4MVQZHIoQ9ADPMBY5PplizPwzes82imSlL5fUGhPzjSX9bK9LOD%2BI6bLp7RUDYBfTA9%2B50sH%2Bkz%2Fvi0rha6CVsGFQO4lNEZjjWxXfNnhtTV0GDabkCiobVGeUtm8uyo%2BtFjf9A%2FtVEb6A%2BQxntZO1k1nr5CfC7sR0X74K3QzixwVwxrMzyz2zy9XBHw%2B5WnhyrkvATjhoAPDuVWzsQpUVGsUwhDFglC392cDl%2FNoPKKQW%2B3sFsIr2VN4eObdGGc6NA7ZN5wINg96smXYLzH4Kw%2BcB4EwJ4AFiN8mVwb0gBnbaSCorO12ausZtJ9CtDrXKQjZouQVn7P4l2iI8wWl%2BrvhtnmCyaup%2FZFbo3ysXgfC47bEvh1kVosNGT7OxeXxrfWCB7sFV4iIeDFTSsxkCrkDStG9G153HXtTpQumlZiRl3YhGqLPqV5zS5ThoWzc5barsqbFTwMdbhZUTVRiHsNKwpoCitChZfSXTluMSMprvDigsTeAkprpV0RoECekbQVj%2FH7Gl2UdhXb9Ux1%2FsehoQkMNYcTXBFO%2BhXVwQNp%2BdpwAgWWonRXMFrsdrDA7XKJoVzQUyOhtKIeyWXtryOpVL5Q26jZ2H0h1y6IAXQhEMuT3pwlz55TOohNfcESIXHSeA8TbbNAGpahrMs6RBoS9XL1GrAS0NRNA7GnyV4F6PxNqBK6UaG0%2B6HyJwJ6qTIA6ijDze%2Bso%2BxSPoToZXqpfK3%2Fz9JLT3S5Hk%2FhRNNmX9%2B%2B338yHccr%2FLCqHfLGFaE1%2BkizM%2BpWtTS2X2VrSKgnw2JeqDLc4iOZqvaoW6HPVWJuEQOzXcOaeMQPIlxxwi0ZY%2Ffk1q%2Bj2Gp6XVI7pM4JakoLOq6DGpaiQAuIiGVQGIie6Pxnq6mAl6wJqu9Cv9g3mFVT%2F1WL%2Bfa74OmW%2Brk2T%2Fnkbu4Lg8pFxIKiqtUee0WnLBnW3P%2Bnj7j7%2Fv%2BloLv%2FAA%3D%3D + #### Destruction of a key in use Problem: In [Key destruction long-term requirements](#key-destruction-long-term-requirements) we require that the key slot is destroyed (by `psa_wipe_key_slot`) even while it's in use (FILLING or with at least one reader). From 3b9de382084bb7a8b5f8735457844b0048e51748 Mon Sep 17 00:00:00 2001 From: Wenxing Hou Date: Thu, 14 Dec 2023 16:22:01 +0800 Subject: [PATCH 20/39] Make clienthello comment clear Signed-off-by: Wenxing Hou --- library/ssl_tls12_server.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index 923b093af9..96b65f8b0e 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -1128,11 +1128,11 @@ read_record_header: msg_len -= mbedtls_ssl_hs_hdr_len(ssl); /* - * ClientHello layer: + * ClientHello layout: * 0 . 1 protocol version * 2 . 33 random bytes (starting with 4 bytes of Unix time) - * 34 . 35 session id length (1 byte) - * 35 . 34+x session id + * 34 . 34 session id length (1 byte) + * 35 . 34+x session id, where x = session id length from byte 34 * 35+x . 35+x DTLS only: cookie length (1 byte) * 36+x . .. DTLS only: cookie * .. . .. ciphersuite list length (2 bytes) From 3eb4274a57cbfc514e9dcb36bf92fc231dcf9e1e Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 14 Dec 2023 14:47:03 +0000 Subject: [PATCH 21/39] Fix transitions in diagram Move the finish_key_creation transition Neaten the diagram Add transitions for the key loading functions in psa_get_and_lock_key_slot Add psa_wipe_key_slot transition Change file to be a png Signed-off-by: Ryan Everett --- .../key-slot-state-transitions.jpg | Bin 46583 -> 0 bytes .../key-slot-state-transitions.png | Bin 0 -> 70492 bytes .../psa-thread-safety/psa-thread-safety.md | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg create mode 100644 docs/architecture/psa-thread-safety/key-slot-state-transitions.png diff --git a/docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg b/docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg deleted file mode 100644 index ebfadcb4963d39f59cc3f21d30a21a62ce1676c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46583 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 diff --git a/docs/architecture/psa-thread-safety/key-slot-state-transitions.png b/docs/architecture/psa-thread-safety/key-slot-state-transitions.png new file mode 100644 index 0000000000000000000000000000000000000000..34cc79b3596d12a8e4e98ffbbb5620d0c7c8221c GIT binary patch literal 70492 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 Date: Thu, 14 Dec 2023 14:48:43 +0000 Subject: [PATCH 22/39] Add some clarifications in thread_safety.md Make it clearer how it is possible to reason here using linearization Signed-off-by: Ryan Everett --- docs/architecture/psa-thread-safety/psa-thread-safety.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 8e9f59e302..f7452a5e19 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -292,9 +292,11 @@ To change `slot` to state `new_state`, a function must call `psa_slot_state_tran A counter field within each slot keeps track of how many readers have registered. Library functions must call `psa_register_read` before reading the key data witin a slot, and `psa_unregister_read` after they have finished operating. +Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_read` must be performed by a function which holds the global mutex. + Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. -A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.jpg. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. +A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.png. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. (A function which: locks the global mutex, performs some operation, calls `psa_slot_state_transition` and then unlocks the global mutex, cleans up and returns can satisfy this requirement). #### Generating the state transition diagram from source @@ -316,7 +318,7 @@ When calling `psa_wipe_key_slot` it is the callers responsibility to set the slo `psa_wipe_all_key_slots` is only called from `mbedtls_psa_crypto_free`, here we will need to return an error as we won't be able to free the key store if a key is in use without compromising the state of the secure side. This is acceptable as an untrusted application cannot call `mbedtls_psa_crypto_free` in a crypto service. In a service integration, `mbedtls_psa_crypto_free` on the client cuts the communication with the crypto service. Also, this is the current behaviour. -`psa_destroy_key` registers as a reader, marks the slot as deleted, deletes persistent keys and opaque keys and unregisters before returning. This will free the key ID, but the slot might be still in use. This only works if drivers are protected by a mutex (and the persistent storage as well if needed). `psa_destroy_key` transfers to PENDING_DELETION as an intermediate state. The last reading operation will wipe the key slot upon unregistering. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. +`psa_destroy_key` registers as a reader, marks the slot as deleted, deletes persistent keys and opaque keys and unregisters before returning. This will free the key ID, but the slot might be still in use. This only works if drivers are protected by a mutex (and the persistent storage as well if needed). `psa_destroy_key` transfers to PENDING_DELETION as an intermediate state. The last reading operation will wipe the key slot upon unregistering. In case of volatile keys freeing up the ID while the slot is still in use does not provide any benefit and we don't need to do it. These are serious limitations, but this can be implemented with mutexes only and arguably satisfies the [Key destruction short-term requirements](#key-destruction-short-term-requirements). From 6ecb9ce5fc7d77b6d54881ab4d78231fb4784f98 Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Thu, 14 Dec 2023 14:54:24 +0000 Subject: [PATCH 23/39] Link directly to the state transition diagram Signed-off-by: Ryan Everett --- docs/architecture/psa-thread-safety/psa-thread-safety.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index f7452a5e19..4b122c838f 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -296,7 +296,9 @@ Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_ Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. -A state transition diagram can be found in docs/architecture/psa-thread-safety/key-slot-state-transitions.png. In this diagram, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. (A function which: locks the global mutex, performs some operation, calls `psa_slot_state_transition` and then unlocks the global mutex, cleans up and returns can satisfy this requirement). +![](key-slot-state-transitions.png) + +In the state transition diagram above, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. (A function which: locks the global mutex, performs some operation, calls `psa_slot_state_transition` and then unlocks the global mutex, cleans up and returns can satisfy this requirement). #### Generating the state transition diagram from source From c1c6e0d906664103438fe96e7fc09c4f7a5e6d70 Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Fri, 15 Dec 2023 12:26:38 +0000 Subject: [PATCH 24/39] Justify linearization points Signed-off-by: Ryan Everett --- .../psa-thread-safety/psa-thread-safety.md | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 4b122c838f..70b1341c23 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -283,8 +283,8 @@ Note that a thread must hold the global mutex when it reads or changes a slot's For concurrency purposes, a slot can be in one of four states: -* EMPTY: no thread is currently accessing the slot, and no information is stored in the slot. -* FILLING: one thread is currently loading or creating material to fill the slot, this thread is responsible for the next state transition. +* EMPTY: no thread is currently accessing the slot, and no information is stored in the slot. Any thread is able to change the slot's state to FILLING and begin loading data. +* FILLING: one thread is currently loading or creating material to fill the slot, this thread is responsible for the next state transition. Other threads cannot read the contents of a slot which is in FILLING. * FULL: the slot contains a key, and any thread is able to use the key after registering as a reader. * PENDING_DELETION: the key within the slot has been destroyed or marked for destruction, but at least one thread is still registered as a reader. No thread can register to read this slot. The slot must not be wiped until the last reader de-registers, wiping the slot by calling `psa_wipe_key_slot`. @@ -292,15 +292,32 @@ To change `slot` to state `new_state`, a function must call `psa_slot_state_tran A counter field within each slot keeps track of how many readers have registered. Library functions must call `psa_register_read` before reading the key data witin a slot, and `psa_unregister_read` after they have finished operating. -Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_read` must be performed by a function which holds the global mutex. +Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_read` must be performed by a thread which holds the global mutex. + +##### Linearizability of the system + +To satisfy the requirements in [Correctness out of the box](#correctness-out-of-the-box), we require our functions to be "linearizable" (under certain constraints). This means that any (constraint satisfying) set of concurrent calls are performed as if they were executed in some sequential order. + +The standard way of reasoning that this is the case is to identify a "linearization point" for each call, this is a single execution step where the function takes effect (this is usually a step in which the effects of the call become visible to other threads). If every call has a linearization point, the set of calls is equivalent to sequentially performing the calls in order of when their linearization point occured. + +We only access and modify a slot's state and reader count while we hold the global lock. This ensures the memory in which these fields are stored is correctly synchronized. It also ensures that the key data within the slot is synchronised where needed (the writer unlocks the mutex after filling the data, and any reader must lock the mutex before reading the data). + +To help justify that our system is linearizable, here is a list of key slot state changing functions and their linearization points (for the sake of brevity not all failure cases are covered, but those cases are not complex): +* `psa_wipe_key_slot, psa_register_read, psa_unregister_read, psa_slot_state_transition,` - These functions are all always performed under the global mutex, so they have no effects visible to other threads (this implies that they are linearizable). +* `psa_get_empty_key_slot, psa_get_and_lock_key_slot_in_memory, psa_load_X_key_into_slot, psa_fail_key_creation` - These functions hold the mutex for all non-setup/finalizing code, their linearization points are the release of the mutex. +* `psa_get_and_lock_key_slot` - If the key is already in a slot, the linearization point is the linearization point of the call to `psa_get_and_lock_key_slot_in_memory`. If the key is not in a slot and is loaded into one, the linearization point is the linearization point of the call to `psa_load_X_key_into_slot`. +* `psa_finish_key_creation` - On a successful load, we lock the mutex and set the state of the slot to FULL, the linearization point is then the following unlock. On an unsuccessful load, the linearization point is when we return - no action we have performed has been made visible to another thread as the slot is still in a FILLING state. +* `psa_destroy_key, psa_close_key, psa_purge_key` - As per the requirements, we need only argue for the case where the key is not in use here. The linearization point is the unlock after wiping the data and setting the slot state to EMPTY. Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. +##### Key slot state transition diagram + ![](key-slot-state-transitions.png) -In the state transition diagram above, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. The linearization point of a state changing call to a function must be a call to `psa_slot_state_transition`. (A function which: locks the global mutex, performs some operation, calls `psa_slot_state_transition` and then unlocks the global mutex, cleans up and returns can satisfy this requirement). +In the state transition diagram above, an arrow between two states `q1` and `q2` with label `f` indicates that if the state of a slot is `q1` immediately before `f`'s linearization point, it may be `q2` immediately after `f`'s linearization point. -#### Generating the state transition diagram from source +##### Generating the key slot state transition diagram from source To generate the state transition diagram in https://app.diagrams.net/, open the following url: From abd8977cc15a460213afc72a02aeca778c3f2bfd Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Fri, 15 Dec 2023 12:28:38 +0000 Subject: [PATCH 25/39] Make check_files ignore png files in docs Signed-off-by: Ryan Everett --- tests/scripts/check_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/scripts/check_files.py b/tests/scripts/check_files.py index a2a9dfa8d0..a93b8256f0 100755 --- a/tests/scripts/check_files.py +++ b/tests/scripts/check_files.py @@ -105,6 +105,7 @@ class FileIssueTracker: BINARY_FILE_PATH_RE_LIST = [ r'docs/.*\.pdf\Z', + r'docs/.*\.png\Z', r'programs/fuzz/corpuses/[^.]+\Z', r'tests/data_files/[^.]+\Z', r'tests/data_files/.*\.(crt|csr|db|der|key|pubkey)\Z', From f5e135670b5e86e38e20b62beac9942cb982ac58 Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Wed, 20 Dec 2023 15:24:47 +0000 Subject: [PATCH 26/39] Clarify key generation and memory-management correctness Signed-off-by: Ryan Everett --- .../psa-thread-safety/psa-thread-safety.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 70b1341c23..075c8c4e37 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -29,7 +29,7 @@ Tempting platform requirements that we cannot add to the default `MBEDTLS_THREAD If you build with `MBEDTLS_PSA_CRYPTO_C` and `MBEDTLS_THREADING_C`, the code must be functionally correct: no race conditions, deadlocks or livelocks. -The [PSA Crypto API specification](https://armmbed.github.io/mbed-crypto/html/overview/conventions.html#concurrent-calls) defines minimum expectations for concurrent calls. They must work as if they had been executed one at a time, except that the following cases have undefined behavior: +The [PSA Crypto API specification](https://armmbed.github.io/mbed-crypto/html/overview/conventions.html#concurrent-calls) defines minimum expectations for concurrent calls. They must work as if they had been executed one at a time (excluding resource-management errors), except that the following cases have undefined behavior: * Destroying a key while it's in use. * Concurrent calls using the same operation object. (An operation object may not be used by more than one thread at a time. But it can move from one thread to another between calls.) @@ -290,7 +290,7 @@ For concurrency purposes, a slot can be in one of four states: To change `slot` to state `new_state`, a function must call `psa_slot_state_transition(slot, new_state)`. -A counter field within each slot keeps track of how many readers have registered. Library functions must call `psa_register_read` before reading the key data witin a slot, and `psa_unregister_read` after they have finished operating. +A counter field within each slot keeps track of how many readers have registered. Library functions must call `psa_register_read` before reading the key data within a slot, and `psa_unregister_read` after they have finished operating. Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_read` must be performed by a thread which holds the global mutex. @@ -298,7 +298,9 @@ Any call to `psa_slot_state_transition`, `psa_register_read` or `psa_unregister_ To satisfy the requirements in [Correctness out of the box](#correctness-out-of-the-box), we require our functions to be "linearizable" (under certain constraints). This means that any (constraint satisfying) set of concurrent calls are performed as if they were executed in some sequential order. -The standard way of reasoning that this is the case is to identify a "linearization point" for each call, this is a single execution step where the function takes effect (this is usually a step in which the effects of the call become visible to other threads). If every call has a linearization point, the set of calls is equivalent to sequentially performing the calls in order of when their linearization point occured. +The standard way of reasoning that this is the case is to identify a "linearization point" for each call, this is a single execution step where the function takes effect (this is usually a step in which the effects of the call become visible to other threads). If every call has a linearization point, the set of calls is equivalent to sequentially performing the calls in order of when their linearization point occurred. + +We only require linearizability to hold in the case where a resource-management error is not returned. In a set of concurrent calls, it is permitted for a call c to fail with a PSA_ERROR_INSUFFICIENT_MEMORY return code even if there does not exist a sequential ordering of the calls in which c returns this error. We only access and modify a slot's state and reader count while we hold the global lock. This ensures the memory in which these fields are stored is correctly synchronized. It also ensures that the key data within the slot is synchronised where needed (the writer unlocks the mutex after filling the data, and any reader must lock the mutex before reading the data). @@ -306,8 +308,11 @@ To help justify that our system is linearizable, here is a list of key slot stat * `psa_wipe_key_slot, psa_register_read, psa_unregister_read, psa_slot_state_transition,` - These functions are all always performed under the global mutex, so they have no effects visible to other threads (this implies that they are linearizable). * `psa_get_empty_key_slot, psa_get_and_lock_key_slot_in_memory, psa_load_X_key_into_slot, psa_fail_key_creation` - These functions hold the mutex for all non-setup/finalizing code, their linearization points are the release of the mutex. * `psa_get_and_lock_key_slot` - If the key is already in a slot, the linearization point is the linearization point of the call to `psa_get_and_lock_key_slot_in_memory`. If the key is not in a slot and is loaded into one, the linearization point is the linearization point of the call to `psa_load_X_key_into_slot`. +* `psa_start_key_creation` - From the perspective of other threads, the only effect of a successful call to this function is that the amount of usable resources decreases (a key slot which was usable is now unusable). Since we do not consider resource management as linearizable behaviour, when arguing for linearizability of the system we consider this function to have no visible effect to other threads. * `psa_finish_key_creation` - On a successful load, we lock the mutex and set the state of the slot to FULL, the linearization point is then the following unlock. On an unsuccessful load, the linearization point is when we return - no action we have performed has been made visible to another thread as the slot is still in a FILLING state. * `psa_destroy_key, psa_close_key, psa_purge_key` - As per the requirements, we need only argue for the case where the key is not in use here. The linearization point is the unlock after wiping the data and setting the slot state to EMPTY. +* `psa_import_key, psa_copy_key, psa_generate_key, mbedtls_psa_register_se_key` - These functions call both `psa_start_key_creation` and `psa_finish_key_creation`, the linearization point of a successful call is the linearization point of the call to `psa_finish_key_creation`. The linearization point of an unsuccessful call is the linearization point of the call to `psa_fail_key_creation`. +* `psa_key_derivation_output_key` - Same as above. If the operation object is in use by multiple threads, the behaviour need not be linearizable. Library functions which operate on a slot will return `PSA_ERROR_BAD_STATE` if the slot is in an inappropriate state for the function at the linearization point. From 3dd6cde0d819a9eb45f3be694fbf72e70d54cceb Mon Sep 17 00:00:00 2001 From: Ryan Everett Date: Wed, 20 Dec 2023 16:45:31 +0000 Subject: [PATCH 27/39] Mention functional correctness explicitly Signed-off-by: Ryan Everett --- docs/architecture/psa-thread-safety/psa-thread-safety.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/psa-thread-safety/psa-thread-safety.md b/docs/architecture/psa-thread-safety/psa-thread-safety.md index 075c8c4e37..dc5d7e1894 100644 --- a/docs/architecture/psa-thread-safety/psa-thread-safety.md +++ b/docs/architecture/psa-thread-safety/psa-thread-safety.md @@ -300,7 +300,7 @@ To satisfy the requirements in [Correctness out of the box](#correctness-out-of- The standard way of reasoning that this is the case is to identify a "linearization point" for each call, this is a single execution step where the function takes effect (this is usually a step in which the effects of the call become visible to other threads). If every call has a linearization point, the set of calls is equivalent to sequentially performing the calls in order of when their linearization point occurred. -We only require linearizability to hold in the case where a resource-management error is not returned. In a set of concurrent calls, it is permitted for a call c to fail with a PSA_ERROR_INSUFFICIENT_MEMORY return code even if there does not exist a sequential ordering of the calls in which c returns this error. +We only require linearizability to hold in the case where a resource-management error is not returned. In a set of concurrent calls, it is permitted for a call c to fail with a PSA_ERROR_INSUFFICIENT_MEMORY return code even if there does not exist a sequential ordering of the calls in which c returns this error. Even if such an error occurs, all calls are still required to be functionally correct. We only access and modify a slot's state and reader count while we hold the global lock. This ensures the memory in which these fields are stored is correctly synchronized. It also ensures that the key data within the slot is synchronised where needed (the writer unlocks the mutex after filling the data, and any reader must lock the mutex before reading the data). From afccc1a6d5fb0fa7133928bca27f3e9faa302a5e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 09:35:34 +0100 Subject: [PATCH 28/39] Indent nested conditionals Signed-off-by: Gilles Peskine --- programs/Makefile | 21 +++++++++++---------- tests/Makefile | 27 ++++++++++++++------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/programs/Makefile b/programs/Makefile index ebdadc0567..c0856b0ac3 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -43,16 +43,17 @@ WINDOWS_BUILD=1 endif ifdef WINDOWS_BUILD -DLEXT=dll -EXEXT=.exe -LOCAL_LDFLAGS += -lws2_32 -lbcrypt -ifdef SHARED -SHARED_SUFFIX=.$(DLEXT) -endif -else -DLEXT ?= so -EXEXT= -SHARED_SUFFIX= + DLEXT=dll + EXEXT=.exe + LOCAL_LDFLAGS += -lws2_32 -lbcrypt + ifdef SHARED + SHARED_SUFFIX=.$(DLEXT) + endif + +else # Not building for Windows + DLEXT ?= so + EXEXT= + SHARED_SUFFIX= endif ifdef WINDOWS diff --git a/tests/Makefile b/tests/Makefile index 29197b7c71..b044d2522c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -47,20 +47,21 @@ WINDOWS_BUILD=1 endif ifdef WINDOWS_BUILD -DLEXT=dll -EXEXT=.exe -LOCAL_LDFLAGS += -lws2_32 -lbcrypt -ifdef SHARED -SHARED_SUFFIX=.$(DLEXT) -endif -else -DLEXT ?= so -EXEXT= -SHARED_SUFFIX= + DLEXT=dll + EXEXT=.exe + LOCAL_LDFLAGS += -lws2_32 -lbcrypt + ifdef SHARED + SHARED_SUFFIX=.$(DLEXT) + endif -ifeq ($(THREADING),pthread) -LOCAL_LDFLAGS += -lpthread -endif +else # Not building for Windows + DLEXT ?= so + EXEXT= + SHARED_SUFFIX= + + ifeq ($(THREADING),pthread) + LOCAL_LDFLAGS += -lpthread + endif endif ifdef WINDOWS From 4ad5733836fcdcaf95e48b6578e017673378f247 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:30:30 +0100 Subject: [PATCH 29/39] Unify treatment of MBEDTLS_TEST_OBJS Unify the treatment of MBEDTLS_TEST_OBJS between programs/Makefile and tests/Makefile: include it via LOCAL_LD_FLAGS in both cases. Document why the definition of MBEDTLS_TEST_OBJS is different. Signed-off-by: Gilles Peskine --- programs/Makefile | 6 ++++-- tests/Makefile | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/programs/Makefile b/programs/Makefile index c0856b0ac3..dc6b7a3d66 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -6,8 +6,10 @@ WARNING_CFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral WARNING_CXXFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral LDFLAGS ?= -MBEDTLS_TEST_PATH:=../tests/src -MBEDTLS_TEST_OBJS:=$(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/*.c ${MBEDTLS_TEST_PATH}/drivers/*.c)) +MBEDTLS_TEST_PATH = ../tests +# Support code used by test programs and test builds, excluding TLS-specific +# code which is in the src/test_helpers subdirectory. +MBEDTLS_TEST_OBJS = $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/*.c ${MBEDTLS_TEST_PATH}/src/drivers/*.c)) LOCAL_CFLAGS = $(WARNING_CFLAGS) -I../tests/include -I../include -D_FILE_OFFSET_BITS=64 LOCAL_CXXFLAGS = $(WARNING_CXXFLAGS) -I../include -I../tests/include -D_FILE_OFFSET_BITS=64 diff --git a/tests/Makefile b/tests/Makefile index b044d2522c..3caa88e2f4 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -14,7 +14,8 @@ default: all # from ./include, and private header files (used by some invasive tests) # from ../library. LOCAL_CFLAGS = $(WARNING_CFLAGS) -I./include -I../include -I../library -D_FILE_OFFSET_BITS=64 -LOCAL_LDFLAGS = -L../library \ +LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ + -L../library \ -lmbedtls$(SHARED_SUFFIX) \ -lmbedx509$(SHARED_SUFFIX) \ -lmbedcrypto$(SHARED_SUFFIX) @@ -175,7 +176,9 @@ all: $(BINARIES) $(MBEDLIBS): $(MAKE) -C ../library -MBEDTLS_TEST_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/drivers/*.c src/test_helpers/*.c)) +MBEDTLS_TEST_PATH = . +MBEDTLS_TEST_OBJS = $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/*.c ${MBEDTLS_TEST_PATH}/src/drivers/*.c)) +MBEDTLS_TEST_OBJS += $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/test_helpers/*.c)) mbedtls_test: $(MBEDTLS_TEST_OBJS) @@ -231,7 +234,7 @@ c: $(C_FILES) $(BINARIES): %$(EXEXT): %.c $(MBEDLIBS) $(TEST_OBJS_DEPS) $(MBEDTLS_TEST_OBJS) echo " CC $<" - $(CC) $(LOCAL_CFLAGS) $(CFLAGS) $< $(MBEDTLS_TEST_OBJS) $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@ + $(CC) $(LOCAL_CFLAGS) $(CFLAGS) $< $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@ clean: ifndef WINDOWS From f5c5ce7789f23960b9e1aadef47d9c6aa9338d7c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:36:53 +0100 Subject: [PATCH 30/39] Partly unify LOCAL_CFLAGS Signed-off-by: Gilles Peskine --- programs/Makefile | 2 +- tests/Makefile | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/programs/Makefile b/programs/Makefile index dc6b7a3d66..9ee9820b7a 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -11,7 +11,7 @@ MBEDTLS_TEST_PATH = ../tests # code which is in the src/test_helpers subdirectory. MBEDTLS_TEST_OBJS = $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/*.c ${MBEDTLS_TEST_PATH}/src/drivers/*.c)) -LOCAL_CFLAGS = $(WARNING_CFLAGS) -I../tests/include -I../include -D_FILE_OFFSET_BITS=64 +LOCAL_CFLAGS = $(WARNING_CFLAGS) -I$(MBEDTLS_TEST_PATH)/include -I../include -D_FILE_OFFSET_BITS=64 LOCAL_CXXFLAGS = $(WARNING_CXXFLAGS) -I../include -I../tests/include -D_FILE_OFFSET_BITS=64 LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ -L../library \ diff --git a/tests/Makefile b/tests/Makefile index 3caa88e2f4..f242b51576 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,10 +10,9 @@ TEST_FLAGS ?= $(if $(filter-out 0 OFF Off off NO No no FALSE False false N n,$(C default: all -# Include public header files from ../include, test-specific header files -# from ./include, and private header files (used by some invasive tests) -# from ../library. -LOCAL_CFLAGS = $(WARNING_CFLAGS) -I./include -I../include -I../library -D_FILE_OFFSET_BITS=64 +LOCAL_CFLAGS = $(WARNING_CFLAGS) -I$(MBEDTLS_TEST_PATH)/include -I../include -D_FILE_OFFSET_BITS=64 +# Also include library headers, for the sake of invasive tests. +LOCAL_CFLAGS += -I../library LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ -L../library \ -lmbedtls$(SHARED_SUFFIX) \ From f3d1ae1f0502ecb1a3555bb3fb4ee408231dae54 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:40:58 +0100 Subject: [PATCH 31/39] Create common.make with LOCAL_CFLAGS and friends Create a common.make for definitions that are shared between tests/Makefile and programs/Makefile, to facilitate maintenance. Start populating it with CFLAGS/LDFLAGS variables. More to follow in subsequent commits. Keep library/Makefile independent, at least for the time being. Signed-off-by: Gilles Peskine --- programs/Makefile | 15 +-------------- scripts/common.make | 14 ++++++++++++++ tests/Makefile | 13 +------------ 3 files changed, 16 insertions(+), 26 deletions(-) create mode 100644 scripts/common.make diff --git a/programs/Makefile b/programs/Makefile index 9ee9820b7a..b03e613b4c 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -1,23 +1,10 @@ - -# To compile on SunOS: add "-lsocket -lnsl" to LDFLAGS - -CFLAGS ?= -O2 -WARNING_CFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -WARNING_CXXFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -LDFLAGS ?= - MBEDTLS_TEST_PATH = ../tests # Support code used by test programs and test builds, excluding TLS-specific # code which is in the src/test_helpers subdirectory. MBEDTLS_TEST_OBJS = $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/*.c ${MBEDTLS_TEST_PATH}/src/drivers/*.c)) LOCAL_CFLAGS = $(WARNING_CFLAGS) -I$(MBEDTLS_TEST_PATH)/include -I../include -D_FILE_OFFSET_BITS=64 -LOCAL_CXXFLAGS = $(WARNING_CXXFLAGS) -I../include -I../tests/include -D_FILE_OFFSET_BITS=64 -LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ - -L../library \ - -lmbedtls$(SHARED_SUFFIX) \ - -lmbedx509$(SHARED_SUFFIX) \ - -lmbedcrypto$(SHARED_SUFFIX) +include ../scripts/common.make ifeq ($(shell uname -s),Linux) DLOPEN_LDFLAGS ?= -ldl diff --git a/scripts/common.make b/scripts/common.make new file mode 100644 index 0000000000..cee8bd245d --- /dev/null +++ b/scripts/common.make @@ -0,0 +1,14 @@ +# To compile on SunOS: add "-lsocket -lnsl" to LDFLAGS + +CFLAGS ?= -O2 +WARNING_CFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral +WARNING_CXXFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral +LDFLAGS ?= + +LOCAL_CFLAGS = $(WARNING_CFLAGS) -I../tests/include -I../include -D_FILE_OFFSET_BITS=64 +LOCAL_CXXFLAGS = $(WARNING_CXXFLAGS) -I../include -I../tests/include -D_FILE_OFFSET_BITS=64 +LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ + -L../library \ + -lmbedtls$(SHARED_SUFFIX) \ + -lmbedx509$(SHARED_SUFFIX) \ + -lmbedcrypto$(SHARED_SUFFIX) diff --git a/tests/Makefile b/tests/Makefile index f242b51576..3a6fd593ba 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,23 +1,12 @@ - -# To compile on SunOS: add "-lsocket -lnsl" to LDFLAGS - -CFLAGS ?= -O2 -WARNING_CFLAGS ?= -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -LDFLAGS ?= +include ../scripts/common.make # Set this to -v to see the details of failing test cases TEST_FLAGS ?= $(if $(filter-out 0 OFF Off off NO No no FALSE False false N n,$(CTEST_OUTPUT_ON_FAILURE)),-v,) default: all -LOCAL_CFLAGS = $(WARNING_CFLAGS) -I$(MBEDTLS_TEST_PATH)/include -I../include -D_FILE_OFFSET_BITS=64 # Also include library headers, for the sake of invasive tests. LOCAL_CFLAGS += -I../library -LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ - -L../library \ - -lmbedtls$(SHARED_SUFFIX) \ - -lmbedx509$(SHARED_SUFFIX) \ - -lmbedcrypto$(SHARED_SUFFIX) include ../3rdparty/Makefile.inc LOCAL_CFLAGS+=$(THIRDPARTY_INCLUDES) From 076fd2548038c18bb11a9518c125eb12ada152a3 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:45:53 +0100 Subject: [PATCH 32/39] Unify common variables of programs/Makefile and tests/Makefile Signed-off-by: Gilles Peskine --- programs/Makefile | 45 ---------------------------------------- scripts/common.make | 50 +++++++++++++++++++++++++++++++++++++++++++++ tests/Makefile | 50 --------------------------------------------- 3 files changed, 50 insertions(+), 95 deletions(-) diff --git a/programs/Makefile b/programs/Makefile index b03e613b4c..590b54eba5 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -12,45 +12,8 @@ else DLOPEN_LDFLAGS ?= endif -include ../3rdparty/Makefile.inc -LOCAL_CFLAGS+=$(THIRDPARTY_INCLUDES) - -ifndef SHARED -MBEDLIBS=../library/libmbedcrypto.a ../library/libmbedx509.a ../library/libmbedtls.a -else -MBEDLIBS=../library/libmbedcrypto.$(DLEXT) ../library/libmbedx509.$(DLEXT) ../library/libmbedtls.$(DLEXT) -endif DEP=${MBEDLIBS} ${MBEDTLS_TEST_OBJS} -ifdef DEBUG -LOCAL_CFLAGS += -g3 -endif - -# if we're running on Windows, build for Windows -ifdef WINDOWS -WINDOWS_BUILD=1 -endif - -ifdef WINDOWS_BUILD - DLEXT=dll - EXEXT=.exe - LOCAL_LDFLAGS += -lws2_32 -lbcrypt - ifdef SHARED - SHARED_SUFFIX=.$(DLEXT) - endif - -else # Not building for Windows - DLEXT ?= so - EXEXT= - SHARED_SUFFIX= -endif - -ifdef WINDOWS -PYTHON ?= python -else -PYTHON ?= $(shell if type python3 >/dev/null 2>/dev/null; then echo python3; else echo python; fi) -endif - # Only build the dlopen test in shared library builds, and not when building # for Windows. ifdef BUILD_DLOPEN @@ -168,14 +131,6 @@ ${MBEDTLS_TEST_OBJS}: GENERATED_FILES = psa/psa_constant_names_generated.c test/query_config.c generated_files: $(GENERATED_FILES) -# See root Makefile -GEN_FILES ?= yes -ifdef GEN_FILES -gen_file_dep = -else -gen_file_dep = | -endif - psa/psa_constant_names_generated.c: $(gen_file_dep) ../scripts/generate_psa_constants.py psa/psa_constant_names_generated.c: $(gen_file_dep) ../include/psa/crypto_values.h psa/psa_constant_names_generated.c: $(gen_file_dep) ../include/psa/crypto_extra.h diff --git a/scripts/common.make b/scripts/common.make index cee8bd245d..12fd27fb41 100644 --- a/scripts/common.make +++ b/scripts/common.make @@ -12,3 +12,53 @@ LOCAL_LDFLAGS = ${MBEDTLS_TEST_OBJS} \ -lmbedtls$(SHARED_SUFFIX) \ -lmbedx509$(SHARED_SUFFIX) \ -lmbedcrypto$(SHARED_SUFFIX) + +include ../3rdparty/Makefile.inc +LOCAL_CFLAGS+=$(THIRDPARTY_INCLUDES) + +ifndef SHARED +MBEDLIBS=../library/libmbedcrypto.a ../library/libmbedx509.a ../library/libmbedtls.a +else +MBEDLIBS=../library/libmbedcrypto.$(DLEXT) ../library/libmbedx509.$(DLEXT) ../library/libmbedtls.$(DLEXT) +endif + +ifdef DEBUG +LOCAL_CFLAGS += -g3 +endif + +# if we're running on Windows, build for Windows +ifdef WINDOWS +WINDOWS_BUILD=1 +endif + +ifdef WINDOWS_BUILD + DLEXT=dll + EXEXT=.exe + LOCAL_LDFLAGS += -lws2_32 -lbcrypt + ifdef SHARED + SHARED_SUFFIX=.$(DLEXT) + endif + +else # Not building for Windows + DLEXT ?= so + EXEXT= + SHARED_SUFFIX= + + ifeq ($(THREADING),pthread) + LOCAL_LDFLAGS += -lpthread + endif +endif + +ifdef WINDOWS +PYTHON ?= python +else +PYTHON ?= $(shell if type python3 >/dev/null 2>/dev/null; then echo python3; else echo python; fi) +endif + +# See root Makefile +GEN_FILES ?= yes +ifdef GEN_FILES +gen_file_dep = +else +gen_file_dep = | +endif diff --git a/tests/Makefile b/tests/Makefile index 3a6fd593ba..8e4149b94d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -8,65 +8,15 @@ default: all # Also include library headers, for the sake of invasive tests. LOCAL_CFLAGS += -I../library -include ../3rdparty/Makefile.inc -LOCAL_CFLAGS+=$(THIRDPARTY_INCLUDES) - # Enable definition of various functions used throughout the testsuite # (gethostname, strdup, fileno...) even when compiling with -std=c99. Harmless # on non-POSIX platforms. LOCAL_CFLAGS += -D_POSIX_C_SOURCE=200809L -ifndef SHARED -MBEDLIBS=../library/libmbedcrypto.a ../library/libmbedx509.a ../library/libmbedtls.a -else -MBEDLIBS=../library/libmbedcrypto.$(DLEXT) ../library/libmbedx509.$(DLEXT) ../library/libmbedtls.$(DLEXT) -endif - -ifdef DEBUG -LOCAL_CFLAGS += -g3 -endif - ifdef RECORD_PSA_STATUS_COVERAGE_LOG LOCAL_CFLAGS += -Werror -DRECORD_PSA_STATUS_COVERAGE_LOG endif -# if we're running on Windows, build for Windows -ifdef WINDOWS -WINDOWS_BUILD=1 -endif - -ifdef WINDOWS_BUILD - DLEXT=dll - EXEXT=.exe - LOCAL_LDFLAGS += -lws2_32 -lbcrypt - ifdef SHARED - SHARED_SUFFIX=.$(DLEXT) - endif - -else # Not building for Windows - DLEXT ?= so - EXEXT= - SHARED_SUFFIX= - - ifeq ($(THREADING),pthread) - LOCAL_LDFLAGS += -lpthread - endif -endif - -ifdef WINDOWS -PYTHON ?= python -else -PYTHON ?= $(shell if type python3 >/dev/null 2>/dev/null; then echo python3; else echo python; fi) -endif - -# See root Makefile -GEN_FILES ?= yes -ifdef GEN_FILES -gen_file_dep = -else -gen_file_dep = | -endif - .PHONY: generated_files GENERATED_BIGNUM_DATA_FILES := $(patsubst tests/%,%,$(shell \ $(PYTHON) scripts/generate_bignum_tests.py --list || \ From 4392fc101ff3f5ef8d273359f73ad9a83f9923ed Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:49:35 +0100 Subject: [PATCH 33/39] Unify some common rules of programs/Makefile and tests/Makefile Signed-off-by: Gilles Peskine --- programs/Makefile | 10 ---------- scripts/common.make | 12 ++++++++++++ tests/Makefile | 12 ------------ 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/programs/Makefile b/programs/Makefile index 590b54eba5..64f7cc1a32 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -121,9 +121,6 @@ endif fuzz: ${MBEDTLS_TEST_OBJS} $(MAKE) -C fuzz THIRDPARTY_INCLUDES=$(THIRDPARTY_INCLUDES) -$(MBEDLIBS): - $(MAKE) -C ../library - ${MBEDTLS_TEST_OBJS}: $(MAKE) -C ../tests mbedtls_test @@ -432,12 +429,5 @@ else endif $(MAKE) -C fuzz clean -neat: clean -ifndef WINDOWS - rm -f $(GENERATED_FILES) -else - for %f in ($(subst /,\,$(GENERATED_FILES))) if exist %f del /Q /F %f -endif - list: echo $(EXES) diff --git a/scripts/common.make b/scripts/common.make index 12fd27fb41..1350b12e32 100644 --- a/scripts/common.make +++ b/scripts/common.make @@ -62,3 +62,15 @@ gen_file_dep = else gen_file_dep = | endif + +default: all + +$(MBEDLIBS): + $(MAKE) -C ../library + +neat: clean +ifndef WINDOWS + rm -f $(GENERATED_FILES) +else + for %f in ($(subst /,\,$(GENERATED_FILES))) if exist %f del /Q /F %f +endif diff --git a/tests/Makefile b/tests/Makefile index 8e4149b94d..7a10af271c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,8 +3,6 @@ include ../scripts/common.make # Set this to -v to see the details of failing test cases TEST_FLAGS ?= $(if $(filter-out 0 OFF Off off NO No no FALSE False false N n,$(CTEST_OUTPUT_ON_FAILURE)),-v,) -default: all - # Also include library headers, for the sake of invasive tests. LOCAL_CFLAGS += -I../library @@ -111,9 +109,6 @@ BINARIES := $(addsuffix $(EXEXT),$(APPS)) all: $(BINARIES) -$(MBEDLIBS): - $(MAKE) -C ../library - MBEDTLS_TEST_PATH = . MBEDTLS_TEST_OBJS = $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/*.c ${MBEDTLS_TEST_PATH}/src/drivers/*.c)) MBEDTLS_TEST_OBJS += $(patsubst %.c,%.o,$(wildcard ${MBEDTLS_TEST_PATH}/src/test_helpers/*.c)) @@ -193,13 +188,6 @@ else if exist include/test/instrument_record_status.h del /Q /F include/test/instrument_record_status.h endif -neat: clean -ifndef WINDOWS - rm -f $(GENERATED_FILES) -else - for %f in ($(subst /,\,$(GENERATED_FILES))) if exist %f del /Q /F %f -endif - # Test suites caught by SKIP_TEST_SUITES are built but not executed. check: $(BINARIES) perl scripts/run-test-suites.pl $(TEST_FLAGS) --skip=$(SKIP_TEST_SUITES) From 21570cf2327a804cca9bf60c6b5a7cd4fc5b2e7b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 11:49:50 +0100 Subject: [PATCH 34/39] Auto-detect the need to link with pthread on Unix-like platforms When building with Make on a Unix-like platform (shell and compiler), auto-detect configurations that may require linking with pthread. This removes the need for MAKE_THREADING_FLAGS in all.sh. Signed-off-by: Gilles Peskine --- scripts/common.make | 27 +++++++ tests/scripts/all.sh | 169 +++++++++++++++++++++---------------------- 2 files changed, 110 insertions(+), 86 deletions(-) diff --git a/scripts/common.make b/scripts/common.make index 1350b12e32..a2d1449fea 100644 --- a/scripts/common.make +++ b/scripts/common.make @@ -31,6 +31,27 @@ ifdef WINDOWS WINDOWS_BUILD=1 endif +## Usage: $(call remove_unset_options,PREPROCESSOR_INPUT) +## Remove the preprocessor symbols that are not set in the current configuration +## from PREPROCESSOR_INPUT. Also normalize whitespace. +## Example: +## $(call remove_unset_options,MBEDTLS_FOO MBEDTLS_BAR) +## This expands to an empty string "" if MBEDTLS_FOO and MBEDTLS_BAR are both +## disabled, to "MBEDTLS_FOO" if MBEDTLS_BAR is enabled but MBEDTLS_FOO is +## disabled, etc. +## +## This only works with a Unix-like shell environment (Bourne/POSIX-style shell +## and standard commands) and a Unix-like compiler (supporting -E). In +## other environments, the output is likely to be empty. +define remove_unset_options +$(strip $(shell + exec 2>/dev/null; + { echo '#include '; echo $(1); } | + $(CC) $(LOCAL_CFLAGS) $(CFLAGS) -E - | + tail -n 1 +)) +endef + ifdef WINDOWS_BUILD DLEXT=dll EXEXT=.exe @@ -43,6 +64,12 @@ else # Not building for Windows DLEXT ?= so EXEXT= SHARED_SUFFIX= + ifndef THREADING + # Auto-detect configurations with pthread. + ifeq (control,$(call remove_unset_options,control MBEDTLS_THREADING_C MBEDTLS_THREADING_PTHREAD)) + THREADING := pthread + endif + endif ifeq ($(THREADING),pthread) LOCAL_LDFLAGS += -lpthread diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh index 933c563d30..315c6e5cd7 100755 --- a/tests/scripts/all.sh +++ b/tests/scripts/all.sh @@ -216,9 +216,6 @@ pre_initialize_variables () { esac SUPPORTED_COMPONENTS="$SUPPORTED_COMPONENTS $component" done - - # Option to enable linking with pthreads under make - MAKE_THREADING_FLAGS="THREADING=pthread" } # Test whether the component $1 is included in the command line patterns. @@ -933,7 +930,7 @@ helper_get_psa_key_type_list() { # Here "things" are PSA_WANT_ symbols but with PSA_WANT_ removed. helper_libtestdriver1_make_drivers() { loc_accel_flags=$( echo "$1 ${2-}" | sed 's/[^ ]* */-DLIBTESTDRIVER1_MBEDTLS_PSA_ACCEL_&/g' ) - make CC=$ASAN_CC -C tests libtestdriver1.a CFLAGS=" $ASAN_CFLAGS $loc_accel_flags" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC -C tests libtestdriver1.a CFLAGS=" $ASAN_CFLAGS $loc_accel_flags" LDFLAGS="$ASAN_CFLAGS" } # Build the main libraries, programs and tests, @@ -951,7 +948,7 @@ helper_libtestdriver1_make_main() { # we need flags both with and without the LIBTESTDRIVER1_ prefix loc_accel_flags=$( echo "$loc_accel_list" | sed 's/[^ ]* */-DLIBTESTDRIVER1_MBEDTLS_PSA_ACCEL_&/g' ) loc_accel_flags="$loc_accel_flags $( echo "$loc_accel_list" | sed 's/[^ ]* */-DMBEDTLS_PSA_ACCEL_&/g' )" - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -I../tests/include -I../tests -I../../tests -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_TEST_LIBTESTDRIVER1 $loc_accel_flags" LDFLAGS="-ltestdriver1 $ASAN_CFLAGS" $MAKE_THREADING_FLAGS "$@" + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -I../tests/include -I../tests -I../../tests -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_TEST_LIBTESTDRIVER1 $loc_accel_flags" LDFLAGS="-ltestdriver1 $ASAN_CFLAGS" "$@" } ################################################################ @@ -1446,7 +1443,7 @@ component_test_psa_external_rng_no_drbg_classic () { # When MBEDTLS_USE_PSA_CRYPTO is disabled and there is no DRBG, # the SSL test programs don't have an RNG and can't work. Explicitly # make them use the PSA RNG with -DMBEDTLS_TEST_USE_PSA_CRYPTO_RNG. - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DMBEDTLS_TEST_USE_PSA_CRYPTO_RNG" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DMBEDTLS_TEST_USE_PSA_CRYPTO_RNG" LDFLAGS="$ASAN_CFLAGS" msg "test: PSA_CRYPTO_EXTERNAL_RNG minus *_DRBG, classic crypto - main suites" make test @@ -1465,7 +1462,7 @@ component_test_psa_external_rng_no_drbg_use_psa () { scripts/config.py unset MBEDTLS_CTR_DRBG_C scripts/config.py unset MBEDTLS_HMAC_DRBG_C scripts/config.py unset MBEDTLS_ECDSA_DETERMINISTIC # requires HMAC_DRBG - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: PSA_CRYPTO_EXTERNAL_RNG minus *_DRBG, PSA crypto - main suites" make test @@ -1480,7 +1477,7 @@ component_test_psa_external_rng_use_psa_crypto () { scripts/config.py set MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG scripts/config.py set MBEDTLS_USE_PSA_CRYPTO scripts/config.py unset MBEDTLS_CTR_DRBG_C - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: full + PSA_CRYPTO_EXTERNAL_RNG + USE_PSA_CRYPTO minus CTR_DRBG" make test @@ -1498,7 +1495,7 @@ component_test_psa_inject_entropy () { scripts/config.py unset MBEDTLS_PLATFORM_NV_SEED_ALT scripts/config.py unset MBEDTLS_PLATFORM_STD_NV_SEED_READ scripts/config.py unset MBEDTLS_PLATFORM_STD_NV_SEED_WRITE - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS '-DMBEDTLS_USER_CONFIG_FILE=\"../tests/configs/user-config-for-test.h\"'" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS '-DMBEDTLS_USER_CONFIG_FILE=\"../tests/configs/user-config-for-test.h\"'" LDFLAGS="$ASAN_CFLAGS" msg "test: full + MBEDTLS_PSA_INJECT_ENTROPY" make test @@ -1532,14 +1529,14 @@ component_test_crypto_full_md_light_only () { # Note: MD-light is auto-enabled in build_info.h by modules that need it, # which we haven't disabled, so no need to explicitly enable it. - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" # Make sure we don't have the HMAC functions, but the hashing functions not grep mbedtls_md_hmac library/md.o grep mbedtls_md library/md.o msg "test: crypto_full with only the light subset of MD" - make $MAKE_THREADING_FLAGS test + make test } component_test_full_no_cipher () { @@ -1565,7 +1562,7 @@ component_test_full_no_cipher () { scripts/config.py unset MBEDTLS_LMS_PRIVATE msg "test: full no CIPHER no PSA_CRYPTO_C" - make $MAKE_THREADING_FLAGS test + make test } # This is a common configurator and test function that is used in: @@ -1614,7 +1611,7 @@ common_test_full_no_cipher_with_psa_crypto () { scripts/config.py unset MBEDTLS_PKCS12_C scripts/config.py unset MBEDTLS_PKCS5_C - make $MAKE_THREADING_FLAGS + make # Ensure that CIPHER_C was not re-enabled not grep mbedtls_cipher_init library/cipher.o @@ -1647,7 +1644,7 @@ component_test_full_no_ccm() { # PSA_WANT_ALG_CCM to be re-enabled. scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_CCM - make $MAKE_THREADING_FLAGS + make msg "test: full no PSA_WANT_ALG_CCM" make test @@ -1675,7 +1672,7 @@ component_test_full_no_ccm_star_no_tag() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_CBC_NO_PADDING scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_CBC_PKCS7 - make $MAKE_THREADING_FLAGS + make # Ensure MBEDTLS_PSA_BUILTIN_CIPHER was not enabled not grep mbedtls_psa_cipher library/psa_crypto_cipher.o @@ -1732,7 +1729,7 @@ component_test_full_no_bignum () { scripts/config.py unset MBEDTLS_SSL_ASYNC_PRIVATE scripts/config.py unset MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK - make $MAKE_THREADING_FLAGS + make msg "test: full minus bignum" make test @@ -2010,7 +2007,7 @@ component_test_small_mbedtls_ssl_dtls_max_buffering () { component_test_psa_collect_statuses () { msg "build+test: psa_collect_statuses" # ~30s scripts/config.py full - tests/scripts/psa_collect_statuses.py --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/psa_collect_statuses.py # Check that psa_crypto_init() succeeded at least once grep -q '^0:psa_crypto_init:' tests/statuses.log rm -f tests/statuses.log @@ -2189,7 +2186,7 @@ component_test_default_no_deprecated () { component_test_full_no_deprecated () { msg "build: make, full_no_deprecated config" # ~ 30s scripts/config.py full_no_deprecated - make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' msg "test: make, full_no_deprecated config" # ~ 5s make test @@ -2206,7 +2203,7 @@ component_test_full_no_deprecated_deprecated_warning () { scripts/config.py full_no_deprecated scripts/config.py unset MBEDTLS_DEPRECATED_REMOVED scripts/config.py set MBEDTLS_DEPRECATED_WARNING - make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' msg "test: make, full_no_deprecated config, MBEDTLS_DEPRECATED_WARNING" # ~ 5s make test @@ -2226,7 +2223,7 @@ component_test_full_deprecated_warning () { # By default those are disabled when MBEDTLS_DEPRECATED_WARNING is set. # Expect warnings from '#warning' directives in check_config.h and # from the use of deprecated functions in test suites. - make CC=gcc CFLAGS='-O -Werror -Wall -Wextra -Wno-error=deprecated-declarations -Wno-error=cpp -DMBEDTLS_TEST_DEPRECATED' $MAKE_THREADING_FLAGS tests + make CC=gcc CFLAGS='-O -Werror -Wall -Wextra -Wno-error=deprecated-declarations -Wno-error=cpp -DMBEDTLS_TEST_DEPRECATED' tests msg "test: full config + MBEDTLS_TEST_DEPRECATED" # ~ 30s make test @@ -2251,7 +2248,7 @@ component_build_crypto_default () { component_build_crypto_full () { msg "build: make, crypto only, full config" scripts/config.py crypto_full - make CFLAGS='-O1 -Werror' $MAKE_THREADING_FLAGS + make CFLAGS='-O1 -Werror' are_empty_libraries library/libmbedx509.* library/libmbedtls.* } @@ -2311,73 +2308,73 @@ support_build_baremetal () { # depends.py family of tests component_test_depends_py_cipher_id () { msg "test/build: depends.py cipher_id (gcc)" - tests/scripts/depends.py cipher_id --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_id --unset-use-psa } component_test_depends_py_cipher_chaining () { msg "test/build: depends.py cipher_chaining (gcc)" - tests/scripts/depends.py cipher_chaining --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_chaining --unset-use-psa } component_test_depends_py_cipher_padding () { msg "test/build: depends.py cipher_padding (gcc)" - tests/scripts/depends.py cipher_padding --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_padding --unset-use-psa } component_test_depends_py_curves () { msg "test/build: depends.py curves (gcc)" - tests/scripts/depends.py curves --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py curves --unset-use-psa } component_test_depends_py_hashes () { msg "test/build: depends.py hashes (gcc)" - tests/scripts/depends.py hashes --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py hashes --unset-use-psa } component_test_depends_py_kex () { msg "test/build: depends.py kex (gcc)" - tests/scripts/depends.py kex --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py kex --unset-use-psa } component_test_depends_py_pkalgs () { msg "test/build: depends.py pkalgs (gcc)" - tests/scripts/depends.py pkalgs --unset-use-psa --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py pkalgs --unset-use-psa } # PSA equivalents of the depends.py tests component_test_depends_py_cipher_id_psa () { msg "test/build: depends.py cipher_id (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py cipher_id --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_id } component_test_depends_py_cipher_chaining_psa () { msg "test/build: depends.py cipher_chaining (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py cipher_chaining --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_chaining } component_test_depends_py_cipher_padding_psa () { msg "test/build: depends.py cipher_padding (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py cipher_padding --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py cipher_padding } component_test_depends_py_curves_psa () { msg "test/build: depends.py curves (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py curves --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py curves } component_test_depends_py_hashes_psa () { msg "test/build: depends.py hashes (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py hashes --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py hashes } component_test_depends_py_kex_psa () { msg "test/build: depends.py kex (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py kex --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py kex } component_test_depends_py_pkalgs_psa () { msg "test/build: depends.py pkalgs (gcc) with MBEDTLS_USE_PSA_CRYPTO defined" - tests/scripts/depends.py pkalgs --make-vars="$MAKE_THREADING_FLAGS" + tests/scripts/depends.py pkalgs } component_build_no_pk_rsa_alt_support () { @@ -2389,7 +2386,7 @@ component_build_no_pk_rsa_alt_support () { scripts/config.py set MBEDTLS_X509_CRT_WRITE_C # Only compile - this is primarily to test for compile issues - make CC=gcc CFLAGS='-Werror -Wall -Wextra -I../tests/include/alt-dummy' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-Werror -Wall -Wextra -I../tests/include/alt-dummy' } component_build_module_alt () { @@ -2603,7 +2600,7 @@ component_test_psa_crypto_config_reference_ffdh () { # Disable things that are not supported scripts/config.py unset MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED scripts/config.py unset MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED - make $MAKE_THREADING_FLAGS + make msg "test suites: full with non-accelerated FFDH alg" make test @@ -2642,7 +2639,7 @@ component_test_psa_crypto_config_accel_pake() { # ------------- msg "test: full with accelerated PAKE" - make $MAKE_THREADING_FLAGS test + make test } component_test_psa_crypto_config_accel_ecc_some_key_types () { @@ -2702,7 +2699,7 @@ component_test_psa_crypto_config_accel_ecc_some_key_types () { # ------------- msg "test suites: full with accelerated EC algs and some key types" - make $MAKE_THREADING_FLAGS test + make test } # Run tests with only (non-)Weierstrass accelerated @@ -2901,7 +2898,7 @@ component_test_psa_crypto_config_accel_ecc_ecp_light_only () { # ------------- msg "test suites: full with accelerated EC algs" - make $MAKE_THREADING_FLAGS test + make test msg "ssl-opt: full with accelerated EC algs" tests/ssl-opt.sh @@ -2913,7 +2910,7 @@ component_test_psa_crypto_config_reference_ecc_ecp_light_only () { config_psa_crypto_config_ecp_light_only 0 - make $MAKE_THREADING_FLAGS + make msg "test suites: full with non-accelerated EC algs" make test @@ -3006,7 +3003,7 @@ component_test_psa_crypto_config_accel_ecc_no_ecp_at_all () { # ------------- msg "test: full + accelerated EC algs - ECP" - make $MAKE_THREADING_FLAGS test + make test msg "ssl-opt: full + accelerated EC algs - ECP" tests/ssl-opt.sh @@ -3020,7 +3017,7 @@ component_test_psa_crypto_config_reference_ecc_no_ecp_at_all () { config_psa_crypto_no_ecp_at_all 0 - make $MAKE_THREADING_FLAGS + make msg "test: full + non accelerated EC algs" make test @@ -3183,7 +3180,7 @@ common_test_psa_crypto_config_accel_ecc_ffdh_no_bignum () { msg "test suites: full + accelerated $accel_text algs + USE_PSA - $removed_text - DHM - BIGNUM" - make $MAKE_THREADING_FLAGS test + make test msg "ssl-opt: full + accelerated $accel_text algs + USE_PSA - $removed_text - BIGNUM" tests/ssl-opt.sh @@ -3214,7 +3211,7 @@ common_test_psa_crypto_config_reference_ecc_ffdh_no_bignum () { config_psa_crypto_config_accel_ecc_ffdh_no_bignum 0 "$test_target" - make $MAKE_THREADING_FLAGS + make msg "test suites: full + non accelerated EC algs + USE_PSA" make test @@ -3333,7 +3330,7 @@ build_full_minus_something_and_test_tls () { scripts/config.py unset $sym done - make $MAKE_THREADING_FLAGS + make msg "test: full minus something, test TLS" ( cd tests; ./test_suite_ssl ) @@ -3372,7 +3369,7 @@ build_and_test_psa_want_key_pair_partial() { # crypto_config.h so we just disable the one we don't want. scripts/config.py -f "$CRYPTO_CONFIG_H" unset "$disabled_psa_want" - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: full - MBEDTLS_USE_PSA_CRYPTO - ${disabled_psa_want}" make test @@ -3438,7 +3435,7 @@ component_test_psa_crypto_config_accel_rsa_crypto () { # ------------- msg "test: crypto_full with accelerated RSA" - make $MAKE_THREADING_FLAGS test + make test } component_test_psa_crypto_config_reference_rsa_crypto () { @@ -3450,7 +3447,7 @@ component_test_psa_crypto_config_reference_rsa_crypto () { # Build # ----- - make $MAKE_THREADING_FLAGS + make # Run the tests # ------------- @@ -3652,7 +3649,7 @@ component_test_psa_crypto_config_reference_hash_use_psa() { config_psa_crypto_hash_use_psa 0 - make $MAKE_THREADING_FLAGS + make msg "test: full without accelerated hashes" make test @@ -3817,7 +3814,7 @@ component_test_psa_crypto_config_accel_cipher_aead () { # ------------- msg "test: full config with accelerated cipher and AEAD" - make $MAKE_THREADING_FLAGS test + make test msg "ssl-opt: full config with accelerated cipher and AEAD" tests/ssl-opt.sh @@ -3830,7 +3827,7 @@ component_test_psa_crypto_config_reference_cipher_aead () { msg "build: full config with non-accelerated cipher and AEAD" common_psa_crypto_config_accel_cipher_aead - make $MAKE_THREADING_FLAGS + make msg "test: full config with non-accelerated cipher and AEAD" make test @@ -3847,7 +3844,7 @@ component_test_aead_chachapoly_disabled() { scripts/config.py full scripts/config.py unset MBEDTLS_CHACHAPOLY_C scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_CHACHA20_POLY1305 - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: full minus CHACHAPOLY" make test @@ -3860,7 +3857,7 @@ component_test_aead_only_ccm() { scripts/config.py unset MBEDTLS_GCM_C scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_CHACHA20_POLY1305 scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_GCM - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: full minus CHACHAPOLY and GCM" make test @@ -3891,7 +3888,7 @@ component_build_psa_accel_alg_ecdh() { scripts/config.py unset MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED scripts/config.py unset MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_ECDH -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_ECDH -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator HMAC code is in place and ready to test. @@ -3901,7 +3898,7 @@ component_build_psa_accel_alg_hmac() { scripts/config.py unset MBEDTLS_USE_PSA_CRYPTO scripts/config.py unset MBEDTLS_SSL_PROTO_TLS1_3 # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_HMAC -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_HMAC -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator HKDF code is in place and ready to test. @@ -3914,7 +3911,7 @@ component_build_psa_accel_alg_hkdf() { # Make sure to unset TLS1_3 since it requires HKDF_C and will not build properly without it. scripts/config.py unset MBEDTLS_SSL_PROTO_TLS1_3 # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_HKDF -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_HKDF -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator MD5 code is in place and ready to test. @@ -3933,7 +3930,7 @@ component_build_psa_accel_alg_md5() { scripts/config.py unset MBEDTLS_LMS_C scripts/config.py unset MBEDTLS_LMS_PRIVATE # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_MD5 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_MD5 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RIPEMD160 code is in place and ready to test. @@ -3952,7 +3949,7 @@ component_build_psa_accel_alg_ripemd160() { scripts/config.py unset MBEDTLS_LMS_C scripts/config.py unset MBEDTLS_LMS_PRIVATE # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RIPEMD160 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RIPEMD160 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator SHA1 code is in place and ready to test. @@ -3971,7 +3968,7 @@ component_build_psa_accel_alg_sha1() { scripts/config.py unset MBEDTLS_LMS_C scripts/config.py unset MBEDTLS_LMS_PRIVATE # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_1 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_1 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator SHA224 code is in place and ready to test. @@ -3987,7 +3984,7 @@ component_build_psa_accel_alg_sha224() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_SHA_512 scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_224 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_224 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator SHA256 code is in place and ready to test. @@ -4003,7 +4000,7 @@ component_build_psa_accel_alg_sha256() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_SHA_384 scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_SHA_512 # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_256 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_256 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator SHA384 code is in place and ready to test. @@ -4021,7 +4018,7 @@ component_build_psa_accel_alg_sha384() { scripts/config.py unset MBEDTLS_LMS_C scripts/config.py unset MBEDTLS_LMS_PRIVATE # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_384 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_384 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator SHA512 code is in place and ready to test. @@ -4040,7 +4037,7 @@ component_build_psa_accel_alg_sha512() { scripts/config.py unset MBEDTLS_LMS_C scripts/config.py unset MBEDTLS_LMS_PRIVATE # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_512 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_SHA_512 -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4054,7 +4051,7 @@ component_build_psa_accel_alg_rsa_pkcs1v15_crypt() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_OAEP scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_PSS # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PKCS1V15_CRYPT -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PKCS1V15_CRYPT -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4068,7 +4065,7 @@ component_build_psa_accel_alg_rsa_pkcs1v15_sign() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_OAEP scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_PSS # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PKCS1V15_SIGN -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PKCS1V15_SIGN -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4082,7 +4079,7 @@ component_build_psa_accel_alg_rsa_oaep() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_PKCS1V15_SIGN scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_PSS # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_OAEP -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_OAEP -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4096,7 +4093,7 @@ component_build_psa_accel_alg_rsa_pss() { scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_PKCS1V15_SIGN scripts/config.py -f "$CRYPTO_CONFIG_H" unset PSA_WANT_ALG_RSA_OAEP # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PSS -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_ALG_RSA_PSS -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4111,7 +4108,7 @@ component_build_psa_accel_key_type_rsa_key_pair() { scripts/config.py -f "$CRYPTO_CONFIG_H" set PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT 1 scripts/config.py -f "$CRYPTO_CONFIG_H" set PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE 1 # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_KEY_TYPE_RSA_KEY_PAIR -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_KEY_TYPE_RSA_KEY_PAIR -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } # This should be renamed to test and updated once the accelerator RSA code is in place and ready to test. @@ -4123,7 +4120,7 @@ component_build_psa_accel_key_type_rsa_public_key() { scripts/config.py -f "$CRYPTO_CONFIG_H" set PSA_WANT_ALG_RSA_PSS 1 scripts/config.py -f "$CRYPTO_CONFIG_H" set PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY 1 # Need to define the correct symbol and include the test driver header path in order to build with the test driver - make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_KEY_TYPE_RSA_PUBLIC_KEY -I../tests/include" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="$ASAN_CFLAGS -DPSA_CRYPTO_DRIVER_TEST -DMBEDTLS_PSA_ACCEL_KEY_TYPE_RSA_PUBLIC_KEY -I../tests/include" LDFLAGS="$ASAN_CFLAGS" } @@ -4292,7 +4289,7 @@ component_test_no_platform () { # Note, _DEFAULT_SOURCE needs to be defined for platforms using glibc version >2.19, # to re-enable platform integration features otherwise disabled in C99 builds make CC=gcc CFLAGS='-Werror -Wall -Wextra -std=c99 -pedantic -Os -D_DEFAULT_SOURCE' lib programs - make CC=gcc CFLAGS='-Werror -Wall -Wextra -Os' $MAKE_THREADING_FLAGS test + make CC=gcc CFLAGS='-Werror -Wall -Wextra -Os' test } component_build_no_std_function () { @@ -4310,14 +4307,14 @@ component_build_no_ssl_srv () { msg "build: full config except SSL server, make, gcc" # ~ 30s scripts/config.py full scripts/config.py unset MBEDTLS_SSL_SRV_C - make CC=gcc CFLAGS='-Werror -Wall -Wextra -O1' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-Werror -Wall -Wextra -O1' } component_build_no_ssl_cli () { msg "build: full config except SSL client, make, gcc" # ~ 30s scripts/config.py full scripts/config.py unset MBEDTLS_SSL_CLI_C - make CC=gcc CFLAGS='-Werror -Wall -Wextra -O1' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-Werror -Wall -Wextra -O1' } component_build_no_sockets () { @@ -4492,7 +4489,7 @@ component_test_platform_calloc_macro () { component_test_malloc_0_null () { msg "build: malloc(0) returns NULL (ASan+UBSan build)" scripts/config.py full - make CC=$ASAN_CC CFLAGS="'-DMBEDTLS_USER_CONFIG_FILE=\"$PWD/tests/configs/user-config-malloc-0-null.h\"' $ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="'-DMBEDTLS_USER_CONFIG_FILE=\"$PWD/tests/configs/user-config-malloc-0-null.h\"' $ASAN_CFLAGS" LDFLAGS="$ASAN_CFLAGS" msg "test: malloc(0) returns NULL (ASan+UBSan build)" make test @@ -5104,7 +5101,7 @@ component_test_psa_crypto_drivers () { loc_cflags="${loc_cflags} '-DMBEDTLS_USER_CONFIG_FILE=\"../tests/configs/user-config-for-test.h\"'" loc_cflags="${loc_cflags} -I../tests/include -O2" - make CC=$ASAN_CC CFLAGS="${loc_cflags}" LDFLAGS="$ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=$ASAN_CC CFLAGS="${loc_cflags}" LDFLAGS="$ASAN_CFLAGS" msg "test: full + test drivers dispatching to builtins" make test @@ -5131,7 +5128,7 @@ test_build_opt () { $cc --version for opt in "$@"; do msg "build/test: $cc $opt, $info" # ~ 30s - make CC="$cc" CFLAGS="$opt -std=c99 -pedantic -Wall -Wextra -Werror" $MAKE_THREADING_FLAGS + make CC="$cc" CFLAGS="$opt -std=c99 -pedantic -Wall -Wextra -Werror" # We're confident enough in compilers to not run _all_ the tests, # but at least run the unit tests. In particular, runs with # optimizations use inline assembly whereas runs with -O0 @@ -5186,7 +5183,7 @@ component_build_mbedtls_config_file () { msg "build: make with MBEDTLS_CONFIG_FILE" # ~40s scripts/config.py -w full_config.h full echo '#error "MBEDTLS_CONFIG_FILE is not working"' >"$CONFIG_H" - make CFLAGS="-I '$PWD' -DMBEDTLS_CONFIG_FILE='\"full_config.h\"'" $MAKE_THREADING_FLAGS + make CFLAGS="-I '$PWD' -DMBEDTLS_CONFIG_FILE='\"full_config.h\"'" # Make sure this feature is enabled. We'll disable it in the next phase. programs/test/query_compile_time_config MBEDTLS_NIST_KW_C make clean @@ -5195,7 +5192,7 @@ component_build_mbedtls_config_file () { # In the user config, disable one feature (for simplicity, pick a feature # that nothing else depends on). echo '#undef MBEDTLS_NIST_KW_C' >user_config.h - make CFLAGS="-I '$PWD' -DMBEDTLS_CONFIG_FILE='\"full_config.h\"' -DMBEDTLS_USER_CONFIG_FILE='\"user_config.h\"'" $MAKE_THREADING_FLAGS + make CFLAGS="-I '$PWD' -DMBEDTLS_CONFIG_FILE='\"full_config.h\"' -DMBEDTLS_USER_CONFIG_FILE='\"user_config.h\"'" not programs/test/query_compile_time_config MBEDTLS_NIST_KW_C rm -f user_config.h full_config.h @@ -5254,7 +5251,7 @@ component_test_m32_no_asm () { scripts/config.py unset MBEDTLS_HAVE_ASM scripts/config.py unset MBEDTLS_PADLOCK_C scripts/config.py unset MBEDTLS_AESNI_C # AESNI for 32-bit is tested in test_aesni_m32 - make CC=gcc CFLAGS="$ASAN_CFLAGS -m32" LDFLAGS="-m32 $ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS="$ASAN_CFLAGS -m32" LDFLAGS="-m32 $ASAN_CFLAGS" msg "test: i386, make, gcc, no asm (ASan build)" make test @@ -5272,7 +5269,7 @@ component_test_m32_o2 () { msg "build: i386, make, gcc -O2 (ASan build)" # ~ 30s scripts/config.py full scripts/config.py unset MBEDTLS_AESNI_C # AESNI for 32-bit is tested in test_aesni_m32 - make CC=gcc CFLAGS="$ASAN_CFLAGS -m32" LDFLAGS="-m32 $ASAN_CFLAGS" $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS="$ASAN_CFLAGS -m32" LDFLAGS="-m32 $ASAN_CFLAGS" msg "test: i386, make, gcc -O2 (ASan build)" make test @@ -5307,7 +5304,7 @@ support_test_m32_everest () { component_test_mx32 () { msg "build: 64-bit ILP32, make, gcc" # ~ 30s scripts/config.py full - make CC=gcc CFLAGS='-Werror -Wall -Wextra -mx32' LDFLAGS='-mx32' $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS='-Werror -Wall -Wextra -mx32' LDFLAGS='-mx32' msg "test: 64-bit ILP32, make, gcc" make test @@ -5371,7 +5368,7 @@ component_test_no_udbl_division () { msg "build: MBEDTLS_NO_UDBL_DIVISION native" # ~ 10s scripts/config.py full scripts/config.py set MBEDTLS_NO_UDBL_DIVISION - make CFLAGS='-Werror -O1' $MAKE_THREADING_FLAGS + make CFLAGS='-Werror -O1' msg "test: MBEDTLS_NO_UDBL_DIVISION native" # ~ 10s make test @@ -5381,7 +5378,7 @@ component_test_no_64bit_multiplication () { msg "build: MBEDTLS_NO_64BIT_MULTIPLICATION native" # ~ 10s scripts/config.py full scripts/config.py set MBEDTLS_NO_64BIT_MULTIPLICATION - make CFLAGS='-Werror -O1' $MAKE_THREADING_FLAGS + make CFLAGS='-Werror -O1' msg "test: MBEDTLS_NO_64BIT_MULTIPLICATION native" # ~ 10s make test @@ -5395,7 +5392,7 @@ component_test_no_strings () { scripts/config.py unset MBEDTLS_ERROR_C scripts/config.py set MBEDTLS_ERROR_STRERROR_DUMMY scripts/config.py unset MBEDTLS_VERSION_FEATURES - make CFLAGS='-Werror -Os' $MAKE_THREADING_FLAGS + make CFLAGS='-Werror -Os' msg "test: no strings" # ~ 10s make test @@ -5406,7 +5403,7 @@ component_test_no_x509_info () { scripts/config.pl full scripts/config.pl unset MBEDTLS_MEMORY_BACKTRACE # too slow for tests scripts/config.pl set MBEDTLS_X509_REMOVE_INFO - make CFLAGS='-Werror -O2' $MAKE_THREADING_FLAGS + make CFLAGS='-Werror -O2' msg "test: full + MBEDTLS_X509_REMOVE_INFO" # ~ 10s make test @@ -6009,7 +6006,7 @@ component_build_zeroize_checks () { scripts/config.py full # Only compile - we're looking for sizeof-pointer-memaccess warnings - make CC=gcc CFLAGS="'-DMBEDTLS_USER_CONFIG_FILE=\"../tests/configs/user-config-zeroize-memset.h\"' -DMBEDTLS_TEST_DEFINES_ZEROIZE -Werror -Wsizeof-pointer-memaccess" $MAKE_THREADING_FLAGS + make CC=gcc CFLAGS="'-DMBEDTLS_USER_CONFIG_FILE=\"../tests/configs/user-config-zeroize-memset.h\"' -DMBEDTLS_TEST_DEFINES_ZEROIZE -Werror -Wsizeof-pointer-memaccess" } From 811daaa48c3003a563e4e06102743703e323ac1f Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 13:16:59 +0100 Subject: [PATCH 35/39] Revert "Add ability to pass make variables to psa_collect_statuses.py" This reverts commit 6587959a32f978aeb02766c27cf30b04d8a245e1. The feature is no longer needed, and the script is broken if you don't pass --make-vars. Signed-off-by: Gilles Peskine --- tests/scripts/psa_collect_statuses.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/scripts/psa_collect_statuses.py b/tests/scripts/psa_collect_statuses.py index 6291d7898e..11bbebcc1f 100755 --- a/tests/scripts/psa_collect_statuses.py +++ b/tests/scripts/psa_collect_statuses.py @@ -82,15 +82,10 @@ def collect_status_logs(options): cwd='tests', stdout=sys.stderr) with open(os.devnull, 'w') as devnull: - build_command = ['make', '-q'] + options.make_vars.split(' ') + \ - ['lib', 'tests'] - make_q_ret = subprocess.call(build_command, stdout=devnull, - stderr=devnull) - print("blagh") + make_q_ret = subprocess.call(['make', '-q', 'lib', 'tests'], + stdout=devnull, stderr=devnull) if make_q_ret != 0: - build_command = ['make'] + options.make_vars.split(' ') + \ - ['RECORD_PSA_STATUS_COVERAGE_LOG=1'] - subprocess.check_call(build_command, + subprocess.check_call(['make', 'RECORD_PSA_STATUS_COVERAGE_LOG=1'], stdout=sys.stderr) rebuilt = True subprocess.check_call(['make', 'test'], @@ -117,9 +112,6 @@ def main(): help='Log file location (default: {})'.format( DEFAULT_STATUS_LOG_FILE )) - parser.add_argument('--make-vars', - help='optional variable/value pairs to pass to make', - action='store', default='') parser.add_argument('--psa-constant-names', metavar='PROGRAM', default=DEFAULT_PSA_CONSTANT_NAMES, help='Path to psa_constant_names (default: {})'.format( From 259df9897267d95e97d6c2a813b4112f11c39637 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 13:17:33 +0100 Subject: [PATCH 36/39] Revert "Add option to pass make variables to depends.py" This reverts commit be978a8c4fc52965b486125f2993251025b1a399. The feature is no longer needed, and the script is broken if you don't pass --make-vars. Signed-off-by: Gilles Peskine --- tests/scripts/depends.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/scripts/depends.py b/tests/scripts/depends.py index 5fe26f158b..38c184a6ae 100755 --- a/tests/scripts/depends.py +++ b/tests/scripts/depends.py @@ -381,7 +381,7 @@ class DomainData: def __init__(self, options, conf): """Gather data about the library and establish a list of domains to test.""" - build_command = [options.make_command] + options.make_vars.split(' ') + ['CFLAGS=-Werror'] + build_command = [options.make_command, 'CFLAGS=-Werror'] build_and_test = [build_command, [options.make_command, 'test']] self.all_config_symbols = set(conf.settings.keys()) # Find hash modules by name. @@ -526,9 +526,6 @@ def main(): parser.add_argument('--make-command', metavar='CMD', help='Command to run instead of make (e.g. gmake)', action='store', default='make') - parser.add_argument('--make-vars', - help='optional variable/value pairs to pass to make', - action='store', default='') parser.add_argument('--unset-use-psa', help='Unset MBEDTLS_USE_PSA_CRYPTO before any test', action='store_true', dest='unset_use_psa') From 2337a3b8864bdf4e700a9af020f665e9f4bec56d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 13:25:18 +0100 Subject: [PATCH 37/39] Explain the use of control Signed-off-by: Gilles Peskine --- scripts/common.make | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/common.make b/scripts/common.make index a2d1449fea..6d2fbc3e2b 100644 --- a/scripts/common.make +++ b/scripts/common.make @@ -66,6 +66,10 @@ else # Not building for Windows SHARED_SUFFIX= ifndef THREADING # Auto-detect configurations with pthread. + # If the call to remove_unset_options returns "control", the symbols + # are confirmed set and we link with pthread. + # If the auto-detection fails, the result of the call is empty and + # we keep THREADING undefined. ifeq (control,$(call remove_unset_options,control MBEDTLS_THREADING_C MBEDTLS_THREADING_PTHREAD)) THREADING := pthread endif From 7602298a16d3e668d7168234c118f50f1664ac3a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 15:28:07 +0100 Subject: [PATCH 38/39] Allow *.make to contain tabs Signed-off-by: Gilles Peskine --- tests/scripts/check_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/scripts/check_files.py b/tests/scripts/check_files.py index a93b8256f0..f6f6d6c713 100755 --- a/tests/scripts/check_files.py +++ b/tests/scripts/check_files.py @@ -318,6 +318,7 @@ class TabIssueTracker(LineIssueTracker): heading = "Tabs present:" suffix_exemptions = frozenset([ + ".make", ".pem", # some openssl dumps have tabs ".sln", "/Makefile", From f3316f132bc73b59c24b31216a45561561ff5ba8 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 22 Dec 2023 18:30:37 +0100 Subject: [PATCH 39/39] Correct name and documentation of preprocessor symbol check function It's not remove_unset_options, it's remove_enabled_options (or keep_disabled_options). Signed-off-by: Gilles Peskine --- scripts/common.make | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/common.make b/scripts/common.make index 6d2fbc3e2b..2f27d0ef52 100644 --- a/scripts/common.make +++ b/scripts/common.make @@ -31,19 +31,19 @@ ifdef WINDOWS WINDOWS_BUILD=1 endif -## Usage: $(call remove_unset_options,PREPROCESSOR_INPUT) -## Remove the preprocessor symbols that are not set in the current configuration +## Usage: $(call remove_enabled_options,PREPROCESSOR_INPUT) +## Remove the preprocessor symbols that are set in the current configuration ## from PREPROCESSOR_INPUT. Also normalize whitespace. ## Example: -## $(call remove_unset_options,MBEDTLS_FOO MBEDTLS_BAR) +## $(call remove_set_options,MBEDTLS_FOO MBEDTLS_BAR) ## This expands to an empty string "" if MBEDTLS_FOO and MBEDTLS_BAR are both -## disabled, to "MBEDTLS_FOO" if MBEDTLS_BAR is enabled but MBEDTLS_FOO is +## enabled, to "MBEDTLS_FOO" if MBEDTLS_BAR is enabled but MBEDTLS_FOO is ## disabled, etc. ## ## This only works with a Unix-like shell environment (Bourne/POSIX-style shell ## and standard commands) and a Unix-like compiler (supporting -E). In ## other environments, the output is likely to be empty. -define remove_unset_options +define remove_enabled_options $(strip $(shell exec 2>/dev/null; { echo '#include '; echo $(1); } | @@ -66,11 +66,11 @@ else # Not building for Windows SHARED_SUFFIX= ifndef THREADING # Auto-detect configurations with pthread. - # If the call to remove_unset_options returns "control", the symbols + # If the call to remove_enabled_options returns "control", the symbols # are confirmed set and we link with pthread. # If the auto-detection fails, the result of the call is empty and # we keep THREADING undefined. - ifeq (control,$(call remove_unset_options,control MBEDTLS_THREADING_C MBEDTLS_THREADING_PTHREAD)) + ifeq (control,$(call remove_enabled_options,control MBEDTLS_THREADING_C MBEDTLS_THREADING_PTHREAD)) THREADING := pthread endif endif