diff --git a/configure.ac b/configure.ac index 03d345c2cf9..5ebeca64ce6 100644 --- a/configure.ac +++ b/configure.ac @@ -2035,7 +2035,11 @@ then if test "$ENABLED_EXPERIMENTAL" = "yes" then - test "$enable_extra_pqc_hybrids" = "" && enable_extra_pqc_hybrids=yes + # FIPS v7 allows only the draft-ietf-tls-ecdhe-mlkem hybrid groups. + if test "$ENABLED_FIPS" = "no" || test "$HAVE_FIPS_VERSION" -lt 7 + then + test "$enable_extra_pqc_hybrids" = "" && enable_extra_pqc_hybrids=yes + fi test "$enable_frodokem" = "" && enable_frodokem=yes test "$enable_frodokem" = yes && test "$enable_frodokem_shake" = "" && enable_frodokem_shake=yes test "$enable_frodokem" = yes && test "$enable_frodokem_aes" = "" && enable_frodokem_aes=yes @@ -7895,18 +7899,10 @@ AS_CASE([$FIPS_VERSION], (test "$ENABLED_FIPS_DEV" != "yes" || test "$enable_ed25519" != "no")], [ENABLED_ED25519="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED25519 -DHAVE_ED25519_KEY_IMPORT"]) - AS_IF([test "$ENABLED_CURVE25519" != "no" && - (test "$ENABLED_FIPS_DEV" != "yes" || test "$enable_curve25519" = "")], - [ENABLED_CURVE25519="no"; AM_CFLAGS="$AM_CFLAGS"]) - AS_IF([test "x$ENABLED_ED448" != "xyes" && (test "$ENABLED_FIPS_DEV" != "yes" || test "$enable_ed448" != "no")], [ENABLED_ED448="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_ED448 -DHAVE_ED448_KEY_IMPORT"]) - AS_IF([test "$ENABLED_CURVE448" != "no" && - (test "$ENABLED_FIPS_DEV" != "yes" || test "$enable_curve448" = "")], - [ENABLED_CURVE448="no"; AM_CFLAGS="$AM_CFLAGS"]) - AS_IF([test "x$ENABLED_ED25519_STREAM" != "xyes" && (test "$ENABLED_FIPS_DEV" != "yes" || test "$enable_ed25519_stream" != "no")], [ENABLED_ED25519_STREAM="yes"]) @@ -9150,6 +9146,7 @@ if test "$ENABLED_EXTRA_PQC_HYBRIDS" = "yes" then AS_IF([ test "$ENABLED_EXPERIMENTAL" != "yes" ],[ AC_MSG_ERROR([extra-pqc-hybrids requires --enable-experimental.]) ]) AS_IF([ test "$ENABLED_ML_KEM" = "no" ],[ AC_MSG_ERROR([extra-pqc-hybrids requires ML-KEM.]) ]) + AS_IF([ test "$ENABLED_FIPS" != "no" && test "$HAVE_FIPS_VERSION" -ge 7 ],[ AC_MSG_ERROR([extra-pqc-hybrids is not available in FIPS v7 builds.]) ]) AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_EXTRA_PQC_HYBRIDS" fi diff --git a/src/include.am b/src/include.am index f0db2bc0af1..481995eb6d9 100644 --- a/src/include.am +++ b/src/include.am @@ -1299,18 +1299,10 @@ if BUILD_CMAC src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/cmac.c endif -if BUILD_CURVE448 -src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/curve448.c -endif - if BUILD_ED448 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ed448.c endif -if BUILD_CURVE25519 -src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/curve25519.c -endif - if BUILD_ED25519 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ed25519.c endif @@ -1482,6 +1474,14 @@ src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/wolfentropy.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/entropy-timers.c endif +# Past wolfcrypt_last.c: X25519 and X448 are not v7 module services. +if BUILD_CURVE25519 +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/curve25519.c +endif +if BUILD_CURVE448 +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/curve448.c +endif + endif BUILD_FIPS_V7_PLUS endif BUILD_FIPS diff --git a/src/tls.c b/src/tls.c index ee221d10a63..b4263bffb4a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -10640,6 +10640,7 @@ static int TLSX_KeyShare_ProcessPqcHybridClient(WOLFSSL* ssl, KeyShareEntry *ecc_kse = NULL; word32 ctSz = 0; word32 ssSzPqc = 0; + byte ssPqc[WC_ML_KEM_SS_SZ]; if (ssl->options.side == WOLFSSL_SERVER_END) { /* I am the server, the shared secret has already been generated and @@ -10780,15 +10781,15 @@ static int TLSX_KeyShare_ProcessPqcHybridClient(WOLFSSL* ssl, #endif } - /* Process ECDH key share part. The generated shared secret is directly - * stored in the ssl->arrays->preMasterSecret buffer. Depending on the - * pqc_first flag, the ECDH shared secret part goes before or after the - * KEM part. */ + /* Process ECDH key share part into ssl->arrays->preMasterSecret: at the + * start, or after the KEM part if pqc_first and the module cannot join. */ if (ret == 0) { int offset = 0; + #ifndef WOLFSSL_HAVE_TLS13_HYBRID_SECRET if (pqc_first) offset = ssSzPqc; + #endif #ifdef HAVE_CURVE25519 if (ecc_group == WOLFSSL_ECC_X25519) { @@ -10846,9 +10847,12 @@ static int TLSX_KeyShare_ProcessPqcHybridClient(WOLFSSL* ssl, } } - /* Process PQC KEM key share part. Depending on the pqc_first flag, the - * KEM shared secret part goes before or after the ECDH part. */ + /* Process PQC KEM key share part. The module joins its shared secret, or + * without WOLFSSL_HAVE_TLS13_HYBRID_SECRET pqc_first places it. */ if (ret == 0) { + #ifdef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + ret = TLSX_KeyShare_ProcessPqcClient_ex(ssl, pqc_kse, ssPqc, &ssSzPqc); + #else int offset = ssl->arrays->preMasterSz; if (pqc_first) @@ -10856,12 +10860,23 @@ static int TLSX_KeyShare_ProcessPqcHybridClient(WOLFSSL* ssl, ret = TLSX_KeyShare_ProcessPqcClient_ex(ssl, pqc_kse, ssl->arrays->preMasterSecret + offset, &ssSzPqc); + #endif } if (ret == 0) { keyShareEntry->privKey = (byte*)pqc_kse->key; + #ifdef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + /* The module joins the secrets; FIPS builds enforce the group's + * order and sizes. */ + ret = wc_Tls13_HybridSharedSecret(keyShareEntry->group, pqc_first, + ssl->arrays->preMasterSecret, MAX_PREMASTER_SZ, + &ssl->arrays->preMasterSz, ssPqc, ssSzPqc); + if (ret != 0) + ForceZero(ssl->arrays->preMasterSecret, MAX_PREMASTER_SZ); + #else ssl->arrays->preMasterSz += ssSzPqc; + #endif } else #ifdef WOLFSSL_ASYNC_CRYPT @@ -10886,6 +10901,7 @@ static int TLSX_KeyShare_ProcessPqcHybridClient(WOLFSSL* ssl, } } + ForceZero(ssPqc, sizeof(ssPqc)); TLSX_KeyShare_FreeAll(ecc_kse, ssl->heap); TLSX_KeyShare_FreeAll(pqc_kse, ssl->heap); @@ -11404,6 +11420,7 @@ int TLSX_KeyShare_HandlePqcHybridKeyServer(WOLFSSL* ssl, word32 pubSz = 0; word32 ctSz = 0; word32 ssSzPqc = 0; + byte ssPqc[WC_ML_KEM_SS_SZ]; if (data == NULL) { WOLFSSL_MSG("No hybrid key share data from the client."); @@ -11550,10 +11567,8 @@ int TLSX_KeyShare_HandlePqcHybridKeyServer(WOLFSSL* ssl, } } - /* Process ECDH key share part. The generated shared secret is directly - * stored in the ssl->arrays->preMasterSecret buffer. Depending on the - * pqc_first flag, the ECDH shared secret part goes before or after the - * KEM part. */ + /* Process ECDH key share part into ssl->arrays->preMasterSecret: at the + * start, or after the KEM part if pqc_first and the module cannot join. */ if (ret == 0) { ecc_kse->keLen = len - pubSz; ecc_kse->ke = (byte*)XMALLOC(ecc_kse->keLen, ssl->heap, @@ -11568,7 +11583,9 @@ int TLSX_KeyShare_HandlePqcHybridKeyServer(WOLFSSL* ssl, if (pqc_first) { pubOffset = pubSz; + #ifndef WOLFSSL_HAVE_TLS13_HYBRID_SECRET ssOffset = ssSzPqc; + #endif } XMEMCPY(ecc_kse->ke, data + pubOffset, ecc_kse->keLen); @@ -11619,26 +11636,44 @@ int TLSX_KeyShare_HandlePqcHybridKeyServer(WOLFSSL* ssl, ret = LENGTH_ERROR; } - /* Process PQC KEM key share part. Depending on the pqc_first flag, the - * KEM shared secret part goes before or after the ECDH part. */ + /* Process PQC KEM key share part. The module joins its shared secret, or + * without WOLFSSL_HAVE_TLS13_HYBRID_SECRET pqc_first places it. */ if (ret == 0) { int input_offset = ecc_kse->keLen; - int output_offset = ssl->arrays->preMasterSz; + #ifdef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + byte* output = ssPqc; + #else + byte* output = ssl->arrays->preMasterSecret + ssl->arrays->preMasterSz; + #endif if (pqc_first) { input_offset = 0; - output_offset = 0; + #ifndef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + output = ssl->arrays->preMasterSecret; + #endif } ret = TLSX_KeyShare_HandlePqcKeyServer(ssl, pqc_kse, - data + input_offset, pubSz, - ssl->arrays->preMasterSecret + output_offset, &ssSzPqc); + data + input_offset, pubSz, output, &ssSzPqc); } +#ifdef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + /* The module joins the secrets; FIPS builds enforce the group's order and + * sizes. */ + if (ret == 0) { + ret = wc_Tls13_HybridSharedSecret(keyShareEntry->group, pqc_first, + ssl->arrays->preMasterSecret, MAX_PREMASTER_SZ, + &ssl->arrays->preMasterSz, ssPqc, ssSzPqc); + } +#endif + ForceZero(ssPqc, sizeof(ssPqc)); + if (ret == 0) { XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + #ifndef WOLFSSL_HAVE_TLS13_HYBRID_SECRET ssl->arrays->preMasterSz += ssSzPqc; + #endif keyShareEntry->ke = NULL; keyShareEntry->keLen = 0; #ifdef WOLFSSL_ASYNC_CRYPT diff --git a/tests/api/test_kdf.c b/tests/api/test_kdf.c index 86ce87f6d36..462366050b0 100644 --- a/tests/api/test_kdf.c +++ b/tests/api/test_kdf.c @@ -1015,3 +1015,86 @@ int test_wc_SshKdfReuse(void) #endif return EXPECT_RESULT(); } + +/* draft-ietf-tls-ecdhe-mlkem-03 sec 4.3: X25519MLKEM768 puts the ML-KEM secret + * first, SecP256r1MLKEM768 and SecP384r1MLKEM1024 put the ECDH secret first. */ +int test_wc_Tls13_HybridSharedSecret(void) +{ + EXPECT_DECLS; +#ifdef WOLFSSL_HAVE_TLS13_HYBRID_SECRET + byte ec[48], kem[32], ss[96], want[96]; + word32 ssSz; + word32 i; + + /* Distinct bytes, so a wrong offset or length inside a part shows. */ + for (i = 0; i < sizeof(ec); i++) + ec[i] = (byte)i; + for (i = 0; i < sizeof(kem); i++) + kem[i] = (byte)(0x80 | i); + + /* SecP256r1MLKEM768: ECDH (32) then ML-KEM (32), exact fit */ + XMEMCPY(ss, ec, 32); + ssSz = 32; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + 64, &ssSz, kem, sizeof(kem)), 0); + ExpectIntEQ(ssSz, 64); + XMEMCPY(want, ec, 32); + XMEMCPY(want + 32, kem, sizeof(kem)); + ExpectBufEQ(ss, want, 64); + + /* SecP384r1MLKEM1024: ECDH (48) then ML-KEM (32) */ + XMEMCPY(ss, ec, 48); + ssSz = 48; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP384R1MLKEM1024, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), 0); + ExpectIntEQ(ssSz, 80); + XMEMCPY(want, ec, 48); + XMEMCPY(want + 48, kem, sizeof(kem)); + ExpectBufEQ(ss, want, 80); + + /* X25519MLKEM768: ML-KEM (32) then X25519 (32), exact fit */ + XMEMCPY(ss, ec, 32); + ssSz = 32; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_X25519MLKEM768, 1, ss, + 64, &ssSz, kem, sizeof(kem)), 0); + ExpectIntEQ(ssSz, 64); + XMEMCPY(want, kem, sizeof(kem)); + XMEMCPY(want + 32, ec, 32); + ExpectBufEQ(ss, want, 64); + + /* Bad arguments and a result that does not fit. */ + ssSz = 32; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, NULL, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), NULL, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), &ssSz, NULL, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), &ssSz, kem, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + 63, &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BUFFER_E)); + ssSz = 0; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifndef HAVE_FIPS + ssSz = 97; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BUFFER_E)); +#else + /* FIPS: only the module's groups, each in its own order and sizes. */ + ssSz = 32; + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 1, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_X25519MLKEM768, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM512, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP384R1MLKEM1024, 0, ss, + sizeof(ss), &ssSz, kem, sizeof(kem)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Tls13_HybridSharedSecret(WOLFSSL_SECP256R1MLKEM768, 0, ss, + sizeof(ss), &ssSz, kem, 16), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_kdf.h b/tests/api/test_kdf.h index 8ea13b43cca..a6fb726aed6 100644 --- a/tests/api/test_kdf.h +++ b/tests/api/test_kdf.h @@ -27,10 +27,12 @@ int test_wc_KdfDecisionCoverage(void); int test_wc_KdfFeatureCoverage(void); int test_wc_SshKdfReuse(void); +int test_wc_Tls13_HybridSharedSecret(void); #define TEST_KDF_DECLS \ TEST_DECL_GROUP("kdf", test_wc_KdfDecisionCoverage), \ TEST_DECL_GROUP("kdf", test_wc_KdfFeatureCoverage), \ - TEST_DECL_GROUP("kdf", test_wc_SshKdfReuse) + TEST_DECL_GROUP("kdf", test_wc_SshKdfReuse), \ + TEST_DECL_GROUP("kdf", test_wc_Tls13_HybridSharedSecret) #endif /* WOLFCRYPT_TEST_KDF_H */ diff --git a/tests/api/test_mlkem.c b/tests/api/test_mlkem.c index ba335cb6393..418aad30b47 100644 --- a/tests/api/test_mlkem.c +++ b/tests/api/test_mlkem.c @@ -5404,6 +5404,10 @@ int test_wc_mlkem_decode_reused_key(void) !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) #define MLKEM_OOM_TEST +#endif + +#if (defined(MLKEM_OOM_TEST) || defined(MLKEM_PCT_OOM_TEST)) && \ + defined(USE_WOLFSSL_MEMORY) static wolfSSL_Malloc_cb mlkem_oom_mf; static wolfSSL_Free_cb mlkem_oom_ff; @@ -5436,12 +5440,9 @@ static void* mlkem_oom_realloc(void* p, size_t n) return (mlkem_oom_rf != NULL) ? mlkem_oom_rf(p, n) : realloc(p, n); } -/* Decodes into kA with one key vector allocation failing. */ -static int mlkem_oom_decode(MlKemKey* kA, int priv, const byte* in, - word32 inLen) +/* Fails the next allocation of mlkem_oom_size bytes. */ +static int mlkem_oom_arm(void) { - int ret; - if ((wolfSSL_GetAllocators(&mlkem_oom_mf, &mlkem_oom_ff, &mlkem_oom_rf) != 0) || (wolfSSL_SetAllocators(mlkem_oom_malloc, mlkem_oom_free, @@ -5449,14 +5450,33 @@ static int mlkem_oom_decode(MlKemKey* kA, int priv, const byte* in, return -1; } mlkem_oom_armed = 1; + return 0; +} + +static void mlkem_oom_disarm(void) +{ + mlkem_oom_armed = 0; + (void)wolfSSL_SetAllocators(mlkem_oom_mf, mlkem_oom_ff, mlkem_oom_rf); +} +#endif + +#ifdef MLKEM_OOM_TEST +/* Decodes into kA with one key vector allocation failing. */ +static int mlkem_oom_decode(MlKemKey* kA, int priv, const byte* in, + word32 inLen) +{ + int ret; + + if (mlkem_oom_arm() != 0) { + return -1; + } if (priv) { ret = wc_MlKemKey_DecodePrivateKey(kA, in, inLen); } else { ret = wc_MlKemKey_DecodePublicKey(kA, in, inLen); } - mlkem_oom_armed = 0; - (void)wolfSSL_SetAllocators(mlkem_oom_mf, mlkem_oom_ff, mlkem_oom_rf); + mlkem_oom_disarm(); return ret; } #endif @@ -5533,6 +5553,51 @@ int test_wc_mlkem_decode_alloc_fail(void) return EXPECT_RESULT(); } +#ifdef MLKEM_PCT_OOM_TEST +/* ISO/IEC 19790:2012 sec 7.10.3.3: every generated key pair is tested; a key + * whose test cannot finish is freed, so it cannot be exported or used. */ +int test_wc_mlkem_pct_alloc_fail(void) +{ + EXPECT_DECLS; + MlKemKey* key = NULL; + WC_RNG rng; + byte pub[WC_ML_KEM_768_PUBLIC_KEY_SIZE]; + byte ct[WC_ML_KEM_768_CIPHER_TEXT_SIZE]; + byte ss[WC_ML_KEM_SS_SZ]; + int ret = -1; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(ct, 0, sizeof(ct)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(key = (MlKemKey*)XMALLOC(sizeof(*key), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + ExpectIntEQ(wc_MlKemKey_Init(key, WC_ML_KEM_768, NULL, INVALID_DEVID), 0); + + /* Fail the key-pair test's ciphertext buffer. */ + mlkem_oom_size = WC_ML_KEM_MAX_CIPHER_TEXT_SIZE; + if (EXPECT_SUCCESS() && (mlkem_oom_arm() == 0)) { + ret = wc_MlKemKey_MakeKey(key, &rng); + mlkem_oom_disarm(); + } + ExpectIntEQ(ret, WC_NO_ERR_TRACE(MEMORY_E)); + + ExpectIntEQ(wc_MlKemKey_EncodePublicKey(key, pub, sizeof(pub)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wc_MlKemKey_Encapsulate(key, ct, ss, &rng), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wc_MlKemKey_Decapsulate(key, ss, ct, sizeof(ct)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + DoExpectIntEQ(wc_MlKemKey_Free(key), 0); + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + return EXPECT_RESULT(); +} +#endif + #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) && \ !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && !defined(WOLFSSL_NO_ML_KEM_768) && \ defined(WC_CRYPTOCB_KEYGEN_ID_CHECK) && \ diff --git a/tests/api/test_mlkem.h b/tests/api/test_mlkem.h index 5c42ecbca7c..8b67ea8cba0 100644 --- a/tests/api/test_mlkem.h +++ b/tests/api/test_mlkem.h @@ -47,6 +47,24 @@ int test_wc_mlkem_decode_alloc_fail(void); int test_wc_mlkem_make_key_id_dev(void); int test_wc_mlkem_operation_cleanup(void); +/* The key-pair test is FIPS v7 (or WOLFSSL_VALIDATE_MLKEM_KEYGEN) only, and + * allocates its buffer only in small-stack builds. */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) && \ + !defined(WOLFSSL_NO_ML_KEM_768) && defined(WC_DECLARE_VAR_IS_HEAP_ALLOC) && \ + (FIPS_VERSION3_GE(7,0,0) || defined(WOLFSSL_VALIDATE_MLKEM_KEYGEN)) && \ + defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_DEBUG_MEMORY) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) && !defined(WC_NO_RNG) +#define MLKEM_PCT_OOM_TEST +int test_wc_mlkem_pct_alloc_fail(void); +#define TEST_MLKEM_PCT_OOM_DECL \ + , TEST_DECL_GROUP("mlkem", test_wc_mlkem_pct_alloc_fail) +#else +#define TEST_MLKEM_PCT_OOM_DECL +#endif + #define TEST_MLKEM_DECLS \ TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_kats), \ TEST_DECL_GROUP("mlkem", test_wc_MlKemKey_seed_service_indicator), \ @@ -69,6 +87,7 @@ int test_wc_mlkem_operation_cleanup(void); TEST_DECL_GROUP("mlkem", test_wc_mlkem_decode_reused_key), \ TEST_DECL_GROUP("mlkem", test_wc_mlkem_decode_alloc_fail), \ TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_id_dev), \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_operation_cleanup) + TEST_DECL_GROUP("mlkem", test_wc_mlkem_operation_cleanup) \ + TEST_MLKEM_PCT_OOM_DECL #endif /* WOLFCRYPT_TEST_MLKEM_H */ diff --git a/tests/quic.c b/tests/quic.c index 2b67b9b060e..233b484a860 100644 --- a/tests/quic.c +++ b/tests/quic.c @@ -2279,7 +2279,8 @@ static int test_quic_key_share(int verbose) { QuicTestContext_init(&tclient, ctx_c, "client", verbose); QuicTestContext_init(&tserver, ctx_s, "server", verbose); -#ifdef HAVE_CURVE25519 +/* FIPS TLS offers X25519 only inside a hybrid group. */ +#if defined(HAVE_CURVE25519) && !defined(HAVE_FIPS) ExpectTrue(wolfSSL_set1_curves_list(tclient.ssl, "X25519:P-256") == WOLFSSL_SUCCESS); ExpectTrue(wolfSSL_set1_curves_list(tserver.ssl, "X25519") @@ -2304,7 +2305,7 @@ static int test_quic_key_share(int verbose) { QuicTestContext_init(&tclient, ctx_c, "client", verbose); QuicTestContext_init(&tserver, ctx_s, "server", verbose); -#ifdef HAVE_CURVE25519 +#if defined(HAVE_CURVE25519) && !defined(HAVE_FIPS) ExpectTrue(wolfSSL_set1_curves_list(tclient.ssl, "X25519:P-256") == WOLFSSL_SUCCESS); ExpectTrue(wolfSSL_set1_curves_list(tserver.ssl, "P-256") @@ -2329,7 +2330,7 @@ static int test_quic_key_share(int verbose) { QuicTestContext_init(&tclient, ctx_c, "client", verbose); QuicTestContext_init(&tserver, ctx_s, "server", verbose); -#ifdef HAVE_CURVE25519 +#if defined(HAVE_CURVE25519) && !defined(HAVE_FIPS) ExpectTrue(wolfSSL_set1_curves_list(tclient.ssl, "P-256") == WOLFSSL_SUCCESS); ExpectTrue(wolfSSL_set1_curves_list(tserver.ssl, "X25519") diff --git a/wolfcrypt/src/kdf.c b/wolfcrypt/src/kdf.c index 9fe011a10c2..7e1d977e4ea 100644 --- a/wolfcrypt/src/kdf.c +++ b/wolfcrypt/src/kdf.c @@ -625,6 +625,59 @@ int wc_PRF_TLS(byte* digest, word32 digLen, const byte* secret, word32 secLen, #endif /* HAVE_HKDF && !NO_HMAC */ +#ifdef WOLFSSL_HAVE_MLKEM +/* Puts the ML-KEM secret after the ECDH secret in ss, or before it if kemFirst. + * FIPS builds allow only the groups, orders and sizes of + * draft-ietf-tls-ecdhe-mlkem-03 sec 4.3. */ +int wc_Tls13_HybridSharedSecret(word16 group, int kemFirst, byte* ss, + word32 ssMax, word32* ssSz, const byte* kemSs, word32 kemSsSz) +{ +#ifdef HAVE_FIPS + /* TLS code point, ML-KEM first, ECDH size; ML-KEM is 32 (FIPS 203). */ + static const word16 hybrid[][3] = { + { 4587, 0, 32 }, /* SecP256r1MLKEM768 */ + { 4588, 1, 32 }, /* X25519MLKEM768 */ + { 4589, 0, 48 } /* SecP384r1MLKEM1024 */ + }; + word32 i; +#endif + word32 ecSz; + + if ((ss == NULL) || (ssSz == NULL) || (kemSs == NULL) || (*ssSz == 0) || + (kemSsSz == 0)) + return BAD_FUNC_ARG; + kemFirst = (kemFirst != 0); + +#ifdef HAVE_FIPS + for (i = 0; i < sizeof(hybrid) / sizeof(hybrid[0]); i++) { + if (hybrid[i][0] == group) + break; + } + if ((i == sizeof(hybrid) / sizeof(hybrid[0])) || + (hybrid[i][1] != kemFirst) || (hybrid[i][2] != *ssSz) || + (kemSsSz != 32)) + return BAD_FUNC_ARG; +#else + (void)group; +#endif + + ecSz = *ssSz; + if ((ecSz > ssMax) || (kemSsSz > ssMax - ecSz)) + return BUFFER_E; + + if (kemFirst) { + XMEMMOVE(ss + kemSsSz, ss, ecSz); + XMEMCPY(ss, kemSs, kemSsSz); + } + else { + XMEMCPY(ss + ecSz, kemSs, kemSsSz); + } + *ssSz = ecSz + kemSsSz; + + return 0; +} +#endif /* WOLFSSL_HAVE_MLKEM */ + #ifdef WOLFSSL_WOLFSSH diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index 6ccd3db9eca..84e15f8be28 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -140,12 +140,14 @@ * All valid reports are true. * Fast fail gives faster signing times on average. * DO NOT enable this if implementation must be conformant to FIPS 204. + * Cannot be used in a FIPS build. * WOLFSSL_MLDSA_SIGN_CHECK_W0 Default: OFF * Check vector w0 is in required range as an early check on valid vector r0. * Falsely reports invalid in approximately 3-5% of checks. * All valid reports are true. * Fast fail gives faster signing times on average. * DO NOT enable this if implementation must be conformant to FIPS 204. + * Cannot be used in a FIPS build. * WOLFSSL_MLDSA_TEST_REJ_NTT_FAIL Default: OFF * Test aid: wc_mldsa_test_rej_ntt_fail() makes a chosen later call of * mldsa_rej_ntt_poly_ex() fail, to test error handling. Every call counts, @@ -252,6 +254,11 @@ #if defined(HAVE_FIPS) && defined(WOLFSSL_MLDSA_ALLOW_WEAK_PREHASH) #error "WOLFSSL_MLDSA_ALLOW_WEAK_PREHASH is not allowed in a FIPS build" #endif +/* Extra rejections change the signature FIPS 204 Algorithm 7 produces. */ +#if defined(HAVE_FIPS) && (defined(WOLFSSL_MLDSA_SIGN_CHECK_Y) || \ + defined(WOLFSSL_MLDSA_SIGN_CHECK_W0)) + #error "WOLFSSL_MLDSA_SIGN_CHECK_Y/_W0 are not allowed in a FIPS build" +#endif /* Signing drives the whole-vector helpers when it holds a full vector: the * default implementation, and PRECALC_A which multiplies the pre-calculated diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 1192caffe63..bb18473259f 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -871,8 +871,9 @@ int wc_MlKemKey_MakeKey(MlKemKey* key, WC_RNG* rng) * @return NOT_COMPILED_IN when key type is not supported. * @return MEMORY_E when dynamic memory allocation failed. * @return BAD_COND_E when fault attack detected. - * @return ML_KEM_PCT_E when the key pair fails its consistency test. The - * key is freed in that case and must be re-initialised before reuse. + * @return ML_KEM_PCT_E when the key pair fails its consistency test. On + * any error from that test the key is freed and must be + * re-initialised before reuse. */ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, int len) @@ -1226,12 +1227,12 @@ key-pair test required by ISO/IEC 19790:2012 sec 7.10.3.3" WC_FREE_VAR_EX(pct_ct, key->heap, DYNAMIC_TYPE_TMP_BUFFER); - /* Free a key that failed the test, so a caller ignoring the return - * value cannot use it. ISO/IEC 19790:2012 sec 7.10.1 forbids using - * anything that failed a self-test. MEMORY_E is excluded: it - * means the test never ran, so the key is not implicated. */ - if ((ret != 0) && (ret != WC_NO_ERR_TRACE(MEMORY_E))) { - wc_MlKemKey_Free(key); + /* Free a key that failed or could not finish the test. + * ISO/IEC 19790:2012 sec 7.10.3.3: every generated key pair is tested. */ + if (ret != 0) { + int freeRet = wc_MlKemKey_Free(key); + if (freeRet != 0) + ret = freeRet; } } #endif /* FIPS v7 or WOLFSSL_VALIDATE_MLKEM_KEYGEN */ diff --git a/wolfssl/wolfcrypt/kdf.h b/wolfssl/wolfcrypt/kdf.h index da5990e7793..99de9acffd0 100644 --- a/wolfssl/wolfcrypt/kdf.h +++ b/wolfssl/wolfcrypt/kdf.h @@ -115,6 +115,13 @@ WOLFSSL_API int wc_Tls13_HKDF_Expand_Label_Alloc(byte* okm, word32 okmLen, #endif /* HAVE_HKDF */ +#ifdef WOLFSSL_HAVE_MLKEM +/* Set when wc_Tls13_HybridSharedSecret() is available. */ +#define WOLFSSL_HAVE_TLS13_HYBRID_SECRET +WOLFSSL_API int wc_Tls13_HybridSharedSecret(word16 group, int kemFirst, + byte* ss, word32 ssMax, word32* ssSz, const byte* kemSs, word32 kemSsSz); +#endif + #ifdef WOLFSSL_WOLFSSH WOLFSSL_API int wc_SSH_KDF(byte hashId, byte keyId, diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 4cf00c8f08f..ccaeb84e65a 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -5574,6 +5574,11 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #error "Neither PQ/T hybrid combinations nor ML-KEM as standalone TLS key " \ "exchange are enabled" #endif +/* FIPS v7 allows only the draft-ietf-tls-ecdhe-mlkem hybrid groups. */ +#if defined(HAVE_FIPS) && FIPS_VERSION3_GE(7,0,0) && \ + defined(WOLFSSL_EXTRA_PQC_HYBRIDS) + #error "WOLFSSL_EXTRA_PQC_HYBRIDS is not available in FIPS v7 builds" +#endif /* SRTP requires DTLS */ #if defined(WOLFSSL_SRTP) && !defined(WOLFSSL_DTLS) diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index c7c9b327362..8e6dcca0500 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -49,7 +49,7 @@ if(CONFIG_WOLFSSL) if(CONFIG_WOLFCRYPT_FIPS_V7 OR CONFIG_WOLFCRYPT_FIPS_READY) # src/include.am BUILD_FIPS_V7_PLUS order; files a build does not select compile empty. set(WOLF_BOUNDARY hmac random kdf rsa ecc aes sha sha256 sha512 sha3 dh cmac - curve448 ed448 curve25519 ed25519 pwdbased sp_c32 sp_c64 sp_x86_64 + ed448 ed25519 pwdbased sp_c32 sp_c64 sp_x86_64 sp_arm32 sp_armthumb sp_arm64 sp_cortexm wc_mlkem wc_mlkem_poly wc_mldsa wc_lms wc_lms_impl wc_xmss wc_xmss_impl wc_slhdsa) set(WOLF_BOUNDARY_ASM ON)