From 5d613c3c13e8613648fd33199a3b41ac7756e466 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 10 Nov 2026 12:01:44 -0800 Subject: [PATCH 1/2] Add opt-in single-update AES-GCM for non-streaming builds Callers that set wolfprov-gcm-single-update pass the complete payload in one Update, with the expected tag set first when decrypting. wolfProvider then calls wolfCrypt one-shot instead of caching and replaying the payload. Ordinary callers are unchanged. --- src/wp_aes_aead.c | 173 +++++++++++++++++++++++++++++++++++++++++++-- test/test_aestag.c | 112 +++++++++++++++++++++++++++++ test/unit.c | 3 + test/unit.h | 3 + 4 files changed, 287 insertions(+), 4 deletions(-) diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index f4f9018d..c467120b 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -42,6 +42,9 @@ #define WP_AES_GCM_EXTRA_BUF_LEN 128 #endif +/* Opt-in for one complete GCM payload per Update. */ +#define WP_PARAM_GCM_SINGLE_UPDATE "wolfprov-gcm-single-update" + /** * Authenticated Encryption with Associated Data structure. */ @@ -112,6 +115,9 @@ typedef struct wp_AeadCtx { size_t bufSize; /* Original IV */ unsigned char origIv[AES_BLOCK_SIZE]; + unsigned int singleUpdate:1; + unsigned int singleDone:1; + unsigned int singleTagSet:1; #endif } wp_AeadCtx; @@ -413,6 +419,18 @@ static int wp_aead_get_ctx_params(wp_AeadCtx* ctx, OSSL_PARAM params[]) WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aead_get_ctx_params"); +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + p = OSSL_PARAM_locate(params, WP_PARAM_GCM_SINGLE_UPDATE); + if (p != NULL && (ctx->mode != EVP_CIPH_GCM_MODE || + !OSSL_PARAM_set_int(p, ctx->singleUpdate))) { + return 0; + } + if (ctx->singleUpdate && OSSL_PARAM_locate(params, + OSSL_CIPHER_PARAM_AEAD_TLS1_GET_IV_GEN) != NULL) { + return 0; + } +#endif + p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN); if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->ivLen)) { ok = 0; @@ -540,6 +558,9 @@ static int wp_aead_set_param_tag(wp_AeadCtx* ctx, if (ok) { ctx->tagLen = sz; } +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + ctx->singleTagSet = ok && (p->data != NULL); +#endif WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -696,17 +717,49 @@ static int wp_aead_set_param_tls1_iv_rand(wp_AeadCtx* ctx, static int wp_aead_set_ctx_params(wp_AeadCtx* ctx, const OSSL_PARAM params[]) { int ok = 1; +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + /* With the opt-in, fail on the first rejected parameter. */ + int singleParams = ctx->singleUpdate || (params != NULL && + OSSL_PARAM_locate_const(params, WP_PARAM_GCM_SINGLE_UPDATE) != NULL); +#endif WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aead_set_ctx_params"); while ((params != NULL) && (params->key != NULL)) { +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + if (ctx->singleUpdate && + (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_AAD) == 0 || + XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_IV_FIXED) == 0 || + XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_SET_IV_INV) == 0)) { + return 0; + } +#endif /* params->key is a NUL-terminated provider parameter name. Use * XSTRCMP so we never read past the end of the caller's key when * it is shorter than the target constant (e.g. "tlsivinv" vs * "tlsivfixed"). */ if (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TAG) == 0) { +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + if (ctx->singleUpdate && (ctx->singleDone || ctx->authErr)) { + return 0; + } +#endif ok = wp_aead_set_param_tag(ctx, params); } +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + else if (XSTRCMP(params->key, WP_PARAM_GCM_SINGLE_UPDATE) == 0) { + int enabled; + if (ctx->mode != EVP_CIPH_GCM_MODE || + !OSSL_PARAM_get_int(params, &enabled) || + (enabled != 0 && enabled != 1) || ctx->inLen != 0 || + ctx->singleDone || ctx->authErr || + ctx->tlsAadLen != UNINITIALISED_SIZET || ctx->ivGen || + ctx->ivState != IV_STATE_BUFFERED) { + return 0; + } + ctx->singleUpdate = enabled; + } +#endif else if (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_IVLEN) == 0) { ok = wp_aead_set_param_iv_len(ctx, params); } @@ -723,6 +776,11 @@ static int wp_aead_set_ctx_params(wp_AeadCtx* ctx, const OSSL_PARAM params[]) ok = wp_aead_set_param_tls1_iv_rand(ctx, params); } +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + if (!ok && singleParams) { + return 0; + } +#endif params++; } @@ -837,6 +895,9 @@ static const OSSL_PARAM *wp_aead_gettable_ctx_params(wp_AeadCtx* ctx, * Supported gettable parameters for AEAD context. */ static const OSSL_PARAM wp_aead_supported_gettable_ctx_params[] = { +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + OSSL_PARAM_int(WP_PARAM_GCM_SINGLE_UPDATE, NULL), +#endif OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL), OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, NULL), OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_AEAD_TAGLEN, NULL), @@ -852,6 +913,12 @@ static const OSSL_PARAM *wp_aead_gettable_ctx_params(wp_AeadCtx* ctx, }; (void)ctx; (void)provCtx; +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + /* The first descriptor requires a GCM context. */ + if (ctx == NULL || ctx->mode != EVP_CIPH_GCM_MODE) { + return wp_aead_supported_gettable_ctx_params + 1; + } +#endif return wp_aead_supported_gettable_ctx_params; } @@ -869,6 +936,9 @@ static const OSSL_PARAM *wp_aead_settable_ctx_params(wp_AeadCtx* ctx, * Supported settable parameters for AEAD context. */ static const OSSL_PARAM wp_aead_supported_settable_ctx_params[] = { +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + OSSL_PARAM_int(WP_PARAM_GCM_SINGLE_UPDATE, NULL), +#endif OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN, NULL), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, NULL, 0), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TLS1_AAD, NULL, 0), @@ -881,6 +951,12 @@ static const OSSL_PARAM *wp_aead_settable_ctx_params(wp_AeadCtx* ctx, }; (void)ctx; (void)provCtx; +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + /* The first descriptor requires a GCM context. */ + if (ctx == NULL || ctx->mode != EVP_CIPH_GCM_MODE) { + return wp_aead_supported_settable_ctx_params + 1; + } +#endif return wp_aead_supported_settable_ctx_params; } @@ -908,6 +984,9 @@ static void wp_aead_reset_op_state(wp_AeadCtx* ctx) } ctx->inLen = 0; ctx->bufSize = 0; + ctx->singleUpdate = 0; + ctx->singleDone = 0; + ctx->singleTagSet = 0; #endif } #endif @@ -1566,6 +1645,58 @@ static int wp_aesgcm_stream_final(wp_AeadCtx *ctx, unsigned char *out, #else +/* Decryption requires the expected tag before the complete payload. */ +static int wp_aesgcm_single_update(wp_AeadCtx *ctx, unsigned char *out, + size_t *outLen, size_t outSize, const unsigned char *in, size_t inLen) +{ + int rc; + byte empty = 0; + + *outLen = 0; + if (ctx->singleDone || ctx->authErr || !ctx->keySet || + ctx->ivState != IV_STATE_BUFFERED || !WP_FITS_WORD32(inLen) || + !WP_FITS_WORD32(ctx->aadLen) || outSize < inLen || + (inLen != 0 && (in == NULL || out == NULL)) || + (!ctx->enc && !ctx->singleTagSet)) { + ctx->authErr = 1; + ctx->ivState = IV_STATE_FINISHED; + return 0; + } + if (ctx->tagLen == UNINITIALISED_SIZET) { + ctx->tagLen = EVP_GCM_TLS_TAG_LEN; + } + ctx->singleDone = 1; + XMEMCPY(ctx->origIv, ctx->iv, ctx->ivLen); + ctx->ivState = IV_STATE_COPIED; + rc = wc_AesGcmSetExtIV(&ctx->aes, ctx->origIv, (word32)ctx->ivLen); + if (rc == 0) { + if (ctx->enc) { + ctx->ivSet = 1; + rc = wc_AesGcmEncrypt_ex(&ctx->aes, out != NULL ? out : &empty, + in != NULL ? in : &empty, (word32)inLen, ctx->origIv, + (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, + ctx->aad, (word32)ctx->aadLen); + ctx->tagAvail = (rc == 0); + } + else { + rc = wc_AesGcmDecrypt(&ctx->aes, out != NULL ? out : &empty, + in != NULL ? in : &empty, (word32)inLen, ctx->origIv, + (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, + ctx->aad, (word32)ctx->aadLen); + } + } + if (rc != 0) { + if (out != NULL && inLen != 0) { + OPENSSL_cleanse(out, inLen); + } + ctx->authErr = 1; + ctx->ivState = IV_STATE_FINISHED; + return 0; + } + *outLen = inLen; + return 1; +} + /** * Encrypt/decrypt the data using AES-GCM. * @@ -1739,6 +1870,10 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, if (ctx->ivState == IV_STATE_FINISHED) { ok = 0; } + else if (ctx->singleUpdate && ctx->singleDone && inLen != 0) { + ctx->authErr = 1; + ok = 0; + } else if ((out == NULL) && (in == NULL)) { /* Nothing to do. */ oLen = (word32)inLen; @@ -1754,12 +1889,18 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, ok = 0; } else if (inLen > 0) { - curLen = ctx->inLen; - if (!wp_aead_cache_in(ctx, in, inLen)) { - ok = 0; + if (ctx->singleUpdate) { + return wp_aesgcm_single_update(ctx, out, outLen, outSize, + in, inLen); } else { - process = 1; + curLen = ctx->inLen; + if (!wp_aead_cache_in(ctx, in, inLen)) { + ok = 0; + } + else { + process = 1; + } } } @@ -1770,6 +1911,11 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, else { *outLen = oLen; } + if (!ok && ctx->singleUpdate) { + *outLen = 0; + ctx->authErr = 1; + ctx->ivState = IV_STATE_FINISHED; + } } WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -1794,12 +1940,31 @@ static int wp_aesgcm_stream_final(wp_AeadCtx *ctx, unsigned char *out, WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aesgcm_stream_final"); + /* authErr can bypass the single-update Final branch. */ + if (ctx->singleUpdate) { + *outLen = 0; + } + if (ctx->tlsAadLen != UNINITIALISED_SIZET) { ok = wp_aesgcm_tls_cipher(ctx, out, outLen, NULL, 0); } else if (ctx->authErr) { ok = 0; } + else if (ctx->singleUpdate) { + if (ctx->ivState == IV_STATE_FINISHED) { + ok = 0; + } + else if (!ctx->singleDone) { + ok = wp_aesgcm_single_update(ctx, NULL, outLen, 0, NULL, 0); + } + OPENSSL_free(ctx->aad); + ctx->aad = NULL; + ctx->aadLen = 0; + ctx->aadSet = 0; + ctx->ivSet = 0; + ctx->ivState = IV_STATE_FINISHED; + } else { ok = wp_aesgcm_encdec(ctx, out, outLen, ctx->inLen, 1); ctx->ivSet = 0; diff --git a/test/test_aestag.c b/test/test_aestag.c index ab639e0c..d8207c98 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -2510,6 +2510,118 @@ int test_aes_gcm_final_no_update_no_iv(void *data) return err; } +#ifndef WOLFSSL_AESGCM_STREAM + +static EVP_CIPHER_CTX *test_aes_gcm_single_ctx(EVP_CIPHER *cipher, int enc, + unsigned char *key, unsigned char *iv, unsigned char *aad, int aadLen) +{ + EVP_CIPHER_CTX *ctx; + OSSL_PARAM params[2]; + int on = 1; + int len = 0; + + params[0] = OSSL_PARAM_construct_int("wolfprov-gcm-single-update", &on); + params[1] = OSSL_PARAM_construct_end(); + + ctx = EVP_CIPHER_CTX_new(); + if ((ctx != NULL) && + ((EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc) != 1) || + (EVP_CIPHER_CTX_set_params(ctx, params) != 1) || + (EVP_CipherUpdate(ctx, NULL, &len, aad, aadLen) != 1))) { + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + } + return ctx; +} + +static int test_aes_gcm_single_dec(EVP_CIPHER *cipher, unsigned char *key, + unsigned char *iv, unsigned char *aad, int aadLen, unsigned char *ct, + int len, unsigned char *tag, unsigned char *pt) +{ + EVP_CIPHER_CTX *ctx; + unsigned char dec[64]; + int decLen = 0, fLen = 0; + int ok; + + ctx = test_aes_gcm_single_ctx(cipher, 0, key, iv, aad, aadLen); + ok = (ctx != NULL) && + (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, tag) == 1) && + (EVP_DecryptUpdate(ctx, dec, &decLen, ct, len) == 1) && + (EVP_DecryptFinal_ex(ctx, dec + decLen, &fLen) == 1) && + (memcmp(dec, pt, len) == 0); + EVP_CIPHER_CTX_free(ctx); + return ok; +} + +int test_aes_gcm_single_update(void *data) +{ + int err; + EVP_CIPHER *ossl; + EVP_CIPHER *wp; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[16]; + unsigned char iv[12]; + unsigned char aad[] = "additional data"; + int aadLen = (int)(sizeof(aad) - 1); + unsigned char pt[] = "GCM plaintext for single update test"; + int ptLen = (int)(sizeof(pt) - 1); + unsigned char refCt[64]; + unsigned char refTag[16]; + unsigned char ct[64]; + unsigned char tag[16]; + int len = 0, fLen = 0; + + (void)data; + + memset(key, 0xAA, sizeof(key)); + memset(iv, 0xBB, sizeof(iv)); + + ossl = EVP_CIPHER_fetch(osslLibCtx, "AES-128-GCM", ""); + wp = EVP_CIPHER_fetch(wpLibCtx, "AES-128-GCM", ""); + err = (ossl == NULL) || (wp == NULL); + if (err == 0) { + err = test_aes_tag_enc(ossl, key, iv, sizeof(iv), aad, pt, ptLen, + refCt, refTag, 0, 0); + } + + if (err == 0) { + PRINT_MSG("Single-update encrypt matches OpenSSL"); + ctx = test_aes_gcm_single_ctx(wp, 1, key, iv, aad, aadLen); + err = (ctx == NULL) || + (EVP_EncryptUpdate(ctx, ct, &len, pt, ptLen) != 1) || + (EVP_EncryptFinal_ex(ctx, ct + len, &fLen) != 1) || + (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) || + (memcmp(ct, refCt, ptLen) != 0) || (memcmp(tag, refTag, 16) != 0); + EVP_CIPHER_CTX_free(ctx); + } + if (err == 0) { + PRINT_MSG("Single-update rejects a second payload Update"); + ctx = test_aes_gcm_single_ctx(wp, 1, key, iv, aad, aadLen); + err = (ctx == NULL) || + (EVP_EncryptUpdate(ctx, ct, &len, pt, 16) != 1) || + (EVP_EncryptUpdate(ctx, ct + 16, &len, pt + 16, + ptLen - 16) == 1); + EVP_CIPHER_CTX_free(ctx); + } + if (err == 0) { + PRINT_MSG("Single-update decrypt with early tag"); + err = !test_aes_gcm_single_dec(wp, key, iv, aad, aadLen, refCt, ptLen, + refTag, pt); + } + if (err == 0) { + PRINT_MSG("Single-update decrypt rejects a bad tag"); + refTag[0] ^= 0x01; + err = test_aes_gcm_single_dec(wp, key, iv, aad, aadLen, refCt, ptLen, + refTag, pt); + } + + EVP_CIPHER_free(wp); + EVP_CIPHER_free(ossl); + return err; +} + +#endif /* !WOLFSSL_AESGCM_STREAM */ + #endif /* WP_HAVE_AESGCM */ /******************************************************************************/ diff --git a/test/unit.c b/test/unit.c index be7f72a2..453fa72d 100644 --- a/test/unit.c +++ b/test/unit.c @@ -337,6 +337,9 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes_gcm_key_no_iv, NULL), TEST_DECL(test_aes_gcm_stream_key_no_iv, NULL), TEST_DECL(test_aes_gcm_final_no_update_no_iv, NULL), +#ifndef WOLFSSL_AESGCM_STREAM + TEST_DECL(test_aes_gcm_single_update, NULL), +#endif #endif #ifdef WP_HAVE_AESCCM TEST_DECL(test_aes128_ccm, NULL), diff --git a/test/unit.h b/test/unit.h index 35bc245b..5a0ce806 100644 --- a/test/unit.h +++ b/test/unit.h @@ -298,6 +298,9 @@ int test_aes_gcm_tag_len_undersized(void *data); int test_aes_gcm_key_no_iv(void *data); int test_aes_gcm_stream_key_no_iv(void *data); int test_aes_gcm_final_no_update_no_iv(void *data); +#ifndef WOLFSSL_AESGCM_STREAM +int test_aes_gcm_single_update(void *data); +#endif #endif /* WP_HAVE_AESGCM */ From 6f3345341518c87dfeb7b32e34d48fe20ba9e1e8 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 10 Nov 2026 12:57:14 -0800 Subject: [PATCH 2/2] Address review feedback on single-update AES-GCM Move WP_PARAM_GCM_SINGLE_UPDATE to wp_wolfprov.h, follow the ok/WOLFPROV_LEAVE pattern without early returns, document the new fields and function, and test an empty Final and decrypt without a tag on a reused context. --- include/wolfprovider/wp_wolfprov.h | 4 + src/wp_aes_aead.c | 208 +++++++++++++++++++---------- test/test_aestag.c | 46 +++++-- 3 files changed, 179 insertions(+), 79 deletions(-) diff --git a/include/wolfprovider/wp_wolfprov.h b/include/wolfprovider/wp_wolfprov.h index 172dacd5..405077f7 100644 --- a/include/wolfprovider/wp_wolfprov.h +++ b/include/wolfprovider/wp_wolfprov.h @@ -30,4 +30,8 @@ int wolfssl_prov_bio_up_ref(OSSL_CORE_BIO *bio); /* Prototype of public function that initializes the wolfSSL provider. */ OSSL_provider_init_fn wolfssl_provider_init; +/* AES-GCM context parameter (int) on non-streaming builds: 1 opts in to one + * complete payload per operation, with the tag set first when decrypting. */ +#define WP_PARAM_GCM_SINGLE_UPDATE "wolfprov-gcm-single-update" + #endif /* WP_WOLFPROV_H */ diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index c467120b..ea1acb58 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -27,6 +27,7 @@ #include #include +#include #include @@ -42,9 +43,6 @@ #define WP_AES_GCM_EXTRA_BUF_LEN 128 #endif -/* Opt-in for one complete GCM payload per Update. */ -#define WP_PARAM_GCM_SINGLE_UPDATE "wolfprov-gcm-single-update" - /** * Authenticated Encryption with Associated Data structure. */ @@ -115,8 +113,11 @@ typedef struct wp_AeadCtx { size_t bufSize; /* Original IV */ unsigned char origIv[AES_BLOCK_SIZE]; + /** Caller opted in to one complete payload per operation. */ unsigned int singleUpdate:1; + /** Single-update payload has been processed. */ unsigned int singleDone:1; + /** Expected tag set for single-update decryption. */ unsigned int singleTagSet:1; #endif } wp_AeadCtx; @@ -421,19 +422,21 @@ static int wp_aead_get_ctx_params(wp_AeadCtx* ctx, OSSL_PARAM params[]) #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) p = OSSL_PARAM_locate(params, WP_PARAM_GCM_SINGLE_UPDATE); - if (p != NULL && (ctx->mode != EVP_CIPH_GCM_MODE || - !OSSL_PARAM_set_int(p, ctx->singleUpdate))) { - return 0; + if ((p != NULL) && ((ctx->mode != EVP_CIPH_GCM_MODE) || + (!OSSL_PARAM_set_int(p, ctx->singleUpdate)))) { + ok = 0; } - if (ctx->singleUpdate && OSSL_PARAM_locate(params, - OSSL_CIPHER_PARAM_AEAD_TLS1_GET_IV_GEN) != NULL) { - return 0; + if (ok && ctx->singleUpdate && (OSSL_PARAM_locate(params, + OSSL_CIPHER_PARAM_AEAD_TLS1_GET_IV_GEN) != NULL)) { + ok = 0; } #endif - p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN); - if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->ivLen)) { - ok = 0; + if (ok) { + p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN); + if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->ivLen)) { + ok = 0; + } } if (ok) { p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN); @@ -706,6 +709,40 @@ static int wp_aead_set_param_tls1_iv_rand(wp_AeadCtx* ctx, #endif } +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) +/** + * Set the single-update opt-in from the parameters. + * + * @param [in, out] ctx AEAD context object. + * @param [in] params Array of parameters and values. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_aead_set_param_single_update(wp_AeadCtx* ctx, + const OSSL_PARAM params[]) +{ + int ok = 1; + int enabled = 0; + + WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aead_set_param_single_update"); + + if ((ctx->mode != EVP_CIPH_GCM_MODE) || + (!OSSL_PARAM_get_int(params, &enabled)) || + ((enabled != 0) && (enabled != 1)) || (ctx->inLen != 0) || + ctx->singleDone || ctx->authErr || + (ctx->tlsAadLen != UNINITIALISED_SIZET) || ctx->ivGen || + (ctx->ivState != IV_STATE_BUFFERED)) { + ok = 0; + } + if (ok) { + ctx->singleUpdate = enabled; + } + + WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; +} +#endif + /** * Set the AEAD context parameters. * @@ -731,7 +768,8 @@ static int wp_aead_set_ctx_params(wp_AeadCtx* ctx, const OSSL_PARAM params[]) (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_AAD) == 0 || XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_IV_FIXED) == 0 || XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TLS1_SET_IV_INV) == 0)) { - return 0; + ok = 0; + break; } #endif /* params->key is a NUL-terminated provider parameter name. Use @@ -741,23 +779,15 @@ static int wp_aead_set_ctx_params(wp_AeadCtx* ctx, const OSSL_PARAM params[]) if (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_TAG) == 0) { #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) if (ctx->singleUpdate && (ctx->singleDone || ctx->authErr)) { - return 0; + ok = 0; + break; } #endif ok = wp_aead_set_param_tag(ctx, params); } #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) else if (XSTRCMP(params->key, WP_PARAM_GCM_SINGLE_UPDATE) == 0) { - int enabled; - if (ctx->mode != EVP_CIPH_GCM_MODE || - !OSSL_PARAM_get_int(params, &enabled) || - (enabled != 0 && enabled != 1) || ctx->inLen != 0 || - ctx->singleDone || ctx->authErr || - ctx->tlsAadLen != UNINITIALISED_SIZET || ctx->ivGen || - ctx->ivState != IV_STATE_BUFFERED) { - return 0; - } - ctx->singleUpdate = enabled; + ok = wp_aead_set_param_single_update(ctx, params); } #endif else if (XSTRCMP(params->key, OSSL_CIPHER_PARAM_AEAD_IVLEN) == 0) { @@ -778,7 +808,7 @@ static int wp_aead_set_ctx_params(wp_AeadCtx* ctx, const OSSL_PARAM params[]) #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) if (!ok && singleParams) { - return 0; + break; } #endif params++; @@ -884,7 +914,7 @@ static int wp_aead_get_params(OSSL_PARAM params[], unsigned int md, /** * Return an array of supported gettable parameters for the AEAD context. * - * @param [in] ctx AEAD context object. Unused. + * @param [in] ctx AEAD context object. * @param [in] provCtx Provider context object. Unused. * @return Array of parameters with data type. */ @@ -911,21 +941,23 @@ static const OSSL_PARAM *wp_aead_gettable_ctx_params(wp_AeadCtx* ctx, #endif OSSL_PARAM_END }; + const OSSL_PARAM* ret = wp_aead_supported_gettable_ctx_params; + (void)ctx; (void)provCtx; #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) /* The first descriptor requires a GCM context. */ - if (ctx == NULL || ctx->mode != EVP_CIPH_GCM_MODE) { - return wp_aead_supported_gettable_ctx_params + 1; + if ((ctx == NULL) || (ctx->mode != EVP_CIPH_GCM_MODE)) { + ret++; } #endif - return wp_aead_supported_gettable_ctx_params; + return ret; } /** * Return an array of supported settable parameters for the AEAD context. * - * @param [in] ctx AEAD context object. Unused. + * @param [in] ctx AEAD context object. * @param [in] provCtx Provider context object. Unused. * @return Array of parameters with data type. */ @@ -949,15 +981,17 @@ static const OSSL_PARAM *wp_aead_settable_ctx_params(wp_AeadCtx* ctx, #endif OSSL_PARAM_END }; + const OSSL_PARAM* ret = wp_aead_supported_settable_ctx_params; + (void)ctx; (void)provCtx; #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) /* The first descriptor requires a GCM context. */ - if (ctx == NULL || ctx->mode != EVP_CIPH_GCM_MODE) { - return wp_aead_supported_settable_ctx_params + 1; + if ((ctx == NULL) || (ctx->mode != EVP_CIPH_GCM_MODE)) { + ret++; } #endif - return wp_aead_supported_settable_ctx_params; + return ret; } #if defined(WP_HAVE_AESGCM) || defined(WP_HAVE_AESCCM) @@ -1645,56 +1679,86 @@ static int wp_aesgcm_stream_final(wp_AeadCtx *ctx, unsigned char *out, #else -/* Decryption requires the expected tag before the complete payload. */ +/** + * Encrypt/decrypt a complete AES-GCM payload in one call. + * + * Decryption requires the expected tag before the payload. + * + * @param [in, out] ctx AEAD context object. + * @param [out] out Buffer to hold encrypted/decrypted data. + * @param [out] outLen Length of data in output buffer. + * @param [in] outSize Size of output buffer in bytes. + * @param [in] in Data to be encrypted/decrypted. + * @param [in] inLen Length of data to be encrypted/decrypted. + * @return 1 on success. + * @return 0 on failure. + */ static int wp_aesgcm_single_update(wp_AeadCtx *ctx, unsigned char *out, size_t *outLen, size_t outSize, const unsigned char *in, size_t inLen) { - int rc; + int ok = 1; + int rc = 0; byte empty = 0; + WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aesgcm_single_update"); + *outLen = 0; - if (ctx->singleDone || ctx->authErr || !ctx->keySet || - ctx->ivState != IV_STATE_BUFFERED || !WP_FITS_WORD32(inLen) || - !WP_FITS_WORD32(ctx->aadLen) || outSize < inLen || - (inLen != 0 && (in == NULL || out == NULL)) || - (!ctx->enc && !ctx->singleTagSet)) { - ctx->authErr = 1; - ctx->ivState = IV_STATE_FINISHED; - return 0; + if (ctx->singleDone || ctx->authErr || (!ctx->keySet) || + (ctx->ivState != IV_STATE_BUFFERED) || (!WP_FITS_WORD32(inLen)) || + (!WP_FITS_WORD32(ctx->aadLen)) || (outSize < inLen) || + ((inLen != 0) && ((in == NULL) || (out == NULL))) || + ((!ctx->enc) && (!ctx->singleTagSet))) { + ok = 0; } - if (ctx->tagLen == UNINITIALISED_SIZET) { - ctx->tagLen = EVP_GCM_TLS_TAG_LEN; + if (ok) { + if (ctx->tagLen == UNINITIALISED_SIZET) { + ctx->tagLen = EVP_GCM_TLS_TAG_LEN; + } + ctx->singleDone = 1; + XMEMCPY(ctx->origIv, ctx->iv, ctx->ivLen); + ctx->ivState = IV_STATE_COPIED; + rc = wc_AesGcmSetExtIV(&ctx->aes, ctx->origIv, (word32)ctx->ivLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmSetExtIV", rc); + ok = 0; + } } - ctx->singleDone = 1; - XMEMCPY(ctx->origIv, ctx->iv, ctx->ivLen); - ctx->ivState = IV_STATE_COPIED; - rc = wc_AesGcmSetExtIV(&ctx->aes, ctx->origIv, (word32)ctx->ivLen); - if (rc == 0) { - if (ctx->enc) { - ctx->ivSet = 1; - rc = wc_AesGcmEncrypt_ex(&ctx->aes, out != NULL ? out : &empty, - in != NULL ? in : &empty, (word32)inLen, ctx->origIv, - (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, - ctx->aad, (word32)ctx->aadLen); - ctx->tagAvail = (rc == 0); + if (ok && ctx->enc) { + ctx->ivSet = 1; + rc = wc_AesGcmEncrypt_ex(&ctx->aes, (out != NULL) ? out : &empty, + (in != NULL) ? in : &empty, (word32)inLen, ctx->origIv, + (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, + ctx->aad, (word32)ctx->aadLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmEncrypt_ex", rc); + ok = 0; } - else { - rc = wc_AesGcmDecrypt(&ctx->aes, out != NULL ? out : &empty, - in != NULL ? in : &empty, (word32)inLen, ctx->origIv, - (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, - ctx->aad, (word32)ctx->aadLen); + ctx->tagAvail = ok; + } + else if (ok) { + rc = wc_AesGcmDecrypt(&ctx->aes, (out != NULL) ? out : &empty, + (in != NULL) ? in : &empty, (word32)inLen, ctx->origIv, + (word32)ctx->ivLen, ctx->buf, (word32)ctx->tagLen, + ctx->aad, (word32)ctx->aadLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmDecrypt", rc); + ok = 0; } } - if (rc != 0) { - if (out != NULL && inLen != 0) { + + if (ok) { + *outLen = inLen; + } + else { + if ((rc != 0) && (out != NULL) && (inLen != 0)) { OPENSSL_cleanse(out, inLen); } ctx->authErr = 1; ctx->ivState = IV_STATE_FINISHED; - return 0; } - *outLen = inLen; - return 1; + + WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; } /** @@ -1855,6 +1919,7 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, { int ok = 1; int process = 0; + int single = 0; size_t curLen = 0; WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aesgcm_stream_update"); @@ -1890,8 +1955,7 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, } else if (inLen > 0) { if (ctx->singleUpdate) { - return wp_aesgcm_single_update(ctx, out, outLen, outSize, - in, inLen); + single = 1; } else { curLen = ctx->inLen; @@ -1904,8 +1968,12 @@ static int wp_aesgcm_stream_update(wp_AeadCtx *ctx, unsigned char *out, } } + if (single) { + ok = wp_aesgcm_single_update(ctx, out, outLen, outSize, in, + inLen); + } /* If there is data to process, do it now */ - if (process) { + else if (process) { ok = wp_aesgcm_encdec(ctx, out, outLen, curLen, 0); } else { diff --git a/test/test_aestag.c b/test/test_aestag.c index d8207c98..38214eba 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -19,6 +19,7 @@ */ #include "unit.h" +#include #ifndef EVP_CCM_TLS_FIXED_IV_LEN #define EVP_CCM_TLS_FIXED_IV_LEN EVP_GCM_TLS_FIXED_IV_LEN @@ -2512,22 +2513,29 @@ int test_aes_gcm_final_no_update_no_iv(void *data) #ifndef WOLFSSL_AESGCM_STREAM -static EVP_CIPHER_CTX *test_aes_gcm_single_ctx(EVP_CIPHER *cipher, int enc, - unsigned char *key, unsigned char *iv, unsigned char *aad, int aadLen) +static int test_aes_gcm_single_init(EVP_CIPHER_CTX *ctx, EVP_CIPHER *cipher, + int enc, unsigned char *key, unsigned char *iv, unsigned char *aad, + int aadLen) { - EVP_CIPHER_CTX *ctx; OSSL_PARAM params[2]; int on = 1; int len = 0; - params[0] = OSSL_PARAM_construct_int("wolfprov-gcm-single-update", &on); + params[0] = OSSL_PARAM_construct_int(WP_PARAM_GCM_SINGLE_UPDATE, &on); params[1] = OSSL_PARAM_construct_end(); - ctx = EVP_CIPHER_CTX_new(); + return (EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc) == 1) && + (EVP_CIPHER_CTX_set_params(ctx, params) == 1) && + (EVP_CipherUpdate(ctx, NULL, &len, aad, aadLen) == 1); +} + +static EVP_CIPHER_CTX *test_aes_gcm_single_ctx(EVP_CIPHER *cipher, int enc, + unsigned char *key, unsigned char *iv, unsigned char *aad, int aadLen) +{ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if ((ctx != NULL) && - ((EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc) != 1) || - (EVP_CIPHER_CTX_set_params(ctx, params) != 1) || - (EVP_CipherUpdate(ctx, NULL, &len, aad, aadLen) != 1))) { + !test_aes_gcm_single_init(ctx, cipher, enc, key, iv, aad, aadLen)) { EVP_CIPHER_CTX_free(ctx); ctx = NULL; } @@ -2567,6 +2575,7 @@ int test_aes_gcm_single_update(void *data) int ptLen = (int)(sizeof(pt) - 1); unsigned char refCt[64]; unsigned char refTag[16]; + unsigned char emptyTag[16]; unsigned char ct[64]; unsigned char tag[16]; int len = 0, fLen = 0; @@ -2583,6 +2592,10 @@ int test_aes_gcm_single_update(void *data) err = test_aes_tag_enc(ossl, key, iv, sizeof(iv), aad, pt, ptLen, refCt, refTag, 0, 0); } + if (err == 0) { + err = test_aes_tag_enc(ossl, key, iv, sizeof(iv), aad, pt, 0, ct, + emptyTag, 0, 0); + } if (err == 0) { PRINT_MSG("Single-update encrypt matches OpenSSL"); @@ -2592,8 +2605,14 @@ int test_aes_gcm_single_update(void *data) (EVP_EncryptFinal_ex(ctx, ct + len, &fLen) != 1) || (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) || (memcmp(ct, refCt, ptLen) != 0) || (memcmp(tag, refTag, 16) != 0); - EVP_CIPHER_CTX_free(ctx); } + if (err == 0) { + /* The reused context still holds the computed tag. */ + PRINT_MSG("Single-update decrypt rejects a missing tag"); + err = !test_aes_gcm_single_init(ctx, NULL, 0, key, iv, aad, aadLen) || + (EVP_DecryptUpdate(ctx, ct, &len, refCt, ptLen) == 1); + } + EVP_CIPHER_CTX_free(ctx); if (err == 0) { PRINT_MSG("Single-update rejects a second payload Update"); ctx = test_aes_gcm_single_ctx(wp, 1, key, iv, aad, aadLen); @@ -2603,6 +2622,15 @@ int test_aes_gcm_single_update(void *data) ptLen - 16) == 1); EVP_CIPHER_CTX_free(ctx); } + if (err == 0) { + PRINT_MSG("Single-update empty Final matches OpenSSL"); + ctx = test_aes_gcm_single_ctx(wp, 1, key, iv, aad, aadLen); + err = (ctx == NULL) || + (EVP_EncryptFinal_ex(ctx, ct, &fLen) != 1) || (fLen != 0) || + (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) || + (memcmp(tag, emptyTag, 16) != 0); + EVP_CIPHER_CTX_free(ctx); + } if (err == 0) { PRINT_MSG("Single-update decrypt with early tag"); err = !test_aes_gcm_single_dec(wp, key, iv, aad, aadLen, refCt, ptLen,