diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index 7cfabe9e..f4f9018d 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -1960,6 +1960,7 @@ static int wp_aesccm_init(wp_AeadCtx* ctx, const unsigned char *key, { int ok = 1; int rc; + int freshIv = 0; WOLFPROV_ENTER(WP_LOG_COMP_AES, "wp_aesccm_init"); @@ -1980,10 +1981,14 @@ static int wp_aesccm_init(wp_AeadCtx* ctx, const unsigned char *key, if (ivLen != ctx->ivLen) { ok = 0; } - if (ok) { + /* Avoid reusing the IV if it is finished. Only encryption is held + * back; decryption may repeat an IV. */ + if (ok && ((!enc) || (ctx->ivState != IV_STATE_FINISHED) || + (XMEMCMP(ctx->iv, iv, ivLen) != 0))) { XMEMCPY(ctx->iv, iv, ivLen); ctx->ivState = IV_STATE_BUFFERED; ctx->ivSet = 0; + freshIv = 1; } } if (ok) { @@ -1992,7 +1997,7 @@ static int wp_aesccm_init(wp_AeadCtx* ctx, const unsigned char *key, /* A fresh IV starts a new operation and clears per-operation state. * Without one, a context that already produced output is made terminal * so its IV/nonce cannot be reused. */ - if (iv != NULL) { + if (freshIv) { ctx->authErr = 0; ctx->tagAvail = 0; wp_aead_reset_op_state(ctx); diff --git a/test/test_aestag.c b/test/test_aestag.c index 9083d65f..ab639e0c 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -2979,6 +2979,132 @@ int test_aes_ccm_oneshot_encrypt(void *data) return err; } +static int test_aes_ccm_iv_reuse_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err = 0; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + unsigned char iv[13]; + unsigned char iv2[13]; + unsigned char aad[] = "additional data"; + unsigned char pt[] = "CCM plaintext for IV reuse test"; + int ptLen = (int)(sizeof(pt) - 1); + int aadLen = (int)(sizeof(aad) - 1); + unsigned char ct[64]; + unsigned char ct2[64]; + unsigned char tag[16]; + int outLen = 0, fLen = 0; + + memset(key, 0xAA, keyLen); + memset(iv, 0xBB, sizeof(iv)); + memset(iv2, 0xCC, sizeof(iv2)); + memset(ct, 0, sizeof(ct)); + memset(ct2, 0, sizeof(ct2)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + err = cipher == NULL; + + if (err == 0) { + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + (int)sizeof(iv), NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, NULL) != 1; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, aadLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1; + } + + /* Reusing the IV that already produced output would repeat the nonce + * under the same key, so the second encryption must not complete. */ + if (err == 0) { + int ret = EVP_EncryptInit(ctx, NULL, key, iv); + if (ret == 1) { + ret = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen); + } + if (ret == 1) { + ret = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, aadLen); + } + if (ret == 1) { + ret = EVP_EncryptUpdate(ctx, ct2, &outLen, pt, ptLen); + } + if (ret == 1) { + PRINT_ERR_MSG("%s: second encrypt reused the IV", cipherName); + err = 1; + } + } + + /* A different IV starts a new operation and must still work. */ + if (err == 0) { + err = EVP_EncryptInit(ctx, NULL, key, iv2) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, aadLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct2, &outLen, pt, ptLen) != 1; + if (err) { + PRINT_ERR_MSG("%s: encrypt with a fresh IV failed", cipherName); + } + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct2 + outLen, &fLen) != 1; + } + if (err == 0) { + err = (outLen != ptLen) || (memcmp(ct, ct2, ptLen) == 0); + if (err) { + PRINT_ERR_MSG("%s: fresh IV did not change the ciphertext", + cipherName); + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_ccm_iv_reuse(void *data) +{ + int err = 0; + + (void)data; + + PRINT_MSG("AES-128-CCM encrypt with a reused IV"); + err = test_aes_ccm_iv_reuse_helper(wpLibCtx, "AES-128-CCM", 16); + if (err == 0) { + PRINT_MSG("AES-256-CCM encrypt with a reused IV"); + err = test_aes_ccm_iv_reuse_helper(wpLibCtx, "AES-256-CCM", 32); + } + + return err; +} + int test_aes_ccm_bad_tag(void *data) { int err = 0; diff --git a/test/unit.c b/test/unit.c index 4d975451..d27da429 100644 --- a/test/unit.c +++ b/test/unit.c @@ -337,6 +337,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes_ccm_bad_tag, NULL), TEST_DECL(test_aes_ccm_ctx_reuse, NULL), TEST_DECL(test_aes_ccm_oneshot_encrypt, NULL), + TEST_DECL(test_aes_ccm_iv_reuse, NULL), TEST_DECL(test_aes_ccm_tag_len_undersized, NULL), TEST_DECL(test_aes_ccm_key_no_iv, NULL), #endif diff --git a/test/unit.h b/test/unit.h index 1892e242..60ec6318 100644 --- a/test/unit.h +++ b/test/unit.h @@ -304,6 +304,7 @@ int test_aes128_ccm_tls(void *data); int test_aes_ccm_bad_tag(void *data); int test_aes_ccm_ctx_reuse(void *data); int test_aes_ccm_oneshot_encrypt(void *data); +int test_aes_ccm_iv_reuse(void *data); int test_aes_ccm_tag_len_undersized(void *data); int test_aes_ccm_key_no_iv(void *data);