diff --git a/tests/api/test_ed25519.c b/tests/api/test_ed25519.c index 634cf47af2d..34d39099d33 100644 --- a/tests/api/test_ed25519.c +++ b/tests/api/test_ed25519.c @@ -169,6 +169,52 @@ int test_wc_ed25519_sign_msg(void) } /* END test_wc_ed25519_sign_msg */ +/* + * Test that wc_ed25519_sign_msg() rejects a public-key-only key object. + * A key with pubKeySet=1 but privKeySet=0 must not silently sign. + */ +int test_wc_ed25519_sign_msg_pubonly_fails(void) +{ + EXPECT_DECLS; +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN) && \ + defined(HAVE_ED25519_KEY_IMPORT) && defined(HAVE_ED25519_KEY_EXPORT) + ed25519_key fullKey; + ed25519_key pubOnlyKey; + WC_RNG rng; + byte pubBuf[ED25519_PUB_KEY_SIZE]; + word32 pubSz = sizeof(pubBuf); + byte msg[] = "test message for pubonly check"; + byte sig[ED25519_SIG_SIZE]; + word32 sigLen = sizeof(sig); + + XMEMSET(&fullKey, 0, sizeof(fullKey)); + XMEMSET(&pubOnlyKey, 0, sizeof(pubOnlyKey)); + XMEMSET(&rng, 0, sizeof(rng)); + + ExpectIntEQ(wc_ed25519_init(&fullKey), 0); + ExpectIntEQ(wc_ed25519_init(&pubOnlyKey), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Generate a real key pair and export its public key. */ + ExpectIntEQ(wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &fullKey), 0); + ExpectIntEQ(wc_ed25519_export_public(&fullKey, pubBuf, &pubSz), 0); + + /* Import only the public key into a fresh key object. */ + ExpectIntEQ(wc_ed25519_import_public(pubBuf, pubSz, &pubOnlyKey), 0); + + /* Signing with a public-key-only object must fail. */ + ExpectIntEQ(wc_ed25519_sign_msg(msg, sizeof(msg), sig, &sigLen, + &pubOnlyKey), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_ed25519_free(&pubOnlyKey); + wc_ed25519_free(&fullKey); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_ed25519_sign_msg_pubonly_fails */ + /* * Testing wc_ed25519_import_public() */ diff --git a/tests/api/test_ed25519.h b/tests/api/test_ed25519.h index 81f71c30965..42b23ddda02 100644 --- a/tests/api/test_ed25519.h +++ b/tests/api/test_ed25519.h @@ -27,6 +27,7 @@ int test_wc_ed25519_make_key(void); int test_wc_ed25519_init(void); int test_wc_ed25519_sign_msg(void); +int test_wc_ed25519_sign_msg_pubonly_fails(void); int test_wc_ed25519_import_public(void); int test_wc_ed25519_import_private_key(void); int test_wc_ed25519_export(void); @@ -40,6 +41,7 @@ int test_wc_Ed25519PrivateKeyToDer(void); TEST_DECL_GROUP("ed25519", test_wc_ed25519_make_key), \ TEST_DECL_GROUP("ed25519", test_wc_ed25519_init), \ TEST_DECL_GROUP("ed25519", test_wc_ed25519_sign_msg), \ + TEST_DECL_GROUP("ed25519", test_wc_ed25519_sign_msg_pubonly_fails), \ TEST_DECL_GROUP("ed25519", test_wc_ed25519_import_public), \ TEST_DECL_GROUP("ed25519", test_wc_ed25519_import_private_key), \ TEST_DECL_GROUP("ed25519", test_wc_ed25519_export), \ diff --git a/tests/api/test_ed448.c b/tests/api/test_ed448.c index f4f4bf5f50f..fd2b2393bc0 100644 --- a/tests/api/test_ed448.c +++ b/tests/api/test_ed448.c @@ -162,6 +162,52 @@ int test_wc_ed448_sign_msg(void) return EXPECT_RESULT(); } /* END test_wc_ed448_sign_msg */ +/* + * Test that wc_ed448_sign_msg() rejects a public-key-only key object. + * A key with pubKeySet=1 but privKeySet=0 must not silently sign. + */ +int test_wc_ed448_sign_msg_pubonly_fails(void) +{ + EXPECT_DECLS; +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) +#if defined(HAVE_ED448) && defined(HAVE_ED448_SIGN) && \ + defined(HAVE_ED448_KEY_IMPORT) && defined(HAVE_ED448_KEY_EXPORT) + ed448_key fullKey; + ed448_key pubOnlyKey; + WC_RNG rng; + byte pubBuf[ED448_PUB_KEY_SIZE]; + word32 pubSz = sizeof(pubBuf); + byte msg[] = "test message for pubonly check"; + byte sig[ED448_SIG_SIZE]; + word32 sigLen = sizeof(sig); + + XMEMSET(&fullKey, 0, sizeof(fullKey)); + XMEMSET(&pubOnlyKey, 0, sizeof(pubOnlyKey)); + XMEMSET(&rng, 0, sizeof(rng)); + + ExpectIntEQ(wc_ed448_init(&fullKey), 0); + ExpectIntEQ(wc_ed448_init(&pubOnlyKey), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Generate a real key pair and export its public key. */ + ExpectIntEQ(wc_ed448_make_key(&rng, ED448_KEY_SIZE, &fullKey), 0); + ExpectIntEQ(wc_ed448_export_public(&fullKey, pubBuf, &pubSz), 0); + + /* Import only the public key into a fresh key object. */ + ExpectIntEQ(wc_ed448_import_public(pubBuf, pubSz, &pubOnlyKey), 0); + + /* Signing with a public-key-only object must fail. */ + ExpectIntEQ(wc_ed448_sign_msg(msg, sizeof(msg), sig, &sigLen, + &pubOnlyKey, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_ed448_free(&pubOnlyKey); + wc_ed448_free(&fullKey); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_ed448_sign_msg_pubonly_fails */ + /* * Testing wc_ed448_import_public() */ diff --git a/tests/api/test_ed448.h b/tests/api/test_ed448.h index 7b331fa6c00..f0dc2611724 100644 --- a/tests/api/test_ed448.h +++ b/tests/api/test_ed448.h @@ -27,6 +27,7 @@ int test_wc_ed448_make_key(void); int test_wc_ed448_init(void); int test_wc_ed448_sign_msg(void); +int test_wc_ed448_sign_msg_pubonly_fails(void); int test_wc_ed448_import_public(void); int test_wc_ed448_import_private_key(void); int test_wc_ed448_export(void); @@ -40,6 +41,7 @@ int test_wc_Ed448PrivateKeyToDer(void); TEST_DECL_GROUP("ed448", test_wc_ed448_make_key), \ TEST_DECL_GROUP("ed448", test_wc_ed448_init), \ TEST_DECL_GROUP("ed448", test_wc_ed448_sign_msg), \ + TEST_DECL_GROUP("ed448", test_wc_ed448_sign_msg_pubonly_fails), \ TEST_DECL_GROUP("ed448", test_wc_ed448_import_public), \ TEST_DECL_GROUP("ed448", test_wc_ed448_import_private_key), \ TEST_DECL_GROUP("ed448", test_wc_ed448_export), \ diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index 2aa788e8615..8664ae58126 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -685,6 +685,84 @@ int test_wc_dilithium(void) return EXPECT_RESULT(); } +/* + * Test that wc_dilithium_sign_msg() rejects a public-key-only key object. + * A key with prvKeySet=0 must not silently sign with zeroed key data. + */ +int test_wc_dilithium_sign_pubonly_fails(void) +{ + EXPECT_DECLS; +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_CTX) + dilithium_key* key; + dilithium_key* pubOnlyKey; + WC_RNG rng; + byte* pubBuf = NULL; + word32 pubLen = DILITHIUM_MAX_PUB_KEY_SIZE; + byte msg[] = "test message for pubonly check"; + byte* sig = NULL; + word32 sigLen = DILITHIUM_MAX_SIG_SIZE; + + key = (dilithium_key*)XMALLOC(sizeof(*key), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + pubOnlyKey = (dilithium_key*)XMALLOC(sizeof(*pubOnlyKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pubOnlyKey); + pubBuf = (byte*)XMALLOC(DILITHIUM_MAX_PUB_KEY_SIZE, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pubBuf); + sig = (byte*)XMALLOC(DILITHIUM_MAX_SIG_SIZE, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(sig); + + if (key != NULL) + XMEMSET(key, 0, sizeof(*key)); + if (pubOnlyKey != NULL) + XMEMSET(pubOnlyKey, 0, sizeof(*pubOnlyKey)); + XMEMSET(&rng, 0, sizeof(rng)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_dilithium_init(key), 0); + ExpectIntEQ(wc_dilithium_init(pubOnlyKey), 0); + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_dilithium_set_level(key, WC_ML_DSA_44), 0); + ExpectIntEQ(wc_dilithium_set_level(pubOnlyKey, WC_ML_DSA_44), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_65) + ExpectIntEQ(wc_dilithium_set_level(key, WC_ML_DSA_65), 0); + ExpectIntEQ(wc_dilithium_set_level(pubOnlyKey, WC_ML_DSA_65), 0); +#else + ExpectIntEQ(wc_dilithium_set_level(key, WC_ML_DSA_87), 0); + ExpectIntEQ(wc_dilithium_set_level(pubOnlyKey, WC_ML_DSA_87), 0); +#endif + + /* Generate a real key pair and export its public key. */ + ExpectIntEQ(wc_dilithium_make_key(key, &rng), 0); + ExpectIntEQ(wc_dilithium_export_public(key, pubBuf, &pubLen), 0); + + /* Import only the public key into a fresh key object. */ + ExpectIntEQ(wc_dilithium_import_public(pubBuf, pubLen, pubOnlyKey), 0); + + /* Signing with a public-key-only object must fail. */ + ExpectIntEQ(wc_dilithium_sign_ctx_msg(NULL, 0, msg, sizeof(msg), sig, + &sigLen, pubOnlyKey, &rng), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_dilithium_free(pubOnlyKey); + wc_dilithium_free(key); + XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubOnlyKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_dilithium_sign_pubonly_fails */ + int test_wc_dilithium_make_key(void) { EXPECT_DECLS; diff --git a/tests/api/test_mldsa.h b/tests/api/test_mldsa.h index 2493100d0ab..92381c1812c 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -25,6 +25,7 @@ #include int test_wc_dilithium(void); +int test_wc_dilithium_sign_pubonly_fails(void); int test_wc_dilithium_make_key(void); int test_wc_dilithium_sign(void); int test_wc_dilithium_verify(void); @@ -42,6 +43,7 @@ int test_mldsa_pkcs12(void); #define TEST_MLDSA_DECLS \ TEST_DECL_GROUP("mldsa", test_wc_dilithium), \ + TEST_DECL_GROUP("mldsa", test_wc_dilithium_sign_pubonly_fails), \ TEST_DECL_GROUP("mldsa", test_wc_dilithium_make_key), \ TEST_DECL_GROUP("mldsa", test_wc_dilithium_sign), \ TEST_DECL_GROUP("mldsa", test_wc_dilithium_verify), \ diff --git a/tests/api/test_mlkem.c b/tests/api/test_mlkem.c index c727194fbe1..e9e42661a2f 100644 --- a/tests/api/test_mlkem.c +++ b/tests/api/test_mlkem.c @@ -3872,3 +3872,81 @@ int test_wc_mlkem_decapsulate_kats(void) return EXPECT_RESULT(); } +/* + * Test that wc_MlKemKey_Decapsulate() rejects a public-key-only key object. + * A key with MLKEM_FLAG_PUB_SET but not MLKEM_FLAG_PRIV_SET must not + * silently decapsulate with zeroed private key data. + */ +int test_wc_mlkem_decapsulate_pubonly_fails(void) +{ + EXPECT_DECLS; +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) +#if defined(WOLFSSL_HAVE_MLKEM) && defined(WOLFSSL_WC_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) + MlKemKey* fullKey; + MlKemKey* pubOnlyKey; + WC_RNG rng; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ss[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + byte pubBuf[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + word32 pubLen = 0; + word32 ctLen = 0; + + fullKey = (MlKemKey*)XMALLOC(sizeof(*fullKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(fullKey); + pubOnlyKey = (MlKemKey*)XMALLOC(sizeof(*pubOnlyKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pubOnlyKey); + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + +#ifndef WOLFSSL_NO_ML_KEM_768 + ExpectIntEQ(wc_MlKemKey_Init(fullKey, WC_ML_KEM_768, NULL, + INVALID_DEVID), 0); + ExpectIntEQ(wc_MlKemKey_Init(pubOnlyKey, WC_ML_KEM_768, NULL, + INVALID_DEVID), 0); +#elif !defined(WOLFSSL_NO_ML_KEM_512) + ExpectIntEQ(wc_MlKemKey_Init(fullKey, WC_ML_KEM_512, NULL, + INVALID_DEVID), 0); + ExpectIntEQ(wc_MlKemKey_Init(pubOnlyKey, WC_ML_KEM_512, NULL, + INVALID_DEVID), 0); +#else + ExpectIntEQ(wc_MlKemKey_Init(fullKey, WC_ML_KEM_1024, NULL, + INVALID_DEVID), 0); + ExpectIntEQ(wc_MlKemKey_Init(pubOnlyKey, WC_ML_KEM_1024, NULL, + INVALID_DEVID), 0); +#endif + + /* Get correct sizes for this key type. */ + ExpectIntEQ(wc_MlKemKey_PublicKeySize(fullKey, &pubLen), 0); + ExpectIntEQ(wc_MlKemKey_CipherTextSize(fullKey, &ctLen), 0); + + /* Generate a real key pair. */ + ExpectIntEQ(wc_MlKemKey_MakeKey(fullKey, &rng), 0); + + /* Encapsulate with the full key to get a valid ciphertext. */ + ExpectIntEQ(wc_MlKemKey_Encapsulate(fullKey, ct, ss, &rng), 0); + + /* Export and import only the public key. */ + ExpectIntEQ(wc_MlKemKey_EncodePublicKey(fullKey, pubBuf, pubLen), 0); + ExpectIntEQ(wc_MlKemKey_DecodePublicKey(pubOnlyKey, pubBuf, pubLen), 0); + + /* Decapsulating with a public-key-only object must fail. */ + ExpectIntEQ(wc_MlKemKey_Decapsulate(pubOnlyKey, ssDec, ct, ctLen), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_MlKemKey_Free(pubOnlyKey); + wc_MlKemKey_Free(fullKey); + XFREE(pubOnlyKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(fullKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_mlkem_decapsulate_pubonly_fails */ + diff --git a/tests/api/test_mlkem.h b/tests/api/test_mlkem.h index 5b9e7bdf624..24c05d6a0cd 100644 --- a/tests/api/test_mlkem.h +++ b/tests/api/test_mlkem.h @@ -27,10 +27,12 @@ int test_wc_mlkem_make_key_kats(void); int test_wc_mlkem_encapsulate_kats(void); int test_wc_mlkem_decapsulate_kats(void); +int test_wc_mlkem_decapsulate_pubonly_fails(void); #define TEST_MLKEM_DECLS \ TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_kats), \ TEST_DECL_GROUP("mlkem", test_wc_mlkem_encapsulate_kats), \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_kats) + TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_kats), \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_pubonly_fails) #endif /* WOLFCRYPT_TEST_MLKEM_H */ diff --git a/wolfcrypt/src/dilithium.c b/wolfcrypt/src/dilithium.c index 17a2dd8eb50..bc1deb42500 100644 --- a/wolfcrypt/src/dilithium.c +++ b/wolfcrypt/src/dilithium.c @@ -10192,6 +10192,9 @@ int wc_dilithium_sign_ctx_msg(const byte* ctx, byte ctxLen, const byte* msg, if ((ret == 0) && (ctx == NULL) && (ctxLen > 0)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->prvKeySet)) { + ret = BAD_FUNC_ARG; + } #ifdef WOLF_CRYPTO_CB if (ret == 0) { diff --git a/wolfcrypt/src/ed25519.c b/wolfcrypt/src/ed25519.c index 2e30d74899d..425531575b4 100644 --- a/wolfcrypt/src/ed25519.c +++ b/wolfcrypt/src/ed25519.c @@ -401,6 +401,8 @@ int wc_ed25519_sign_msg_ex(const byte* in, word32 inLen, byte* out, if (!key->pubKeySet) return BAD_FUNC_ARG; + if (!key->privKeySet) + return BAD_FUNC_ARG; /* check and set up out length */ if (*outLen < ED25519_SIG_SIZE) { diff --git a/wolfcrypt/src/ed448.c b/wolfcrypt/src/ed448.c index b46d5f93f09..37674cad51d 100644 --- a/wolfcrypt/src/ed448.c +++ b/wolfcrypt/src/ed448.c @@ -367,6 +367,9 @@ int wc_ed448_sign_msg_ex(const byte* in, word32 inLen, byte* out, if ((ret == 0) && (!key->pubKeySet)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->privKeySet)) { + ret = BAD_FUNC_ARG; + } /* check and set up out length */ if ((ret == 0) && (*outLen < ED448_SIG_SIZE)) { diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 66c86e9f7b6..13944148b7a 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -1481,6 +1481,9 @@ int wc_MlKemKey_Decapsulate(MlKemKey* key, unsigned char* ss, if ((key == NULL) || (ss == NULL) || (ct == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_PRIV_SET) == 0)) { + ret = BAD_STATE_E; + } if (ret == 0) { /* Establish cipher text size based on key type. */