Skip to content

wc_ChaCha20Poly1305_Final rejects valid empty-plaintext + empty-AAD input (BAD_STATE_E) #10040

@MarkAtwood

Description

@MarkAtwood

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() with inAADLen == 0 is a no-op (does not transition state)
  • wc_ChaCha20Poly1305_UpdateData() requires non-NULL inData/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 Final does not have this state restriction)
  • The one-shot wc_ChaCha20Poly1305_Encrypt/Decrypt functions 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).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions