mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-06 01:10:48 +02:00
evp: verify Poly1305 tag on ChaCha20-Poly1305 decrypt
EVP_DecryptFinal_ex() called wc_ChaCha20Poly1305_Final() which only computes the Poly1305 tag, writing it into ctx->authTag and overwriting the expected tag stored there by EVP_CTRL_AEAD_SET_TAG. No comparison was ever performed, so any forged tag was accepted. Fix: save the expected tag before calling Final(), then verify with wc_ChaCha20Poly1305_CheckTag() on the decrypt path, mirroring the existing AES-GCM branch. Add a regression test that asserts EVP_DecryptFinal_ex() rejects an all-zero forged tag. Reported-by: Nicholas Carlini (Anthropic) & Bronson Yen (Calif.io)
This commit is contained in:
@@ -1915,6 +1915,7 @@ int test_wolfssl_EVP_chacha20_poly1305(void)
|
||||
byte cipherText[sizeof(plainText)];
|
||||
byte decryptedText[sizeof(plainText)];
|
||||
byte tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
|
||||
byte badTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
int outSz;
|
||||
|
||||
@@ -1979,6 +1980,28 @@ int test_wolfssl_EVP_chacha20_poly1305(void)
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
ctx = NULL;
|
||||
|
||||
/* Negative test: forged (all-zero) tag must be rejected. */
|
||||
XMEMSET(badTag, 0, sizeof(badTag));
|
||||
ExpectNotNull((ctx = EVP_CIPHER_CTX_new()));
|
||||
ExpectIntEQ(EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL,
|
||||
NULL, NULL), WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
|
||||
CHACHA20_POLY1305_AEAD_IV_SIZE, NULL), WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
|
||||
CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE, badTag),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(EVP_DecryptUpdate(ctx, NULL, &outSz, aad, sizeof(aad)),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(EVP_DecryptUpdate(ctx, decryptedText, &outSz, cipherText,
|
||||
sizeof(cipherText)), WOLFSSL_SUCCESS);
|
||||
/* EVP_DecryptFinal_ex MUST return failure on tag mismatch */
|
||||
ExpectIntNE(EVP_DecryptFinal_ex(ctx, decryptedText, &outSz),
|
||||
WOLFSSL_SUCCESS);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
ctx = NULL;
|
||||
|
||||
/* Test partial Inits. CipherInit() allow setting of key and iv
|
||||
* in separate calls. */
|
||||
ExpectNotNull((ctx = EVP_CIPHER_CTX_new()));
|
||||
|
||||
+21
-4
@@ -1499,16 +1499,33 @@ int wolfSSL_EVP_CipherFinal(WOLFSSL_EVP_CIPHER_CTX *ctx, unsigned char *out,
|
||||
* HAVE_FIPS_VERSION >= 2 */
|
||||
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
|
||||
case WC_CHACHA20_POLY1305_TYPE:
|
||||
{
|
||||
byte computedTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
|
||||
if (!ctx->enc) {
|
||||
/* Save the expected tag before _Final() overwrites
|
||||
* ctx->authTag */
|
||||
XMEMCPY(computedTag, ctx->authTag, sizeof(computedTag));
|
||||
}
|
||||
if (wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly,
|
||||
ctx->authTag) != 0) {
|
||||
WOLFSSL_MSG("wc_ChaCha20Poly1305_Final failed");
|
||||
return WOLFSSL_FAILURE;
|
||||
}
|
||||
else {
|
||||
*outl = 0;
|
||||
return WOLFSSL_SUCCESS;
|
||||
if (!ctx->enc) {
|
||||
/* ctx->authTag now holds computed tag; computedTag holds
|
||||
* expected */
|
||||
int tagErr = wc_ChaCha20Poly1305_CheckTag(computedTag,
|
||||
ctx->authTag);
|
||||
ForceZero(computedTag, sizeof(computedTag));
|
||||
if (tagErr != 0) {
|
||||
WOLFSSL_MSG("ChaCha20-Poly1305 tag mismatch");
|
||||
return WOLFSSL_FAILURE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
*outl = 0;
|
||||
return WOLFSSL_SUCCESS;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef WOLFSSL_SM4_GCM
|
||||
case WC_SM4_GCM_TYPE:
|
||||
|
||||
Reference in New Issue
Block a user