Skip to content

Fix all-zero content encryption key (CEK) for AES-CBC-HMAC JWE encryption#132

Open
zandbelt wants to merge 1 commit into
cisco:masterfrom
OpenIDC:cisco-pr-cbc-zero-cek
Open

Fix all-zero content encryption key (CEK) for AES-CBC-HMAC JWE encryption#132
zandbelt wants to merge 1 commit into
cisco:masterfrom
OpenIDC:cisco-pr-cbc-zero-cek

Conversation

@zandbelt

@zandbelt zandbelt commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

When encrypting a JWE with an AES-CBC-HMAC content-encryption algorithm
(A128CBC-HS256, A192CBC-HS384, A256CBC-HS512) and a key-management
algorithm that generates a fresh content-encryption key (CEK), the CEK is
all zero bytes instead of being randomly generated. The produced JWE is
therefore encrypted and authenticated under a fixed, publicly known key.

Impact

For affected JWEs both confidentiality and integrity are lost:

  • the AES-CBC key is a known constant (zeros), so the ciphertext can be
    decrypted by anyone, and
  • the HMAC key is the same constant, so the authentication tag can be
    recomputed and the content forged.

This affects the encryption side (JWEs produced by cjose). Existing
ciphertexts produced with an affected algorithm pair should be treated as
compromised.

Affected configurations

Triggered only when both hold at encryption time:

  • enc is one of A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, and
  • alg is one of A128KW, A192KW, A256KW, RSA-OAEP, RSA1_5 (any alg
    that asks cjose to generate a random CEK).

Not affected: dir (CEK comes from the JWK), all AES-GCM enc values,
ECDH-ES (CEK is derived via Concat-KDF), and the decryption path.

Root cause

_cjose_jwe_set_cek_aes_cbc() allocated the CEK with the random flag
inverted:

/* before */
if (!_cjose_jwe_malloc(keysize, !random, &jwe->cek, err))   /* note: !random */

_cjose_jwe_malloc(..., random=true, ...) fills the buffer via RAND_bytes,
while random=false zero-fills it. On the encryption path the caller passes
random = true, so !random zero-fills the CEK. The sibling AES-GCM helper
_cjose_jwe_set_cek_aes_gcm() correctly passes random, which is why GCM is
unaffected. The defect dates back to #68 ("Better support for AES-CBC-HMAC
with other key management algs").

Fix

Pass random directly so the CEK is generated from RAND_bytes:

/* after */
if (!_cjose_jwe_malloc(keysize, random, &jwe->cek, err))

Testing

Adds test_cjose_jwe_encrypt_cbc_cek_random to test/check_jwe.c: it encrypts
the same plaintext twice for each AES-CBC-HMAC variant and asserts the
encrypted_key differs. Because AES Key Wrap is deterministic, a fixed CEK
yields a byte-identical encrypted_key — so this test fails before the fix and
passes after it. Full suite passes (Checks: 77, Failures: 0, Errors: 0).

Note: this tree predates OpenSSL 3.x support, so building against OpenSSL 3
with -Werror fails on unrelated deprecation warnings in jwk.c. I verified
the build/tests with CFLAGS=-DOPENSSL_API_COMPAT=0x10000000L; no source
change to that effect is included here.

References

The same issue was found and fixed in the maintained fork (OpenIDC/cjose,
v0.6.2.6) and is tracked in security advisory
GHSA-f6wf-pqg3-6wqq.

_cjose_jwe_set_cek_aes_cbc() allocated the content-encryption key with
_cjose_jwe_malloc(keysize, !random, ...) -- the random flag was inverted
(the AES-GCM sibling correctly passes `random`). On the encryption path
random is true, so !random zero-filled the CEK instead of generating
random bytes.

As a result every JWE produced with an AES-CBC-HMAC enc
(A128CBC-HS256 / A192CBC-HS384 / A256CBC-HS512) combined with a non-dir
key-management alg (A128/192/256KW, RSA-OAEP, RSA1_5) was encrypted and
MAC'd under an all-zero key -- a complete loss of confidentiality and
integrity for those ciphertexts. "dir" (key supplied by the JWK) and the
AES-GCM enc values were unaffected.

Pass `random` so the CEK is generated from RAND_bytes, and correct the
now-inaccurate comment to match the GCM path. Add a regression test that
encrypts the same plaintext twice and asserts the encrypted_key differs
(it is byte-identical with a fixed zero CEK because AES key wrap is
deterministic).

Signed-off-by: Hans Zandbelt <hans.zandbelt@openidc.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant