diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 79494996bc..c20e76d92c 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -946,6 +946,7 @@ WOLFSSL_NO_DH186 WOLFSSL_NO_DILITHIUM_LEGACY_GATES WOLFSSL_NO_DILITHIUM_LEGACY_NAMES WOLFSSL_NO_DTLS_SIZE_CHECK +WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK WOLFSSL_NO_ETM_ALERT WOLFSSL_NO_FENCE diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index 2592e5cd00..135122b753 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/doc/dox_comments/header_files/asn_public.h @@ -2267,6 +2267,18 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz); input, parses the private key, and uses it to generate an ecc_key object, which it stores in key. + When the encoding carries only the private scalar, the public point is + derived on a best-effort basis; a failed derivation is logged but does not + fail the decode. Observable effects of a successful derivation: key->type + becomes ECC_PRIVATEKEY rather than ECC_PRIVATEKEY_ONLY, key->pubkey is + populated, and the decode costs an additional base-point scalar multiply. + Under ECC_TIMING_RESISTANT that multiply is blinded with the key's rng when + one was set via wc_ecc_set_rng() beforehand; no rng is created for the + derivation, so with none set the projective-coordinate randomization is + simply skipped. No derivation is attempted for a key with a devId set. + Define WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE to disable the derivation + entirely. + \return 0 On successfully decoding the private key and storing the result in the ecc_key struct \return ASN_PARSE_E: Returned if there is an error parsing the der file diff --git a/doc/dox_comments/header_files/wc_mldsa.h b/doc/dox_comments/header_files/wc_mldsa.h index 8aea9086de..0f9d20ed8f 100644 --- a/doc/dox_comments/header_files/wc_mldsa.h +++ b/doc/dox_comments/header_files/wc_mldsa.h @@ -257,6 +257,31 @@ int wc_MlDsaKey_MakeKey(wc_MlDsaKey* key, WC_RNG* rng); */ int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed); +/*! + \ingroup ML_DSA + + \brief Derives public key for a wc_MlDsaKey with private key set. + No-op if already set. + + Derives in software, so it is not supported on keys with a devId set, + unless the public key is already set - in that case this is a no-op + regardless of devId. + + \return 0 on success or already set. + \return BAD_FUNC_ARG if invalid args, or the public key is not yet set + and key has a devId set. + \return MEMORY_E on allocation failure. + \return PUBLIC_KEY_E if the derived public key does not match the t0 or + tr values stored in the private key. + \return Other negative on error. + + \param [in,out] key Pointer to wc_MlDsaKey. + + \sa wc_MlDsaKey_ImportPrivRaw + \sa wc_MlDsaKey_MakeKey +*/ +int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key); + /*! \ingroup ML_DSA @@ -453,6 +478,7 @@ int wc_MlDsaKey_SignWithSeed(wc_MlDsaKey* key, byte* sig, word32* sigLen, \return 0 if verification completed (check res for the result). \return BAD_FUNC_ARG if any required pointer is NULL or ctxLen is invalid. + \return PUBLIC_KEY_E when the public key is not set. \param [in,out] key Pointer to a wc_MlDsaKey with the public key. \param [in] sig Signature bytes to verify. @@ -480,6 +506,7 @@ int wc_MlDsaKey_VerifyCtx(wc_MlDsaKey* key, const byte* sig, word32 sigLen, \return 0 if verification completed (check res for the result). \return BAD_FUNC_ARG if any required pointer is NULL, ctxLen is invalid, or hashAlg is unsupported. + \return PUBLIC_KEY_E when the public key is not set. \param [in,out] key Pointer to a wc_MlDsaKey with the public key. \param [in] sig Signature bytes to verify. @@ -508,6 +535,7 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, \return 0 if verification completed (check res for the result). \return BAD_FUNC_ARG if any required pointer is NULL or muLen is not 64. + \return PUBLIC_KEY_E when the public key is not set. \param [in,out] key Pointer to a wc_MlDsaKey with the public key. \param [in] sig Signature bytes to verify. @@ -756,9 +784,15 @@ int wc_MlDsaKey_ImportKey(wc_MlDsaKey* key, const byte* priv, word32 privSz, \brief Exports the raw ML-DSA public key. On entry *outLen is the size of out; on success it is updated to the bytes written. + If only the private key is set, the public key is derived and cached + in key. Don't share key across threads during this call. + \return 0 on success. \return BAD_FUNC_ARG if any required pointer is NULL. \return BUFFER_E if *outLen is smaller than the public key size. + \return MEMORY_E if deriving the public key fails to allocate. + \return PUBLIC_KEY_E if deriving the public key fails to verify + against the private key. \param [in] key Pointer to a wc_MlDsaKey with a public key. \param [out] out Buffer that receives the public key. @@ -818,6 +852,10 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz, Only available when WOLFSSL_MLDSA_NO_ASN1 is not defined. + For a private-only encoding the public key is left unset. The export + functions derive it on demand, and fail with PUBLIC_KEY_E or MEMORY_E if + that derivation does not succeed. + \return 0 on success. \return BAD_FUNC_ARG if any required pointer is NULL. \return ASN_PARSE_E on malformed encoding. @@ -830,6 +868,7 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz, \sa wc_MlDsaKey_PrivateKeyToDer \sa wc_MlDsaKey_PublicKeyDecode + \sa wc_MlDsaKey_MakePublicKey */ int wc_MlDsaKey_PrivateKeyDecode(wc_MlDsaKey* key, const byte* input, word32 inSz, word32* inOutIdx); @@ -867,11 +906,17 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input, Pass NULL as output to query the required buffer size. + If only the private key is set, the public key is derived and cached + in key. Don't share key across threads during this call. + \return Size of the encoded DER in bytes on success. \return BAD_FUNC_ARG if key is NULL or no parameter set is selected. \return BUFFER_E if output is non-NULL and inLen is smaller than the required size. + \return MEMORY_E if deriving the public key fails to allocate. + \return PUBLIC_KEY_E if deriving the public key fails to verify + against the private key. \param [in] key Pointer to a wc_MlDsaKey with a public key. \param [out] output Buffer that receives the DER encoding, or @@ -893,11 +938,17 @@ int wc_MlDsaKey_PublicKeyToDer(wc_MlDsaKey* key, byte* output, PKCS#8 OneAsymmetricKey structure. Pass NULL as output to query the required buffer size. + If only the private key is set, the public key is derived and cached + in key. Don't share key across threads during this call. + \return Size of the encoded DER in bytes on success. \return BAD_FUNC_ARG if key is NULL or no parameter set is selected. \return MISSING_KEY if the private key has not been set. \return BUFFER_E if output is non-NULL and inLen is too small. + \return MEMORY_E if deriving the public key fails to allocate. + \return PUBLIC_KEY_E if deriving the public key fails to verify + against the private key. \param [in] key Pointer to a wc_MlDsaKey with the private key. \param [out] output Buffer that receives the DER encoding, or diff --git a/src/internal.c b/src/internal.c index 3de4b1d060..7e9d16d7d9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -33090,10 +33090,13 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key, /* Set start of data to beginning of buffer. */ idx = 0; - /* Decode the key assuming it is an ECC private key. */ - ret = wc_EccPrivateKeyDecode(key->buffer, &idx, + /* Decode the key assuming it is an ECC private key. Skip the + * best-effort public point derivation: this key is only ever used to + * sign (CertificateVerify / ServerKeyExchange), which needs the + * private scalar alone, and this runs once per handshake. */ + ret = EccPrivateKeyDecodeEx(key->buffer, &idx, (ecc_key*)*hsKey, - key->length); + key->length, 0); #ifdef WOLF_PRIVATE_KEY_ID /* if using external key then allow using a public key */ if (ret != 0 && (ssl->devId != INVALID_DEVID diff --git a/src/sniffer.c b/src/sniffer.c index 5808475ed7..0bb30510c3 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -2836,8 +2836,12 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session, #endif if (ret == 0) { idx = 0; - ret = wc_EccPrivateKeyDecode(args->keyBuf->buffer, &idx, - &args->key->priv.ecc, args->keyBuf->length); + /* Skip the best-effort public point derivation done on + * decode: this key only ever feeds wc_ecc_shared_secret(), + * which accepts an ECC_PRIVATEKEY_ONLY key, and this runs + * once per sniffed session. */ + ret = EccPrivateKeyDecodeEx(args->keyBuf->buffer, &idx, + &args->key->priv.ecc, args->keyBuf->length, 0); if (ret != 0) { SetError(ECC_DECODE_STR, error, session, FATAL_ERROR_STATE); } diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index 1f91406aac..205c9538e2 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -1263,8 +1263,11 @@ int wolfSSL_SetHsDoneCb(WOLFSSL* ssl, HandShakeDoneCb cb, void* user_ctx) #endif if (ssl->options.haveStaticECC && ssl->buffers.key != NULL) { if (wc_ecc_init(key) >= 0) { - if (wc_EccPrivateKeyDecode(ssl->buffers.key->buffer, &idx, - key, ssl->buffers.key->length) != 0) { + /* Pure type/success probe - key is freed right below, so + * skip the best-effort public point derivation done on + * decode. */ + if (EccPrivateKeyDecodeEx(ssl->buffers.key->buffer, &idx, + key, ssl->buffers.key->length, 0) != 0) { ssl->options.haveECDSAsig = 0; ssl->options.haveECC = 0; ssl->options.haveStaticECC = 0; diff --git a/src/ssl_api_pk.c b/src/ssl_api_pk.c index dab8ad61e5..01d6d064ed 100644 --- a/src/ssl_api_pk.c +++ b/src/ssl_api_pk.c @@ -2290,6 +2290,10 @@ int wolfSSL_StaticEphemeralKeyLoad(WOLFSSL* ssl, int keyAlgo, void* keyPtr) if (der != NULL) { ecc_key* key = (ecc_key*)keyPtr; WOLFSSL_MSG("Using static ECDH key"); + /* Keep the best-effort public point derivation: the caller + * (TLSX_KeyShare_GenEccKey) exports this key's public point + * with wc_ecc_export_x963(), which fails ECC_PRIVATEONLY_E on + * an ECC_PRIVATEKEY_ONLY key. */ ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key, der->length); } @@ -2370,7 +2374,9 @@ static int DetectStaticEphemeralKeyType(const byte* keyBuf, unsigned int keySz, ret = wc_ecc_init_ex(eccKey, heap, INVALID_DEVID); } if (ret == 0) { - ret = wc_EccPrivateKeyDecode(keyBuf, &idx, eccKey, keySz); + /* Pure type probe - key is freed right below, so skip the + * best-effort public point derivation done on decode. */ + ret = EccPrivateKeyDecodeEx(keyBuf, &idx, eccKey, keySz, 0); if (ret == 0) { *keyAlgo = WC_PK_TYPE_ECDH; } diff --git a/src/ssl_load.c b/src/ssl_load.c index 921bc68f6e..2597dadabe 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -568,9 +568,11 @@ static int ProcessBufferTryDecodeEcc(WOLFSSL_CTX* ctx, WOLFSSL* ssl, /* Initialize ECC key. */ if (wc_ecc_init_ex(key, heap, devId) == 0) { - /* Decode as an ECC private key. */ + /* Decode as an ECC private key. Skip the best-effort public point + * derivation - this only probes for the key format and size, and the + * key is freed below. */ idx = 0; - ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key, der->length); + ret = EccPrivateKeyDecodeEx(der->buffer, &idx, key, der->length, 0); #ifdef WOLF_PRIVATE_KEY_ID /* If that didn't work then maybe a public key if device ID or callback. */ diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index c42124a458..a0eab1f4d1 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -2781,3 +2781,254 @@ int test_wc_AsnFeatureCoverage(void) #endif /* !NO_ASN && HAVE_ECC && USE_CERT_BUFFERS_256 && !HAVE_FIPS */ return EXPECT_RESULT(); } + +#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \ + !defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) && !defined(NO_ASN) && \ + defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \ + defined(HAVE_ECC_KEY_EXPORT) && \ + defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \ + !defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \ + !defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM) +/* Fail Nth alloc to target public key derive. */ +static int ecc_oom_failed = 0; +static int ecc_oom_inject = 0; +static int ecc_oom_count = 0; +static int ecc_oom_fail_at = 0; + +/* Custom malloc for testing OOM. */ +#ifdef WOLFSSL_DEBUG_MEMORY +static void* ecc_oom_malloc_cb(size_t size, const char* func, + unsigned int line) +{ + (void)func; + (void)line; +#else +static void* ecc_oom_malloc_cb(size_t size) +{ +#endif + if (ecc_oom_inject) { + ecc_oom_count++; + if (!ecc_oom_failed && + (ecc_oom_fail_at != 0) && (ecc_oom_count == ecc_oom_fail_at)) { + ecc_oom_failed = 1; + return NULL; + } + } + return malloc(size); +} + +/* Custom free for testing OOM. */ +#ifdef WOLFSSL_DEBUG_MEMORY +static void ecc_oom_free_cb(void* ptr, const char* func, unsigned int line) +{ + (void)func; + (void)line; +#else +static void ecc_oom_free_cb(void* ptr) +{ +#endif + free(ptr); +} + +/* Custom realloc for testing OOM. */ +#ifdef WOLFSSL_DEBUG_MEMORY +static void* ecc_oom_realloc_cb(void* ptr, size_t size, const char* func, + unsigned int line) +{ + (void)func; + (void)line; +#else +static void* ecc_oom_realloc_cb(void* ptr, size_t size) +{ +#endif + return realloc(ptr, size); +} +#endif /* USE_WOLFSSL_MEMORY && ... */ + +/* Decode should best-effort derive omitted SEC1 public point. */ +int test_wc_EccPrivateKeyDecode_derive_pub(void) +{ + EXPECT_DECLS; +#if !defined(NO_ASN) && defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \ + !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \ + defined(HAVE_ECC_KEY_EXPORT) && \ + defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \ + !defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \ + !defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM) + ecc_key fullKey; + ecc_key privOnlyKey; + WC_RNG rng; + word32 idx; + byte privOnlyDer[256]; + int privOnlyDerSz = 0; + byte fullPub[256]; + word32 fullPubSz = sizeof(fullPub); + byte derivedPub[256]; + word32 derivedPubSz = sizeof(derivedPub); + + XMEMSET(&fullKey, 0, sizeof(fullKey)); + XMEMSET(&privOnlyKey, 0, sizeof(privOnlyKey)); + /* wc_FreeRng() below runs unconditionally, so rng must be safe to free + * even if wc_InitRng() fails. */ + XMEMSET(&rng, 0, sizeof(rng)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + + ExpectIntEQ(wc_ecc_init(&fullKey), 0); + idx = 0; + ExpectIntEQ(wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &fullKey, + sizeof_ecc_clikey_der_256), 0); + ExpectIntEQ(fullKey.type, ECC_PRIVATEKEY); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_ecc_export_x963(&fullKey, fullPub, &fullPubSz), 0); + PRIVATE_KEY_LOCK(); + + /* Re-encode as private-key-only SEC1 DER. */ + ExpectIntGT(privOnlyDerSz = wc_EccPrivateKeyToDer(&fullKey, privOnlyDer, + sizeof(privOnlyDer)), 0); + + /* No RNG set: derivation still runs, blinding with a temporary RNG when + * ECC_TIMING_RESISTANT is on. */ + ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); + idx = 0; + ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz), 0); + ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz), + 0); + PRIVATE_KEY_LOCK(); + ExpectIntEQ(derivedPubSz, fullPubSz); + ExpectBufEQ(derivedPub, fullPub, fullPubSz); + wc_ecc_free(&privOnlyKey); + + /* Setting an RNG blinds the scalar mult; same derived point. */ + derivedPubSz = sizeof(derivedPub); + ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); + ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0); + idx = 0; + ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz), 0); + + /* Public point derived, key fully usable. */ + ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz), + 0); + PRIVATE_KEY_LOCK(); + ExpectIntEQ(derivedPubSz, fullPubSz); + ExpectBufEQ(derivedPub, fullPub, fullPubSz); + + wc_ecc_free(&privOnlyKey); + wc_ecc_free(&fullKey); + +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + /* devId key left ECC_PRIVATEKEY_ONLY: device derives it. */ + ExpectIntEQ(wc_ecc_init_ex(&privOnlyKey, NULL, 1), 0); + ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0); + idx = 0; + ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz), 0); + ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY); + wc_ecc_free(&privOnlyKey); +#endif + +#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \ + !defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) + { + wolfSSL_Malloc_cb prevMalloc = NULL; + wolfSSL_Free_cb prevFree = NULL; + wolfSSL_Realloc_cb prevRealloc = NULL; + int allocatorsSet = 0; + int totalAllocCount = 0; + int i; + + ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc), + 0); + ExpectIntEQ(wolfSSL_SetAllocators(ecc_oom_malloc_cb, ecc_oom_free_cb, + ecc_oom_realloc_cb), 0); + if (EXPECT_SUCCESS()) { + allocatorsSet = 1; + } + + /* Count the allocations one decode-with-derive makes. Injection is + * armed only around the decode so wc_ecc_init() is never starved. */ + ecc_oom_count = 0; + ecc_oom_fail_at = 0; + ecc_oom_failed = 0; + ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); + idx = 0; + ecc_oom_inject = 1; + ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz), 0); + ecc_oom_inject = 0; + totalAllocCount = ecc_oom_count; + wc_ecc_free(&privOnlyKey); + + /* Fail each allocation in turn. Whatever fails, decode must never + * report a derived public key it does not have: the key comes back + * either fully derived and correct, or still ECC_PRIVATEKEY_ONLY. */ + for (i = 1; EXPECT_SUCCESS() && (i <= totalAllocCount); i++) { + int decodeRet; + + ecc_oom_count = 0; + ecc_oom_fail_at = i; + ecc_oom_failed = 0; + derivedPubSz = sizeof(derivedPub); + + ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); + idx = 0; + ecc_oom_inject = 1; + decodeRet = wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz); + ecc_oom_inject = 0; + /* The injection must actually have fired, otherwise this + * iteration passes vacuously without exercising any OOM path. */ + ExpectIntEQ(ecc_oom_failed, 1); + + /* A failure inside the decode itself is fine; only the + * best-effort derivation is required to be non-fatal. */ + if (decodeRet == 0) { + ExpectIntNE(privOnlyKey.type, ECC_PUBLICKEY); + if (privOnlyKey.type == ECC_PRIVATEKEY) { + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, + &derivedPubSz), 0); + PRIVATE_KEY_LOCK(); + ExpectIntEQ(derivedPubSz, fullPubSz); + ExpectBufEQ(derivedPub, fullPub, fullPubSz); + } + else { + ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY); + } + } + + wc_ecc_free(&privOnlyKey); + } + + ecc_oom_inject = 0; + ecc_oom_fail_at = 0; + + if (allocatorsSet) { + (void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc); + } + } +#endif /* USE_WOLFSSL_MEMORY */ + + wc_FreeRng(&rng); +#endif /* !NO_ASN && HAVE_ECC && !NO_ECC_MAKE_PUB && + * !WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE && HAVE_ECC_KEY_EXPORT && + * USE_CERT_BUFFERS_256 && !HAVE_FIPS && !HAVE_SELFTEST && + * !WOLF_CRYPTO_CB_ONLY_ECC */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 8798bbc6aa..f0dc4ce7b0 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -45,6 +45,7 @@ int test_ToTraditional_ex_negative(void); int test_ToTraditional_ex_mldsa_bad_params(void); int test_wc_AsnDecisionCoverage(void); int test_wc_AsnFeatureCoverage(void); +int test_wc_EccPrivateKeyDecode_derive_pub(void); #define TEST_ASN_DECLS \ TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \ @@ -67,6 +68,7 @@ int test_wc_AsnFeatureCoverage(void); TEST_DECL_GROUP("asn", test_ToTraditional_ex_negative), \ TEST_DECL_GROUP("asn", test_ToTraditional_ex_mldsa_bad_params), \ TEST_DECL_GROUP("asn", test_wc_AsnDecisionCoverage), \ - TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage) + TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage), \ + TEST_DECL_GROUP("asn", test_wc_EccPrivateKeyDecode_derive_pub) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index e490b6e594..4dc4f2aa86 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -7777,6 +7777,673 @@ int test_mldsa_make_key_from_seed(void) return EXPECT_RESULT(); } +int test_mldsa_make_public_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) + wc_MlDsaKey* key; + + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + /* NULL key. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + + /* Private key not set yet. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Public key set but private key still not set. */ + if (key != NULL) { + key->pubKeySet = 1; + } + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + if (key != NULL) { + key->pubKeySet = 0; + } + + /* Import private-only key and derive its public key. */ + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_44_key, + sizeof_bench_mldsa_44_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_44_pubkey, + sizeof_bench_mldsa_44_pubkey), 0); + + /* No-op when the public key is already set. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + + wc_MlDsaKey_Free(key); + +#ifdef WOLF_CRYPTO_CB + /* devId key: reject software derivation. */ + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, 1), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_44_key, + sizeof_bench_mldsa_44_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(key->pubKeySet, 0); + + /* devId key with public key already set: no-op, not rejected. */ + if (key != NULL) { + key->pubKeySet = 1; + } + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + wc_MlDsaKey_Free(key); +#endif /* WOLF_CRYPTO_CB */ +#endif /* !WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_65), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_65_key, + sizeof_bench_mldsa_65_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_65_pubkey, + sizeof_bench_mldsa_65_pubkey), 0); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_87), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_87_key, + sizeof_bench_mldsa_87_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_87_pubkey, + sizeof_bench_mldsa_87_pubkey), 0); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_87 */ + + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +int test_mldsa_private_key_export_derives_public_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) + wc_MlDsaKey* privKey; + wc_MlDsaKey* pubKey; + word32 idx; + byte derivedPub[WC_MLDSA_87_PUB_KEY_SIZE]; + word32 derivedPubSz; + + privKey = (wc_MlDsaKey*)XMALLOC(sizeof(*privKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(privKey); + pubKey = (wc_MlDsaKey*)XMALLOC(sizeof(*pubKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pubKey); + if (privKey != NULL) { + XMEMSET(privKey, 0, sizeof(*privKey)); + } + if (pubKey != NULL) { + XMEMSET(pubKey, 0, sizeof(*pubKey)); + } + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_44), 0); + + /* Private-only DER: decode leaves pubKeySet unset; export derives it. */ + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa44_priv_only, + sizeof_mldsa44_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 0); + derivedPubSz = sizeof(derivedPub); + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(privKey, derivedPub, &derivedPubSz), + 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + /* Confirm the derived public key matches the known public key for the + * same key pair. */ + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_44), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa44_pub_spki, + sizeof_mldsa44_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_44_PUB_KEY_SIZE), 0); + +#if !defined(WOLFSSL_MLDSA_NO_ASN1) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) + /* wc_MlDsaKey_KeyToDer: private-only key with genuine key material -> + * auto-derives the public key and succeeds, caching a public key that + * matches the known-good public key for the same key pair. */ + { + wc_MlDsaKey* privKey2; + byte* der; + + privKey2 = (wc_MlDsaKey*)XMALLOC(sizeof(*privKey2), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(privKey2); + der = (byte*)XMALLOC(MLDSA_MAX_DER_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(der); + if (privKey2 != NULL) { + XMEMSET(privKey2, 0, sizeof(*privKey2)); + } + + ExpectIntEQ(wc_MlDsaKey_Init(privKey2, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey2, WC_ML_DSA_44), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey2, mldsa44_priv_only, + sizeof_mldsa44_priv_only, &idx), 0); + ExpectIntEQ(privKey2->pubKeySet, 0); + + ExpectIntGT(wc_MlDsaKey_KeyToDer(privKey2, der, MLDSA_MAX_DER_SIZE), + 0); + ExpectIntEQ(privKey2->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(privKey2->p, pubKey->p, WC_MLDSA_44_PUB_KEY_SIZE), + 0); + + wc_MlDsaKey_Free(privKey2); + XFREE(privKey2, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + /* wc_MlDsaKey_PublicKeyToDer: same auto-derive contract as KeyToDer, + * but via its own guard (no prvKeySet pre-check - MakePublicKey does + * that internally and returns BAD_FUNC_ARG if it's not set). */ + { + wc_MlDsaKey* privKey3; + byte* der; + + privKey3 = (wc_MlDsaKey*)XMALLOC(sizeof(*privKey3), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(privKey3); + der = (byte*)XMALLOC(MLDSA_MAX_DER_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(der); + if (privKey3 != NULL) { + XMEMSET(privKey3, 0, sizeof(*privKey3)); + } + + ExpectIntEQ(wc_MlDsaKey_Init(privKey3, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey3, WC_ML_DSA_44), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey3, mldsa44_priv_only, + sizeof_mldsa44_priv_only, &idx), 0); + ExpectIntEQ(privKey3->pubKeySet, 0); + + ExpectIntGT(wc_MlDsaKey_PublicKeyToDer(privKey3, der, + MLDSA_MAX_DER_SIZE, 1), 0); + ExpectIntEQ(privKey3->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(privKey3->p, pubKey->p, WC_MLDSA_44_PUB_KEY_SIZE), + 0); + + wc_MlDsaKey_Free(privKey3); + XFREE(privKey3, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif /* !WOLFSSL_MLDSA_NO_ASN1 && WOLFSSL_MLDSA_PUBLIC_KEY */ + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_65), 0); + + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa65_priv_only, + sizeof_mldsa65_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 0); + derivedPubSz = sizeof(derivedPub); + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(privKey, derivedPub, &derivedPubSz), + 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_65), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa65_pub_spki, + sizeof_mldsa65_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_65_PUB_KEY_SIZE), 0); + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_87), 0); + + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa87_priv_only, + sizeof_mldsa87_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 0); + derivedPubSz = sizeof(derivedPub); + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(privKey, derivedPub, &derivedPubSz), + 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_87), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa87_pub_spki, + sizeof_mldsa87_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_87_PUB_KEY_SIZE), 0); + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_87 */ + + XFREE(privKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +int test_mldsa_verify_pubkeyset_guard(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) && !defined(WOLFSSL_MLDSA_NO_SIGN) + wc_MlDsaKey* key; + byte sig[WC_MLDSA_87_SIG_SIZE]; /* Size for largest supported parameter set */ + byte msg[32]; + byte mu[MLDSA_MU_SZ]; + int res; + + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(msg, 0x55, sizeof(msg)); + XMEMSET(mu, 0, sizeof(mu)); + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + + /* Import private-only key: pubKeySet stays 0. */ + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_44_key, + sizeof_bench_mldsa_44_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(key, sig, WC_MLDSA_44_SIG_SIZE, NULL, 0, msg, + sizeof(msg), &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#ifdef WOLFSSL_MLDSA_NO_CTX + ExpectIntEQ(wc_MlDsaKey_Verify(key, sig, WC_MLDSA_44_SIG_SIZE, msg, sizeof(msg), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#endif + ExpectIntEQ(wc_MlDsaKey_VerifyCtxHash(key, sig, WC_MLDSA_44_SIG_SIZE, NULL, 0, + msg, sizeof(msg), WC_HASH_TYPE_SHA256, &res), + WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + ExpectIntEQ(wc_MlDsaKey_VerifyMu(key, sig, WC_MLDSA_44_SIG_SIZE, mu, sizeof(mu), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_65), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_65_key, + sizeof_bench_mldsa_65_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(key, sig, WC_MLDSA_65_SIG_SIZE, NULL, 0, msg, + sizeof(msg), &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#ifdef WOLFSSL_MLDSA_NO_CTX + ExpectIntEQ(wc_MlDsaKey_Verify(key, sig, WC_MLDSA_65_SIG_SIZE, msg, sizeof(msg), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#endif + ExpectIntEQ(wc_MlDsaKey_VerifyCtxHash(key, sig, WC_MLDSA_65_SIG_SIZE, NULL, 0, + msg, sizeof(msg), WC_HASH_TYPE_SHA256, &res), + WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + ExpectIntEQ(wc_MlDsaKey_VerifyMu(key, sig, WC_MLDSA_65_SIG_SIZE, mu, sizeof(mu), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_87), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_87_key, + sizeof_bench_mldsa_87_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(key, sig, WC_MLDSA_87_SIG_SIZE, NULL, 0, msg, + sizeof(msg), &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#ifdef WOLFSSL_MLDSA_NO_CTX + ExpectIntEQ(wc_MlDsaKey_Verify(key, sig, WC_MLDSA_87_SIG_SIZE, msg, sizeof(msg), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#endif + ExpectIntEQ(wc_MlDsaKey_VerifyCtxHash(key, sig, WC_MLDSA_87_SIG_SIZE, NULL, 0, + msg, sizeof(msg), WC_HASH_TYPE_SHA256, &res), + WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + ExpectIntEQ(wc_MlDsaKey_VerifyMu(key, sig, WC_MLDSA_87_SIG_SIZE, mu, sizeof(mu), + &res), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_87 */ + + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \ + !defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) +/* OOM injector: counts allocations, fails the Nth. Guard must stay identical + * to mldsa_oom_derive_fail_level()'s below - that helper is compiled for any + * enabled level and references these counters. */ +static int mldsa_oom_active = 0; +static int mldsa_oom_count = 0; +static int mldsa_oom_fail_at = 0; +static int mldsa_oom_failed = 0; + +#ifdef WOLFSSL_DEBUG_MEMORY +static void* mldsa_oom_malloc_cb(size_t size, const char* func, + unsigned int line) +{ + (void)func; + (void)line; +#else +static void* mldsa_oom_malloc_cb(size_t size) +{ +#endif + if (mldsa_oom_active) { + mldsa_oom_count++; + if ((mldsa_oom_fail_at != 0) && + (mldsa_oom_count == mldsa_oom_fail_at)) { + mldsa_oom_failed = 1; + return NULL; + } + } + return malloc(size); +} + +#ifdef WOLFSSL_DEBUG_MEMORY +static void mldsa_oom_free_cb(void* ptr, const char* func, unsigned int line) +{ + (void)func; + (void)line; +#else +static void mldsa_oom_free_cb(void* ptr) +{ +#endif + free(ptr); +} + +#ifdef WOLFSSL_DEBUG_MEMORY +static void* mldsa_oom_realloc_cb(void* ptr, size_t size, const char* func, + unsigned int line) +{ + (void)func; + (void)line; +#else +static void* mldsa_oom_realloc_cb(void* ptr, size_t size) +{ +#endif + return realloc(ptr, size); +} +#endif + +/* Verify MakePublicKey fails gracefully on OOM. */ +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \ + !defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) +/* Shared by test_mldsa_private_key_export_derive_fail() for each level: + * count the allocations MakePublicKey makes on a private-only key, then + * fail each in turn and verify clean failure (prvKeySet stays set, + * pubKeySet stays unset). */ +static int mldsa_oom_derive_fail_level(int level, const byte* privOnly, + word32 privOnlySz) +{ + EXPECT_DECLS; + wc_MlDsaKey* key; + word32 idx; + int allocCount = 0; + int i; + + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + /* Pass 1: count the allocations MakePublicKey makes. */ + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, level), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(key, privOnly, privOnlySz, + &idx), 0); + ExpectIntEQ(key->pubKeySet, 0); + mldsa_oom_count = 0; + mldsa_oom_fail_at = 0; + mldsa_oom_failed = 0; + mldsa_oom_active = 1; + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + mldsa_oom_active = 0; + ExpectIntEQ(key->pubKeySet, 1); + allocCount = mldsa_oom_count; + ExpectIntGE(allocCount, 1); + wc_MlDsaKey_Free(key); + + /* Pass 2: fail each alloc in turn; verify clean failure. */ + for (i = 1; (i <= allocCount) && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, level), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(key, privOnly, privOnlySz, + &idx), 0); + ExpectIntEQ(key->pubKeySet, 0); + + mldsa_oom_count = 0; + mldsa_oom_fail_at = i; + mldsa_oom_failed = 0; + mldsa_oom_active = 1; + ExpectIntNE(wc_MlDsaKey_MakePublicKey(key), 0); + mldsa_oom_active = 0; + + ExpectIntEQ(mldsa_oom_failed, 1); + ExpectIntEQ(key->prvKeySet, 1); + ExpectIntEQ(key->pubKeySet, 0); + wc_MlDsaKey_Free(key); + } + + mldsa_oom_active = 0; + mldsa_oom_fail_at = 0; + + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); +} +#endif + +int test_mldsa_private_key_export_derive_fail(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \ + !defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) + wolfSSL_Malloc_cb prevMalloc = NULL; + wolfSSL_Free_cb prevFree = NULL; + wolfSSL_Realloc_cb prevRealloc = NULL; + int allocatorsSet = 0; + + ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc), + 0); + ExpectIntEQ(wolfSSL_SetAllocators(mldsa_oom_malloc_cb, mldsa_oom_free_cb, + mldsa_oom_realloc_cb), 0); + if (EXPECT_SUCCESS()) { + allocatorsSet = 1; + } + +#ifndef WOLFSSL_NO_ML_DSA_44 + EXPECT_TEST(mldsa_oom_derive_fail_level(WC_ML_DSA_44, mldsa44_priv_only, + sizeof_mldsa44_priv_only)); +#endif +#ifndef WOLFSSL_NO_ML_DSA_65 + EXPECT_TEST(mldsa_oom_derive_fail_level(WC_ML_DSA_65, mldsa65_priv_only, + sizeof_mldsa65_priv_only)); +#endif +#ifndef WOLFSSL_NO_ML_DSA_87 + EXPECT_TEST(mldsa_oom_derive_fail_level(WC_ML_DSA_87, mldsa87_priv_only, + sizeof_mldsa87_priv_only)); +#endif + + if (allocatorsSet) { + (void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc); + } +#endif + return EXPECT_RESULT(); +} + +/* MakePublicKey reuses key->s1/s2/t0 as raw (non-NTT) domain scratch space + * during derivation when WC_MLDSA_CACHE_PRIV_VECTORS is on. It must + * invalidate a stale NTT-domain privVecsSet cache rather than leave it + * pointing at now-corrupted vectors, or a subsequent Sign() would trust + * garbage instead of re-deriving. */ +int test_mldsa_make_public_key_cache_invalidation(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_NO_ML_DSA_44) + wc_MlDsaKey* key; + WC_RNG rng; + byte msg[] = "cache invalidation test message"; + byte sig[WC_MLDSA_44_SIG_SIZE]; + word32 sigLen; + int verifyRes = 0; + word32 idx; + + XMEMSET(&rng, 0, sizeof(rng)); + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(key, mldsa44_priv_only, + sizeof_mldsa44_priv_only, &idx), 0); + ExpectIntEQ(key->pubKeySet, 0); + /* ImportPrivRaw (called by PrivateKeyDecode) already populates the + * NTT-domain private-vector cache as part of import. */ +#ifdef WC_MLDSA_CACHE_PRIV_VECTORS + ExpectIntEQ(key->privVecsSet, 1); +#endif + + /* Derive the public key; the stale cache must be invalidated. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); +#ifdef WC_MLDSA_CACHE_PRIV_VECTORS +#ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + ExpectIntEQ(key->privVecsSet, 0); +#else + ExpectIntEQ(key->privVecsSet, 1); +#endif +#endif + + /* Signing again must re-derive fresh vectors, and the result must + * still verify against the newly derived public key - if MakePublicKey + * had left privVecsSet=1 over corrupted vectors, this Sign would skip + * re-deriving and either fail or produce a bad signature. */ + sigLen = sizeof(sig); + ExpectIntEQ(wc_MlDsaKey_SignCtx(key, NULL, 0, sig, &sigLen, msg, + sizeof(msg), &rng), 0); +#ifdef WC_MLDSA_CACHE_PRIV_VECTORS + ExpectIntEQ(key->privVecsSet, 1); +#endif + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(key, sig, sigLen, NULL, 0, msg, + sizeof(msg), &verifyRes), 0); + ExpectIntEQ(verifyRes, 1); + + wc_MlDsaKey_Free(key); + wc_FreeRng(&rng); + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* Corrupt embedded 'tr' hash to verify MakePublicKey rejects mismatch. */ +int test_mldsa_make_public_key_tr_mismatch(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_NO_ML_DSA_44) + wc_MlDsaKey* key; + + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_44_key, + sizeof_bench_mldsa_44_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + /* Flip 'tr' byte so derived public key can never match it. */ + if (key != NULL) { + key->k[MLDSA_PUB_SEED_SZ + MLDSA_K_SZ] ^= 0xFF; + } + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), WC_NO_ERR_TRACE(PUBLIC_KEY_E)); + ExpectIntEQ(key->pubKeySet, 0); + + wc_MlDsaKey_Free(key); + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + int test_mldsa_sig_kats(void) { EXPECT_DECLS; @@ -31296,15 +31963,26 @@ int test_wc_MldsaDecisionCoverage2(void) #if !defined(WOLFSSL_MLDSA_NO_ASN1) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) /* wc_MlDsaKey_KeyToDer: independence of prvKeySet/pubKeySet. * Private-only key (prvKeySet TRUE, pubKeySet FALSE) -> - * BAD_FUNC_ARG. */ + * KeyToDer auto-derives the public key from the private key + * material. The raw private key imported above is zero-filled + * (not an authentic key), so the derived public key's 'tr' hash + * does not match the one stored in the private key blob -> + * PUBLIC_KEY_E. */ { byte der[16]; byte pubBuf[WC_MLDSA_44_PUB_KEY_SIZE]; wc_MlDsaKey pubOnlyKey; XMEMSET(der, 0, sizeof(der)); +#if !defined(WOLFSSL_MLDSA_VERIFY_ONLY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + ExpectIntEQ(wc_MlDsaKey_KeyToDer(&key, der, (word32)sizeof(der)), + WC_NO_ERR_TRACE(PUBLIC_KEY_E)); +#else + /* Auto-derive is compiled out; pubKeySet stays FALSE. */ ExpectIntEQ(wc_MlDsaKey_KeyToDer(&key, der, (word32)sizeof(der)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif /* Public-only key (prvKeySet FALSE, pubKeySet TRUE) -> * BAD_FUNC_ARG (independence of the prvKeySet operand, held diff --git a/tests/api/test_mldsa.h b/tests/api/test_mldsa.h index 4a81faba1c..de213a0ce8 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -38,6 +38,12 @@ int test_mldsa_public_der_decode(void); int test_mldsa_der(void); int test_mldsa_oneasymkey_version(void); int test_mldsa_make_key_from_seed(void); +int test_mldsa_make_public_key(void); +int test_mldsa_private_key_export_derives_public_key(void); +int test_mldsa_verify_pubkeyset_guard(void); +int test_mldsa_private_key_export_derive_fail(void); +int test_mldsa_make_public_key_cache_invalidation(void); +int test_mldsa_make_public_key_tr_mismatch(void); int test_mldsa_sig_kats(void); int test_mldsa_sign_ctx_kats(void); int test_mldsa_verify_ctx_kats(void); @@ -81,6 +87,12 @@ int test_wc_MldsaDerDecisionCoverage(void); TEST_DECL_GROUP("mldsa", test_mldsa_der), \ TEST_DECL_GROUP("mldsa", test_mldsa_oneasymkey_version), \ TEST_DECL_GROUP("mldsa", test_mldsa_make_key_from_seed), \ + TEST_DECL_GROUP("mldsa", test_mldsa_make_public_key), \ + TEST_DECL_GROUP("mldsa", test_mldsa_private_key_export_derives_public_key), \ + TEST_DECL_GROUP("mldsa", test_mldsa_verify_pubkeyset_guard), \ + TEST_DECL_GROUP("mldsa", test_mldsa_private_key_export_derive_fail), \ + TEST_DECL_GROUP("mldsa", test_mldsa_make_public_key_cache_invalidation), \ + TEST_DECL_GROUP("mldsa", test_mldsa_make_public_key_tr_mismatch), \ TEST_DECL_GROUP("mldsa", test_mldsa_sig_kats), \ TEST_DECL_GROUP("mldsa", test_mldsa_sign_ctx_kats), \ TEST_DECL_GROUP("mldsa", test_mldsa_verify_ctx_kats), \ diff --git a/tests/suites.c b/tests/suites.c index 13ff4480ad..cdb7849772 100644 --- a/tests/suites.c +++ b/tests/suites.c @@ -1329,8 +1329,11 @@ int SuiteTest(int argc, char** argv) #if defined(WOLFSSL_HAVE_SLHDSA) && \ !defined(WOLFSSL_MLDSA_VERIFY_ONLY) && defined(WOLFSSL_HAVE_MLDSA) && \ defined(WOLFSSL_SLHDSA_PARAM_128S) && \ - defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_ML_DSA_44) - /* SLH-DSA-SHAKE-128s root + ML-DSA-44 entity cert tests (TLS 1.3) */ + defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_ML_DSA_44) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) + /* SLH-DSA-SHAKE-128s root + ML-DSA-44 entity cert tests (TLS 1.3). + * Both sides use ML-DSA-44 entity certs, so this needs ML-DSA sign and + * verify; SLH-DSA sign is not needed as the certs are pre-generated. */ XSTRLCPY(argv0[1], "tests/test-tls13-slhdsa-shake.conf", sizeof(argv0[1])); printf("starting TLSv13 SLH-DSA-SHAKE-128s root + ML-DSA-44 entity tests\n"); @@ -1360,8 +1363,11 @@ int SuiteTest(int argc, char** argv) #if defined(WOLFSSL_HAVE_SLHDSA) && \ !defined(WOLFSSL_MLDSA_VERIFY_ONLY) && defined(WOLFSSL_SLHDSA_SHA2) && \ defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) && defined(WOLFSSL_HAVE_MLDSA) && \ - defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_ML_DSA_44) - /* SLH-DSA-SHA2-128s root + ML-DSA-44 entity cert tests (TLS 1.3) */ + defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_ML_DSA_44) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) + /* SLH-DSA-SHA2-128s root + ML-DSA-44 entity cert tests (TLS 1.3). + * Both sides use ML-DSA-44 entity certs, so this needs ML-DSA sign and + * verify; SLH-DSA sign is not needed as the certs are pre-generated. */ XSTRLCPY(argv0[1], "tests/test-tls13-slhdsa-sha2.conf", sizeof(argv0[1])); printf("starting TLSv13 SLH-DSA-SHA2-128s root + ML-DSA-44 entity tests\n"); diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 198fdcbecf..627197e54f 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -9614,8 +9614,11 @@ int wc_CheckPrivateKey(const byte* privKey, word32 privKeySz, return ret; } - if ((ret = wc_EccPrivateKeyDecode(privKey, &keyIdx, key_pair, - privKeySz)) == 0) { + /* Skip public point derivation: the decoded key_pair is freed and + * reimported from privDer with the caller's own pubKey below, so + * any derived public point would be discarded unused. */ + if ((ret = EccPrivateKeyDecodeEx(privKey, &keyIdx, key_pair, + privKeySz, 0)) == 0) { WOLFSSL_MSG("Checking ECC key pair"); if ((ret = wc_ecc_export_private_only(key_pair, privDer, &privSz)) @@ -10262,7 +10265,9 @@ int wc_GetKeyOID(byte* key, word32 keySz, const byte** curveOID, word32* oidSz, tmpIdx = 0; if (wc_ecc_init_ex(ecc, heap, INVALID_DEVID) == 0) { - if (wc_EccPrivateKeyDecode(key, &tmpIdx, ecc, keySz) == 0) { + /* Only probing the key type/curve OID here; skip public point + * derivation since ecc is freed right after. */ + if (EccPrivateKeyDecodeEx(key, &tmpIdx, ecc, keySz, 0) == 0) { *algoID = ECDSAk; /* now find oid */ @@ -33161,6 +33166,70 @@ static int EccSpecifiedECDomainDecode(const byte* input, word32 inSz, #endif /* WOLFSSL_ASN_TEMPLATE */ #ifdef HAVE_ECC +/* Like HAVE_ECC_MAKE_PUB, but also excludes the hardware-only ECC ports and + * the FIPS/selftest boundaries, where a host-side derivation cannot succeed, + * plus the WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE opt-out. */ +#if !defined(NO_ECC_MAKE_PUB) && \ + !defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \ + !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \ + !defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(HAVE_FIPS) && \ + !defined(HAVE_SELFTEST) +#define WOLFSSL_ECC_DERIVE_PUB_BEST_EFFORT +/* Best-effort derivation of the public point for a SEC1 key that carried only + * the private scalar. Failures are logged and swallowed, leaving the key + * ECC_PRIVATEKEY_ONLY exactly as it was before. + * + * Defined once for both ASN backends: this sits ahead of the point where + * asn_orig.c is #included into this file, so the non-template + * wc_EccPrivateKeyDecode() sees it too. Same pattern as SetCurve()/ + * CheckCurve(). */ +static void EccDerivePubBestEffort(ecc_key* key) +{ + int pubRet; + + if (key->type != ECC_PRIVATEKEY_ONLY) { + return; + } + +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + /* Don't derive host-side public points for devId-tagged keys. */ + if (key->devId != INVALID_DEVID) { + return; + } +#endif + + /* key->rng is normally NULL here - callers rarely set an RNG before + * decoding a key. Pass it through as-is rather than standing up a + * temporary DRBG: decode sits on the per-handshake signing path, and the + * RNG only feeds the optional projective-coordinate randomization in + * ecc_mulmod(), which already tests it for NULL. The SP base-point + * routines ignore it outright. This matches what wc_ecc_check_pub_priv() + * and wc_ecc_shared_secret_gen() already do with a possibly-NULL + * key->rng. */ + pubRet = wc_ecc_make_pub_ex(key, NULL, +#ifdef ECC_TIMING_RESISTANT + key->rng +#else + NULL +#endif + ); +#ifdef WOLFSSL_ASYNC_CRYPT + /* wc_ecc_make_pub_ex() places async results directly into the key and is + * documented as not needing to be called again, so block for completion + * rather than re-issuing on WC_PENDING_E. Matches ecc_sign_hash_sw(). */ + pubRet = wc_AsyncWait(pubRet, &key->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + + if (pubRet != 0) { + WOLFSSL_MSG_EX("Best-effort ECC public key derivation failed: %d", + pubRet); + } +} +#endif /* !NO_ECC_MAKE_PUB && ... */ + #ifdef WOLFSSL_ASN_TEMPLATE /* ASN.1 template for ECC private key. * SEC.1 Ver 2.0, C.4 - Syntax for Elliptic Curve Private Keys @@ -33198,9 +33267,9 @@ enum { #endif #ifdef WOLFSSL_ASN_TEMPLATE -WOLFSSL_ABI -int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, - word32 inSz) +/* Implements the WOLFSSL_LOCAL declaration in asn.h. */ +int EccPrivateKeyDecodeEx(const byte* input, word32* inOutIdx, + ecc_key* key, word32 inSz, int derivePub) { DECL_ASNGETDATA(dataASN, eccKeyASN_Length); byte version = 0; @@ -33276,9 +33345,24 @@ int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, key, curve_id); } +#ifdef WOLFSSL_ECC_DERIVE_PUB_BEST_EFFORT + if ((ret == 0) && derivePub) { + EccDerivePubBestEffort(key); + } +#else + (void)derivePub; +#endif + FREE_ASNGETDATA(dataASN, key != NULL ? key->heap : NULL); return ret; } + +WOLFSSL_ABI +int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, + word32 inSz) +{ + return EccPrivateKeyDecodeEx(input, inOutIdx, key, inSz, 1); +} #endif /* WOLFSSL_ASN_TEMPLATE */ #ifdef WOLFSSL_CUSTOM_CURVES diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index 4feeb3380a..64653a3ae6 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -7596,9 +7596,20 @@ int DecodeECC_DSA_Sig_Ex(const byte* sig, word32 sigLen, mp_int* r, mp_int* s, #endif #ifdef HAVE_ECC -WOLFSSL_ABI -int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, - word32 inSz) + +/* EccDerivePubBestEffort() and WOLFSSL_ECC_DERIVE_PUB_BEST_EFFORT are + * defined once in asn.c, ahead of the point where this file is + * #included into it. */ + +/* Read ECC private key. + * + * SEC.1 Ver 2.0, C.4 - Syntax for Elliptic Curve Private Keys + * + * return 0 on success + */ +/* Implements the WOLFSSL_LOCAL declaration in asn.h. */ +int EccPrivateKeyDecodeEx(const byte* input, word32* inOutIdx, + ecc_key* key, word32 inSz, int derivePub) { word32 oidSum; int version, length; @@ -7718,12 +7729,27 @@ int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, (word32)pubSz, key, curve_id); } +#ifdef WOLFSSL_ECC_DERIVE_PUB_BEST_EFFORT + if ((ret == 0) && derivePub) { + EccDerivePubBestEffort(key); + } +#else + (void)derivePub; +#endif + WC_FREE_VAR_EX(priv, key->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(pub, key->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; } +WOLFSSL_ABI +int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, + word32 inSz) +{ + return EccPrivateKeyDecodeEx(input, inOutIdx, key, inSz, 1); +} + #ifdef WOLFSSL_CUSTOM_CURVES /* returns 0 on success */ static int ASNToHexString(const byte* input, word32* inOutIdx, char** out, diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index ec0b5d5c46..239aa76750 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -5762,11 +5762,34 @@ static int ecc_make_pub_ex(ecc_key* key, ecc_curve_spec* curve, err = NOT_COMPILED_IN; #endif /* HAVE_ECC_MAKE_PUB */ - /* change key state if public part is cached */ - if (key->type == ECC_PRIVATEKEY_ONLY && pubOut == NULL) { + /* Change key state if public part is cached. WC_PENDING_E counts as + * success here: the async result lands directly in key->pubkey and the + * operation is not re-issued, so callers that re-enter on resume (e.g. + * wc_ecc_verify_hash_ex's public-key recovery) must see ECC_PRIVATEKEY or + * they re-run make-pub forever. Matches _ecc_make_key_ex() above. */ + if (((err == MP_OKAY) + #ifdef WOLFSSL_ASYNC_CRYPT + || (err == WC_NO_ERR_TRACE(WC_PENDING_E)) + #endif + ) && (key->type == ECC_PRIVATEKEY_ONLY) && (pubOut == NULL)) { key->type = ECC_PRIVATEKEY; } +#ifdef WOLFSSL_ECC_BLIND_K + /* ecc_get_k() leaves the unblinded scalar in key->ku; don't let it outlive + * the operation. Safe to clear unconditionally - ecc_get_k() recomputes it + * from k ^ kb on every call, and mp_forcezero() tolerates a NULL ku (only + * reachable under ALT_ECC_SIZE, where ku is a pointer rather than a + * one-element array) - but not while an async op may still be reading it. + */ + #ifdef WOLFSSL_ASYNC_CRYPT + if (err != WC_NO_ERR_TRACE(WC_PENDING_E)) + #endif + { + mp_forcezero(key->ku); + } +#endif + return err; } @@ -6470,7 +6493,7 @@ int wc_ecc_init_ex(ecc_key* key, void* heap, int devId) alt_fp_init(key->k); #ifdef WOLFSSL_ECC_BLIND_K key->kb = (mp_int*)key->kba; - key->ku = (mp_int*)key->kia; + key->ku = (mp_int*)key->kua; alt_fp_init(key->kb); alt_fp_init(key->ku); #endif diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 73b9ddc3f6..935f4e92e4 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -195,8 +195,10 @@ static int d2iTryEccKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, /* Try decoding data as an ECC private/public key. */ if (priv) { + /* Pure type probe - the key is freed below, so skip the best-effort + * public point derivation done on decode. */ isEccKey = - (wc_EccPrivateKeyDecode(mem, &keyIdx, ecc, (word32)memSz) == 0); + (EccPrivateKeyDecodeEx(mem, &keyIdx, ecc, (word32)memSz, 0) == 0); } else { isEccKey = diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index d9f1dbb172..57500fc72f 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -8670,7 +8670,10 @@ static int wc_PKCS7_KariParseRecipCert(WC_PKCS7_KARI* kari, const byte* cert, else if (kari->direction == WC_PKCS7_DECODE) { if (key != NULL && keySz > 0) { idx = 0; - ret = wc_EccPrivateKeyDecode(key, &idx, kari->recipKey, keySz); + /* Skip the best-effort public point derivation done on decode: + * the recipient key is only used for the ECDH shared secret, + * which accepts an ECC_PRIVATEKEY_ONLY key. */ + ret = EccPrivateKeyDecodeEx(key, &idx, kari->recipKey, keySz, 0); } else if (kari->devId == INVALID_DEVID) { ret = BAD_FUNC_ARG; diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index 609ad38aae..6198f8665f 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -246,6 +246,27 @@ void print_data(const char* name, const byte* d, int len) #error "Cannot use both WOLFSSL_MLDSA_DYNAMIC_KEYS and WOLFSSL_MLDSA_ASSIGN_KEY" #endif +/* MakePublicKey derives the public key from the private key. Requires the + * sign-side polynomial arithmetic (mldsa_vec_add et al.) that is compiled + * out under WOLFSSL_MLDSA_VERIFY_ONLY, so that build excludes it too. */ +#if !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_VERIFY_ONLY) + #define WC_MLDSA_HAVE_MAKE_PUBLIC_KEY +#endif + +/* CheckKey is compiled when explicitly enabled, and when MakePublicKey uses it + * as an opt-in fault check under WC_MLDSA_FAULT_HARDEN. Small-mem re-derives + * streaming instead, so it never pulls CheckKey (and full matrix A) in. + * One macro each so the guards can't drift. */ +#if defined(WOLFSSL_MLDSA_CHECK_KEY) || \ + (!defined(WOLFSSL_MLDSA_NO_CHECK_KEY) && \ + defined(WC_MLDSA_HAVE_MAKE_PUBLIC_KEY) && \ + defined(WC_MLDSA_FAULT_HARDEN) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM)) + #define WC_MLDSA_HAVE_CHECK_KEY +#endif + /* Number of bytes from first block to use for sign. */ #define MLDSA_SIGN_BYTES 8 @@ -1169,7 +1190,8 @@ static void mldsa_vec_encode_eta_bits(const sword32* s, byte d, byte eta, } #endif /* !WOLFSSL_MLDSA_NO_MAKE_KEY */ -#if !defined(WOLFSSL_MLDSA_NO_SIGN) || defined(WOLFSSL_MLDSA_CHECK_KEY) +#if !defined(WOLFSSL_MLDSA_NO_SIGN) || defined(WC_MLDSA_HAVE_CHECK_KEY) || \ + defined(WC_MLDSA_HAVE_MAKE_PUBLIC_KEY) #if !defined(WOLFSSL_NO_ML_DSA_44) || !defined(WOLFSSL_NO_ML_DSA_87) /* Decode polynomial with range -2..2. @@ -1303,7 +1325,8 @@ static void mldsa_decode_eta_4_bits(const byte* p, sword32* s) } #endif -#if defined(WOLFSSL_MLDSA_CHECK_KEY) || \ +#if defined(WC_MLDSA_HAVE_CHECK_KEY) || \ + defined(WC_MLDSA_HAVE_MAKE_PUBLIC_KEY) || \ (!defined(WOLFSSL_MLDSA_NO_SIGN) && \ (defined(WC_MLDSA_CACHE_PRIV_VECTORS) || \ !defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM))) @@ -1386,7 +1409,8 @@ static void mldsa_vec_decode_eta_bits(const byte* p, byte eta, sword32* s, #endif } #endif -#endif /* !WOLFSSL_MLDSA_NO_SIGN || WOLFSSL_MLDSA_CHECK_KEY */ +#endif /* !WOLFSSL_MLDSA_NO_SIGN || WC_MLDSA_HAVE_CHECK_KEY || + * WC_MLDSA_HAVE_MAKE_PUBLIC_KEY */ #ifndef WOLFSSL_MLDSA_NO_MAKE_KEY /* Encode t into t0 and t1. @@ -1574,7 +1598,7 @@ static void mldsa_vec_encode_t0_t1(const sword32* t, byte d, byte* t0, } #endif /* !WOLFSSL_MLDSA_NO_MAKE_KEY */ -#if !defined(WOLFSSL_MLDSA_NO_SIGN) || defined(WOLFSSL_MLDSA_CHECK_KEY) +#if !defined(WOLFSSL_MLDSA_NO_SIGN) || defined(WC_MLDSA_HAVE_CHECK_KEY) /* Decode bottom D bits of t as t0. * * FIPS 204 Section 7.2, Algorithm 25 skDecode(sk) @@ -1669,7 +1693,7 @@ static void mldsa_decode_t0(const byte* t0, sword32* t) } } -#if defined(WOLFSSL_MLDSA_CHECK_KEY) || \ +#if defined(WC_MLDSA_HAVE_CHECK_KEY) || \ (!defined(WOLFSSL_MLDSA_NO_SIGN) && \ (defined(WC_MLDSA_CACHE_PRIV_VECTORS) || \ !defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM))) @@ -1712,10 +1736,9 @@ static void mldsa_vec_decode_t0(const byte* t0, byte d, sword32* t) } } #endif -#endif /* !WOLFSSL_MLDSA_NO_SIGN || WOLFSSL_MLDSA_CHECK_KEY */ +#endif /* !WOLFSSL_MLDSA_NO_SIGN || WC_MLDSA_HAVE_CHECK_KEY */ -#if !defined(WOLFSSL_MLDSA_NO_VERIFY) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY) +#if !defined(WOLFSSL_MLDSA_NO_VERIFY) || defined(WC_MLDSA_HAVE_CHECK_KEY) /* Decode top bits of t as t1. * * FIPS 204 Section 7.2, Algorithm 23 pkDecode(pk) @@ -1816,7 +1839,7 @@ static void mldsa_decode_t1(const byte* t1, sword32* t) #if (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY) + defined(WC_MLDSA_HAVE_CHECK_KEY) /* Decode top bits of t as t1. * * FIPS 204 Section 7.2, Algorithm 23 pkDecode(pk) @@ -2970,7 +2993,7 @@ static int mldsa_rej_ntt_poly(wc_Shake* shake128, byte* seed, sword32* a, #if (!defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM)) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY) || \ (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) || \ (!defined(WOLFSSL_MLDSA_NO_SIGN) && \ @@ -6275,9 +6298,8 @@ static sword32 mldsa_mont_red(sword64 a) (!defined(WOLFSSL_MLDSA_NO_SIGN) || \ (defined(WOLFSSL_MLDSA_SMALL) && \ (!defined(WOLFSSL_MLDSA_NO_MAKE_KEY) || \ - (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ - !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY)))) + !defined(WOLFSSL_MLDSA_NO_VERIFY) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY)))) /* Reduce 32-bit a modulo q. r = a mod q. * * Barrett reduction. @@ -8267,9 +8289,8 @@ static void mldsa_vec_mul(sword32* r, sword32* a, sword32* b, byte l) #if !defined(WOLFSSL_MLDSA_NO_SIGN) || \ (defined(WOLFSSL_MLDSA_SMALL) && \ (!defined(WOLFSSL_MLDSA_NO_MAKE_KEY) || \ - (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ - !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY))) + !defined(WOLFSSL_MLDSA_NO_VERIFY) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY))) /* Modulo reduce values in polynomial. Range (-2^31)..(2^31-1). * * @param [in, out] a Polynomial. @@ -8347,7 +8368,7 @@ static void mldsa_vec_red(sword32* a, byte l) #if (!defined(WOLFSSL_MLDSA_NO_SIGN) || \ (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM))) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY) + defined(WC_MLDSA_HAVE_CHECK_KEY) /* Subtract polynomials a from r. r -= a. * * @param [out] r Polynomial to subtract from. @@ -8400,9 +8421,9 @@ static void mldsa_sub(sword32* r, const sword32* a) } } -#if defined(WOLFSSL_MLDSA_CHECK_KEY) || \ - (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ - !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) +#if defined(WC_MLDSA_HAVE_CHECK_KEY) || \ + (!defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM)) /* Subtract vector a from r. r -= a. * * @param [out] r Vector of polynomials that is result. @@ -8554,7 +8575,7 @@ static void mldsa_make_pos(sword32* a) } #if !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) || \ - defined(WOLFSSL_MLDSA_CHECK_KEY) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY) || \ (!defined(WOLFSSL_MLDSA_NO_SIGN) && \ !defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM)) /* Make values in polynomials of vector be in positive range. @@ -8577,6 +8598,192 @@ static void mldsa_vec_make_pos(sword32* a, byte l) /******************************************************************************/ +#if (!defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM)) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY) +/* Compute t = NTT^-1(A_circum o NTT(s1)) + s2. + * NTTs s1 in-place. Leaves t decomposition/encoding to callers. + * + * @param [in, out] key ML-DSA key (uses shake/heap/params). + * @param [in] rho Public seed. + * @param [in, out] s1 Vector s1 (l polys); NTT'd in-place. + * @param [in] s2 Vector s2 (k polys), added into t. + * @param [out] t Result vector (k polys). + * @param [in, out] a Matrix A scratch (full k*l). + * @param [in] aValid Non-zero if `a` already holds expanded `rho` matrix. + * @return 0 on success, negative on error. + */ +static int mldsa_calc_t_std(wc_MlDsaKey* key, const byte* rho, sword32* s1, + sword32* s2, sword32* t, sword32* a, int aValid) +{ + int ret = 0; + const wc_MlDsaParams* params = key->params; + + if (!aValid) { + ret = mldsa_expand_a(&key->shake, rho, params->k, params->l, a, + key->heap); + } + if (ret == 0) { + mldsa_vec_ntt_small_full(s1, params->l); + mldsa_matrix_mul(t, a, s1, params->k, params->l); + #ifdef WOLFSSL_MLDSA_SMALL + mldsa_vec_red(t, params->k); + #endif + mldsa_vec_invntt_full(t, params->k); + mldsa_vec_add(t, s2, params->k); + /* Callers must call mldsa_vec_make_pos() before decomposing t. */ + } + return ret; +} +#endif /* (!WOLFSSL_MLDSA_NO_MAKE_KEY && !WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) || + * WC_MLDSA_HAVE_CHECK_KEY */ + +#if defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) +/* Streaming small-mem variant of mldsa_calc_t_std (expands A per-polynomial). + * Same contract: NTTs s1 in-place, leaves t decomposition/encoding to callers. + * + * @param [in, out] key ML-DSA key (uses shake). + * @param [in] rho Public seed. + * @param [in, out] s1 Vector s1 (l polys); NTT'd in-place. + * @param [in] s2 Vector s2 (k polys), added into t. + * @param [out] t Result vector (k polys). + * @param [in, out] a Single-polynomial matrix scratch. + * @param [in, out] h Rejection-sampling scratch. + * @param [in, out] t64 64-bit accumulator (WOLFSSL_MLDSA_SMALL_MEM_POLY64). + * @return 0 on success, negative on error. + */ +static int mldsa_calc_t_small_mem(wc_MlDsaKey* key, const byte* rho, + sword32* s1, sword32* s2, sword32* t, sword32* a, byte* h, + sword64* t64) +{ + int ret = 0; + const wc_MlDsaParams* params = key->params; + byte aseed[MLDSA_GEN_A_SEED_SZ]; + sword32* s2t = s2; + sword32* tt = t; + unsigned int r; + unsigned int s; + + (void)t64; + + mldsa_vec_ntt_small_full(s1, params->l); + XMEMCPY(aseed, rho, MLDSA_PUB_SEED_SZ); + for (r = 0; (ret == 0) && (r < params->k); r++) { + sword32* s1t = s1; + unsigned int e; + + /* Put r/i into buffer to be hashed. */ + aseed[MLDSA_PUB_SEED_SZ + 1] = (byte)r; + for (s = 0; (ret == 0) && (s < params->l); s++) { + /* Put s into buffer to be hashed. */ + aseed[MLDSA_PUB_SEED_SZ + 0] = (byte)s; + /* Step 3: Expand public seed into a matrix of polynomials. */ + ret = mldsa_rej_ntt_poly_ex(&key->shake, aseed, a, h); + if (ret != 0) { + break; + } + /* Matrix multiply. */ + #ifndef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + if (s == 0) { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + tt[e] = mldsa_mont_red((sword64)a[e] * s1t[e]); + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + tt[e+0] = mldsa_mont_red((sword64)a[e+0]*s1t[e+0]); + tt[e+1] = mldsa_mont_red((sword64)a[e+1]*s1t[e+1]); + tt[e+2] = mldsa_mont_red((sword64)a[e+2]*s1t[e+2]); + tt[e+3] = mldsa_mont_red((sword64)a[e+3]*s1t[e+3]); + tt[e+4] = mldsa_mont_red((sword64)a[e+4]*s1t[e+4]); + tt[e+5] = mldsa_mont_red((sword64)a[e+5]*s1t[e+5]); + tt[e+6] = mldsa_mont_red((sword64)a[e+6]*s1t[e+6]); + tt[e+7] = mldsa_mont_red((sword64)a[e+7]*s1t[e+7]); + } + #endif + } + else { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + tt[e] += mldsa_mont_red((sword64)a[e] * s1t[e]); + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + tt[e+0] += mldsa_mont_red((sword64)a[e+0]*s1t[e+0]); + tt[e+1] += mldsa_mont_red((sword64)a[e+1]*s1t[e+1]); + tt[e+2] += mldsa_mont_red((sword64)a[e+2]*s1t[e+2]); + tt[e+3] += mldsa_mont_red((sword64)a[e+3]*s1t[e+3]); + tt[e+4] += mldsa_mont_red((sword64)a[e+4]*s1t[e+4]); + tt[e+5] += mldsa_mont_red((sword64)a[e+5]*s1t[e+5]); + tt[e+6] += mldsa_mont_red((sword64)a[e+6]*s1t[e+6]); + tt[e+7] += mldsa_mont_red((sword64)a[e+7]*s1t[e+7]); + } + #endif + } + #else + if (s == 0) { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + t64[e] = (sword64)a[e] * s1t[e]; + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + t64[e+0] = (sword64)a[e+0] * s1t[e+0]; + t64[e+1] = (sword64)a[e+1] * s1t[e+1]; + t64[e+2] = (sword64)a[e+2] * s1t[e+2]; + t64[e+3] = (sword64)a[e+3] * s1t[e+3]; + t64[e+4] = (sword64)a[e+4] * s1t[e+4]; + t64[e+5] = (sword64)a[e+5] * s1t[e+5]; + t64[e+6] = (sword64)a[e+6] * s1t[e+6]; + t64[e+7] = (sword64)a[e+7] * s1t[e+7]; + } + #endif + } + else { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + t64[e] += (sword64)a[e] * s1t[e]; + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + t64[e+0] += (sword64)a[e+0] * s1t[e+0]; + t64[e+1] += (sword64)a[e+1] * s1t[e+1]; + t64[e+2] += (sword64)a[e+2] * s1t[e+2]; + t64[e+3] += (sword64)a[e+3] * s1t[e+3]; + t64[e+4] += (sword64)a[e+4] * s1t[e+4]; + t64[e+5] += (sword64)a[e+5] * s1t[e+5]; + t64[e+6] += (sword64)a[e+6] * s1t[e+6]; + t64[e+7] += (sword64)a[e+7] * s1t[e+7]; + } + #endif + } + #endif + /* Next polynomial. */ + s1t += MLDSA_N; + } + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + for (e = 0; e < MLDSA_N; e++) { + tt[e] = mldsa_mont_red(t64[e]); + } + #endif + #ifdef WOLFSSL_MLDSA_SMALL + /* Reduce before invntt to avoid sword32 overflow, as in + * mldsa_calc_t_std()'s vec_red() call. */ + mldsa_poly_red(tt); + #endif + mldsa_invntt_full(tt); + mldsa_add(tt, s2t); + /* Make positive for decomposing. */ + mldsa_make_pos(tt); + + tt += MLDSA_N; + s2t += MLDSA_N; + } + return ret; +} +#endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM && !WOLFSSL_MLDSA_NO_MAKE_KEY */ + #ifndef WOLFSSL_MLDSA_NO_MAKE_KEY /* Make a key from a random seed. @@ -8724,14 +8931,6 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) pub_seed, MLDSA_SEEDS_SZ); } } - if (ret == 0) { - /* Step 7; Alg 22 Step 1: Copy public seed into public key. */ - XMEMCPY(key->p, pub_seed, MLDSA_PUB_SEED_SZ); - - /* Step 3: Expand public seed into a matrix of polynomials. */ - ret = mldsa_expand_a(&key->shake, pub_seed, params->k, params->l, - a, key->heap); - } if (ret == 0) { byte* priv_seed = key->k + MLDSA_PUB_SEED_SZ; @@ -8754,24 +8953,21 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) /* Step 9. Alg 24 Steps 5-7: Encode s2 into private key. */ mldsa_vec_encode_eta_bits(s2, params->k, params->eta, s2p); - /* Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2 */ - mldsa_vec_ntt_small_full(s1, params->l); - mldsa_matrix_mul(t, a, s1, params->k, params->l); - #ifdef WOLFSSL_MLDSA_SMALL - mldsa_vec_red(t, params->k); - #endif - mldsa_vec_invntt_full(t, params->k); - mldsa_vec_add(t, s2, params->k); - - /* Make positive for decomposing. */ - mldsa_vec_make_pos(t, params->k); - /* Step 6, Step 7, Step 9. Alg 22 Steps 2-4, Alg 24 Steps 8-10. - * Decompose t in t0 and t1 and encode into public and private key. - */ - mldsa_vec_encode_t0_t1(t, params->k, t0, t1); - /* Step 8. Alg 24, Step 1: Hash public key into private key. */ - ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, - MLDSA_TR_SZ); + /* Step 3, Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2 */ + ret = mldsa_calc_t_std(key, pub_seed, s1, s2, t, a, 0); + if (ret == 0) { + /* Step 7; Alg 22 Step 1: Copy public seed into public key. */ + XMEMCPY(key->p, pub_seed, MLDSA_PUB_SEED_SZ); + /* Make positive for decomposing. */ + mldsa_vec_make_pos(t, params->k); + /* Step 6, Step 7, Step 9. Alg 22 Steps 2-4, Alg 24 Steps 8-10. + * Decompose t in t0 and t1 and encode into public and private + * key. */ + mldsa_vec_encode_t0_t1(t, params->k, t0, t1); + /* Step 8. Alg 24, Step 1: Hash public key into private key. */ + ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, + MLDSA_TR_SZ); + } } if (ret == 0) { /* Public key and private key are available. */ @@ -8813,8 +9009,6 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) #endif byte* h = NULL; byte* pub_seed = NULL; - unsigned int r; - unsigned int s; byte kl[2]; unsigned int allocSz = 0; @@ -8831,10 +9025,11 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) /* Allocate memory for large intermediates. */ if (ret == 0) { - /* s1-l, s2-k, t-k, a-1 */ + /* s1-l, s2-k, t-k, a-1, h */ + /* Note: t has same size as s2 */ allocSz = (unsigned int)params->s1Sz + params->s2Sz + params->s2Sz + - (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE + - (unsigned int)MLDSA_POLY_SIZE; + (unsigned int)MLDSA_POLY_SIZE + + (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 /* t64 */ allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; @@ -8846,10 +9041,12 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) else { s2 = s1 + params->s1Sz / sizeof(*s1); t = s2 + params->s2Sz / sizeof(*s2); - h = (byte*)(t + params->s2Sz / sizeof(*t)); - a = (sword32*)(h + MLDSA_REJ_NTT_POLY_H_SIZE); + a = t + params->s2Sz / sizeof(*t); #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = (sword64*)(a + MLDSA_N); + h = (byte*)(t64 + MLDSA_N); + #else + h = (byte*)(a + MLDSA_N); #endif } } @@ -8878,9 +9075,6 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) if (ret == 0) { byte* priv_seed = key->k + MLDSA_PUB_SEED_SZ; - /* Step 7; Alg 22 Step 1: Copy public seed into public key. */ - XMEMCPY(key->p, pub_seed, MLDSA_PUB_SEED_SZ); - /* Step 4: Expand private seed into to vectors of polynomials. */ ret = mldsa_expand_s(&key->shake, priv_seed, params->eta, s1, params->l, s2, params->k, key->heap); @@ -8892,9 +9086,6 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) byte* s2p = s1p + params->s1EncSz; byte* t0 = s2p + params->s2EncSz; byte* t1 = key->p + MLDSA_PUB_SEED_SZ; - byte aseed[MLDSA_GEN_A_SEED_SZ]; - sword32* s2t = s2; - sword32* tt = t; /* Step 9: Move k down to after public seed. */ XMEMCPY(k, k + MLDSA_PRIV_SEED_SZ, MLDSA_K_SZ); @@ -8903,124 +9094,25 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) /* Step 9. Alg 24 Steps 5-7: Encode s2 into private key. */ mldsa_vec_encode_eta_bits(s2, params->k, params->eta, s2p); - /* Step 5: NTT(s1) */ - mldsa_vec_ntt_small_full(s1, params->l); - /* Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2 */ - XMEMCPY(aseed, pub_seed, MLDSA_PUB_SEED_SZ); - for (r = 0; (ret == 0) && (r < params->k); r++) { - sword32* s1t = s1; - unsigned int e; - - /* Put r/i into buffer to be hashed. */ - aseed[MLDSA_PUB_SEED_SZ + 1] = (byte)r; - for (s = 0; s < params->l; s++) { - /* Put s into buffer to be hashed. */ - aseed[MLDSA_PUB_SEED_SZ + 0] = (byte)s; - /* Step 3: Expand public seed into a matrix of polynomials. */ - ret = mldsa_rej_ntt_poly_ex(&key->shake, aseed, a, h); - if (ret != 0) { - break; - } - /* Matrix multiply. */ - #ifndef WOLFSSL_MLDSA_SMALL_MEM_POLY64 - if (s == 0) { - #ifdef WOLFSSL_MLDSA_SMALL - for (e = 0; e < MLDSA_N; e++) { - tt[e] = mldsa_mont_red((sword64)a[e] * s1t[e]); - } - #else - for (e = 0; e < MLDSA_N; e += 8) { - tt[e+0] = mldsa_mont_red((sword64)a[e+0]*s1t[e+0]); - tt[e+1] = mldsa_mont_red((sword64)a[e+1]*s1t[e+1]); - tt[e+2] = mldsa_mont_red((sword64)a[e+2]*s1t[e+2]); - tt[e+3] = mldsa_mont_red((sword64)a[e+3]*s1t[e+3]); - tt[e+4] = mldsa_mont_red((sword64)a[e+4]*s1t[e+4]); - tt[e+5] = mldsa_mont_red((sword64)a[e+5]*s1t[e+5]); - tt[e+6] = mldsa_mont_red((sword64)a[e+6]*s1t[e+6]); - tt[e+7] = mldsa_mont_red((sword64)a[e+7]*s1t[e+7]); - } - #endif - } - else { - #ifdef WOLFSSL_MLDSA_SMALL - for (e = 0; e < MLDSA_N; e++) { - tt[e] += mldsa_mont_red((sword64)a[e] * s1t[e]); - } - #else - for (e = 0; e < MLDSA_N; e += 8) { - tt[e+0] += mldsa_mont_red((sword64)a[e+0]*s1t[e+0]); - tt[e+1] += mldsa_mont_red((sword64)a[e+1]*s1t[e+1]); - tt[e+2] += mldsa_mont_red((sword64)a[e+2]*s1t[e+2]); - tt[e+3] += mldsa_mont_red((sword64)a[e+3]*s1t[e+3]); - tt[e+4] += mldsa_mont_red((sword64)a[e+4]*s1t[e+4]); - tt[e+5] += mldsa_mont_red((sword64)a[e+5]*s1t[e+5]); - tt[e+6] += mldsa_mont_red((sword64)a[e+6]*s1t[e+6]); - tt[e+7] += mldsa_mont_red((sword64)a[e+7]*s1t[e+7]); - } - #endif - } - #else - if (s == 0) { - #ifdef WOLFSSL_MLDSA_SMALL - for (e = 0; e < MLDSA_N; e++) { - t64[e] = (sword64)a[e] * s1t[e]; - } - #else - for (e = 0; e < MLDSA_N; e += 8) { - t64[e+0] = (sword64)a[e+0] * s1t[e+0]; - t64[e+1] = (sword64)a[e+1] * s1t[e+1]; - t64[e+2] = (sword64)a[e+2] * s1t[e+2]; - t64[e+3] = (sword64)a[e+3] * s1t[e+3]; - t64[e+4] = (sword64)a[e+4] * s1t[e+4]; - t64[e+5] = (sword64)a[e+5] * s1t[e+5]; - t64[e+6] = (sword64)a[e+6] * s1t[e+6]; - t64[e+7] = (sword64)a[e+7] * s1t[e+7]; - } - #endif - } - else { - #ifdef WOLFSSL_MLDSA_SMALL - for (e = 0; e < MLDSA_N; e++) { - t64[e] += (sword64)a[e] * s1t[e]; - } - #else - for (e = 0; e < MLDSA_N; e += 8) { - t64[e+0] += (sword64)a[e+0] * s1t[e+0]; - t64[e+1] += (sword64)a[e+1] * s1t[e+1]; - t64[e+2] += (sword64)a[e+2] * s1t[e+2]; - t64[e+3] += (sword64)a[e+3] * s1t[e+3]; - t64[e+4] += (sword64)a[e+4] * s1t[e+4]; - t64[e+5] += (sword64)a[e+5] * s1t[e+5]; - t64[e+6] += (sword64)a[e+6] * s1t[e+6]; - t64[e+7] += (sword64)a[e+7] * s1t[e+7]; - } - #endif - } - #endif - /* Next polynomial. */ - s1t += MLDSA_N; - } - #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 - for (e = 0; e < MLDSA_N; e++) { - tt[e] = mldsa_mont_red(t64[e]); - } - #endif - mldsa_invntt_full(tt); - mldsa_add(tt, s2t); - /* Make positive for decomposing. */ - mldsa_make_pos(tt); - - tt += MLDSA_N; - s2t += MLDSA_N; + /* Step 3, Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2 */ +#ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + ret = mldsa_calc_t_small_mem(key, pub_seed, s1, s2, t, a, h, t64); +#else + ret = mldsa_calc_t_small_mem(key, pub_seed, s1, s2, t, a, h, NULL); +#endif + if (ret == 0) { + /* Step 7; Alg 22 Step 1: Copy public seed into public key. */ + XMEMCPY(key->p, pub_seed, MLDSA_PUB_SEED_SZ); + /* mldsa_calc_t_small_mem() already made t positive for + * decomposing, per row, internally. */ + /* Step 6, Step 7, Step 9. Alg 22 Steps 2-4, Alg 24 Steps 8-10. + * Decompose t in t0 and t1 and encode into public and private + * key. */ + mldsa_vec_encode_t0_t1(t, params->k, t0, t1); + /* Step 8. Alg 24, Step 1: Hash public key into private key. */ + ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, + MLDSA_TR_SZ); } - - /* Step 6, Step 7, Step 9. Alg 22 Steps 2-4, Alg 24 Steps 8-10. - * Decompose t in t0 and t1 and encode into public and private key. - */ - mldsa_vec_encode_t0_t1(t, params->k, t0, t1); - /* Step 8. Alg 24, Step 1: Hash public key into private key. */ - ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, - MLDSA_TR_SZ); } if (ret == 0) { /* Public key and private key are available. */ @@ -9543,12 +9635,12 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, /* Allocate memory for large intermediates. */ if (ret == 0) { - /* y-l, w0-k, w1-k, blocks, c-1, z-1, A-1 */ + /* y-l, w0-k, w1-k, c-1, z-1, A-1, blocks */ allocSz = (unsigned int)params->s1Sz + params->s2Sz + params->s2Sz + - (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE + (unsigned int)MLDSA_POLY_SIZE + (unsigned int)MLDSA_POLY_SIZE + - (unsigned int)MLDSA_POLY_SIZE; + (unsigned int)MLDSA_POLY_SIZE + + (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE; #ifdef WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC allocSz += (unsigned int)params->s1Sz + params->s2Sz + params->s2Sz; #elif defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A) @@ -9568,8 +9660,7 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, #endif w0 = y + params->s1Sz / sizeof(*y_ntt); w1 = w0 + params->s2Sz / sizeof(*w0); - blocks = (byte*)(w1 + params->s2Sz / sizeof(*w1)); - c = (sword32*)(blocks + MLDSA_REJ_NTT_POLY_H_SIZE); + c = w1 + params->s2Sz / sizeof(*w1); z = c + MLDSA_N; a = z + MLDSA_N; ct0 = z; @@ -9580,6 +9671,9 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, t0 = z; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = (sword64*)(a + (1 + maxK * params->l) * MLDSA_N); + blocks = (byte*)(t64 + MLDSA_N); + #else + blocks = (byte*)(a + (1 + maxK * params->l) * MLDSA_N); #endif #elif defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC) y_ntt = z; @@ -9588,6 +9682,9 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, t0 = s2 + params->s2Sz / sizeof(*s2); #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = (sword64*)(t0 + params->s2Sz / sizeof(*t0)); + blocks = (byte*)(t64 + MLDSA_N); + #else + blocks = (byte*)(t0 + params->s2Sz / sizeof(*t0)); #endif #else y_ntt = z; @@ -9596,6 +9693,9 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, t0 = z; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = (sword64*)(a + MLDSA_N); + blocks = (byte*)(t64 + MLDSA_N); + #else + blocks = (byte*)(a + MLDSA_N); #endif #endif } @@ -9819,6 +9919,11 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, for (e = 0; e < MLDSA_N; e++) { wt[e] = mldsa_mont_red(t64[e]); } + #endif + #ifdef WOLFSSL_MLDSA_SMALL + /* Reduce before invntt to avoid sword32 overflow, as in + * mldsa_calc_t_std()'s vec_red() call. */ + mldsa_poly_red(wt); #endif mldsa_invntt_full(wt); /* Step 14, Step 22: Make values positive and decompose. */ @@ -10541,6 +10646,11 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, int valid = 0; sword32 hi; + if (!key->pubKeySet) { + *res = 0; + return PUBLIC_KEY_E; + } + /* Ensure the signature is the right size for the parameters. */ if (sigLen != params->sigSz) { ret = BUFFER_E; @@ -10708,6 +10818,11 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, byte* encW1; byte* seed = commit_calc; + if (!key->pubKeySet) { + *res = 0; + return PUBLIC_KEY_E; + } + /* Ensure the signature is the right size for the parameters. */ if (sigLen != params->sigSz) { ret = BUFFER_E; @@ -10723,9 +10838,10 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, /* z, c, w, t1, w1e. */ unsigned int allocSz; - allocSz = (unsigned int)params->s1Sz + params->w1EncSz + + allocSz = (unsigned int)params->s1Sz + 3U * (unsigned int)MLDSA_POLY_SIZE + - (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE; + (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE + + params->w1EncSz; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; #endif @@ -10738,12 +10854,14 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, c = z + params->s1Sz / sizeof(*t1); w = c + MLDSA_N; t1 = w + MLDSA_N; - block = (byte*)(t1 + MLDSA_N); - w1e = block + MLDSA_REJ_NTT_POLY_H_SIZE; a = t1; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 - t64 = (sword64*)(w1e + params->w1EncSz); + t64 = (sword64*)(t1 + MLDSA_N); + block = (byte*)(t64 + MLDSA_N); + #else + block = (byte*)(t1 + MLDSA_N); #endif + w1e = block + MLDSA_REJ_NTT_POLY_H_SIZE; } } #else @@ -10896,6 +11014,11 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, #endif /* Step 10: w = NTT-1(A o NTT(z) - NTT(c) o NTT(t1)) */ + #ifdef WOLFSSL_MLDSA_SMALL + /* Reduce before invntt to avoid sword32 overflow, as in + * mldsa_calc_t_std()'s vec_red() call. */ + mldsa_poly_red(w); + #endif mldsa_invntt_full(w); #ifndef WOLFSSL_NO_ML_DSA_44 @@ -11195,6 +11318,482 @@ int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed) return ret; } + +#ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + +/* Encoded size of one polynomial of t0 and of t1. */ +#define MLDSA_T0_POLY_ENC_SZ (MLDSA_D * MLDSA_N / 8) +#define MLDSA_T1_POLY_ENC_SZ (MLDSA_U * MLDSA_N / 8) +/* Headroom on the encode scratch buffers below. Required, not defensive: + * wc_mldsa_vec_encode_t0_t1_avx2() writes each output chunk with a 16-byte + * vmovdqu but advances the pointer by only the chunk size, so the last store + * of every polynomial runs past the logical end - 3 bytes for t0 (last of 32 + * stores starts at 403, writes through 418, size 416) and 6 bytes for t1 + * (starts at 310, writes through 325, size 320). Verified by calling the + * symbol directly against sentinel-filled buffers; do not drop this to 0. + * The C encoder writes exactly MLDSA_D/MLDSA_U bytes per chunk and stays in + * bounds, so a build without USE_INTEL_SPEEDUP will not show the overrun. */ +#define MLDSA_POLY_ENC_SLACK 8 + +/* Encode vector t one polynomial at a time, checking it against the private + * key as it goes. + * + * Streaming keeps the encode scratch down to a single polynomial rather than + * the whole vector, so deriving a public key needs no heap allocation for it. + * + * @param [in] t Vector of polynomials to encode. + * @param [in] k Number of polynomials in t. + * @param [in] t0p Authentic encoded t0 from the private key to check + * each chunk against. + * @param [out] t1Out Where to write the encoded t1, or NULL to not write. + * @param [in] t1Cmp Encoded t1 to check against, or NULL to not check. + * @param [in] t0Poly Scratch of MLDSA_T0_POLY_ENC_SZ + slack bytes. + * @param [in] t1Poly Scratch of MLDSA_T1_POLY_ENC_SZ + slack bytes. + * @return 0 when every chunk matched, non-zero otherwise. + */ +static int mldsa_encode_t_stream(const sword32* t, byte k, const byte* t0p, + byte* t1Out, const byte* t1Cmp, byte* t0Poly, byte* t1Poly) +{ + unsigned int i; + int diff = 0; + + for (i = 0; i < (unsigned int)k; i++) { + /* Encoding one polynomial at a time - the encoder loops over the + * vector, so a count of 1 emits exactly one polynomial. */ + mldsa_vec_encode_t0_t1(t + (size_t)i * MLDSA_N, 1, t0Poly, t1Poly); + + /* Accumulate rather than break early so the work stays independent + * of where a mismatch falls. */ + diff |= ConstantCompare(t0Poly, + t0p + (size_t)i * MLDSA_T0_POLY_ENC_SZ, MLDSA_T0_POLY_ENC_SZ); + + if (t1Out != NULL) { + XMEMCPY(t1Out + (size_t)i * MLDSA_T1_POLY_ENC_SZ, t1Poly, + MLDSA_T1_POLY_ENC_SZ); + } + if (t1Cmp != NULL) { + diff |= ConstantCompare(t1Poly, + t1Cmp + (size_t)i * MLDSA_T1_POLY_ENC_SZ, + MLDSA_T1_POLY_ENC_SZ); + } + } + + return diff; +} + +/* Derive public key (t1) from a private-key-only decode. Recomputes t1 from + * rho/s1/s2 like keygen. No-op if already set. + * + * Honors caching options (WC_MLDSA_CACHE_MATRIX_A/WC_MLDSA_CACHE_PRIV_VECTORS) + * unless WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM is set, where local buffers are used. + * + * Note: With WC_MLDSA_CACHE_PRIV_VECTORS, if called after private-key decode, + * reuses the existing NTT-domain s1/s2/t0 buffers as scratch, re-decoding + * s1/s2 from raw bytes (non-NTT) and invalidating privVecsSet. This duplicated + * decode work is a one-time per-decode cost. + * + * Derives in software, so not supported on keys with a devId set, unless the + * public key is already set - in that case this is a no-op regardless of + * devId. + * + * @param [in, out] key ML-DSA key (prvKeySet must be true). + * @return 0 on success or already set. + * @return BAD_FUNC_ARG if key/params is NULL, prvKeySet is false, or the + * public key is not yet set and a devId is set. + * @return MEMORY_E on allocation failure. + * @return Other negative on error. + */ +int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key) +{ + int ret = 0; + const wc_MlDsaParams* params = NULL; + sword32* s1 = NULL; + sword32* s2 = NULL; + sword32* t = NULL; + sword32* a = NULL; + WC_DECLARE_VAR(t0Poly, byte, MLDSA_T0_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK, + NULL); + WC_DECLARE_VAR(t1Poly, byte, MLDSA_T1_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK, + NULL); + unsigned int allocSz = 0; + void* allocPtr = NULL; +#ifdef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + byte* h = NULL; +#ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + sword64* t64 = NULL; +#endif +#endif + int didAlloc = 0; + /* Whether the encode scratch was written. Without WOLFSSL_SMALL_STACK + * WC_VAR_OK() is the literal 1, so the scrub below would otherwise run + * over uninitialized stack on every argument-validation failure. */ + int scratchUsed = 0; + /* Mirror of key->heap, so the cleanup at the end - which runs even when + * key is NULL - can free the encode scratch without dereferencing key. */ + void* heap = NULL; + + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + else { + heap = key->heap; + } + /* Only read by WC_ALLOC_VAR_EX/WC_FREE_VAR_EX, which compile out when + * WOLFSSL_SMALL_STACK is off and the scratch is stack-resident. */ + (void)heap; + if ((ret == 0) && (!key->prvKeySet)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && (key->params == NULL)) { + ret = BAD_FUNC_ARG; + } + +#ifdef WOLF_CRYPTO_CB + /* key->k may be a device handle rather than key material, so don't + * derive in software. Checked only when a derivation would actually be + * attempted, so that a devId-bound key that already has its public key + * set still returns 0 as a no-op instead of BAD_FUNC_ARG. */ + if ((ret == 0) && (!key->pubKeySet) && (key->devId != INVALID_DEVID)) { + ret = BAD_FUNC_ARG; + } +#endif + + if ((ret == 0) && (!key->pubKeySet)) { + params = key->params; + + #if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) + ret = mldsa_alloc_pub_buf(key); + #endif + + /* --- Allocate / reuse matrix A --------------------------------- */ + /* Small-mem mode always streams A locally; never cache it. */ + #if defined(WC_MLDSA_CACHE_MATRIX_A) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) + #ifndef WC_MLDSA_FIXED_ARRAY + if ((ret == 0) && (key->a == NULL)) { + key->a = (sword32*)XMALLOC(params->aSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (key->a == NULL) { + ret = MEMORY_E; + } + } + #endif + if (ret == 0) { + a = key->a; + } + #endif /* WC_MLDSA_CACHE_MATRIX_A && !WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + + /* --- Allocate / reuse private vectors s1, s2, t ---------------- */ + #if defined(WC_MLDSA_CACHE_PRIV_VECTORS) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) + #ifndef WC_MLDSA_FIXED_ARRAY + if ((ret == 0) && (key->s1 == NULL)) { + key->s1 = (sword32*)XMALLOC(params->aSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (key->s1 == NULL) { + ret = MEMORY_E; + } + else { + key->s2 = key->s1 + params->s1Sz / sizeof(*s1); + key->t0 = key->s2 + params->s2Sz / sizeof(*s2); + } + } + #endif + if (ret == 0) { + s1 = key->s1; + s2 = key->s2; + t = key->t0; + } + #endif /* WC_MLDSA_CACHE_PRIV_VECTORS && + * !WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + + /* --- Compute dynamic allocation size for remaining buffers ----- */ + if (ret == 0) { + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + /* Note: t has same size as s2 */ + allocSz = (unsigned int)params->s1Sz + params->s2Sz + + params->s2Sz; + #endif + #ifndef WC_MLDSA_CACHE_MATRIX_A + allocSz += params->aSz; + #endif + #else /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + /* s1-l, s2-k, t-k, a-1 (one poly for streaming), h, t64 (opt) */ + /* Note: t has same size as s2 */ + allocSz = (unsigned int)params->s1Sz + params->s2Sz + + params->s2Sz + + (unsigned int)MLDSA_POLY_SIZE + + (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE; + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + /* t64: extra scratch for 64-bit polynomial arithmetic. */ + allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; + #endif + #endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + } + + /* --- Allocate the dynamic portion ------------------------------ */ + if ((ret == 0) && (allocSz > 0)) { + allocPtr = XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + if (allocPtr == NULL) { + ret = MEMORY_E; + } + else { + didAlloc = 1; + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + s1 = (sword32*)allocPtr; + s2 = s1 + params->s1Sz / sizeof(*s1); + t = s2 + params->s2Sz / sizeof(*s2); + #endif + #ifndef WC_MLDSA_CACHE_MATRIX_A + { + /* Matrix A is appended at the end of the block. */ + sword32* base = (sword32*)allocPtr; + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + base = t + params->s2Sz / sizeof(*t); + #endif + a = base; + } + #endif + #else /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + s1 = (sword32*)allocPtr; + s2 = s1 + params->s1Sz / sizeof(*s1); + t = s2 + params->s2Sz / sizeof(*s2); + a = t + params->s2Sz / sizeof(*t); + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + t64 = (sword64*)(a + MLDSA_N); + h = (byte*)(t64 + MLDSA_N); + #else + h = (byte*)(a + MLDSA_N); + #endif + #endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + } + } + + if (ret == 0) { + /* One polynomial of encode scratch each, so this is a couple of + * hundred bytes on the stack unless WOLFSSL_SMALL_STACK asks for + * it to be allocated. */ + WC_ALLOC_VAR_EX(t0Poly, byte, + MLDSA_T0_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK, heap, + DYNAMIC_TYPE_MLDSA, ret = MEMORY_E); + } + if (ret == 0) { + WC_ALLOC_VAR_EX(t1Poly, byte, + MLDSA_T1_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK, heap, + DYNAMIC_TYPE_MLDSA, ret = MEMORY_E); + } + if (ret == 0) { + /* Past here the encode scratch may hold t0 material, so the + * cleanup below must scrub it. */ + scratchUsed = 1; + } + + if (ret == 0) { + const byte* rho = key->k; + const byte* s1p = key->k + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ + + MLDSA_TR_SZ; + const byte* s2p = s1p + params->s1EncSz; + const byte* t0p = s2p + params->s2EncSz; + byte* t1 = key->p + MLDSA_PUB_SEED_SZ; + + mldsa_vec_decode_eta_bits(s1p, params->eta, s1, params->l); + mldsa_vec_decode_eta_bits(s2p, params->eta, s2, params->k); + +#if defined(WC_MLDSA_CACHE_PRIV_VECTORS) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) + /* s1/s2/t0 are overwritten here (wrong domain). Invalidate cache + * now so a subsequent calc_t failure doesn't leave privVecsSet + * stale with corrupted vectors. Small-mem never aliases + * key->s1/s2/t0 here (s1/s2/t are locally allocated below), so + * the cache is untouched and must not be invalidated. */ + key->privVecsSet = 0; +#endif + + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + /* Standard path: expand full matrix A, then multiply. + * Skip re-expanding A when it is already cached. */ + #ifdef WC_MLDSA_CACHE_MATRIX_A + ret = mldsa_calc_t_std(key, rho, s1, s2, t, a, key->aSet); + if (ret == 0) { + /* key->a now holds the matrix expanded from rho. */ + key->aSet = 1; + } + #else + ret = mldsa_calc_t_std(key, rho, s1, s2, t, a, 0); + #endif + #else + /* Small-mem path: stream matrix A one polynomial at a time. */ + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + ret = mldsa_calc_t_small_mem(key, rho, s1, s2, t, a, h, t64); + #else + ret = mldsa_calc_t_small_mem(key, rho, s1, s2, t, a, h, NULL); + #endif + #endif + if (ret == 0) { + XMEMCPY(key->p, rho, MLDSA_PUB_SEED_SZ); + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + /* mldsa_calc_t_small_mem() already makes each row of t + * positive internally; mldsa_calc_t_std() does not. */ + mldsa_vec_make_pos(t, params->k); + #endif + /* Encode t1 into the public key, and unconditionally verify + * the derived t0 against the authentic t0 stored in the + * private key blob as each polynomial is encoded. t0 and t1 + * are the low and high halves of the same decomposed t, so + * any fault in the derivation shows up here, and this compare + * needs no hashing and no re-derivation. */ + if (mldsa_encode_t_stream(t, params->k, t0p, t1, NULL, t0Poly, + t1Poly) != 0) { + ret = PUBLIC_KEY_E; + } + + /* Unconditionally verify the derived public key against the + * authentic 'tr' hash stored in the private key blob. + * This proves the derived pk is bit-identical to the original, + * up to SHAKE256 collision resistance. Together with the t0 + * compare above, every bit of the derived t is checked against + * what the original keygen produced. */ + if (ret == 0) { + byte trCalc[MLDSA_TR_SZ]; + const byte* tr = key->k + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ; + + ret = mldsa_shake256(&key->shake, key->p, + params->pkSz, trCalc, MLDSA_TR_SZ); + if ((ret == 0) && + (ConstantCompare(trCalc, tr, + MLDSA_TR_SZ) != 0)) { + ret = PUBLIC_KEY_E; + } + } + + /* Opt-in third check: re-run the derivation itself (via + * CheckKey, or streaming in small-mem mode) so a fault during + * the first pass is caught by recomputation rather than by + * comparison alone. Only run if the compares above passed, so + * a genuine failure caught there can never be overwritten by + * this weaker self-consistency check. pubKeySet is set once, + * after this. + * + * Where CheckKey() is compiled out (WOLFSSL_MLDSA_NO_CHECK_KEY) + * and the streaming re-derivation is unavailable, this pass is + * skipped: the unconditional t0 and 'tr' compares above still + * check every bit of the derived key against the private key + * blob, so the derived public key is never trusted unverified. + * + * Invariant relied on below and at the final "Set pubKeySet on + * success" check that closes this function: on any path that + * leaves this whole block with ret != 0, key->pubKeySet must + * be false. The CACHE_MATRIX_A and plain-CheckKey branches + * below speculatively set it to 1 before calling CheckKey() + * (which itself requires pubKeySet) and must revert it to 0 + * on failure - don't drop that revert in a future edit. + */ +#if defined(WC_MLDSA_FAULT_HARDEN) && \ + (defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) || \ + defined(WC_MLDSA_HAVE_CHECK_KEY)) + if (ret == 0) { + #ifdef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + /* CheckKey() expands full matrix A, defeating small-mem. + * Re-derive streaming and compare t0/t1 bytes instead. */ + { + mldsa_vec_decode_eta_bits(s1p, params->eta, s1, + params->l); + mldsa_vec_decode_eta_bits(s2p, params->eta, s2, + params->k); + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + ret = mldsa_calc_t_small_mem(key, rho, s1, s2, t, a, + h, t64); + #else + ret = mldsa_calc_t_small_mem(key, rho, s1, s2, t, a, + h, NULL); + #endif + if (ret == 0) { + /* Re-encode the second derivation and check both + * halves: t0 against the private key, t1 against what + * the first pass just wrote into the public key. */ + if (mldsa_encode_t_stream(t, params->k, t0p, NULL, t1, + t0Poly, t1Poly) != 0) { + ret = PUBLIC_KEY_E; + } + } + } + #elif defined(WC_MLDSA_CACHE_MATRIX_A) + { + /* Force CheckKey() to re-expand A independently. + * Clear aSet so CheckKey() calls mldsa_expand_a() fresh. */ + key->aSet = 0; + key->pubKeySet = 1; + ret = wc_MlDsaKey_CheckKey(key); + /* On success, A is the matrix CheckKey just expanded, so + * mark it cached again. On failure aSet stays 0 and A is + * re-expanded on next use. */ + if (ret == 0) { + key->aSet = 1; + } + if (ret != 0) { + key->pubKeySet = 0; + } + } + #else + { + key->pubKeySet = 1; + ret = wc_MlDsaKey_CheckKey(key); + if (ret != 0) { + key->pubKeySet = 0; + } + } + #endif + } +#endif /* WC_MLDSA_FAULT_HARDEN */ + /* Set pubKeySet on success. Fault-harden paths above + * already handle ret properly. */ + if (ret == 0) { + key->pubKeySet = 1; + } + } + + } + + /* --- Cleanup --------------------------------------------------- */ + /* Small-mem mode always allocated s1/s2/t/h/(t64) locally above. */ + #if !(defined(WC_MLDSA_FIXED_ARRAY) && \ + defined(WC_MLDSA_CACHE_PRIV_VECTORS) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM)) + if (didAlloc && allocPtr != NULL) { + /* Zero secret material (small-mem or no priv-vector cache). + * Skipped if only matrix A was allocated. */ + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + ForceZero(allocPtr, (unsigned int)params->s1Sz + 2U * params->s2Sz); + #endif + #else + ForceZero(allocPtr, allocSz); + #endif + XFREE(allocPtr, key->heap, DYNAMIC_TYPE_MLDSA); + } + #else + (void)didAlloc; + (void)allocPtr; + (void)allocSz; + #endif + } + + /* t0 is private key material, so scrub both encode buffers - but only if + * they were actually written. */ + if (scratchUsed && WC_VAR_OK(t0Poly)) { + ForceZero(t0Poly, MLDSA_T0_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK); + } + if (scratchUsed && WC_VAR_OK(t1Poly)) { + ForceZero(t1Poly, MLDSA_T1_POLY_ENC_SZ + MLDSA_POLY_ENC_SLACK); + } + WC_FREE_VAR_EX(t0Poly, heap, DYNAMIC_TYPE_MLDSA); + WC_FREE_VAR_EX(t1Poly, heap, DYNAMIC_TYPE_MLDSA); + + return ret; +} +#endif /* WC_MLDSA_HAVE_MAKE_PUBLIC_KEY */ #endif #ifndef WOLFSSL_MLDSA_NO_SIGN @@ -11559,8 +12158,9 @@ int wc_MlDsaKey_SignMuWithSeed(wc_MlDsaKey* key, byte* sig, word32 *sigLen, * msgLen [in] Length of the message in bytes. * res [out] *res is set to 1 on successful verification. * key [in] ML-DSA key to use to verify. - * returns BAD_FUNC_ARG when a parameter is NULL, public key not set - * or ctx is NULL and ctxLen is not 0, + * returns BAD_FUNC_ARG when a parameter is NULL or ctx is NULL and + * ctxLen is not 0, + * PUBLIC_KEY_E if no public key, * BUFFER_E when sigLen is less than WC_MLDSA_44_SIG_SIZE, * 0 otherwise. */ @@ -11597,6 +12197,16 @@ int wc_MlDsaKey_VerifyCtx(wc_MlDsaKey* key, const byte* sig, word32 sigLen, } #endif + /* Local fallback needs public key; devId keys without one handled by + * crypto cb above. Must be checked here rather than deferred to + * mldsa_verify_with_mu(): mldsa_verify_ctx_msg() hashes key->p to build + * mu first, and key->p is NULL when no public key is set under + * WOLFSSL_MLDSA_DYNAMIC_KEYS / WOLFSSL_MLDSA_ASSIGN_KEY. */ + if ((ret == 0) && (!key->pubKeySet)) { + *res = 0; + ret = PUBLIC_KEY_E; + } + if (ret == 0) { /* Verify message with signature. */ ret = mldsa_verify_ctx_msg(key, ctx, ctxLen, msg, msgLen, sig, @@ -11615,7 +12225,8 @@ int wc_MlDsaKey_VerifyCtx(wc_MlDsaKey* key, const byte* sig, word32 sigLen, * msgLen [in] Length of the message in bytes. * res [out] *res is set to 1 on successful verification. * key [in] ML-DSA key to use to verify. - * returns BAD_FUNC_ARG when a parameter is NULL or contextLen is zero when and + * returns BAD_FUNC_ARG when a parameter is NULL, + * PUBLIC_KEY_E if no public key, * BUFFER_E when sigLen is less than WC_MLDSA_44_SIG_SIZE, * 0 otherwise. * NOTE: This is a pre-FIPS 204 API without context support. New code should @@ -11630,7 +12241,6 @@ int wc_MlDsaKey_Verify(wc_MlDsaKey* key, const byte* sig, word32 sigLen, if ((key == NULL) || (sig == NULL) || (msg == NULL) || (res == NULL)) { ret = BAD_FUNC_ARG; } - #ifdef WOLF_CRYPTO_CB if (ret == 0) { #ifndef WOLF_CRYPTO_CB_FIND @@ -11647,6 +12257,16 @@ int wc_MlDsaKey_Verify(wc_MlDsaKey* key, const byte* sig, word32 sigLen, } #endif + /* Local fallback needs public key; devId keys without one handled by + * crypto cb above. Must be checked here rather than deferred to + * mldsa_verify_with_mu(): mldsa_verify_msg() hashes key->p to build mu + * first, and key->p is NULL when no public key is set under + * WOLFSSL_MLDSA_DYNAMIC_KEYS / WOLFSSL_MLDSA_ASSIGN_KEY. */ + if ((ret == 0) && (!key->pubKeySet)) { + *res = 0; + ret = PUBLIC_KEY_E; + } + if (ret == 0) { /* Verify message with signature. */ ret = mldsa_verify_msg(key, msg, msgLen, sig, sigLen, res); @@ -11667,8 +12287,9 @@ int wc_MlDsaKey_Verify(wc_MlDsaKey* key, const byte* sig, word32 sigLen, * hashLen [in] Length of the message hash in bytes. * res [out] *res is set to 1 on successful verification. * key [in] ML-DSA key to use to verify. - * returns BAD_FUNC_ARG when a parameter is NULL, public key not set - * or ctx is NULL and ctxLen is not 0, + * returns BAD_FUNC_ARG when a parameter is NULL or ctx is NULL and + * ctxLen is not 0, + * PUBLIC_KEY_E if no public key, * BUFFER_E when sigLen is less than WC_MLDSA_44_SIG_SIZE, * 0 otherwise. */ @@ -11702,6 +12323,16 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, } #endif + /* Local fallback needs public key; devId keys without one handled by + * crypto cb above. Must be checked here rather than deferred to + * mldsa_verify_with_mu(): mldsa_verify_ctx_hash() hashes key->p to build + * mu first, and key->p is NULL when no public key is set under + * WOLFSSL_MLDSA_DYNAMIC_KEYS / WOLFSSL_MLDSA_ASSIGN_KEY. */ + if ((ret == 0) && (!key->pubKeySet)) { + *res = 0; + ret = PUBLIC_KEY_E; + } + if (ret == 0) { /* Verify message with signature. */ ret = mldsa_verify_ctx_hash(key, ctx, ctxLen, hashAlg, hash, @@ -11724,6 +12355,7 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, * res [out] *res is set to 1 on successful verification. * key [in] ML-DSA key to use to verify. * returns BAD_FUNC_ARG when a parameter is NULL or muLen is not 64, + * PUBLIC_KEY_E if no public key, * 0 otherwise. */ int wc_MlDsaKey_VerifyMu(wc_MlDsaKey* key, const byte* sig, word32 sigLen, @@ -11740,6 +12372,10 @@ int wc_MlDsaKey_VerifyMu(wc_MlDsaKey* key, const byte* sig, word32 sigLen, ret = BAD_FUNC_ARG; } + /* No early pubKeySet check needed here, unlike the other verify entry + * points: mu is supplied by the caller, so key->p is never touched before + * mldsa_verify_with_mu()'s own pubKeySet guard, which sets *res and + * returns PUBLIC_KEY_E identically. */ if (ret == 0) { ret = mldsa_verify_with_mu(key, mu, sig, sigLen, res); } @@ -12314,7 +12950,7 @@ int wc_MlDsaKey_GetSigLen(wc_MlDsaKey* key, int* len) } #endif -#ifdef WOLFSSL_MLDSA_CHECK_KEY +#ifdef WC_MLDSA_HAVE_CHECK_KEY /* Check the public key of the ML-DSA key matches the private key. * * @param [in] key ML-DSA private/public key. @@ -12377,26 +13013,24 @@ int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key) #if !defined(WC_MLDSA_CACHE_MATRIX_A) a = t1 + params->s2Sz / sizeof(*t1); #else - a = key->a; + #ifndef WC_MLDSA_FIXED_ARRAY + /* key->a may not have been allocated yet, e.g. for a key + * populated via ImportKey/ImportPrivRaw rather than KeyGen. */ + if (key->a == NULL) { + key->a = (sword32*)XMALLOC(params->aSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (key->a == NULL) { + ret = MEMORY_E; + } + } + #endif + if (ret == 0) { + a = key->a; + } #endif } } - if (ret == 0) { -#ifdef WC_MLDSA_CACHE_MATRIX_A - /* Check that we haven't already cached the matrix A. */ - if (!key->aSet) -#endif - { - const byte* pub_seed = key->p; - - ret = mldsa_expand_a(&key->shake, pub_seed, params->k, - params->l, a, key->heap); -#ifdef WC_MLDSA_CACHE_MATRIX_A - key->aSet = (ret == 0); -#endif - } - } if (ret == 0) { const byte* s1p = key->k + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ + MLDSA_TR_SZ; @@ -12416,30 +13050,36 @@ int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key) /* Get t1 from public key. */ mldsa_vec_decode_t1(t1p, params->k, t1); - /* Calcaluate t = NTT-1(A o NTT(s1)) + s2 */ - mldsa_vec_ntt_small_full(s1, params->l); - mldsa_matrix_mul(t, a, s1, params->k, params->l); - #ifdef WOLFSSL_MLDSA_SMALL - mldsa_vec_red(t, params->k); - #endif - mldsa_vec_invntt_full(t, params->k); - mldsa_vec_add(t, s2, params->k); - /* Subtract t0 from t. */ - mldsa_vec_sub(t, t0, params->k); - /* Make t positive to match t1. */ - mldsa_vec_make_pos(t, params->k); - - /* Check t - t0 and t1 are the same. */ - for (i = 0; i < params->k; i++) { - for (j = 0; j < MLDSA_N; j++) { - x |= tt[j] ^ t1[j]; - } - tt += MLDSA_N; - t1 += MLDSA_N; + /* Calculate t = NTT-1(A o NTT(s1)) + s2. + * Skip A re-expand if cached. */ +#ifdef WC_MLDSA_CACHE_MATRIX_A + ret = mldsa_calc_t_std(key, key->p, s1, s2, t, a, key->aSet); +#else + ret = mldsa_calc_t_std(key, key->p, s1, s2, t, a, 0); +#endif +#ifdef WC_MLDSA_CACHE_MATRIX_A + if (ret == 0) { + key->aSet = 1; } - /* Check the public seed is the same in private and public key. */ - for (i = 0; i < MLDSA_PUB_SEED_SZ; i++) { - x |= key->p[i] ^ key->k[i]; +#endif + if (ret == 0) { + /* Subtract t0 from t. */ + mldsa_vec_sub(t, t0, params->k); + /* Make t positive to match t1. */ + mldsa_vec_make_pos(t, params->k); + + /* Check t - t0 and t1 are the same. */ + for (i = 0; i < params->k; i++) { + for (j = 0; j < MLDSA_N; j++) { + x |= tt[j] ^ t1[j]; + } + tt += MLDSA_N; + t1 += MLDSA_N; + } + /* Check the public seed is the same in private and public key. */ + for (i = 0; i < MLDSA_PUB_SEED_SZ; i++) { + x |= key->p[i] ^ key->k[i]; + } } if (x != 0) { @@ -12457,7 +13097,7 @@ int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key) } return ret; } -#endif /* WOLFSSL_MLDSA_CHECK_KEY */ +#endif /* WC_MLDSA_HAVE_CHECK_KEY */ #ifdef WOLFSSL_MLDSA_PUBLIC_KEY @@ -12467,6 +13107,11 @@ int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key) * @param [out] out Array to hold public key. * @param [in, out] outLen On in, the number of bytes in array. * On out, the number bytes put into array. + * + * @note If only the private key is set, this derives and caches the public + * key in `key`. Not safe to call concurrently with any other + * operation on the same `key`. + * * @return 0 on success. * @return BAD_FUNC_ARG when a parameter is NULL. * @return BUFFER_E when outLen is less than WC_MLDSA_44_PUB_KEY_SIZE. @@ -12543,6 +13188,11 @@ int wc_MlDsaKey_ExportPubRaw(wc_MlDsaKey* key, byte* out, word32* outLen) } } +#ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + if ((ret == 0) && (!key->pubKeySet)) { + ret = wc_MlDsaKey_MakePublicKey(key); + } +#endif /* Check public key available. */ if ((ret == 0) && (!key->pubKeySet)) { ret = BAD_FUNC_ARG; @@ -13295,7 +13945,7 @@ int wc_MlDsaKey_PrivateKeyDecode(wc_MlDsaKey* key, const byte* input, #endif else if (pubKeyLen == 0 && privKeyLen != 0) { - /* No public key data, only import private key data. */ + /* Import private key only. Public key derived on demand. */ ret = wc_MlDsaKey_ImportPrivRaw(key, privKey, privKeyLen); } else { @@ -13720,6 +14370,11 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input, * @param [out] output Buffer to put encoded data in. * @param [in] len Size of buffer in bytes. * @param [in] withAlg Whether to use SubjectPublicKeyInfo format. + * + * @note If only the private key is set, this derives and caches the public + * key in `key`. Not safe to call concurrently with any other + * operation on the same `key`. + * * @return Size of encoded data in bytes on success. * @return BAD_FUNC_ARG when key is NULL. * @return MEMORY_E when dynamic memory allocation failed. @@ -13735,9 +14390,26 @@ int wc_MlDsaKey_PublicKeyToDer(wc_MlDsaKey* key, byte* output, word32 len, if (key == NULL) { ret = BAD_FUNC_ARG; } - /* Check we have a public key to encode. */ - if ((ret == 0) && (!key->pubKeySet)) { - ret = BAD_FUNC_ARG; +#ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + /* Only derive when actually encoding: the size query (output == NULL) + * depends solely on key->params, so answering it must not cost a + * keygen-priced derivation, nor mutate the key. */ + if ((ret == 0) && (output != NULL) && (!key->pubKeySet)) { + ret = wc_MlDsaKey_MakePublicKey(key); + } +#endif + /* Check we have a public key to encode. A size query on a private-only key + * is answerable without it - the key derived above is a fixed size for the + * level - but a key with no material at all is still rejected, so the + * query stays usable as a "does this key have a public part" probe. */ + if (ret == 0) { + int havePub = key->pubKeySet; + #ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + havePub |= (output == NULL) && key->prvKeySet; + #endif + if (!havePub) { + ret = BAD_FUNC_ARG; + } } if (ret == 0) { @@ -13779,7 +14451,19 @@ int wc_MlDsaKey_PublicKeyToDer(wc_MlDsaKey* key, byte* output, word32 len, } if (ret == 0) { - ret = SetAsymKeyDerPublic(key->p, pubKeyLen, output, len, keyType, +#if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) || defined(WOLFSSL_MLDSA_ASSIGN_KEY) + /* key->p is a pointer in these builds, and on a size query it may + * still be NULL - a private-only key we deliberately did not derive + * above. SetAsymKeyDerPublic() rejects a NULL pubKey but only reads it + * when output is non-NULL, so a placeholder keeps the query working. + * Elsewhere key->p is a fixed array and never NULL. */ + byte placeholder = 0; + const byte* pub = (key->p != NULL) ? key->p : &placeholder; +#else + const byte* pub = key->p; +#endif + + ret = SetAsymKeyDerPublic(pub, pubKeyLen, output, len, keyType, withAlg); } @@ -13803,46 +14487,91 @@ int wc_MlDsaKey_PublicKeyToDer(wc_MlDsaKey* key, byte* output, word32 len, * @param [in] key ML-DSA key object. * @param [out] output Buffer to put encoded data in. * @param [in] len Size of buffer in bytes. + * + * @note If only the private key is set, this derives and caches the public + * key in `key`. Not safe to call concurrently with any other + * operation on the same `key`. + * * @return Size of encoded data in bytes on success. * @return BAD_FUNC_ARG when key is NULL. * @return MEMORY_E when dynamic memory allocation failed. */ int wc_MlDsaKey_KeyToDer(wc_MlDsaKey* key, byte* output, word32 len) { - int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + int ret = 0; + /* Validate key pointer first so the derive guard below is safe. */ + if (key == NULL) { + ret = BAD_FUNC_ARG; + } +#ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + /* Only derive when actually encoding: the size query (output == NULL) + * depends solely on the level, so answering it must not cost a + * keygen-priced derivation, nor mutate the key. Matches + * wc_MlDsaKey_PublicKeyToDer(). */ + if ((ret == 0) && (output != NULL) && key->prvKeySet && !key->pubKeySet) { + ret = wc_MlDsaKey_MakePublicKey(key); + } +#endif /* Validate parameters and check public and private key set. */ - if ((key != NULL) && key->prvKeySet && key->pubKeySet) { - /* Create DER for level. */ - #if defined(WOLFSSL_MLDSA_FIPS204_DRAFT) - if (key->params == NULL) { - ret = BAD_FUNC_ARG; - } - else if (key->params->level == WC_ML_DSA_44_DRAFT) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, key->p, - WC_MLDSA_44_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL2k); - } - else if (key->params->level == WC_ML_DSA_65_DRAFT) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, key->p, - WC_MLDSA_65_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL3k); - } - else if (key->params->level == WC_ML_DSA_87_DRAFT) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, key->p, - WC_MLDSA_87_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL5k); - } - else + if (ret == 0) { + /* A size query on a private-only key is answerable without the public + * key: the one we would derive above is a fixed size for the level. */ + int havePub = key->pubKeySet; + #if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) || defined(WOLFSSL_MLDSA_ASSIGN_KEY) + /* key->p is a pointer in these builds, and on such a size query it is + * still NULL. SetAsymKeyDer() rejects a NULL pubKey but only reads it + * when output is non-NULL, so a placeholder keeps the query working. + * Elsewhere key->p is a fixed array and never NULL. */ + byte placeholder = 0; + const byte* pub = (key->p != NULL) ? key->p : &placeholder; + #else + const byte* pub = key->p; #endif - if (key->level == WC_ML_DSA_44) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, key->p, - WC_MLDSA_44_PUB_KEY_SIZE, output, len, ML_DSA_44k); - } - else if (key->level == WC_ML_DSA_65) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, key->p, - WC_MLDSA_65_PUB_KEY_SIZE, output, len, ML_DSA_65k); + + #ifdef WC_MLDSA_HAVE_MAKE_PUBLIC_KEY + havePub |= (output == NULL) && key->prvKeySet; + #endif + + if (key->prvKeySet && havePub) { + /* Create DER for level. */ + #if defined(WOLFSSL_MLDSA_FIPS204_DRAFT) + if (key->params == NULL) { + ret = BAD_FUNC_ARG; + } + else if (key->params->level == WC_ML_DSA_44_DRAFT) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, pub, + WC_MLDSA_44_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL2k); + } + else if (key->params->level == WC_ML_DSA_65_DRAFT) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, pub, + WC_MLDSA_65_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL3k); + } + else if (key->params->level == WC_ML_DSA_87_DRAFT) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, pub, + WC_MLDSA_87_PUB_KEY_SIZE, output, len, DILITHIUM_LEVEL5k); + } + else + #endif + if (key->level == WC_ML_DSA_44) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, pub, + WC_MLDSA_44_PUB_KEY_SIZE, output, len, ML_DSA_44k); + } + else if (key->level == WC_ML_DSA_65) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, pub, + WC_MLDSA_65_PUB_KEY_SIZE, output, len, ML_DSA_65k); + } + else if (key->level == WC_ML_DSA_87) { + ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, pub, + WC_MLDSA_87_PUB_KEY_SIZE, output, len, ML_DSA_87k); + } + else { + /* Level not set. */ + ret = BAD_FUNC_ARG; + } } - else if (key->level == WC_ML_DSA_87) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, key->p, - WC_MLDSA_87_PUB_KEY_SIZE, output, len, ML_DSA_87k); + else { + ret = BAD_FUNC_ARG; } } diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 89b62f23c4..6dbe5a3e25 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2482,6 +2482,18 @@ WOLFSSL_LOCAL int GetHashId(const byte* id, int length, byte* hash, int hashAlg); WOLFSSL_LOCAL int GetName(DecodedCert* cert, int nameType, int maxIdx); +/* Guard must match the definitions in asn.c / asn_orig.c, which are under + * HAVE_ECC alone - callers of this are not NO_ASN_CRYPT-gated. */ +#if defined(HAVE_ECC) +/* Internal variant of wc_EccPrivateKeyDecode() taking derivePub, which lets a + * caller skip the best-effort public point derivation done on decode when it + * will only probe the key, or use it for signing (which needs the private + * scalar alone). Passing 0 restores the pre-derivation decode cost, saving a + * base-point scalar multiply. wc_EccPrivateKeyDecode() passes 1. */ +WOLFSSL_LOCAL int EccPrivateKeyDecodeEx(const byte* input, word32* inOutIdx, + ecc_key* key, word32 inSz, int derivePub); +#endif + #ifdef ASN_BER_TO_DER WOLFSSL_API int wc_BerToDer(const byte* ber, word32 berSz, byte* der, word32* derSz); diff --git a/wolfssl/wolfcrypt/wc_mldsa.h b/wolfssl/wolfcrypt/wc_mldsa.h index aa54101cd9..446afb228b 100644 --- a/wolfssl/wolfcrypt/wc_mldsa.h +++ b/wolfssl/wolfcrypt/wc_mldsa.h @@ -698,6 +698,10 @@ WOLFSSL_API int wc_MlDsaKey_MakeKey(wc_MlDsaKey* key, WC_RNG* rng); WOLFSSL_API int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed); +#if !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) +WOLFSSL_API +int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key); +#endif /* Legacy sign API without context parameter (pre-FIPS 204). * Only available when WOLFSSL_MLDSA_NO_CTX is defined. @@ -799,7 +803,10 @@ WOLFSSL_API int wc_MlDsaKey_SigSize(wc_MlDsaKey* key); #endif -#ifdef WOLFSSL_MLDSA_CHECK_KEY +#if defined(WOLFSSL_MLDSA_CHECK_KEY) || \ + (!defined(WOLFSSL_MLDSA_NO_CHECK_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + defined(WC_MLDSA_FAULT_HARDEN) && !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM)) WOLFSSL_API int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key); #endif