-
Notifications
You must be signed in to change notification settings - Fork 948
Description
Summary
wc_ChaCha20Poly1305_Final() returns BAD_STATE_E when both plaintext and AAD are zero-length. This is valid per RFC 8439 Section 2.8 and produces a well-defined authentication tag.
The bug affects both the one-shot API (wc_ChaCha20Poly1305_Encrypt/Decrypt) and the streaming API (Init/UpdateAad/UpdateData/Final) when neither UpdateAad nor UpdateData is called.
Root Cause
The state machine in wc_ChaCha20Poly1305_Final() (wolfcrypt/src/chacha20_poly1305.c) requires state to be AAD or DATA:
if (aead->state != CHACHA20_POLY1305_STATE_AAD &&
aead->state != CHACHA20_POLY1305_STATE_DATA) {
return BAD_STATE_E;
}After wc_ChaCha20Poly1305_Init(), state is READY (1). When both AAD and plaintext are empty:
wc_ChaCha20Poly1305_UpdateAad()withinAADLen == 0is a no-op (does not transition state)wc_ChaCha20Poly1305_UpdateData()requires non-NULLinData/outData, so it cannot be called with truly empty input
State remains READY, and Final() rejects it.
Suggested Fix
Accept CHACHA20_POLY1305_STATE_READY in Final():
if (aead->state != CHACHA20_POLY1305_STATE_READY &&
aead->state != CHACHA20_POLY1305_STATE_AAD &&
aead->state != CHACHA20_POLY1305_STATE_DATA) {
return BAD_STATE_E;
}When state is READY, aadLen and dataLen are both 0, so the existing Poly1305_Pad, Poly1305_EncodeSizes, and Poly1305Final calls will produce the correct tag (Poly1305 of the length block [0, 0]).
Reproducer
#include <wolfssl/wolfcrypt/chacha20_poly1305.h>
#include <stdio.h>
#include <string.h>
int main(void) {
/* Wycheproof test vector tc 2 */
byte key[32], iv[12], tag[16];
ChaChaPoly_Aead aead;
int rc;
memset(&aead, 0, sizeof(aead));
/* key = 80ba3192c803ce965ea371d5ff073cf0f43b6a2ab576b208426e11409c09b9b0 */
/* iv = 4da5bf8dfd5852c1ea12379d */
/* expected tag = 76acb342cf3166a5b63c0c0ea1383c8d */
rc = wc_ChaCha20Poly1305_Init(&aead, key, iv, 1);
printf("Init: %d\n", rc); /* 0 (success) */
/* No UpdateAad, no UpdateData — both are empty */
rc = wc_ChaCha20Poly1305_Final(&aead, tag);
printf("Final: %d\n", rc); /* -199 (BAD_STATE_E) — should be 0 */
return 0;
}Impact
- Wycheproof ChaCha20-Poly1305 test vectors tc 2 and tc 3 (empty plaintext) cannot be processed through the standard API
- AES-GCM is not affected (its
Finaldoes not have this state restriction) - The one-shot
wc_ChaCha20Poly1305_Encrypt/Decryptfunctions inherit the same bug since they call the streaming API internally
Workaround
Call wc_ChaCha20Poly1305_UpdateData() with a non-NULL pointer and dataLen = 0 to transition the state to DATA before calling Final(). This produces the correct tag.
Version
Found in wolfSSL master as of 2026-03-21 (commit checked against installed headers from source build).