From 66d3c42d59dba56b55c031cff2527d6bbd0e8b07 Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Thu, 15 Oct 2026 11:47:20 +0200 Subject: [PATCH 1/4] Fix error handling in mac kdf and pk --- wolfssl-gnutls-wrapper/src/kdf.c | 9 ----- wolfssl-gnutls-wrapper/src/mac.c | 14 +++---- wolfssl-gnutls-wrapper/src/pk.c | 29 ++++++-------- wolfssl-gnutls-wrapper/tests/test_fips.c | 5 ++- wolfssl-gnutls-wrapper/tests/test_hkdf.c | 44 +++++++++++++++++++++ wolfssl-gnutls-wrapper/tests/test_hmac.c | 46 ++++++++++++++++++++++ wolfssl-gnutls-wrapper/tests/test_pbkdf2.c | 38 ++++++++++++++++++ wolfssl-gnutls-wrapper/tests/test_util.h | 14 +++++++ 8 files changed, 164 insertions(+), 35 deletions(-) diff --git a/wolfssl-gnutls-wrapper/src/kdf.c b/wolfssl-gnutls-wrapper/src/kdf.c index 0ee716a..0acee3b 100644 --- a/wolfssl-gnutls-wrapper/src/kdf.c +++ b/wolfssl-gnutls-wrapper/src/kdf.c @@ -47,11 +47,7 @@ static int wolfssl_hkdf_extract(gnutls_mac_algorithm_t mac, const void *key, if (ret != 0) { WGW_WOLFSSL_ERROR("wc_HKDF_Extract", ret); -#if defined(HAVE_FIPS) - return GNUTLS_FIPS140_OP_NOT_APPROVED; -#else return GNUTLS_E_INTERNAL_ERROR; -#endif } return 0; @@ -147,11 +143,6 @@ static int wolfssl_pbkdf2(gnutls_mac_algorithm_t mac, const void *key, if (ret != 0) { WGW_WOLFSSL_ERROR("wc_PBKDF2_ex", ret); -#if defined(HAVE_FIPS) - if (ret == HMAC_MIN_KEYLEN_E) { - return GNUTLS_FIPS140_OP_NOT_APPROVED; - } -#endif return GNUTLS_E_INTERNAL_ERROR; } diff --git a/wolfssl-gnutls-wrapper/src/mac.c b/wolfssl-gnutls-wrapper/src/mac.c index c5ecc2e..710272c 100644 --- a/wolfssl-gnutls-wrapper/src/mac.c +++ b/wolfssl-gnutls-wrapper/src/mac.c @@ -197,11 +197,7 @@ static int wolfssl_hmac_setkey(void *_ctx, const void *key, size_t keysize) (word32)keysize); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_HmacSetKey", ret); -#if defined(HAVE_FIPS) - return GNUTLS_FIPS140_OP_NOT_APPROVED; -#else return GNUTLS_E_HASH_FAILED; -#endif } WGW_LOG("hmac key set successfully"); @@ -362,6 +358,7 @@ static int wolfssl_hmac_fast(gnutls_mac_algorithm_t algorithm, /* Set key into HMAC context. */ ret = wolfssl_hmac_setkey(ctx, key, keysize); if (ret != 0) { + wolfssl_hmac_deinit(ctx); return ret; } @@ -507,7 +504,6 @@ static int wolfssl_cmac_setkey(void *_ctx, const void *key, size_t keysize) WC_CMAC_AES, NULL); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_InitCmac", ret); - gnutls_free(ctx); return GNUTLS_E_HASH_FAILED; } @@ -548,7 +544,6 @@ static int wolfssl_cmac_hash(void *_ctx, const void *text, size_t textsize) (word32)ctx->key_size, WC_CMAC_AES, NULL); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_InitCmac", ret); - gnutls_free(ctx); return GNUTLS_E_HASH_FAILED; } ctx->set_key = 0; @@ -617,7 +612,6 @@ static int wolfssl_cmac_output(void *_ctx, void *digest, size_t digestsize) (word32)ctx->key_size, WC_CMAC_AES, NULL); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_InitCmac", ret); - gnutls_free(ctx); return GNUTLS_E_HASH_FAILED; } ctx->set_key = 0; @@ -690,6 +684,7 @@ static int wolfssl_cmac_fast(gnutls_mac_algorithm_t algorithm, /* Set key into CMAC context. */ ret = wolfssl_cmac_setkey(ctx, key, keysize); if (ret != 0) { + wolfssl_cmac_deinit(ctx); return ret; } @@ -865,7 +860,6 @@ static int wolfssl_gmac_setkey(void *_ctx, const void *key, size_t keysize) ret = wc_GmacSetKey(&ctx->gmac_ctx, (const byte*)key, (word32)keysize); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_GmacSetKey", ret); - gnutls_free(ctx); return GNUTLS_E_HASH_FAILED; } @@ -1051,12 +1045,14 @@ static int wolfssl_gmac_fast(gnutls_mac_algorithm_t algorithm, /* Set key into GMAC context. */ ret = wolfssl_gmac_setkey(ctx, key, keysize); if (ret != 0) { + wolfssl_gmac_deinit(ctx); return ret; } - /* Set key into GMAC context. */ + /* Set nonce into GMAC context. */ ret = wolfssl_gmac_setnonce(ctx, nonce, nonce_size); if (ret != 0) { + wolfssl_gmac_deinit(ctx); return ret; } diff --git a/wolfssl-gnutls-wrapper/src/pk.c b/wolfssl-gnutls-wrapper/src/pk.c index bb0090b..c05a448 100644 --- a/wolfssl-gnutls-wrapper/src/pk.c +++ b/wolfssl-gnutls-wrapper/src/pk.c @@ -2461,9 +2461,10 @@ static int wolfssl_pk_generate_params(gnutls_pk_algorithm_t algo, * @param [in] bits Number of bits in key. * @param [in, out] params GnuTLS PK parameters. * @return 0 on success. - * @return GNUTLS_FIPS140_OP_NOT_APPROVED when FIPS140 build + * @return GNUTLS_E_INVALID_REQUEST when FIPS140 build and 1024 bits. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing RSA key fails. + * @return GNUTLS_E_PK_GENERATION_ERROR when generating the key fails. * @return GnuTLS error on other failure. */ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, @@ -2483,7 +2484,7 @@ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, /* missing check for 1024, 1024 is not allowed */ if (bits == 1024) { WGW_ERROR("Bits size not valid"); - return GNUTLS_FIPS140_OP_NOT_APPROVED; + return GNUTLS_E_INVALID_REQUEST; } #endif @@ -2509,11 +2510,7 @@ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, if (ret != 0) { WGW_ERROR("RSA key generation failed with code %d", ret); wc_FreeRsaKey(&rsa); -#if defined(HAVE_FIPS) - return GNUTLS_FIPS140_OP_NOT_APPROVED; -#else return GNUTLS_E_PK_GENERATION_ERROR; -#endif } /* Store RSA fields in the GnuTLS parameters. */ @@ -2715,7 +2712,7 @@ static int wolfssl_pk_generate_keys_ecc(unsigned int curve, * @param [in] curve GnuTLS curve. * @param [in, out] params GnuTLS PK parameters. * @return 0 on success. - * @return GNUTLS_ECC_CURVE_INVALID when curve does not match algorithm. + * @return GNUTLS_E_ECC_UNSUPPORTED_CURVE when curve does not match algorithm. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing key fails. * @return GnuTLS error on other failure. @@ -2732,7 +2729,7 @@ static int wolfssl_pk_generate_keys_ed25519(unsigned int curve, /* Check curve matches algorithm. */ if (curve != GNUTLS_ECC_CURVE_ED25519) { - return GNUTLS_ECC_CURVE_INVALID; + return GNUTLS_E_ECC_UNSUPPORTED_CURVE; } /* Initialize a new random for blinding. */ @@ -2817,7 +2814,7 @@ static int wolfssl_pk_generate_keys_ed25519(unsigned int curve, * @param [in] curve GnuTLS curve. * @param [in, out] params GnuTLS PK parameters. * @return 0 on success. - * @return GNUTLS_ECC_CURVE_INVALID when curve does not match algorithm. + * @return GNUTLS_E_ECC_UNSUPPORTED_CURVE when curve does not match algorithm. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing key fails. * @return GnuTLS error on other failure. @@ -2834,7 +2831,7 @@ static int wolfssl_pk_generate_keys_ed448(unsigned int curve, /* Check curve matches algorithm. */ if (curve != GNUTLS_ECC_CURVE_ED448) { - return GNUTLS_ECC_CURVE_INVALID; + return GNUTLS_E_ECC_UNSUPPORTED_CURVE; } /* Initialize a new random for blinding. */ @@ -2919,7 +2916,7 @@ static int wolfssl_pk_generate_keys_ed448(unsigned int curve, * @param [in] curve GnuTLS curve. * @param [in, out] params GnuTLS PK parameters. * @return 0 on success. - * @return GNUTLS_ECC_CURVE_INVALID when curve does not match algorithm. + * @return GNUTLS_E_ECC_UNSUPPORTED_CURVE when curve does not match algorithm. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing key fails. * @return GnuTLS error on other failure. @@ -2936,7 +2933,7 @@ static int wolfssl_pk_generate_keys_x25519(unsigned int curve, /* Check curve matches algorithm. */ if (curve != GNUTLS_ECC_CURVE_X25519) { - return GNUTLS_ECC_CURVE_INVALID; + return GNUTLS_E_ECC_UNSUPPORTED_CURVE; } /* Initialize a new random for generation. */ @@ -3021,7 +3018,7 @@ static int wolfssl_pk_generate_keys_x25519(unsigned int curve, * @param [in] curve GnuTLS curve. * @param [in, out] params GnuTLS PK parameters. * @return 0 on success. - * @return GNUTLS_ECC_CURVE_INVALID when curve does not match algorithm. + * @return GNUTLS_E_ECC_UNSUPPORTED_CURVE when curve does not match algorithm. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing key fails. * @return GnuTLS error on other failure. @@ -3038,7 +3035,7 @@ static int wolfssl_pk_generate_keys_x448(unsigned int curve, /* Check curve matches algorithm. */ if (curve != GNUTLS_ECC_CURVE_X448) { - return GNUTLS_ECC_CURVE_INVALID; + return GNUTLS_E_ECC_UNSUPPORTED_CURVE; } /* Initialize a new random for blinding. */ @@ -3125,8 +3122,8 @@ static int wolfssl_pk_generate_keys_x448(unsigned int curve, * @param [in] ephemeral Key pair is ephemenral - random can be weaker. * Ignored. * @return 0 on success. - * @return GNUTLS_FIPS140_OP_NOT_APPROVED when FIPS140 build - * @return GNUTLS_ECC_CURVE_INVALID when curve does not match algorithm. + * @return GNUTLS_E_INVALID_REQUEST when FIPS140 build and 1024-bit RSA. + * @return GNUTLS_E_ECC_UNSUPPORTED_CURVE when curve does not match algorithm. * @return GNUTLS_E_RANDOM_FAILED when random initialization fails. * @return GNUTLS_E_CRYPTO_INIT_FAILED when initializing key fails. * @return GnuTLS error on other failure. diff --git a/wolfssl-gnutls-wrapper/tests/test_fips.c b/wolfssl-gnutls-wrapper/tests/test_fips.c index 16667fd..67b319b 100644 --- a/wolfssl-gnutls-wrapper/tests/test_fips.c +++ b/wolfssl-gnutls-wrapper/tests/test_fips.c @@ -333,9 +333,12 @@ static int test_invalid_rsa(void) printf("Trying RSA key generation with invalid size %u bits...\n", invalid_key_sizes[i]); ret = gnutls_privkey_generate(privkey, GNUTLS_PK_RSA, invalid_key_sizes[i], 0); - if (ret == 0) { + if (ret >= 0) { + printf("ERROR: RSA key generation with %u bits returned %d, " + "not a negative error\n", invalid_key_sizes[i], ret); return -1; } + printf("Refused: %s (%d)\n", gnutls_strerror(ret), ret); gnutls_privkey_deinit(privkey); } diff --git a/wolfssl-gnutls-wrapper/tests/test_hkdf.c b/wolfssl-gnutls-wrapper/tests/test_hkdf.c index 0b05864..d8569e6 100644 --- a/wolfssl-gnutls-wrapper/tests/test_hkdf.c +++ b/wolfssl-gnutls-wrapper/tests/test_hkdf.c @@ -100,6 +100,47 @@ static int test_hkdf(gnutls_mac_algorithm_t mac_alg, return 0; } +/* RFC 5869 - Test Case 1. The 13-byte salt is the HMAC key of HKDF-Extract + * and is shorter than the FIPS HMAC key minimum (14 bytes). */ +const unsigned char short_salt_ikm_data[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; +const unsigned char short_salt_data[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c +}; +const unsigned char expected_short_salt_prk_sha256[] = { + 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, + 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63, + 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, + 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 +}; + +/* A short salt must be refused with an error or give the right PRK. */ +static int test_hkdf_extract_short_salt(void) +{ + int ret; + unsigned char prk_data[32]; + gnutls_datum_t ikm = { + .data = (unsigned char*)short_salt_ikm_data, + .size = sizeof(short_salt_ikm_data) + }; + gnutls_datum_t salt = { + .data = (unsigned char*)short_salt_data, + .size = sizeof(short_salt_data) + }; + + printf("Testing wolfSSL's SHA256 HKDF-Extract with a short salt via " + "GnuTLS...\n"); + + memset(prk_data, 0, sizeof(prk_data)); + ret = gnutls_hkdf_extract(GNUTLS_MAC_SHA256, &ikm, &salt, prk_data); + return refused_or_compare("HKDF-Extract, short salt", ret, prk_data, + expected_short_salt_prk_sha256, sizeof(prk_data)); +} + int main(void) { int ret; @@ -119,6 +160,9 @@ int main(void) sizeof(expected_prk_sha384), expected_okm_sha384, sizeof(expected_okm_sha384)); } + if (ret == 0) { + ret = test_hkdf_extract_short_salt(); + } if (ret == 0) { printf("Test completed.\n"); } diff --git a/wolfssl-gnutls-wrapper/tests/test_hmac.c b/wolfssl-gnutls-wrapper/tests/test_hmac.c index 81b8ae6..32cc068 100644 --- a/wolfssl-gnutls-wrapper/tests/test_hmac.c +++ b/wolfssl-gnutls-wrapper/tests/test_hmac.c @@ -100,6 +100,48 @@ static int test_hmac(int algorithm, const unsigned char* expected_hmac, return 0; } +/* Test vector from RFC 4231 - Test Case 2, with a 4-byte key. */ +const char short_key[] = "Jefe"; +const char short_key_data[] = "what do ya want for nothing?"; +const unsigned char expected_short_key_hmac_sha256[] = { + 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, + 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7, + 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, + 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43 +}; + +/* A short key must be refused with an error or give the right MAC. */ +static int test_hmac_short_key(void) +{ + int ret; + unsigned char output[32]; + gnutls_hmac_hd_t hmac; + + printf("key: \"%s\" (%zu bytes)\n", short_key, strlen(short_key)); + + memset(output, 0, sizeof(output)); + ret = gnutls_hmac_fast(GNUTLS_MAC_SHA256, short_key, strlen(short_key), + short_key_data, strlen(short_key_data), output); + if (refused_or_compare("HMAC one-shot, short key", ret, output, + expected_short_key_hmac_sha256, sizeof(output)) != 0) { + return 1; + } + + memset(output, 0, sizeof(output)); + ret = gnutls_hmac_init(&hmac, GNUTLS_MAC_SHA256, short_key, + strlen(short_key)); + if (ret == 0) { + ret = gnutls_hmac(hmac, short_key_data, strlen(short_key_data)); + gnutls_hmac_deinit(hmac, output); + } + if (refused_or_compare("HMAC streaming, short key", ret, output, + expected_short_key_hmac_sha256, sizeof(output)) != 0) { + return 1; + } + + return 0; +} + int main(void) { int ret; @@ -129,6 +171,10 @@ int main(void) ret = test_hmac(GNUTLS_MAC_SHA512, expected_hmac_sha512, sizeof(expected_hmac_sha512)); } + if (ret == 0) { + printf("testing wolfssl's HMAC-SHA256 with a short key via gnutls...\n"); + ret = test_hmac_short_key(); + } gnutls_global_deinit(); diff --git a/wolfssl-gnutls-wrapper/tests/test_pbkdf2.c b/wolfssl-gnutls-wrapper/tests/test_pbkdf2.c index e41ed8b..3ff8463 100644 --- a/wolfssl-gnutls-wrapper/tests/test_pbkdf2.c +++ b/wolfssl-gnutls-wrapper/tests/test_pbkdf2.c @@ -52,6 +52,41 @@ static int test_pbkdf2(gnutls_mac_algorithm_t mac_alg, int iterations, return 0; } +/* PBKDF2-HMAC-SHA256, P = "password", S = "salt", c = 1, dkLen = 32. The + * 8-byte password is shorter than the FIPS HMAC key minimum (14 bytes). */ +const char* short_passwd = "password"; +const char* short_passwd_salt = "salt"; +const unsigned char expected_short_passwd_sha256[] = { + 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, + 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, + 0xa8, 0x65, 0x48, 0xc9, 0x2c, 0xcc, 0x35, 0x48, + 0x08, 0x05, 0x98, 0x7c, 0xb7, 0x0b, 0xe1, 0x7b +}; + +/* A short password must be refused with an error or give the right key. */ +static int test_pbkdf2_short_password(void) +{ + int ret; + unsigned char derived[32]; + gnutls_datum_t ikm = { + .data = (unsigned char*)short_passwd, + .size = strlen(short_passwd) + }; + gnutls_datum_t salt = { + .data = (unsigned char*)short_passwd_salt, + .size = strlen(short_passwd_salt) + }; + + printf("Testing wolfSSL's SHA256 PBKDF2 with a short password via " + "GnuTLS...\n"); + + memset(derived, 0, sizeof(derived)); + ret = gnutls_pbkdf2(GNUTLS_MAC_SHA256, &ikm, &salt, 1, derived, + sizeof(derived)); + return refused_or_compare("PBKDF2 derivation, short password", ret, + derived, expected_short_passwd_sha256, sizeof(derived)); +} + int main(void) { int ret; @@ -69,6 +104,9 @@ int main(void) ret = test_pbkdf2(GNUTLS_MAC_SHA384, 4096, expected_sha384, sizeof(expected_sha384)); } + if (ret == 0) { + ret = test_pbkdf2_short_password(); + } if (ret == 0) { printf("Test completed.\n"); } diff --git a/wolfssl-gnutls-wrapper/tests/test_util.h b/wolfssl-gnutls-wrapper/tests/test_util.h index ddea28e..e85af0d 100644 --- a/wolfssl-gnutls-wrapper/tests/test_util.h +++ b/wolfssl-gnutls-wrapper/tests/test_util.h @@ -54,3 +54,17 @@ void print_gnutls_error(const char* op, int ret) fprintf(stderr, "Error %s: %s (%d)\n", op, gnutls_strerror(ret), ret); } +int refused_or_compare(const char* op, int ret, const unsigned char* output, + const unsigned char* expected, size_t sz) +{ + if (ret < 0) { + printf("%s refused: %s (%d)\n", op, gnutls_strerror(ret), ret); + return 0; + } + if (ret > 0) { + printf("FAILURE - %s returned %d, not 0 or a negative error\n", op, + ret); + return 1; + } + return compare(op, output, expected, sz); +} From f932349712d7b7adb499e0136ae6c10224dc2aea Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Thu, 15 Oct 2026 12:05:42 +0200 Subject: [PATCH 2/4] Fix GCM tag output and RSA decrypt2 length handling --- wolfssl-gnutls-wrapper/src/cipher.c | 126 +++++++----------- wolfssl-gnutls-wrapper/src/pk.c | 114 +++++++++------- wolfssl-gnutls-wrapper/tests/test_aesgcm.c | 103 ++++++++++++++ .../tests/test_rsa_encrypt_and_decrypt.c | 104 +++++++++++++++ 4 files changed, 321 insertions(+), 126 deletions(-) diff --git a/wolfssl-gnutls-wrapper/src/cipher.c b/wolfssl-gnutls-wrapper/src/cipher.c index d980667..14d9bcc 100644 --- a/wolfssl-gnutls-wrapper/src/cipher.c +++ b/wolfssl-gnutls-wrapper/src/cipher.c @@ -112,8 +112,6 @@ struct wolfssl_cipher_ctx { size_t data_size; /** Tag has been set. */ unsigned int tag_set:1; - /** Tag has been set from external source. */ - unsigned int tag_set_ext:1; }; /** Array of supported ciphers. */ @@ -654,7 +652,6 @@ int wolfssl_cipher_setiv(void *_ctx, const void *iv, size_t iv_size) /* IV stored and used in encrypt/decrypt/tag. */ /* No tag set, auth data or plaintext now we have a new IV. */ ctx->tag_set = 0; - ctx->tag_set_ext = 0; ctx->auth_data_size = 0; ctx->data_size = 0; break; @@ -1032,7 +1029,7 @@ int wolfssl_cipher_decrypt(void *_ctx, const void *src, size_t src_size, /* Allocate a decrypted data buffer to decrypt all ciphertext into. */ decr = gnutls_malloc(ctx->data_size + src_size); - if (ptr == NULL) { + if (decr == NULL) { WGW_ERROR("realloc of decrypted data failed"); return GNUTLS_E_INVALID_REQUEST; } @@ -1042,31 +1039,26 @@ int wolfssl_cipher_decrypt(void *_ctx, const void *src, size_t src_size, unsigned char *aad = ctx->auth_data_heap ? ctx->auth_data_heap : ctx->auth_data_static; - /* If caller hasn't set tag then we are creating it. */ - if (!ctx->tag_set_ext) { - /* Encrypt the ciphertext to get the plaintext. - * Tag will have been created on plaintext which is of no use. - */ - ret = wc_AesGcmEncrypt(&ctx->cipher.aes_ctx, decr, ctx->data, - ctx->data_size, ctx->iv, ctx->iv_size, - ctx->tag, ctx->tag_size, aad, ctx->auth_data_size); - if (ret != 0) { - WGW_WOLFSSL_ERROR("wc_AesGcmEncrypt", ret); - gnutls_free(decr); - return GNUTLS_E_ENCRYPTION_FAILED; - } - /* Encrypt the plaintext to create the tag. */ - ret = wc_AesGcmEncrypt(&ctx->cipher.aes_ctx, decr, decr, - ctx->data_size, ctx->iv, ctx->iv_size, - ctx->tag, ctx->tag_size, aad, ctx->auth_data_size); - if (ret != 0) { - WGW_WOLFSSL_ERROR("wc_AesGcmEncrypt", ret); - gnutls_free(decr); - return GNUTLS_E_ENCRYPTION_FAILED; - } - /* A tag is now available. */ - ctx->tag_set = 1; + /* Encrypt the ciphertext to get the plaintext. */ + ret = wc_AesGcmEncrypt(&ctx->cipher.aes_ctx, decr, ctx->data, + ctx->data_size, ctx->iv, ctx->iv_size, + ctx->tag, ctx->tag_size, aad, ctx->auth_data_size); + if (ret != 0) { + WGW_WOLFSSL_ERROR("wc_AesGcmEncrypt", ret); + gnutls_free(decr); + return GNUTLS_E_ENCRYPTION_FAILED; } + /* Encrypt the plaintext to create the tag. */ + ret = wc_AesGcmEncrypt(&ctx->cipher.aes_ctx, decr, decr, + ctx->data_size, ctx->iv, ctx->iv_size, + ctx->tag, ctx->tag_size, aad, ctx->auth_data_size); + if (ret != 0) { + WGW_WOLFSSL_ERROR("wc_AesGcmEncrypt", ret); + gnutls_free(decr); + return GNUTLS_E_ENCRYPTION_FAILED; + } + /* A tag is now available. */ + ctx->tag_set = 1; /* Do decryption with cipehtext, IV, authentication data and tag. */ ret = wc_AesGcmDecrypt(&ctx->cipher.aes_ctx, decr, ctx->data, ctx->data_size, ctx->iv, ctx->iv_size, @@ -1118,13 +1110,11 @@ int wolfssl_cipher_decrypt(void *_ctx, const void *src, size_t src_size, } /** - * Get or store the tag; + * Output the tag, or random bytes when it cannot be computed. * - * When tag is all zeros then copy out the stored authentication tag. - * - * @param [in] _ctx Cipher context. - * @param [in, out] tag Authentication tag. - * @param [in] tag_size Size of authentication tag in bytes. + * @param [in] _ctx Cipher context. + * @param [out] tag Authentication tag. + * @param [in] tag_size Size of authentication tag in bytes. */ void wolfssl_cipher_tag(void *_ctx, void *tag, size_t tag_size) { @@ -1132,68 +1122,48 @@ void wolfssl_cipher_tag(void *_ctx, void *tag, size_t tag_size) WGW_LOG("tag_size %zu", tag_size); struct wolfssl_cipher_ctx *ctx = _ctx; + int ret = -1; if (!ctx->initialized) { WGW_LOG("cipher context not initialized"); - return; - } - - /* Make sure copied tag size is no larger than that generated. */ - if (tag_size > ctx->tag_size) { - tag_size = ctx->tag_size; - } - - /* Check if tag available. */ - if (ctx->tag_set) { - if (ctx->mode == GCM) { - XMEMCPY(tag, ctx->tag, tag_size); - /* Authentication data used - reset count. */ - ctx->auth_data_size = 0; - /* Dispose of cached data. */ - gnutls_free(ctx->data); - ctx->data = NULL; - ctx->data_size = 0; - WGW_LOG("tag returned successfully"); - } else { - WGW_LOG("AES mode not supported: %d", ctx->mode); + } else if (ctx->mode != GCM) { + WGW_LOG("AES mode not supported: %d", ctx->mode); + } else { + /* Make sure copied tag size is no larger than that generated. */ + if (tag_size > ctx->tag_size) { + tag_size = ctx->tag_size; } - } else if (ctx->enc) { - int ret = -1; - - /* Encrypting and no tag set means we don't have plaintext. */ - if (ctx->mode == GCM) { - WGW_LOG("wc_AesGcmEncrypt"); + if (ctx->tag_set) { + ret = 0; + } else { unsigned char *aad = ctx->auth_data_heap ? ctx->auth_data_heap : ctx->auth_data_static; /* Do authentication with no plaintext. */ + WGW_LOG("wc_AesGcmEncrypt"); ret = wc_AesGcmEncrypt(&ctx->cipher.aes_ctx, NULL, NULL, 0, ctx->iv, ctx->iv_size, ctx->tag, ctx->tag_size, aad, ctx->auth_data_size); if (ret != 0) { WGW_WOLFSSL_ERROR("wc_AesGcmEncrypt", ret); } else { - /* Copy out tag. */ ctx->tag_set = 1; - XMEMCPY(tag, ctx->tag, tag_size); - WGW_LOG("tag stored successfully"); } - /* Authentication data used - reset count. */ - ctx->auth_data_size = 0; - /* Dispose of cached plaintext. */ - gnutls_free(ctx->data); - ctx->data = NULL; - ctx->data_size = 0; - } else { - WGW_LOG("AES mode not supported: %d", ctx->mode); } - } else { - /* Decrypting and we need to set tag for decrypt operation. */ - XMEMCPY(ctx->tag, tag, tag_size); - ctx->tag_set = 1; - ctx->tag_set_ext = 1; - WGW_LOG("tag provided successfully"); + /* Authentication data used, so we reset count. */ + ctx->auth_data_size = 0; + /* Dispose of cached data. */ + gnutls_free(ctx->data); + ctx->data = NULL; + ctx->data_size = 0; + } + + if (ret == 0) { + XMEMCPY(tag, ctx->tag, tag_size); + WGW_LOG("tag returned successfully"); + } else if (gnutls_rnd(GNUTLS_RND_NONCE, tag, tag_size) < 0) { + WGW_ERROR("no random bytes for the tag"); } } diff --git a/wolfssl-gnutls-wrapper/src/pk.c b/wolfssl-gnutls-wrapper/src/pk.c index c05a448..a0de250 100644 --- a/wolfssl-gnutls-wrapper/src/pk.c +++ b/wolfssl-gnutls-wrapper/src/pk.c @@ -886,6 +886,32 @@ static int wolfssl_pk_encrypt(gnutls_pk_algorithm_t algo, return ret; } +/** + * Constant-time copy into plaintext only when the decrypted length matches. + * + * @param [in, out] plaintext Caller's buffer and its size. + * @param [in] decrypted Decrypted data, at least plaintext->size bytes. + * @param [in] dec_ret Length decrypted by wolfSSL, or error. + * @return 0 on success. + * @return GNUTLS_E_DECRYPTION_FAILED otherwise. + */ +static int rsa_decrypt_fixed_select(gnutls_datum_t *plaintext, + const unsigned char *decrypted, int dec_ret) +{ + unsigned int diff = (unsigned int)dec_ret ^ plaintext->size; + /* All ones when dec_ret equals the size, zero otherwise. */ + unsigned int ok = ((diff | (0U - diff)) >> (sizeof(diff) * 8 - 1)) - 1U; + unsigned char mask = (unsigned char)ok; + unsigned int i; + + for (i = 0; i < plaintext->size; i++) { + plaintext->data[i] = (unsigned char)((decrypted[i] & mask) | + (plaintext->data[i] & (unsigned char)~mask)); + } + + return ok ? 0 : GNUTLS_E_DECRYPTION_FAILED; +} + /** * Decrypt ciphertext using RSA PKCS#1 v1.5 with private key. * @@ -950,13 +976,15 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, wc_FreeRsaKey(&rsa); return GNUTLS_E_MEMORY_ERROR; } - } - /* Set plain to valid buffer. */ - if ((!alloc_plaintext) && - (plaintext->size < (unsigned int)wc_RsaEncryptSize(&rsa))) { - plain = out; - } else { plain = plaintext->data; + } else { + /* Fixed-size output, we decrypt into scratch. */ + if (plain_size > sizeof(out) || plaintext->size > plain_size) { + wc_FreeRsaKey(&rsa); + return GNUTLS_E_DECRYPTION_FAILED; + } + XMEMSET(out, 0, plain_size); + plain = out; } PRIVATE_KEY_UNLOCK(); @@ -969,29 +997,23 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); + + if (!alloc_plaintext) { + ret = rsa_decrypt_fixed_select(plaintext, out, ret); + gnutls_memset(out, 0, plain_size); + return ret; + } + if (ret < 0) { WGW_WOLFSSL_ERROR("wc_RsaPrivateDecrypt", ret); - if (alloc_plaintext) { - /* Dispose of allocated buffer for plaintext. */ - gnutls_free(plaintext->data); - /* Ensure output datum is empty on error. */ - plaintext->data = NULL; - plaintext->size = 0; - } + /* Dispose of allocated buffer for plaintext. */ + gnutls_free(plaintext->data); + /* Ensure output datum is empty on error. */ + plaintext->data = NULL; + plaintext->size = 0; return GNUTLS_E_DECRYPTION_FAILED; } - /* Check if returning through another buffer. */ - if (plain != plaintext->data) { - /* Ensure the output buffer is big enough. */ - if ((unsigned int)ret > plaintext->size) { - WGW_ERROR("Decrypted data too big for plaintext buffer: %d > %d", - ret, plaintext->size); - return GNUTLS_E_DECRYPTION_FAILED; - } - /* Copy the decrypted data into output buffer. */ - XMEMCPY(plaintext->data, plain, ret); - } /* Set the actual size into output datum. */ plaintext->size = ret; @@ -1064,13 +1086,15 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, wc_FreeRsaKey(&rsa); return GNUTLS_E_MEMORY_ERROR; } - } - /* Set plain to valid buffer. */ - if ((!alloc_plaintext) && - (plaintext->size < (unsigned int)wc_RsaEncryptSize(&rsa))) { - plain = out; - } else { plain = plaintext->data; + } else { + /* Fixed-size output, we decrypt into scratch. */ + if (plain_size > sizeof(out) || plaintext->size > plain_size) { + wc_FreeRsaKey(&rsa); + return GNUTLS_E_DECRYPTION_FAILED; + } + XMEMSET(out, 0, plain_size); + plain = out; } PRIVATE_KEY_UNLOCK(); @@ -1084,29 +1108,23 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); + + if (!alloc_plaintext) { + ret = rsa_decrypt_fixed_select(plaintext, out, ret); + gnutls_memset(out, 0, plain_size); + return ret; + } + if (ret < 0) { WGW_WOLFSSL_ERROR("wc_RsaPublicDecrypt_ex", ret); - if (alloc_plaintext) { - /* Dispose of allocated buffer for plaintext. */ - gnutls_free(plaintext->data); - /* Ensure output datum is empty on error. */ - plaintext->data = NULL; - plaintext->size = 0; - } + /* Dispose of allocated buffer for plaintext. */ + gnutls_free(plaintext->data); + /* Ensure output datum is empty on error. */ + plaintext->data = NULL; + plaintext->size = 0; return GNUTLS_E_DECRYPTION_FAILED; } - /* Check if returning through another buffer. */ - if (plain != plaintext->data) { - /* Ensure the output buffer is big enough. */ - if ((unsigned int)ret > plaintext->size) { - WGW_ERROR("Decrypted data too big for plaintext buffer: %d > %d", - ret, plaintext->size); - return GNUTLS_E_DECRYPTION_FAILED; - } - /* Copy the decrypted data into output buffer. */ - XMEMCPY(plaintext->data, plain, ret); - } /* Set the actual size into output datum. */ plaintext->size = ret; diff --git a/wolfssl-gnutls-wrapper/tests/test_aesgcm.c b/wolfssl-gnutls-wrapper/tests/test_aesgcm.c index fa731e4..cdf7458 100644 --- a/wolfssl-gnutls-wrapper/tests/test_aesgcm.c +++ b/wolfssl-gnutls-wrapper/tests/test_aesgcm.c @@ -382,6 +382,106 @@ static int test_aesgcm_big_aad_chunks_encdec(void) return ret; } +/* One-shot decrypt of the AES-128-GCM test vector on the handle. */ +static int one_shot_decrypt(gnutls_aead_cipher_hd_t hd) +{ + int ret; + unsigned char decrypted[sizeof(plaintext_data)]; + size_t decrypted_sz = sizeof(decrypted); + + ret = gnutls_aead_cipher_decrypt(hd, nonce_data, sizeof(nonce_data), + aad_data, sizeof(aad_data), 16, ciphertext_128_data, + sizeof(ciphertext_128_data), decrypted, &decrypted_sz); + if (ret < 0) { + print_gnutls_error("one-shot decrypt", ret); + return 1; + } + return 0; +} + +/* Tags of empty messages on a handle used for a one-shot decrypt. */ +static int test_aesgcm_tag_after_one_shot_decrypt(void) +{ + int ret; + gnutls_aead_cipher_hd_t hd; + gnutls_datum_t key = { (unsigned char *)key_128, sizeof(key_128) }; + giovec_t aad = { (void *)aad_data, sizeof(aad_data) }; + unsigned char aad_tag[16]; + unsigned char zero_tag[16] = { 0 }; + unsigned char tag[16]; + size_t tag_sz; + + printf("Testing AES-GCM tags after a one-shot decrypt...\n"); + + /* Reference tag over the AAD from a fresh handle. */ + if ((ret = gnutls_aead_cipher_init(&hd, GNUTLS_CIPHER_AES_128_GCM, + &key)) < 0) { + print_gnutls_error("initializing cipher", ret); + return 1; + } + tag_sz = sizeof(aad_tag); + ret = gnutls_aead_cipher_encryptv2(hd, nonce_data, sizeof(nonce_data), + &aad, 1, NULL, 0, aad_tag, &tag_sz); + gnutls_aead_cipher_deinit(hd); + if (ret < 0) { + print_gnutls_error("tag over AAD on a fresh handle", ret); + return 1; + } + + if ((ret = gnutls_aead_cipher_init(&hd, GNUTLS_CIPHER_AES_128_GCM, + &key)) < 0) { + print_gnutls_error("initializing cipher", ret); + return 1; + } + + /* A wrong tag on an empty message must be rejected. */ + if (one_shot_decrypt(hd) != 0) { + gnutls_aead_cipher_deinit(hd); + return 1; + } + ret = gnutls_aead_cipher_decryptv2(hd, nonce_data, sizeof(nonce_data), + &aad, 1, NULL, 0, zero_tag, sizeof(zero_tag)); + if (ret >= 0) { + printf("FAILURE - wrong tag accepted after a one-shot decrypt\n"); + gnutls_aead_cipher_deinit(hd); + return 1; + } + + /* The right tag on an empty message must be accepted. */ + if (one_shot_decrypt(hd) != 0) { + gnutls_aead_cipher_deinit(hd); + return 1; + } + ret = gnutls_aead_cipher_decryptv2(hd, nonce_data, sizeof(nonce_data), + &aad, 1, NULL, 0, aad_tag, sizeof(aad_tag)); + if (ret < 0) { + print_gnutls_error("right tag after a one-shot decrypt", ret); + gnutls_aead_cipher_deinit(hd); + return 1; + } + + /* An empty encryption must return the tag over the AAD. */ + if (one_shot_decrypt(hd) != 0) { + gnutls_aead_cipher_deinit(hd); + return 1; + } + memset(tag, 0xaa, sizeof(tag)); + tag_sz = sizeof(tag); + ret = gnutls_aead_cipher_encryptv2(hd, nonce_data, sizeof(nonce_data), + &aad, 1, NULL, 0, tag, &tag_sz); + gnutls_aead_cipher_deinit(hd); + if (ret < 0) { + print_gnutls_error("empty encrypt after a one-shot decrypt", ret); + return 1; + } + if (compare_sz("Tag of empty encrypt after a one-shot decrypt", tag, + tag_sz, aad_tag, sizeof(aad_tag)) != 0) { + return 1; + } + + return 0; +} + int main(void) { int ret; @@ -422,6 +522,9 @@ int main(void) { if (ret == 0) { ret = test_aesgcm_big_aad_chunks_encdec(); } + if (ret == 0) { + ret = test_aesgcm_tag_after_one_shot_decrypt(); + } if (ret == 0) { printf("Test completed.\n"); } diff --git a/wolfssl-gnutls-wrapper/tests/test_rsa_encrypt_and_decrypt.c b/wolfssl-gnutls-wrapper/tests/test_rsa_encrypt_and_decrypt.c index dba50e1..2791038 100644 --- a/wolfssl-gnutls-wrapper/tests/test_rsa_encrypt_and_decrypt.c +++ b/wolfssl-gnutls-wrapper/tests/test_rsa_encrypt_and_decrypt.c @@ -102,6 +102,104 @@ int test_rsa_encrypt_decrypt(unsigned int bits) { return 0; } +#define DECRYPT2_LEN 48 + +/* Only an intact DECRYPT2_LEN-byte message decrypts, else buffer unchanged. */ +static int decrypt2_check(gnutls_privkey_t privkey, gnutls_pubkey_t pubkey, + size_t msg_len, int corrupt) +{ + int ret; + unsigned char msg[64]; + unsigned char buf[DECRYPT2_LEN]; + unsigned char prefill[DECRYPT2_LEN]; + gnutls_datum_t data = { msg, msg_len }; + gnutls_datum_t ciphertext = { NULL, 0 }; + int expect_ok = (msg_len == DECRYPT2_LEN) && !corrupt; + size_t i; + + memset(msg, 'P', sizeof(msg)); + ret = gnutls_pubkey_encrypt_data(pubkey, 0, &data, &ciphertext); + if (ret != 0) { + printf("Error encrypting data: %s\n", gnutls_strerror(ret)); + return 1; + } + if (corrupt) { + ciphertext.data[ciphertext.size / 2] ^= 0x01; + } + + for (i = 0; i < sizeof(prefill); i++) { + prefill[i] = (unsigned char)(0xa0 + i); + } + memcpy(buf, prefill, sizeof(buf)); + + ret = gnutls_privkey_decrypt_data2(privkey, 0, &ciphertext, buf, + sizeof(buf)); + gnutls_free(ciphertext.data); + printf("%zu-byte message%s into %d-byte buffer: %s\n", msg_len, + corrupt ? " (corrupted)" : "", DECRYPT2_LEN, + ret == 0 ? "decrypted" : gnutls_strerror(ret)); + + if (expect_ok) { + if (ret != 0 || memcmp(buf, msg, sizeof(buf)) != 0) { + printf("FAILURE: exact-length message did not decrypt " + "correctly\n"); + return 1; + } + return 0; + } + if (ret >= 0) { + printf("FAILURE: reported as decrypted\n"); + return 1; + } + if (memcmp(buf, prefill, sizeof(buf)) != 0) { + printf("FAILURE: output buffer changed on failure\n"); + return 1; + } + return 0; +} + +static int test_rsa_decrypt2_length(void) +{ + int ret; + gnutls_privkey_t privkey; + gnutls_pubkey_t pubkey; + + printf("\n=== Testing RSA decrypt_data2 with exact and wrong lengths " + "===\n"); + + if ((ret = gnutls_privkey_init(&privkey)) != 0) { + printf("Error initializing private key: %s\n", gnutls_strerror(ret)); + return 1; + } + if ((ret = gnutls_pubkey_init(&pubkey)) != 0) { + printf("Error initializing public key: %s\n", gnutls_strerror(ret)); + gnutls_privkey_deinit(privkey); + return 1; + } + if ((ret = gnutls_privkey_generate2(privkey, GNUTLS_PK_RSA, 2048, 0, + NULL, 0)) != 0 || + (ret = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0)) != 0) { + printf("Error setting up RSA key: %s\n", gnutls_strerror(ret)); + gnutls_pubkey_deinit(pubkey); + gnutls_privkey_deinit(privkey); + return 1; + } + + ret = decrypt2_check(privkey, pubkey, DECRYPT2_LEN, 0) || + decrypt2_check(privkey, pubkey, 16, 0) || + decrypt2_check(privkey, pubkey, DECRYPT2_LEN - 1, 0) || + decrypt2_check(privkey, pubkey, 64, 0) || + decrypt2_check(privkey, pubkey, DECRYPT2_LEN, 1); + + gnutls_pubkey_deinit(pubkey); + gnutls_privkey_deinit(privkey); + + if (ret == 0) { + printf("SUCCESS for RSA decrypt_data2\n"); + } + return ret; +} + int main(void) { int ret; @@ -131,6 +229,12 @@ int main(void) { return 1; } + ret = test_rsa_decrypt2_length(); + if (ret != 0) { + gnutls_global_deinit(); + return 1; + } + /* Clean up global resources */ gnutls_global_deinit(); From f3a0ddffab53efdf99680fee6ac2fa25389504b6 Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Thu, 15 Oct 2026 12:16:45 +0200 Subject: [PATCH 3/4] Lock the shared RNG for every use --- wolfssl-gnutls-wrapper/Makefile | 4 +- wolfssl-gnutls-wrapper/src/pk.c | 32 ++++ wolfssl-gnutls-wrapper/src/random.c | 49 +++--- wolfssl-gnutls-wrapper/src/random.h | 2 + wolfssl-gnutls-wrapper/tests/Makefile | 4 +- .../tests/test_ecdsa_sign_and_verify.c | 157 ++++++++++++++++++ 6 files changed, 220 insertions(+), 28 deletions(-) diff --git a/wolfssl-gnutls-wrapper/Makefile b/wolfssl-gnutls-wrapper/Makefile index b034ca6..bf3b194 100644 --- a/wolfssl-gnutls-wrapper/Makefile +++ b/wolfssl-gnutls-wrapper/Makefile @@ -45,13 +45,13 @@ endif UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) - CFLAGS += -DENABLE_WOLFSSL -fPIC -Wall -Wextra + CFLAGS += -DENABLE_WOLFSSL -fPIC -Wall -Wextra -pthread CFLAGS_DEBUG += -g # Use rpath to wherever we actually link from LDFLAGS += -Wl,--no-as-needed -Wl,-z,now \ -Wl,-rpath,$(GNUTLS_LIBDIR) -Wl,-rpath,$(WOLFSSL_LIBDIR) SOFLAG := -shared - DL_LIBS := -ldl + DL_LIBS := -ldl -pthread endif ifeq ($(UNAME_S),Darwin) diff --git a/wolfssl-gnutls-wrapper/src/pk.c b/wolfssl-gnutls-wrapper/src/pk.c index a0de250..b9a84f0 100644 --- a/wolfssl-gnutls-wrapper/src/pk.c +++ b/wolfssl-gnutls-wrapper/src/pk.c @@ -736,8 +736,10 @@ static int wolfssl_pk_encrypt_rsa(gnutls_datum_t *ciphertext, } /* Encrypt using RSA PKCS#1 v1.5 padding */ + wolfssl_rng_lock(); ret = wc_RsaPublicEncrypt(plaintext->data, plaintext->size, ciphertext->data, ciphertext->size, &rsa, &priv_rng); + wolfssl_rng_unlock(); /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); if (ret < 0) { @@ -808,10 +810,12 @@ static int wolfssl_pk_encrypt_rsa_oaep(gnutls_datum_t *ciphertext, } /* Encrypt using RSA PKCS#1 OAEP. */ + wolfssl_rng_lock(); ret = wc_RsaPublicEncrypt_ex(plaintext->data, plaintext->size, ciphertext->data, ciphertext->size, &rsa, &priv_rng, WC_RSA_OAEP_PAD, hash_type, mgf, params->spki.rsa_oaep_label.data, params->spki.rsa_oaep_label.size); + wolfssl_rng_unlock(); /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); if (ret < 0) { @@ -990,8 +994,10 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 v1.5 padding */ + wolfssl_rng_lock(); ret = wc_RsaPrivateDecrypt(ciphertext->data, ciphertext->size, plain, plain_size, &rsa); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1100,9 +1106,11 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 OAEP. */ + wolfssl_rng_lock(); ret = wc_RsaPrivateDecrypt_ex(ciphertext->data, ciphertext->size, plain, plain_size, &rsa, WC_RSA_OAEP_PAD, hash_type, mgf, params->spki.rsa_oaep_label.data, params->spki.rsa_oaep_label.size); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1293,8 +1301,10 @@ static int wolfssl_pk_sign_rsa(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 v1.5. */ + wolfssl_rng_lock(); ret = wc_RsaSSL_Sign(vdata->data, vdata->size, signature->data, signature->size, &rsa, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1370,9 +1380,11 @@ static int wolfssl_pk_sign_rsa_pss(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Decrypt using RSA PKCS#1 PSS. */ + wolfssl_rng_lock(); ret = wc_RsaPSS_Sign_ex(vdata->data, vdata->size, signature->data, signature->size, hash_type, mgf, sign_params->salt_size, &rsa, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -1454,8 +1466,10 @@ static int wolfssl_pk_sign_ecc(gnutls_datum_t *signature, PRIVATE_KEY_UNLOCK(); /* Sign hash using ECDSA. */ + wolfssl_rng_lock(); ret = wc_ecc_sign_hash(vdata->data, vdata->size, signature->data, &len, &priv_rng, &ecc); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2521,7 +2535,9 @@ static int wolfssl_pk_generate_keys_rsa(unsigned int bits, PRIVATE_KEY_UNLOCK(); /* Generate RSA key */ + wolfssl_rng_lock(); ret = wc_MakeRsaKey(&rsa, bits, WC_RSA_EXPONENT, &priv_rng); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2625,7 +2641,9 @@ static int wolfssl_pk_generate_keys_dh(unsigned int bits, PRIVATE_KEY_UNLOCK(); /* Generate public/private key pair for DH. */ + wolfssl_rng_lock(); ret = wc_DhGenerateKeyPair(&dh, &priv_rng, priv, &privSz, pub, &pubSz); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2703,7 +2721,9 @@ static int wolfssl_pk_generate_keys_ecc(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate ECC key */ + wolfssl_rng_lock(); ret = wc_ecc_make_key_ex(&priv_rng, curve_size, &ecc, curve_id); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2764,7 +2784,9 @@ static int wolfssl_pk_generate_keys_ed25519(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate Ed25519 key */ + wolfssl_rng_lock(); ret = wc_ed25519_make_key(&priv_rng, ED25519_KEY_SIZE, &ed25519); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2866,7 +2888,9 @@ static int wolfssl_pk_generate_keys_ed448(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate Ed448 key */ + wolfssl_rng_lock(); ret = wc_ed448_make_key(&priv_rng, ED448_KEY_SIZE, &ed448); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -2968,7 +2992,9 @@ static int wolfssl_pk_generate_keys_x25519(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate X25519 key */ + wolfssl_rng_lock(); ret = wc_curve25519_make_key(&priv_rng, CURVE25519_KEYSIZE, &x25519); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -3070,7 +3096,9 @@ static int wolfssl_pk_generate_keys_x448(unsigned int curve, PRIVATE_KEY_UNLOCK(); /* Generate X448 key */ + wolfssl_rng_lock(); ret = wc_curve448_make_key(&priv_rng, CURVE448_KEY_SIZE, &x448); + wolfssl_rng_unlock(); /* Random number generator no longer needed. */ PRIVATE_KEY_LOCK(); @@ -4132,7 +4160,9 @@ static int wolfssl_pk_derive_ecc(gnutls_datum_t *out, PRIVATE_KEY_UNLOCK(); /* Calculate the shared secret. */ + wolfssl_rng_lock(); ret = wc_ecc_shared_secret(&private, &public, out->data, &len); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); @@ -4261,8 +4291,10 @@ static int wolfssl_pk_derive_x25519(gnutls_datum_t *out, PRIVATE_KEY_UNLOCK(); /* Calculate the shared secret. */ + wolfssl_rng_lock(); ret = wc_curve25519_shared_secret_ex(&private, &public, out->data, &len, EC25519_LITTLE_ENDIAN); + wolfssl_rng_unlock(); PRIVATE_KEY_LOCK(); diff --git a/wolfssl-gnutls-wrapper/src/random.c b/wolfssl-gnutls-wrapper/src/random.c index c52c4bb..963f975 100644 --- a/wolfssl-gnutls-wrapper/src/random.c +++ b/wolfssl-gnutls-wrapper/src/random.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef ENABLE_WOLFSSL /** Context structure for wolfSSL RNG. */ @@ -24,19 +25,19 @@ pid_t pid = -1; /** Indicates if the DBRG needs to be reseeded.*/ int rng_ready = 0; -/** Ensures that only one process at the time is accessing the RNGs objects */ -static wolfSSL_Mutex wc_rng_lock; -/** indicates that the rng lock object was actually initialized */ -static int wc_rng_lock_init = 0; +/** Serializes all use of the RNG objects above. */ +static pthread_mutex_t wc_rng_lock = PTHREAD_MUTEX_INITIALIZER; -/** Gets called every time we need to do a lock operation, - * initializes the lock object if it wasn't already. */ -static inline void wc_rng_lock_once(void) +/** Take the lock guarding priv_rng and pub_rng. */ +void wolfssl_rng_lock(void) { - if (!wc_rng_lock_init) { - wc_InitMutex(&wc_rng_lock); - wc_rng_lock_init = 1; - } + pthread_mutex_lock(&wc_rng_lock); +} + +/** Release the lock taken by wolfssl_rng_lock(). */ +void wolfssl_rng_unlock(void) +{ + pthread_mutex_unlock(&wc_rng_lock); } /* Seed DRBGs on first use or after a fork */ @@ -46,30 +47,29 @@ int wolfssl_ensure_rng(void) pid_t p = getpid(); - wc_rng_lock_once(); - wc_LockMutex(&wc_rng_lock); + wolfssl_rng_lock(); /* We check if the pid is different (a fork happened) * or if the the first time creating a context (a first seed is needed). */ if (!rng_ready || p != pid) { - if (rng_ready) { - /* child after fork, we drop the old state and - * do a reseed. */ - wc_FreeRng(&priv_rng); - wc_FreeRng(&pub_rng); - } + /* Drop any old state before seeding again. */ + wc_FreeRng(&priv_rng); + wc_FreeRng(&pub_rng); + rng_ready = 0; #ifdef WC_RNG_SEED_CB wc_SetSeed_Cb(wc_GenerateSeed); #endif if (wc_InitRng(&priv_rng) != 0) { + wolfssl_rng_unlock(); return GNUTLS_E_RANDOM_FAILED; } if (wc_InitRng(&pub_rng) != 0) { wc_FreeRng(&priv_rng); + wolfssl_rng_unlock(); return GNUTLS_E_RANDOM_FAILED; } @@ -77,7 +77,7 @@ int wolfssl_ensure_rng(void) rng_ready = 1; } - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); return 0; } @@ -142,8 +142,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) return GNUTLS_E_RANDOM_FAILED; } - wc_rng_lock_once(); - wc_LockMutex(&wc_rng_lock); + wolfssl_rng_lock(); /* clear output buffer before filling */ XMEMSET(data, 0, datasize); @@ -154,7 +153,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) ret = wc_RNG_GenerateBlock(rng, data, size); if (ret != 0) { - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); WGW_ERROR("Requested %d bytes", size); WGW_WOLFSSL_ERROR("wc_RNG_GenerateBlock", ret); return GNUTLS_E_RANDOM_FAILED; @@ -165,7 +164,7 @@ static int wolfssl_rnd(void *_ctx, int level, void *data, size_t datasize) datasize -= size; } while (datasize > 0); - wc_UnLockMutex(&wc_rng_lock); + wolfssl_rng_unlock(); return 0; } @@ -183,7 +182,9 @@ static void wolfssl_rnd_refresh(void *_ctx) /* this forces a reseed of the random number generators * on the next call (wolfssl_ensure_rng). */ + wolfssl_rng_lock(); rng_ready = 0; + wolfssl_rng_unlock(); } /** diff --git a/wolfssl-gnutls-wrapper/src/random.h b/wolfssl-gnutls-wrapper/src/random.h index 18b56a8..3d47650 100644 --- a/wolfssl-gnutls-wrapper/src/random.h +++ b/wolfssl-gnutls-wrapper/src/random.h @@ -5,4 +5,6 @@ extern WC_RNG pub_rng; extern pid_t pid; extern int rng_ready; int wolfssl_ensure_rng(void); +void wolfssl_rng_lock(void); +void wolfssl_rng_unlock(void); #endif diff --git a/wolfssl-gnutls-wrapper/tests/Makefile b/wolfssl-gnutls-wrapper/tests/Makefile index f7fe39d..4a59e3b 100644 --- a/wolfssl-gnutls-wrapper/tests/Makefile +++ b/wolfssl-gnutls-wrapper/tests/Makefile @@ -62,9 +62,9 @@ ifeq ($(UNAME_S),Darwin) -L$(GNUTLS_LIBDIR) endif -# Libraries (dl only on Linux) +# Libraries (dl and pthread only on Linux) ifeq ($(UNAME_S),Linux) - LIBS = $(GNUTLS_LIBS) -ldl + LIBS = $(GNUTLS_LIBS) -ldl -pthread else LIBS = $(GNUTLS_LIBS) endif diff --git a/wolfssl-gnutls-wrapper/tests/test_ecdsa_sign_and_verify.c b/wolfssl-gnutls-wrapper/tests/test_ecdsa_sign_and_verify.c index abd78ff..64fa5ed 100644 --- a/wolfssl-gnutls-wrapper/tests/test_ecdsa_sign_and_verify.c +++ b/wolfssl-gnutls-wrapper/tests/test_ecdsa_sign_and_verify.c @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include #include @@ -158,6 +160,155 @@ int test_ecdsa_curve(gnutls_ecc_curve_t curve, const char *curve_name) { return 0; } +#define THREADS 8 +#define THREAD_ITERATIONS 1000 +#define VALUE_MAX 66 + +struct value { + unsigned char data[VALUE_MAX]; + unsigned int len; +}; + +/* Private keys and signature r values, one slot per operation. */ +static struct value thread_priv[THREADS * THREAD_ITERATIONS]; +static struct value thread_r[THREADS * THREAD_ITERATIONS]; + +static int record_value(struct value *v, const gnutls_datum_t *d) +{ + if (d->size == 0 || d->size > VALUE_MAX) { + return 1; + } + memcpy(v->data, d->data, d->size); + v->len = d->size; + return 0; +} + +/* Generate a P-256 key, sign and verify, and record the key and r. */ +static int keygen_and_sign(int slot) +{ + gnutls_privkey_t privkey = NULL; + gnutls_pubkey_t pubkey = NULL; + const char *test_data = "Test data to be signed"; + gnutls_datum_t data = { (unsigned char *)test_data, strlen(test_data) }; + gnutls_datum_t signature = { NULL, 0 }; + gnutls_datum_t k = { NULL, 0 }; + gnutls_datum_t r = { NULL, 0 }; + gnutls_datum_t s = { NULL, 0 }; + int ret; + + if ((ret = gnutls_privkey_init(&privkey)) != 0 || + (ret = gnutls_privkey_generate(privkey, GNUTLS_PK_ECDSA, 256, + 0)) != 0 || + (ret = gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &data, + &signature)) != 0 || + (ret = gnutls_pubkey_init(&pubkey)) != 0 || + (ret = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0)) != 0 || + (ret = gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_ECDSA_SHA256, 0, + &data, &signature)) < 0 || + (ret = gnutls_privkey_export_ecc_raw2(privkey, NULL, NULL, NULL, &k, + 0)) != 0 || + (ret = gnutls_decode_rs_value(&signature, &r, &s)) != 0) { + ret = 1; + } else { + ret = record_value(&thread_priv[slot], &k) || + record_value(&thread_r[slot], &r); + } + + gnutls_free(k.data); + gnutls_free(r.data); + gnutls_free(s.data); + gnutls_free(signature.data); + gnutls_pubkey_deinit(pubkey); + gnutls_privkey_deinit(privkey); + + return ret; +} + +static void *keygen_and_sign_thread(void *arg) +{ + int first = *(int *)arg * THREAD_ITERATIONS; + long failed = 0; + int i; + + for (i = 0; i < THREAD_ITERATIONS; i++) { + failed += keygen_and_sign(first + i); + } + + return (void *)failed; +} + +static int value_cmp(const void *a, const void *b) +{ + const struct value *x = a; + const struct value *y = b; + + if (x->len != y->len) { + return x->len < y->len ? -1 : 1; + } + return memcmp(x->data, y->data, x->len); +} + +/* Count values seen more than once, ignoring empty slots. */ +static int count_repeats(struct value *values, int n) +{ + int repeats = 0; + int i; + + qsort(values, n, sizeof(*values), value_cmp); + for (i = 1; i < n; i++) { + if (values[i].len != 0 && value_cmp(&values[i], &values[i - 1]) == 0) { + repeats++; + } + } + + return repeats; +} + +/* No private key or ECDSA r may repeat across concurrent threads. */ +static int test_ecdsa_threads(void) +{ + pthread_t threads[THREADS]; + int ids[THREADS]; + long failed = 0; + int priv_repeats; + int r_repeats; + int created; + int i; + + printf("\n=== Testing ECDSA key generation and signing from %d threads " + "===\n", THREADS); + + for (created = 0; created < THREADS; created++) { + ids[created] = created; + if (pthread_create(&threads[created], NULL, keygen_and_sign_thread, + &ids[created]) != 0) { + printf("Error creating thread %d\n", created); + failed++; + break; + } + } + for (i = 0; i < created; i++) { + void *res; + + pthread_join(threads[i], &res); + failed += (long)res; + } + + priv_repeats = count_repeats(thread_priv, THREADS * THREAD_ITERATIONS); + r_repeats = count_repeats(thread_r, THREADS * THREAD_ITERATIONS); + printf("%d operations, failed %ld, repeated private keys %d, " + "repeated r values %d\n", THREADS * THREAD_ITERATIONS, failed, + priv_repeats, r_repeats); + + if (failed != 0 || priv_repeats != 0 || r_repeats != 0) { + printf("FAILURE concurrent ECDSA key generation and signing\n"); + return 1; + } + printf("SUCCESS concurrent ECDSA key generation and signing\n"); + + return 0; +} + int main(void) { int ret; @@ -191,6 +342,12 @@ int main(void) { return 1; } + ret = test_ecdsa_threads(); + if (ret != 0) { + gnutls_global_deinit(); + return 1; + } + /* Clean up global resources */ gnutls_global_deinit(); From 7464b446ecb66b6c4eb665d35961288d0e1850a5 Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Thu, 15 Oct 2026 12:58:21 +0200 Subject: [PATCH 4/4] fwupd: drop main branch from the test matrix --- .github/workflows/fwupd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fwupd.yml b/.github/workflows/fwupd.yml index 85e8241..fd4778b 100644 --- a/.github/workflows/fwupd.yml +++ b/.github/workflows/fwupd.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - fwupd_ref: [ 'main', '1.9.26', '2.0.12' ] + fwupd_ref: [ '1.9.26', '2.0.12' ] fail-fast: false runs-on: ${{ matrix.os }} container: