From 427d18fac4044fffb213eb9953c2d57ba54fb821 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 1 Oct 2026 18:35:25 -0700 Subject: [PATCH 1/2] fix(cipher): GCM tag callback writes the tag GnuTLS calls the provider's tag callback to get the tag it then compares with the received one. wolfssl_cipher_aead_decrypt() set ctx->enc to 0, and on such a handle the callback copied the tag FROM the caller's buffer instead of writing it. After a one-shot gnutls_aead_cipher_decrypt(), a gnutls_aead_cipher_decryptv2() with no ciphertext therefore accepted any tag over any AAD (an all-zero tag over attacker AAD verified) and rejected the correct one, and encryptv/encryptv2 on empty plaintext returned the caller's buffer as the tag. The callback now always writes the tag: the tag of the data processed or, with no data, the tag over the AAD alone. The tag ends the message, so a handle reused without a new IV does not return it again. If it cannot compute one it writes random bytes (gnutls_rnd, then getrandom), which no received tag will match, and aborts if neither gives random bytes. aead_decrypt no longer changes ctx->enc, and the "tag set externally" state is removed. New test test_aesgcm_tag_state, values from pyca/cryptography AESGCM. --- wolfssl-gnutls-wrapper/src/cipher.c | 133 ++++++++---------- wolfssl-gnutls-wrapper/tests/Makefile | 2 +- .../tests/test_aesgcm_tag_state.c | 101 +++++++++++++ 3 files changed, 161 insertions(+), 75 deletions(-) create mode 100644 wolfssl-gnutls-wrapper/tests/test_aesgcm_tag_state.c diff --git a/wolfssl-gnutls-wrapper/src/cipher.c b/wolfssl-gnutls-wrapper/src/cipher.c index d980667..761f791 100644 --- a/wolfssl-gnutls-wrapper/src/cipher.c +++ b/wolfssl-gnutls-wrapper/src/cipher.c @@ -1,6 +1,8 @@ #include #include "gnutls_compat.h" #include "logging.h" +#include +#include #include "mac.h" #include @@ -112,8 +114,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 +654,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; @@ -1042,31 +1041,28 @@ 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. + * 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; /* 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, @@ -1132,68 +1128,59 @@ 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; + /* The tag is output only: gnutls compares it with the received tag after + * a decrypt, so it must always be written. When it cannot be computed it + * is filled with random bytes, which no received tag will match. */ 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) { + /* Tag of the data encrypted or decrypted. */ + ret = 0; + } else { unsigned char *aad = ctx->auth_data_heap ? ctx->auth_data_heap : ctx->auth_data_static; - /* Do authentication with no plaintext. */ + /* No data: the tag is over the authentication data alone, the + * same for encryption and decryption. */ + 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"); + /* The tag ends this message: a handle reused without a new IV must + * not return it again. */ + ctx->tag_set = 0; + /* 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; + } + + 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) && + (getrandom(tag, tag_size, 0) != (ssize_t)tag_size)) { + /* No random tag possible: never return one a sender could match. */ + WGW_ERROR("no random bytes for the tag"); + abort(); } } @@ -1632,8 +1619,6 @@ int wolfssl_cipher_aead_decrypt(void *_ctx, const void *nonce, return GNUTLS_E_SHORT_MEMORY_BUFFER; } - ctx->enc = 0; - /* Encrypted size includes tag. */ encr_size -= tag_size; diff --git a/wolfssl-gnutls-wrapper/tests/Makefile b/wolfssl-gnutls-wrapper/tests/Makefile index f7fe39d..098de49 100644 --- a/wolfssl-gnutls-wrapper/tests/Makefile +++ b/wolfssl-gnutls-wrapper/tests/Makefile @@ -1,4 +1,4 @@ -TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv +TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv test_aesgcm_tag_state PKGCONF ?= pkg-config UNAME_S := $(shell uname -s) diff --git a/wolfssl-gnutls-wrapper/tests/test_aesgcm_tag_state.c b/wolfssl-gnutls-wrapper/tests/test_aesgcm_tag_state.c new file mode 100644 index 0000000..b34b956 --- /dev/null +++ b/wolfssl-gnutls-wrapper/tests/test_aesgcm_tag_state.c @@ -0,0 +1,101 @@ +/* AES-GCM tag after a one-shot decrypt on the same AEAD handle. + * + * GnuTLS calls the provider's tag callback to obtain the tag it compares with + * the received one. If a one-shot gnutls_aead_cipher_decrypt() leaves the + * handle in a state where the callback takes the tag as input, a following + * gnutls_aead_cipher_decryptv2() with no ciphertext accepts any tag over any + * AAD. + * + * Expected values from pyca/cryptography AESGCM: key 0^16, nonce 01 00^11, + * AAD 'A' x 20; plaintext 'M' x 32, and the empty plaintext. + */ +#include +#include +#include +#include +#include "test_util.h" + +static const unsigned char ct_tag[48] = { + 0x47, 0x60, 0x91, 0xab, 0x06, 0x70, 0x21, 0x40, 0xb7, 0x78, 0x73, 0x0e, + 0x6b, 0x7a, 0x2c, 0x15, 0x7b, 0x67, 0x6e, 0xfc, 0x7a, 0xad, 0x73, 0x27, + 0xe5, 0x86, 0x1c, 0x69, 0x27, 0x63, 0xc0, 0x66, 0x7e, 0x46, 0x77, 0x35, + 0xc6, 0x67, 0x19, 0x2e, 0xc6, 0x94, 0xff, 0xa4, 0x74, 0x3e, 0xc6, 0x96 +}; +static const unsigned char aad_tag[16] = { + 0x0e, 0xea, 0x6f, 0x5e, 0xa5, 0x99, 0xa4, 0x4a, 0xd5, 0xa3, 0x94, 0xfe, + 0xec, 0x67, 0x9d, 0xb9 +}; + +int main(void) +{ + unsigned char key_data[16] = { 0 }, nonce[12] = { 1 }, aad[20], msg[32]; + unsigned char out[48], pt[48], tag[16], zero[16] = { 0 }; + unsigned char evil[] = "attacker header"; + gnutls_datum_t key = { key_data, sizeof(key_data) }; + gnutls_aead_cipher_hd_t h; + giovec_t a = { aad, sizeof(aad) }, e = { evil, sizeof(evil) - 1 }; + size_t len, tag_len; + int ret; + + printf("Testing AES-GCM tags after a one-shot decrypt...\n"); + + memset(aad, 'A', sizeof(aad)); + memset(msg, 'M', sizeof(msg)); + if ((ret = gnutls_global_init()) != 0 || + (ret = gnutls_aead_cipher_init(&h, GNUTLS_CIPHER_AES_128_GCM, &key)) != 0) { + print_gnutls_error("initializing", ret); + return 1; + } + + len = sizeof(out); + ret = gnutls_aead_cipher_encrypt(h, nonce, sizeof(nonce), aad, sizeof(aad), + 16, msg, sizeof(msg), out, &len); + if (ret != 0 || compare_sz("AES-GCM encrypt", out, len, ct_tag, + sizeof(ct_tag)) != 0) { + return 1; + } + + /* One-shot decrypt, then a forged empty message on the same handle. */ + len = sizeof(pt); + ret = gnutls_aead_cipher_decrypt(h, nonce, sizeof(nonce), aad, sizeof(aad), + 16, ct_tag, sizeof(ct_tag), pt, &len); + if (ret != 0) { + print_gnutls_error("one-shot decrypt", ret); + return 1; + } + ret = gnutls_aead_cipher_decryptv2(h, nonce, sizeof(nonce), &e, 1, NULL, 0, + zero, sizeof(zero)); + if (ret == 0) { + printf("FAILURE - forged tag accepted after a one-shot decrypt\n"); + return 1; + } + + /* The correct tag over the AAD alone must still verify. */ + len = sizeof(pt); + gnutls_aead_cipher_decrypt(h, nonce, sizeof(nonce), aad, sizeof(aad), 16, + ct_tag, sizeof(ct_tag), pt, &len); + ret = gnutls_aead_cipher_decryptv2(h, nonce, sizeof(nonce), &a, 1, NULL, 0, + (void *)aad_tag, sizeof(aad_tag)); + if (ret != 0) { + print_gnutls_error("correct tag after a one-shot decrypt", ret); + return 1; + } + + /* An empty encryption after a decrypt returns the tag over the AAD. */ + len = sizeof(pt); + gnutls_aead_cipher_decrypt(h, nonce, sizeof(nonce), aad, sizeof(aad), 16, + ct_tag, sizeof(ct_tag), pt, &len); + memset(tag, 0xaa, sizeof(tag)); + tag_len = sizeof(tag); + ret = gnutls_aead_cipher_encryptv2(h, nonce, sizeof(nonce), &a, 1, NULL, 0, + tag, &tag_len); + if (ret != 0 || compare_sz("AES-GCM tag of the AAD alone", tag, tag_len, + aad_tag, sizeof(aad_tag)) != 0) { + return 1; + } + + gnutls_aead_cipher_deinit(h); + gnutls_global_deinit(); + printf("\nAll AES-GCM tag tests completed successfully!\n"); + return 0; +} From 6452f399aea0fb9b11029c5136e46a601b56c679 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 1 Oct 2026 18:35:25 -0700 Subject: [PATCH 2/2] fix(pk): RSA decrypt2 constant-time exact length gnutls_privkey_decrypt_data2() gives the exact plaintext size. A valid PKCS#1 v1.5 or OAEP message shorter than that decrypted "successfully" with the caller's buffer only partly written, and a failed decryption could leave decrypted bytes in it. TLS RSA key exchange prefills the premaster buffer with random bytes and ignores decrypt2's result, so on any failure the buffer must be left unchanged, or the handshake becomes a padding oracle. decrypt2 now always decrypts into a zeroed scratch buffer and copies to the caller's buffer with a constant-time masked select that writes the plaintext only when its length is exactly the requested size; otherwise it returns GNUTLS_E_DECRYPTION_FAILED, as GnuTLS's own implementation does. The scratch buffer is wiped before return. New test test_rsa_decrypt2_length: 48 bytes into a 48-byte buffer decrypts; 16-, 47- and 64-byte messages and corrupted or truncated ciphertexts fail and leave the prefilled buffer unchanged. The test passes without the provider (GnuTLS's nettle path) as the oracle. --- wolfssl-gnutls-wrapper/src/pk.c | 145 ++++++++++++------ wolfssl-gnutls-wrapper/tests/Makefile | 2 +- .../tests/test_rsa_decrypt2_length.c | 103 +++++++++++++ 3 files changed, 201 insertions(+), 49 deletions(-) create mode 100644 wolfssl-gnutls-wrapper/tests/test_rsa_decrypt2_length.c diff --git a/wolfssl-gnutls-wrapper/src/pk.c b/wolfssl-gnutls-wrapper/src/pk.c index bb0090b..7e228ef 100644 --- a/wolfssl-gnutls-wrapper/src/pk.c +++ b/wolfssl-gnutls-wrapper/src/pk.c @@ -886,6 +886,37 @@ static int wolfssl_pk_encrypt(gnutls_pk_algorithm_t algo, return ret; } +/** + * Finish an RSA decrypt2 (caller-sized output buffer). + * + * The caller's buffer is written only when the decrypted length equals its + * size, by a constant-time masked select; on any failure (padding or length) + * it is left unchanged. TLS RSA key exchange prefills it with a random + * premaster and ignores the error, so a failure must not change it. + * + * @param [in, out] plaintext Caller's buffer and its size. + * @param [in] scratch Decrypted data, at least plaintext->size bytes. + * @param [in] ret Result of the wolfCrypt decryption. + * @return 0 on success. + * @return GNUTLS_E_DECRYPTION_FAILED otherwise. + */ +static int rsa_decrypt2_select(gnutls_datum_t *plaintext, + const unsigned char *scratch, int ret) +{ + unsigned int diff = (unsigned int)ret ^ plaintext->size; + /* All ones when ret equals the size, else zero. */ + 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)((scratch[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. * @@ -909,6 +940,7 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, unsigned char out[1024]; unsigned char *plain; word32 plain_size; + word32 scratch_size = 0; WGW_FUNC_ENTER(); @@ -950,13 +982,24 @@ 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 { + /* decrypt2: never decrypt into the caller's buffer (see + * rsa_decrypt2_select()); scratch holds the key size and the + * caller's size. */ + scratch_size = plain_size > plaintext->size ? plain_size : + plaintext->size; + if (scratch_size <= sizeof(out)) { + plain = out; + } else { + plain = gnutls_malloc(scratch_size); + if (plain == NULL) { + WGW_ERROR("Allocating memory for plaintext"); + wc_FreeRsaKey(&rsa); + return GNUTLS_E_MEMORY_ERROR; + } + } + XMEMSET(plain, 0, scratch_size); } PRIVATE_KEY_UNLOCK(); @@ -969,29 +1012,26 @@ static int wolfssl_pk_decrypt_rsa(gnutls_datum_t *plaintext, /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); + + if (!alloc_plaintext) { + ret = rsa_decrypt2_select(plaintext, plain, ret); + gnutls_memset(plain, 0, scratch_size); + if (plain != out) { + gnutls_free(plain); + } + 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; @@ -1021,6 +1061,7 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, unsigned char out[1024]; unsigned char *plain; word32 plain_size; + word32 scratch_size = 0; WGW_FUNC_ENTER(); @@ -1064,13 +1105,24 @@ 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 { + /* decrypt2: never decrypt into the caller's buffer (see + * rsa_decrypt2_select()); scratch holds the key size and the + * caller's size. */ + scratch_size = plain_size > plaintext->size ? plain_size : + plaintext->size; + if (scratch_size <= sizeof(out)) { + plain = out; + } else { + plain = gnutls_malloc(scratch_size); + if (plain == NULL) { + WGW_ERROR("Allocating memory for plaintext"); + wc_FreeRsaKey(&rsa); + return GNUTLS_E_MEMORY_ERROR; + } + } + XMEMSET(plain, 0, scratch_size); } PRIVATE_KEY_UNLOCK(); @@ -1084,29 +1136,26 @@ static int wolfssl_pk_decrypt_rsa_oaep(gnutls_datum_t *plaintext, /* No longer need RSA key. */ wc_FreeRsaKey(&rsa); + + if (!alloc_plaintext) { + ret = rsa_decrypt2_select(plaintext, plain, ret); + gnutls_memset(plain, 0, scratch_size); + if (plain != out) { + gnutls_free(plain); + } + 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/Makefile b/wolfssl-gnutls-wrapper/tests/Makefile index 098de49..4085680 100644 --- a/wolfssl-gnutls-wrapper/tests/Makefile +++ b/wolfssl-gnutls-wrapper/tests/Makefile @@ -1,4 +1,4 @@ -TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv test_aesgcm_tag_state +TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv test_aesgcm_tag_state test_rsa_decrypt2_length PKGCONF ?= pkg-config UNAME_S := $(shell uname -s) diff --git a/wolfssl-gnutls-wrapper/tests/test_rsa_decrypt2_length.c b/wolfssl-gnutls-wrapper/tests/test_rsa_decrypt2_length.c new file mode 100644 index 0000000..907e4d0 --- /dev/null +++ b/wolfssl-gnutls-wrapper/tests/test_rsa_decrypt2_length.c @@ -0,0 +1,103 @@ +/* gnutls_privkey_decrypt_data2() gives the exact plaintext size. A message of + * any other length, or a ciphertext that does not decrypt, must fail and leave + * the caller's buffer unchanged: TLS RSA key exchange prefills it with a + * random premaster and ignores the error. + */ +#include +#include +#include +#include +#include "test_util.h" + +#define BUF_LEN 48 + +/* How the ciphertext is damaged before decryption. */ +#define CT_INTACT 0 +#define CT_CORRUPT 1 +#define CT_TRUNCATE 2 + +static int decrypt_len(gnutls_privkey_t priv, gnutls_pubkey_t pub, + size_t msg_len, int damage, int expect_ok) +{ + unsigned char msg[64], buf[BUF_LEN], prefill[BUF_LEN]; + gnutls_datum_t m = { msg, msg_len }, c = { NULL, 0 }; + size_t i; + int ret; + + memset(msg, 'P', sizeof(msg)); + ret = gnutls_pubkey_encrypt_data(pub, 0, &m, &c); + if (ret != 0) { + print_gnutls_error("encrypting", ret); + return 1; + } + if (damage == CT_CORRUPT) { + c.data[c.size / 2] ^= 0x01; + } else if (damage == CT_TRUNCATE) { + c.size--; + } + + for (i = 0; i < sizeof(prefill); i++) { + prefill[i] = (unsigned char)(0xa0 + i); + } + memcpy(buf, prefill, sizeof(buf)); + ret = gnutls_privkey_decrypt_data2(priv, 0, &c, buf, sizeof(buf)); + gnutls_free(c.data); + printf("message %zu bytes%s into %d-byte buffer: %s\n", msg_len, + damage == CT_CORRUPT ? " (corrupted)" : + damage == CT_TRUNCATE ? " (truncated)" : "", BUF_LEN, + ret == 0 ? "decrypted" : gnutls_strerror(ret)); + + if (expect_ok) { + if (ret != 0) { + printf("FAILURE - exact-length message did not decrypt\n"); + return 1; + } + if (memcmp(buf, msg, sizeof(buf)) != 0) { + printf("FAILURE - decrypted data does not match\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; +} + +int main(void) +{ + gnutls_privkey_t priv; + gnutls_pubkey_t pub; + int ret; + + printf("Testing RSA decrypt_data2 with a mismatched length...\n"); + + if ((ret = gnutls_global_init()) != 0 || + (ret = gnutls_privkey_init(&priv)) != 0 || + (ret = gnutls_privkey_generate(priv, GNUTLS_PK_RSA, 2048, 0)) != 0 || + (ret = gnutls_pubkey_init(&pub)) != 0 || + (ret = gnutls_pubkey_import_privkey(pub, priv, 0, 0)) != 0) { + print_gnutls_error("setting up the key", ret); + return 1; + } + + if (decrypt_len(priv, pub, BUF_LEN, CT_INTACT, 1) != 0 || + decrypt_len(priv, pub, 16, CT_INTACT, 0) != 0 || + decrypt_len(priv, pub, 47, CT_INTACT, 0) != 0 || + decrypt_len(priv, pub, 64, CT_INTACT, 0) != 0 || + decrypt_len(priv, pub, BUF_LEN, CT_CORRUPT, 0) != 0 || + decrypt_len(priv, pub, BUF_LEN, CT_TRUNCATE, 0) != 0) { + return 1; + } + + gnutls_pubkey_deinit(pub); + gnutls_privkey_deinit(priv); + gnutls_global_deinit(); + printf("\nAll RSA decrypt_data2 tests completed successfully!\n"); + return 0; +}