From 75e9a5b74522d51b61c7c8f435222033e3ed9b0b Mon Sep 17 00:00:00 2001 From: Sameeh Jubran Date: Wed, 2 Sep 2026 11:45:31 +0300 Subject: [PATCH 1/2] test: add EdDSA shared private-only key first-use test Load one seed-only EdDSA key and use it from several threads at once. A readiness barrier parks every worker before the parent releases them, so they hit the first use together. Wave one signs and exports, which runs the lazy public-key derivation under contention. Wave two verifies a precomputed signature through a shared wolfProvider public key, which runs the verify lock under contention. Buffers are sized for whichever EdDSA types are built so an Ed25519-only build compiles. Fenrir 11559. --- test/test_ecx.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 1 + 3 files changed, 326 insertions(+) diff --git a/test/test_ecx.c b/test/test_ecx.c index 50977f8b..5551b9c3 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -25,6 +25,10 @@ #include #include #include +#if defined(HAVE_PTHREAD) || defined(_POSIX_THREADS) +#include +#define WP_HAVE_ECX_SHARED_KEY_TEST +#endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) ((sizeof(a)/sizeof(a[0]))) @@ -1174,6 +1178,326 @@ int test_ecx_dup(void *data) return err; } +#ifdef WP_HAVE_ECX_SHARED_KEY_TEST +/* Fixed, small workload: any failure is a defect, not bad timing. */ +#define WP_ECX_SHARED_THREADS 4 +#define WP_ECX_SHARED_TRIALS 2 +#define WP_ECX_SHARED_MSG "wp-ecx-shared-key" +/* Shared helpers run for whichever EdDSA types are built. Size the + * buffers from the types that exist so an Ed25519-only build compiles. */ +#if defined(WP_HAVE_ED25519) && defined(WP_HAVE_ED448) +#define WP_ECX_SHARED_SIG_SIZE MAX(ED25519_SIG_SIZE, ED448_SIG_SIZE) +#define WP_ECX_SHARED_PUB_SIZE MAX(ED25519_PUB_KEY_SIZE, ED448_PUB_KEY_SIZE) +#elif defined(WP_HAVE_ED448) +#define WP_ECX_SHARED_SIG_SIZE ED448_SIG_SIZE +#define WP_ECX_SHARED_PUB_SIZE ED448_PUB_KEY_SIZE +#else +#define WP_ECX_SHARED_SIG_SIZE ED25519_SIG_SIZE +#define WP_ECX_SHARED_PUB_SIZE ED25519_PUB_KEY_SIZE +#endif + +/* Worker modes: sign and export are first uses of the shared private-only + * key; verify runs on a shared wolfProvider public key to exercise the verify + * lock under contention. */ +#define WP_ECX_MODE_SIGN 0 +#define WP_ECX_MODE_EXPORT 1 +#define WP_ECX_MODE_VERIFY 2 + +typedef struct { + EVP_PKEY* key; + EVP_PKEY* verifyKey; + const unsigned char* pub; + size_t pubLen; + size_t sigLen; + unsigned char* vsig; + size_t vsigLen; + const char* algName; + /* Start gate: the parent releases every worker together, but only after + * each one has parked on startCond. A worker bumps ready and signals + * readyCond; the parent waits for all workers before it sets start. The + * mutex and condition variables give a real happens-before edge - a plain + * shared flag would itself be a data race. */ + pthread_mutex_t* lock; + pthread_cond_t* startCond; + pthread_cond_t* readyCond; + int* start; + int* ready; + int mode; + int err; +} wp_ecx_shared_args; + +/* + * Sign with the shared key and verify against a separate public-only key, so + * a bad public half on the shared key cannot hide a bad signature. + */ +static int wp_ecx_shared_sign(wp_ecx_shared_args* w) +{ + int err; + unsigned char sig[WP_ECX_SHARED_SIG_SIZE]; + size_t sigLen = w->sigLen; + unsigned char msg[] = WP_ECX_SHARED_MSG; + EVP_PKEY* pubKey = NULL; + + err = test_digest_sign(w->key, wpLibCtx, msg, sizeof(msg) - 1, NULL, NULL, + sig, &sigLen, 0, 0); + if (err == 0) { + pubKey = EVP_PKEY_new_raw_public_key_ex(osslLibCtx, w->algName, NULL, + w->pub, w->pubLen); + err = (pubKey == NULL); + } + if (err == 0) { + err = test_digest_verify(pubKey, osslLibCtx, msg, sizeof(msg) - 1, + NULL, NULL, sig, sigLen, 0, 0); + } + + EVP_PKEY_free(pubKey); + return err; +} + +/* Export the public key of the shared key and check the value. */ +static int wp_ecx_shared_get_pub(wp_ecx_shared_args* w) +{ + int err; + unsigned char pub[WP_ECX_SHARED_PUB_SIZE]; + size_t pubLen = sizeof(pub); + + err = EVP_PKEY_get_raw_public_key(w->key, pub, &pubLen) != 1; + if (err == 0) { + err = (pubLen != w->pubLen) || (memcmp(pub, w->pub, pubLen) != 0); + } + + return err; +} + +/* + * Verify a precomputed valid signature through wolfProvider on a public key + * shared with the other verify workers. Concurrent verify exercises the key + * mutex that protects the wolfProvider verification state. + */ +static int wp_ecx_shared_verify(wp_ecx_shared_args* w) +{ + unsigned char msg[] = WP_ECX_SHARED_MSG; + + return test_digest_verify(w->verifyKey, wpLibCtx, msg, sizeof(msg) - 1, + NULL, NULL, w->vsig, w->vsigLen, 0, 0); +} + +static void* wp_ecx_shared_key_thread(void* arg) +{ + wp_ecx_shared_args* w = (wp_ecx_shared_args*)arg; + + /* Tell the parent this worker is parked, then wait so that every worker + * reaches its first use of the key together. */ + pthread_mutex_lock(w->lock); + (*w->ready)++; + pthread_cond_broadcast(w->readyCond); + while (*w->start == 0) { + pthread_cond_wait(w->startCond, w->lock); + } + pthread_mutex_unlock(w->lock); + + if (w->mode == WP_ECX_MODE_EXPORT) { + w->err = wp_ecx_shared_get_pub(w); + } + else if (w->mode == WP_ECX_MODE_VERIFY) { + w->err = wp_ecx_shared_verify(w); + } + else { + w->err = wp_ecx_shared_sign(w); + } + + return NULL; +} + +/* + * Create the workers, hold them at the start gate until all have parked, then + * release them together and join. Returns non-zero if any worker failed or a + * thread could not be created. + */ +static int wp_ecx_run_workers(wp_ecx_shared_args* workers, int count) +{ + pthread_t tids[WP_ECX_SHARED_THREADS]; + pthread_mutex_t lock; + pthread_cond_t startCond; + pthread_cond_t readyCond; + int start = 0; + int ready = 0; + int created = 0; + int err = 0; + int i; + + if ((pthread_mutex_init(&lock, NULL) != 0) || + (pthread_cond_init(&startCond, NULL) != 0) || + (pthread_cond_init(&readyCond, NULL) != 0)) { + PRINT_ERR_MSG("Failed to init start primitives"); + return 1; + } + + for (i = 0; i < count; i++) { + workers[i].lock = &lock; + workers[i].startCond = &startCond; + workers[i].readyCond = &readyCond; + workers[i].start = &start; + workers[i].ready = &ready; + if (pthread_create(&tids[i], NULL, wp_ecx_shared_key_thread, + &workers[i]) != 0) { + PRINT_ERR_MSG("Failed to create thread %d", i); + err = 1; + break; + } + created++; + } + + /* Wait until every worker has parked, then release them together so they + * hit the first use at the same time. */ + pthread_mutex_lock(&lock); + while (ready < created) { + pthread_cond_wait(&readyCond, &lock); + } + start = 1; + pthread_cond_broadcast(&startCond); + pthread_mutex_unlock(&lock); + + for (i = 0; i < created; i++) { + pthread_join(tids[i], NULL); + if (workers[i].err != 0) { + err = 1; + } + } + + pthread_cond_destroy(&readyCond); + pthread_cond_destroy(&startCond); + pthread_mutex_destroy(&lock); + return err; +} + +static int wp_ecx_shared_key_first_use(int type, const unsigned char* der, + size_t derLen, const unsigned char* pub, size_t pubLen, + const char* algName, size_t sigSize) +{ + int err = 0; + int t; + + PRINT_MSG("First use of one shared private-only key from %d threads (%s)", + WP_ECX_SHARED_THREADS, algName); + + for (t = 0; (t < WP_ECX_SHARED_TRIALS) && (err == 0); t++) { + EVP_PKEY* key = NULL; + EVP_PKEY* verifyKey = NULL; + EVP_PKEY* signer = NULL; + const unsigned char* p = der; + const unsigned char* sp = der; + unsigned char msg[] = WP_ECX_SHARED_MSG; + unsigned char vsig[WP_ECX_SHARED_SIG_SIZE]; + size_t vsigLen = sizeof(vsig); + wp_ecx_shared_args workers[WP_ECX_SHARED_THREADS]; + int i; + + /* A new key each trial: the public half is only derived on first + * use, so a used key no longer exercises the derivation. */ + key = d2i_PrivateKey_ex(type, NULL, &p, (long)derLen, wpLibCtx, NULL); + if (key == NULL) { + PRINT_ERR_MSG("Failed to load %s private-only key", algName); + return 1; + } + + /* A shared wolfProvider public key for the concurrent verify wave, + * plus a valid signature from an independent signer so the shared key + * above keeps its public half underived for the first-use wave. */ + verifyKey = EVP_PKEY_new_raw_public_key_ex(wpLibCtx, algName, NULL, pub, + pubLen); + signer = d2i_PrivateKey_ex(type, NULL, &sp, (long)derLen, osslLibCtx, + NULL); + if ((verifyKey == NULL) || (signer == NULL) || + (test_digest_sign(signer, osslLibCtx, msg, sizeof(msg) - 1, + NULL, NULL, vsig, &vsigLen, 0, 0) != 0)) { + PRINT_ERR_MSG("Failed to prepare %s verify workload", algName); + EVP_PKEY_free(signer); + EVP_PKEY_free(verifyKey); + EVP_PKEY_free(key); + return 1; + } + EVP_PKEY_free(signer); + + /* Wave 1: every worker first-uses the one shared private-only key, so + * the lazy public-key derivation runs under full contention. */ + memset(workers, 0, sizeof(workers)); + for (i = 0; i < WP_ECX_SHARED_THREADS; i++) { + workers[i].key = key; + workers[i].pub = pub; + workers[i].pubLen = pubLen; + workers[i].sigLen = sigSize; + workers[i].algName = algName; + workers[i].mode = (i & 1) ? WP_ECX_MODE_EXPORT : WP_ECX_MODE_SIGN; + } + if (wp_ecx_run_workers(workers, WP_ECX_SHARED_THREADS) != 0) { + PRINT_ERR_MSG("%s first-use wave failed", algName); + err = 1; + } + + /* Wave 2: every worker verifies the same signature through the one + * shared wolfProvider public key, so the verify lock runs under + * contention. */ + if (err == 0) { + memset(workers, 0, sizeof(workers)); + for (i = 0; i < WP_ECX_SHARED_THREADS; i++) { + workers[i].verifyKey = verifyKey; + workers[i].vsig = vsig; + workers[i].vsigLen = vsigLen; + workers[i].algName = algName; + workers[i].mode = WP_ECX_MODE_VERIFY; + } + if (wp_ecx_run_workers(workers, WP_ECX_SHARED_THREADS) != 0) { + PRINT_ERR_MSG("%s concurrent verify wave failed", algName); + err = 1; + } + } + + EVP_PKEY_free(verifyKey); + EVP_PKEY_free(key); + } + + return err; +} +#endif /* WP_HAVE_ECX_SHARED_KEY_TEST */ + +/* + * Use one private-only EdDSA key from more than one thread at the same time. + * + * A seed-only PKCS#8 key has no public half until wolfProvider derives it. + * The derivation writes into the shared key object, so it must hold the key + * mutex. Without it, a thread signs or exports with a partly written public + * key. + */ +int test_ecx_shared_key_first_use(void *data) +{ + (void)data; + +#ifndef WP_HAVE_ECX_SHARED_KEY_TEST + PRINT_MSG("Skipping shared key first use test (no pthreads)"); + return 0; +#else + int err = 0; + +#ifdef WP_HAVE_ED25519 + if (wp_ecx_shared_key_first_use(EVP_PKEY_ED25519, ed25519_key_der, + sizeof(ed25519_key_der), ed25519_pub_key_raw, + sizeof(ed25519_pub_key_raw), "ED25519", + ED25519_SIG_SIZE) != 0) { + err = 1; + } +#endif +#ifdef WP_HAVE_ED448 + if (wp_ecx_shared_key_first_use(EVP_PKEY_ED448, ed448_key_der, + sizeof(ed448_key_der), ed448_pub_key_raw, + sizeof(ed448_pub_key_raw), "ED448", ED448_SIG_SIZE) != 0) { + err = 1; + } +#endif + return err; +#endif /* WP_HAVE_ECX_SHARED_KEY_TEST */ +} + #endif /* defined(WP_HAVE_ED25519) || defined(WP_HAVE_ED448) */ #if defined(WP_HAVE_X25519) || defined(WP_HAVE_X448) diff --git a/test/unit.c b/test/unit.c index cfb6e8c1..701b8311 100644 --- a/test/unit.c +++ b/test/unit.c @@ -546,6 +546,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_ecx_import_short_pub, NULL), #endif TEST_DECL(test_ecx_dup, NULL), + TEST_DECL(test_ecx_shared_key_first_use, NULL), #endif #if defined(WP_HAVE_X25519) || defined(WP_HAVE_X448) TEST_DECL(test_ecx_x_security_bits, NULL), diff --git a/test/unit.h b/test/unit.h index 5a956043..51c4f171 100644 --- a/test/unit.h +++ b/test/unit.h @@ -560,6 +560,7 @@ int test_ecx_import_zero_priv(void *data); int test_ecx_import_short_pub(void *data); #endif /* WP_HAVE_X25519 */ int test_ecx_dup(void *data); +int test_ecx_shared_key_first_use(void *data); #endif #if defined(WP_HAVE_X25519) || defined(WP_HAVE_X448) From 18433801a1c01477cc763f78758064e4eb95a490 Mon Sep 17 00:00:00 2001 From: Sameeh Jubran Date: Wed, 2 Sep 2026 11:45:31 +0300 Subject: [PATCH 2/2] fix: derive EdDSA public key under the key mutex A private-only EdDSA key derives its public half lazily on first use. The derivation wrote into the shared key object without the key mutex, so concurrent first use could sign or export with a partly written public key. Derive once under the mutex through wp_ecx_ensure_pub and call it from every first-use site; the export and DER helpers become plain export/encode. Fenrir 11559. --- include/wolfprovider/alg_funcs.h | 1 + src/wp_ecx_kmgmt.c | 229 +++++++++++++++++++++++-------- src/wp_ecx_sig.c | 74 +++++----- 3 files changed, 202 insertions(+), 102 deletions(-) diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h index 053756d8..2ce49a79 100644 --- a/include/wolfprovider/alg_funcs.h +++ b/include/wolfprovider/alg_funcs.h @@ -236,6 +236,7 @@ int wp_ecx_up_ref(wp_Ecx* ecx); void wp_ecx_free(wp_Ecx* ecx); void* wp_ecx_get_key(wp_Ecx* ecx); wolfSSL_Mutex* wp_ecx_get_mutex(wp_Ecx* ecx); +int wp_ecx_ensure_pub(wp_Ecx* ecx); /* Internal ML-KEM types and functions. */ typedef struct wp_MlKem wp_MlKem; diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index f1993af1..e6b0a30a 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -85,6 +85,8 @@ typedef int (*WP_ECX_IMPORT_PRIV)(const byte* priv, word32 privLen, void* key, int endian); /** Type for function that exports a private key from a wolfSSL key. */ typedef int (*WP_ECX_EXPORT_PRIV)(void* key, const byte* out, word32* outLen); +/** Type for function that derives the public key from the private key. */ +typedef int (*WP_ECX_DERIVE_PUB)(void* key); /** Type for a wolfSSL function that checks a public key. */ typedef int (*WP_ECX_CHECK_PUB)(const byte* pub, word32 pubLen, int endian); /** Type for a wolfSSL function that checks a key. */ @@ -117,6 +119,8 @@ typedef struct wp_EcxData { WP_ECX_IMPORT_PRIV importPriv; /** Export private key from wolfSSL key object. */ WP_ECX_EXPORT_PRIV exportPriv; + /** Derive the public key from the private key. NULL when not needed. */ + WP_ECX_DERIVE_PUB derivePub; /** wolfSSL check of public key value. */ WP_ECX_CHECK_PUB checkPub; /** wolfSSL check of public/private key. */ @@ -258,6 +262,48 @@ wolfSSL_Mutex* wp_ecx_get_mutex(wp_Ecx* ecx) return &ecx->mutex; } +/** + * Make sure the public key is available in the wolfSSL key object. + * + * Ed25519 and Ed448 keys imported as a private key only have no public half + * until it is derived. Deriving writes into the wolfSSL key object, so the + * key mutex is held: threads that first use one shared key at the same time + * would otherwise sign or export with a partly written public key. + * + * Must not be called with the key mutex already held. + * + * @param [in, out] ecx ECX key object. + * @return 1 on success. + * @return 0 on failure. + */ +int wp_ecx_ensure_pub(wp_Ecx* ecx) +{ + int ok = 1; + + if ((ecx != NULL) && (ecx->data->derivePub != NULL)) { + int rc; + + #ifndef WP_SINGLE_THREADED + if (wp_lock(&ecx->mutex) != 1) { + ok = 0; + } + if (ok) { + #endif + rc = (*ecx->data->derivePub)((void*)&ecx->key); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "derivePub", + rc); + ok = 0; + } + #ifndef WP_SINGLE_THREADED + wp_unlock(&ecx->mutex); + } + #endif + } + + return ok; +} + /** * Create a new ECX key object. Base function. * @@ -364,6 +410,13 @@ static wp_Ecx* wp_ecx_dup(const wp_Ecx* src, int selection) /* Create a new ecx object. */ dst = wp_ecx_new(src->provCtx, src->data); } + /* Cache the public half before the copy below: deriving it later writes + * into the source key object that is being read here. */ + if ((dst != NULL) && src->hasPub && + (!wp_ecx_ensure_pub((wp_Ecx*)src))) { + wp_ecx_free(dst); + dst = NULL; + } if (dst != NULL) { dst->includePublic = src->includePublic; @@ -552,9 +605,18 @@ static int wp_ecx_get_params_enc_pub_key(wp_Ecx* ecx, OSSL_PARAM params[], if (p->data == NULL) { outLen = ecx->data->len; } + else if (!wp_ecx_ensure_pub(ecx)) { + ok = 0; + } + else if (wp_lock(wp_ecx_get_mutex(ecx)) != 1) { + ok = 0; + } else { + /* Signing mutates the key (persistent SHA), so hold the key mutex + * while reading it. */ int rc = (*ecx->data->exportPub)((void*)&ecx->key, p->data, &outLen, ECX_LITTLE_ENDIAN); + wp_unlock(wp_ecx_get_mutex(ecx)); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); ok = 0; @@ -775,22 +837,40 @@ static int wp_ecx_match_pub_key(const wp_Ecx* ecx1, const wp_Ecx* ecx2) XMEMSET(key2, 0, sizeof(key2)); ok &= ecx1->hasPub && ecx2->hasPub; if (ok) { + ok = wp_ecx_ensure_pub((wp_Ecx*)ecx1) && + wp_ecx_ensure_pub((wp_Ecx*)ecx2); + } + if (ok) { + /* Signing mutates the key (persistent SHA), so hold each key mutex + * while reading it. Lock one key at a time to avoid lock ordering. */ len1 = ecx1->data->len; - rc = (*ecx1->data->exportPub)((void*)&ecx1->key, key1, &len1, - ECX_LITTLE_ENDIAN); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); + if (wp_lock(wp_ecx_get_mutex((wp_Ecx*)ecx1)) != 1) { ok = 0; } + else { + rc = (*ecx1->data->exportPub)((void*)&ecx1->key, key1, &len1, + ECX_LITTLE_ENDIAN); + wp_unlock(wp_ecx_get_mutex((wp_Ecx*)ecx1)); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); + ok = 0; + } + } } if (ok) { len2 = ecx2->data->len; - rc = (*ecx2->data->exportPub)((void*)&ecx2->key, key2, &len2, - ECX_LITTLE_ENDIAN); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); + if (wp_lock(wp_ecx_get_mutex((wp_Ecx*)ecx2)) != 1) { ok = 0; } + else { + rc = (*ecx2->data->exportPub)((void*)&ecx2->key, key2, &len2, + ECX_LITTLE_ENDIAN); + wp_unlock(wp_ecx_get_mutex((wp_Ecx*)ecx2)); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); + ok = 0; + } + } } if (ok && (len1 != len2)) { ok = 0; @@ -1126,16 +1206,30 @@ static int wp_ecx_export_keypair(wp_Ecx* ecx, OSSL_PARAM* params, int* pIdx, int ok = 1; int rc; int i = *pIdx; + int locked = 0; word32 outLen; WOLFPROV_ENTER(WP_LOG_COMP_KE, "wp_ecx_export_keypair"); - outLen = ecx->data->len; - rc = (*ecx->data->exportPub)((void*)&ecx->key, data + *idx, &outLen, - ECX_LITTLE_ENDIAN); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); - ok = 0; + ok = wp_ecx_ensure_pub(ecx); + if (ok) { + /* Signing mutates the key (persistent SHA), so hold the key mutex + * while reading it. */ + if (wp_lock(wp_ecx_get_mutex(ecx)) != 1) { + ok = 0; + } + else { + locked = 1; + } + } + if (ok) { + outLen = ecx->data->len; + rc = (*ecx->data->exportPub)((void*)&ecx->key, data + *idx, &outLen, + ECX_LITTLE_ENDIAN); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPub", rc); + ok = 0; + } } if (ok) { wp_param_set_octet_string_ptr(¶ms[i++], OSSL_PKEY_PARAM_PUB_KEY, @@ -1162,6 +1256,9 @@ static int wp_ecx_export_keypair(wp_Ecx* ecx, OSSL_PARAM* params, int* pIdx, *idx += outLen; } } + if (locked) { + wp_unlock(wp_ecx_get_mutex(ecx)); + } *pIdx = i; WOLFPROV_LEAVE(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -1483,6 +1580,7 @@ static const wp_EcxData x25519Data = { (WP_ECX_EXPORT_PUB)&wc_curve25519_export_public_ex, (WP_ECX_IMPORT_PRIV)&wc_curve25519_import_private_ex, (WP_ECX_EXPORT_PRIV)&wp_curve25519_export_private_raw, + NULL, (WP_ECX_CHECK_PUB)&wc_curve25519_check_public, NULL, }; @@ -1565,6 +1663,7 @@ static const wp_EcxData x448Data = { (WP_ECX_EXPORT_PUB)&wc_curve448_export_public_ex, (WP_ECX_IMPORT_PRIV)&wc_curve448_import_private_ex, (WP_ECX_EXPORT_PRIV)&wp_curve448_export_private_raw, + NULL, (WP_ECX_CHECK_PUB)&wc_curve448_check_public, NULL, }; @@ -1650,20 +1749,33 @@ static int wp_ed25519_import_public(const byte* in, word32 inLen, static int wp_ed25519_export_public(ed25519_key* key, const byte* out, word32* outLen, int endian) { - int ret; - (void)endian; + return wc_ed25519_export_public(key, (byte*)out, outLen); +} + +/** + * Derive the Ed25519 public key from the private key and cache it. + * + * A private-only key (a seed-only PKCS#8) has no public half until it is + * derived. Callers must hold the key mutex - see wp_ecx_ensure_pub(). + * + * @param [in, out] key wolfSSL Ed25519 key object. + * @return 0 on success. + * @return -ve on failure. + */ +static int wp_ed25519_derive_public(ed25519_key* key) +{ + int ret = 0; + byte pub[ED25519_PUB_KEY_SIZE]; + if (!key->pubKeySet) { - ret = wc_ed25519_make_public(key, (byte*)out, *outLen); + ret = wc_ed25519_make_public(key, pub, sizeof(pub)); if (ret == 0) { - /* Store the generated public key in the key object for future use. */ - ret = wc_ed25519_import_public((byte*)out, *outLen, key); + /* Only import_public stores the value and sets pubKeySet. */ + ret = wc_ed25519_import_public(pub, sizeof(pub), key); } } - else { - ret = wc_ed25519_export_public(key, (byte*)out, outLen); - } return ret; } @@ -1698,6 +1810,7 @@ static const wp_EcxData ed25519Data = { (WP_ECX_EXPORT_PUB)&wp_ed25519_export_public, (WP_ECX_IMPORT_PRIV)&wp_ed25519_import_private, (WP_ECX_EXPORT_PRIV)&wc_ed25519_export_private_only, + (WP_ECX_DERIVE_PUB)&wp_ed25519_derive_public, NULL, (WP_ECX_CHECK_KEY)&wc_ed25519_check_key, }; @@ -1784,20 +1897,33 @@ static int wp_ed448_import_public(const byte* in, word32 inLen, ed448_key* key, static int wp_ed448_export_public(ed448_key* key, const byte* out, word32* outLen, int endian) { - int ret; - (void)endian; + return wc_ed448_export_public(key, (byte*)out, outLen); +} + +/** + * Derive the Ed448 public key from the private key and cache it. + * + * A private-only key (a seed-only PKCS#8) has no public half until it is + * derived. Callers must hold the key mutex - see wp_ecx_ensure_pub(). + * + * @param [in, out] key wolfSSL Ed448 key object. + * @return 0 on success. + * @return -ve on failure. + */ +static int wp_ed448_derive_public(ed448_key* key) +{ + int ret = 0; + byte pub[ED448_PUB_KEY_SIZE]; + if (!key->pubKeySet) { - ret = wc_ed448_make_public(key, (byte*)out, *outLen); + ret = wc_ed448_make_public(key, pub, sizeof(pub)); if (ret == 0) { - /* Store the generated public key in the key object for future use. */ - ret = wc_ed448_import_public((byte*)out, *outLen, key); + /* Only import_public stores the value and sets pubKeySet. */ + ret = wc_ed448_import_public(pub, sizeof(pub), key); } } - else { - ret = wc_ed448_export_public(key, (byte*)out, outLen); - } return ret; } @@ -1832,6 +1958,7 @@ static const wp_EcxData ed448Data = { (WP_ECX_EXPORT_PUB)&wp_ed448_export_public, (WP_ECX_IMPORT_PRIV)&wp_ed448_import_private, (WP_ECX_EXPORT_PRIV)&wc_ed448_export_private_only, + (WP_ECX_DERIVE_PUB)&wp_ed448_derive_public, NULL, (WP_ECX_CHECK_KEY)&wc_ed448_check_key, }; @@ -2242,6 +2369,12 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, ok = 0; } + /* Only the public formats need the derived public half. Deriving for a + * private format would add the public key to the encoding. */ + if (ok && (ctx->format == WP_ENC_FORMAT_SPKI)) { + ok = wp_ecx_ensure_pub((wp_Ecx*)ecx); + } + if (ok) { rc = ctx->encode((void*)&ecx->key, derData, sizeof(derData)); if (rc <= 0) { @@ -2835,24 +2968,12 @@ const OSSL_DISPATCH wp_ed25519_spki_decoder_functions[] = { static int wp_Ed25519PublicKeyToDer(ed25519_key* key, byte* output, word32 inLen) { - int ok = 1; + int ok; WOLFPROV_ENTER(WP_LOG_COMP_KE, "wp_Ed25519PublicKeyToDer"); - /* Check if this is private key only. */ - if (!key->pubKeySet) { - int rc; - /* Make the public key to encode. */ - rc = wc_ed25519_make_public(key, key->p, ED25519_PUB_KEY_SIZE); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed25519_make_public", rc); - } - ok = key->pubKeySet = (rc == 0); - } - if (ok) { - /* Always include the algorithm. */ - ok = wc_Ed25519PublicKeyToDer(key, output, inLen, 1); - } + /* Always include the algorithm. */ + ok = wc_Ed25519PublicKeyToDer(key, output, inLen, 1); WOLFPROV_LEAVE(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -3485,24 +3606,12 @@ const OSSL_DISPATCH wp_ed448_spki_decoder_functions[] = { */ static int wp_Ed448PublicKeyToDer(ed448_key* key, byte* output, word32 inLen) { - int ok = 1; + int ok; WOLFPROV_ENTER(WP_LOG_COMP_KE, "wp_Ed448PublicKeyToDer"); - /* Check if this is private key only. */ - if (!key->pubKeySet) { - int rc; - /* Make the public key to encode. */ - rc = wc_ed448_make_public(key, key->p, ED448_PUB_KEY_SIZE); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed448_make_public", rc); - } - ok = key->pubKeySet = (rc == 0); - } - if (ok) { - /* Always include the algorithm. */ - ok = wc_Ed448PublicKeyToDer(key, output, inLen, 1); - } + /* Always include the algorithm. */ + ok = wc_Ed448PublicKeyToDer(key, output, inLen, 1); WOLFPROV_LEAVE(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; diff --git a/src/wp_ecx_sig.c b/src/wp_ecx_sig.c index dad4977f..04e6de13 100644 --- a/src/wp_ecx_sig.c +++ b/src/wp_ecx_sig.c @@ -383,22 +383,8 @@ static int wp_ed25519_digest_sign(wp_EcxSigCtx *ctx, unsigned char *sig, } len = (word32)sigSize; - if (!ed25519->pubKeySet) { - unsigned char pubKey[ED25519_PUB_KEY_SIZE]; - - rc = wc_ed25519_make_public(ed25519, pubKey, sizeof(pubKey)); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed25519_make_public", rc); - ok = 0; - } - if (ok) { - rc = wc_ed25519_import_public(pubKey, sizeof(pubKey), ed25519); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed25519_import_public", rc); - ok = 0; - } - } - } + /* EdDSA signs with the public half; make sure it is derived first. */ + ok = wp_ecx_ensure_pub(ctx->ecx); if (ok && (!WP_FITS_WORD32(tbsLen))) { ok = 0; } @@ -470,13 +456,22 @@ static int wp_ed25519_digest_verify(wp_EcxSigCtx *ctx, unsigned char *sig, } if (ok) { int res = 0; - int rc = wc_ed25519_verify_msg(sig, (word32)sigLen, tbs, (word32)tbsLen, - &res, wp_ecx_get_key(ctx->ecx)); - if (rc != 0) { + int rc = 0; + + /* Verify mutates the key (persistent SHA), so hold the key mutex. */ + if (wp_lock(wp_ecx_get_mutex(ctx->ecx)) != 1) { + ok = 0; + } + else { + rc = wc_ed25519_verify_msg(sig, (word32)sigLen, tbs, + (word32)tbsLen, &res, wp_ecx_get_key(ctx->ecx)); + wp_unlock(wp_ecx_get_mutex(ctx->ecx)); + } + if (ok && (rc != 0)) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed25519_verify_msg", rc); ok = 0; } - if (res == 0) { + if (ok && (res == 0)) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "Signature verification", rc); ok = 0; } @@ -596,22 +591,8 @@ static int wp_ed448_digest_sign(wp_EcxSigCtx *ctx, unsigned char *sig, } len = (word32)sigSize; - if (!ed448->pubKeySet) { - unsigned char pubKey[ED448_PUB_KEY_SIZE]; - - rc = wc_ed448_make_public(ed448, pubKey, sizeof(pubKey)); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed448_make_public", rc); - ok = 0; - } - if (ok) { - rc = wc_ed448_import_public(pubKey, sizeof(pubKey), ed448); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed448_import_public", rc); - ok = 0; - } - } - } + /* EdDSA signs with the public half; make sure it is derived first. */ + ok = wp_ecx_ensure_pub(ctx->ecx); if (ok && (!WP_FITS_WORD32(tbsLen))) { ok = 0; } @@ -620,8 +601,8 @@ static int wp_ed448_digest_sign(wp_EcxSigCtx *ctx, unsigned char *sig, ok = 0; } if (ok) { - rc = wc_ed448_sign_msg(tbs, (word32)tbsLen, sig, &len, - (ed448_key*)wp_ecx_get_key(ctx->ecx), NULL, 0); + rc = wc_ed448_sign_msg(tbs, (word32)tbsLen, sig, &len, ed448, + NULL, 0); wp_unlock(wp_ecx_get_mutex(ctx->ecx)); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed448_sign_msg", rc); @@ -689,13 +670,22 @@ static int wp_ed448_digest_verify(wp_EcxSigCtx *ctx, unsigned char *sig, } if (ok) { int res = 0; - int rc = wc_ed448_verify_msg(sig, (word32)sigLen, tbs, (word32)tbsLen, - &res, wp_ecx_get_key(ctx->ecx), NULL, 0); - if (rc != 0) { + int rc = 0; + + /* Verify mutates the key (persistent SHA), so hold the key mutex. */ + if (wp_lock(wp_ecx_get_mutex(ctx->ecx)) != 1) { + ok = 0; + } + else { + rc = wc_ed448_verify_msg(sig, (word32)sigLen, tbs, (word32)tbsLen, + &res, wp_ecx_get_key(ctx->ecx), NULL, 0); + wp_unlock(wp_ecx_get_mutex(ctx->ecx)); + } + if (ok && (rc != 0)) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ed448_verify_msg", rc); ok = 0; } - if (res == 0) { + if (ok && (res == 0)) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "Signature verification", rc); ok = 0; }