Version in base suite: 0.6.2.3-1 Base version: cjose_0.6.2.3-1 Target version: cjose_0.6.2.3-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/cjose/cjose_0.6.2.3-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/cjose/cjose_0.6.2.3-1+deb13u1.dsc changelog | 8 patches/fix-heap-buffer-overflow-in-AES-key-unwrap.patch | 135 ++++++++++ patches/jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch | 129 +++++++++ patches/series | 2 4 files changed, 274 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpwog29chh/cjose_0.6.2.3-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpwog29chh/cjose_0.6.2.3-1+deb13u1.dsc: no acceptable signature found diff: /srv/release.debian.org/tmp/rl76gXZDhi/cjose-0.6.2.3/lib: No such file or directory diff: /srv/release.debian.org/tmp/yhZlnZpwc6/cjose-0.6.2.3/lib: No such file or directory diff -Nru cjose-0.6.2.3/debian/changelog cjose-0.6.2.3/debian/changelog --- cjose-0.6.2.3/debian/changelog 2024-04-25 13:51:09.000000000 +0000 +++ cjose-0.6.2.3/debian/changelog 2026-09-12 08:46:09.000000000 +0000 @@ -1,3 +1,11 @@ +cjose (0.6.2.3-1+deb13u1) trixie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * fix heap buffer overflow in AES key unwrap (CVE-2026-53938) + * jwe: fix all-zero CEK for AES-CBC-HMAC content encryption (CVE-2026-53939) + + -- Salvatore Bonaccorso Sat, 12 Sep 2026 10:46:09 +0200 + cjose (0.6.2.3-1) unstable; urgency=medium * New upstream version 0.6.2.3 diff -Nru cjose-0.6.2.3/debian/patches/fix-heap-buffer-overflow-in-AES-key-unwrap.patch cjose-0.6.2.3/debian/patches/fix-heap-buffer-overflow-in-AES-key-unwrap.patch --- cjose-0.6.2.3/debian/patches/fix-heap-buffer-overflow-in-AES-key-unwrap.patch 1970-01-01 00:00:00.000000000 +0000 +++ cjose-0.6.2.3/debian/patches/fix-heap-buffer-overflow-in-AES-key-unwrap.patch 2026-09-12 08:45:13.000000000 +0000 @@ -0,0 +1,135 @@ +From: Hans Zandbelt +Date: Tue, 2 Jun 2026 11:22:36 +0200 +Subject: fix heap buffer overflow in AES key unwrap +Origin: https://github.com/OpenIDC/cjose/commit/8c51d245273583a658f24ef7b08ba22f848a34a5 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-53938 + +by validating the encrypted_key length before AES_unwrap_key + +Signed-off-by: Hans Zandbelt +--- + CHANGELOG.md | 1 + + src/jwe.c | 9 ++++++ + test/check_jwe.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 89 insertions(+) + +diff --git a/src/jwe.c b/src/jwe.c +index 3bd30950a7af..e1344f0dc4c1 100644 +--- a/src/jwe.c ++++ b/src/jwe.c +@@ -609,6 +609,15 @@ static bool _cjose_jwe_decrypt_ek_aes_kw(_jwe_int_recipient_t *recipient, cjose_ + return false; + } + ++ // the wrapped key (RFC 3394) is always the plaintext CEK length plus 8 bytes; ++ // enforce this before calling AES_unwrap_key, which would otherwise copy the ++ // attacker-controlled encrypted_key into the fixed-size jwe->cek buffer ++ if (recipient->enc_key.raw_len != jwe->cek_len + 8) ++ { ++ CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); ++ return false; ++ } ++ + // AES unwrap the CEK in to jwe->cek + int len = AES_unwrap_key(&akey, (const unsigned char *)NULL, jwe->cek, (const unsigned char *)recipient->enc_key.raw, + recipient->enc_key.raw_len); +diff --git a/test/check_jwe.c b/test/check_jwe.c +index 9efb6b3ad6aa..0b65ea288c16 100644 +--- a/test/check_jwe.c ++++ b/test/check_jwe.c +@@ -1037,6 +1037,84 @@ START_TEST(test_cjose_jwe_decrypt_aes_gcm) + } + END_TEST + ++// build a compact JWE from a valid AES-KW JWE but with the encrypted_key segment ++// replaced by an oversized (attacker-controlled) base64url blob, then confirm that ++// importing parses fine but decryption fails gracefully instead of overflowing the ++// fixed-size CEK buffer in AES_unwrap_key (RFC 3394 wrapped key is always cek_len + 8) ++static void _decrypt_oversized_aes_kw_ek(const char *alg, const char *enc, const char *key) ++{ ++ cjose_err err; ++ ++ cjose_jwk_t *jwk = cjose_jwk_import(key, strlen(key), &err); ++ ck_assert_msg(NULL != jwk, "cjose_jwk_import failed: %s", err.message); ++ ++ cjose_header_t *hdr = cjose_header_new(&err); ++ ck_assert(cjose_header_set(hdr, CJOSE_HDR_ALG, alg, &err)); ++ ck_assert(cjose_header_set(hdr, CJOSE_HDR_ENC, enc, &err)); ++ ++ const char *plain = "Setec Astronomy"; ++ cjose_jwe_t *jwe = cjose_jwe_encrypt(jwk, hdr, (const uint8_t *)plain, strlen(plain), &err); ++ ck_assert_msg(NULL != jwe, "cjose_jwe_encrypt failed: %s", err.message); ++ ++ char *compact = cjose_jwe_export(jwe, &err); ++ ck_assert_msg(NULL != compact, "cjose_jwe_export failed: %s", err.message); ++ ++ // locate the boundaries of the encrypted_key (second) segment ++ char *first_dot = strchr(compact, '.'); ++ ck_assert(NULL != first_dot); ++ char *second_dot = strchr(first_dot + 1, '.'); ++ ck_assert(NULL != second_dot); ++ ++ // craft an oversized encrypted_key: 1024 raw bytes base64url-encoded; this decodes ++ // to far more than any CEK length (16/24/32 ... 64) + 8 bytes ++ uint8_t oversized[1024]; ++ memset(oversized, 0x41, sizeof(oversized)); ++ char *oversized_b64u = NULL; ++ size_t oversized_b64u_len = 0; ++ ck_assert(cjose_base64url_encode(oversized, sizeof(oversized), &oversized_b64u, &oversized_b64u_len, &err)); ++ ++ // reassemble:
.... ++ size_t header_len = first_dot - compact; ++ const char *tail = second_dot; // includes the leading '.' ++ size_t tail_len = strlen(tail); ++ size_t tampered_len = header_len + 1 + oversized_b64u_len + tail_len; ++ char *tampered = (char *)malloc(tampered_len + 1); ++ ck_assert(NULL != tampered); ++ memcpy(tampered, compact, header_len); ++ tampered[header_len] = '.'; ++ memcpy(tampered + header_len + 1, oversized_b64u, oversized_b64u_len); ++ memcpy(tampered + header_len + 1 + oversized_b64u_len, tail, tail_len); ++ tampered[tampered_len] = '\0'; ++ ++ // import must still succeed (parsing the oversized segment is legal) ++ cjose_jwe_t *jwe_bad = cjose_jwe_import(tampered, tampered_len, &err); ++ ck_assert_msg(NULL != jwe_bad, "cjose_jwe_import of oversized encrypted_key failed for alg %s: %s", alg, err.message); ++ ++ // decryption must fail cleanly (no heap overflow) with CJOSE_ERR_INVALID_ARG ++ size_t plain_len = 0; ++ uint8_t *decrypted = cjose_jwe_decrypt(jwe_bad, jwk, &plain_len, &err); ++ ck_assert_msg(NULL == decrypted, "cjose_jwe_decrypt succeeded on oversized encrypted_key for alg %s", alg); ++ ck_assert_msg(CJOSE_ERR_INVALID_ARG == err.code, "cjose_jwe_decrypt returned wrong err.code %d for alg %s", err.code, alg); ++ ++ free(tampered); ++ cjose_get_dealloc()(oversized_b64u); ++ cjose_get_dealloc()(compact); ++ cjose_jwe_release(jwe_bad); ++ cjose_jwe_release(jwe); ++ cjose_header_release(hdr); ++ cjose_jwk_release(jwk); ++} ++ ++START_TEST(test_cjose_jwe_decrypt_aes_kw_oversized_ek) ++{ ++ _decrypt_oversized_aes_kw_ek(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A128CBC_HS256, JWK_OCT_16); ++ _decrypt_oversized_aes_kw_ek(CJOSE_HDR_ALG_A192KW, CJOSE_HDR_ENC_A192CBC_HS384, JWK_OCT_24); ++ _decrypt_oversized_aes_kw_ek(CJOSE_HDR_ALG_A256KW, CJOSE_HDR_ENC_A256CBC_HS512, JWK_OCT_32); ++ _decrypt_oversized_aes_kw_ek(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A128GCM, JWK_OCT_16); ++ _decrypt_oversized_aes_kw_ek(CJOSE_HDR_ALG_A256KW, CJOSE_HDR_ENC_A256GCM, JWK_OCT_32); ++} ++END_TEST ++ + START_TEST(test_cjose_jwe_decrypt_rsa) + { + struct cjose_jwe_decrypt_rsa +@@ -1453,6 +1531,7 @@ Suite *cjose_jwe_suite(void) + tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_many); + tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_aes); + tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_aes_gcm); ++ tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_aes_kw_oversized_ek); + tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_rsa); + tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_with_bad_header); + tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_with_bad_key); +-- +2.55.0 + diff -Nru cjose-0.6.2.3/debian/patches/jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch cjose-0.6.2.3/debian/patches/jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch --- cjose-0.6.2.3/debian/patches/jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch 1970-01-01 00:00:00.000000000 +0000 +++ cjose-0.6.2.3/debian/patches/jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch 2026-09-12 08:45:24.000000000 +0000 @@ -0,0 +1,129 @@ +From: Hans Zandbelt +Date: Tue, 2 Jun 2026 14:44:33 +0200 +Subject: jwe: fix all-zero CEK for AES-CBC-HMAC content encryption +Origin: https://github.com/OpenIDC/cjose/commit/2a6e5bd969fa20059fb00913fb9f57d77ea6a4d9 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-53939 + +_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 +Co-Authored-By: Claude Opus 4.8 +--- + src/jwe.c | 4 ++-- + test/check_jwe.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 60 insertions(+), 2 deletions(-) + +diff --git a/src/jwe.c b/src/jwe.c +index e1344f0dc4c1..6935b6b14663 100644 +--- a/src/jwe.c ++++ b/src/jwe.c +@@ -477,11 +477,11 @@ static bool _cjose_jwe_set_cek_aes_cbc(cjose_jwe_t *jwe, const cjose_jwk_t *jwk, + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + keysize = 64; + ++ // if no JWK is provided, generate a random key + if (NULL == jwk) + { +- // allocate memory for the CEK and fill with random bytes or 0's + _cjose_release_cek(&jwe->cek, jwe->cek_len); +- if (!_cjose_jwe_malloc(keysize, !random, &jwe->cek, err)) ++ if (!_cjose_jwe_malloc(keysize, random, &jwe->cek, err)) + { + return false; + } +diff --git a/test/check_jwe.c b/test/check_jwe.c +index 0b65ea288c16..9745390c80f9 100644 +--- a/test/check_jwe.c ++++ b/test/check_jwe.c +@@ -1516,6 +1516,63 @@ START_TEST(test_cjose_jwe_multiple_recipients) + } + END_TEST + ++// regression test for the AES-CBC-HMAC content-encryption key (CEK): it must ++// be generated from fresh random bytes on every encryption. A previous bug ++// zero-filled it (inverted random flag); because AES key wrap is deterministic, ++// that produced a byte-identical encrypted_key on every encryption with the ++// same KEK. Encrypt the same plaintext twice and assert the encrypted_key (the ++// 2nd compact field) differs. ++static void _assert_cbc_cek_random(const char *alg, const char *enc, const char *jwk_str) ++{ ++ cjose_err err; ++ ++ cjose_jwk_t *jwk = cjose_jwk_import(jwk_str, strlen(jwk_str), &err); ++ ck_assert_msg(NULL != jwk, "cjose_jwk_import failed: %s", err.message); ++ ++ cjose_header_t *hdr = cjose_header_new(&err); ++ ck_assert_msg(NULL != hdr, "cjose_header_new failed: %s", err.message); ++ ck_assert(cjose_header_set(hdr, CJOSE_HDR_ALG, alg, &err)); ++ ck_assert(cjose_header_set(hdr, CJOSE_HDR_ENC, enc, &err)); ++ ++ const char *plain = "Setec Astronomy"; ++ char ek[2][512]; ++ ++ for (int i = 0; i < 2; i++) ++ { ++ cjose_jwe_t *jwe = cjose_jwe_encrypt(jwk, hdr, (const uint8_t *)plain, strlen(plain), &err); ++ ck_assert_msg(NULL != jwe, "cjose_jwe_encrypt failed (%s/%s): %s", alg, enc, err.message); ++ ++ char *cser = cjose_jwe_export(jwe, &err); ++ ck_assert_msg(NULL != cser, "cjose_jwe_export failed: %s", err.message); ++ ++ // the encrypted_key is the 2nd of the 5 dot-separated compact parts ++ const char *d1 = strchr(cser, '.'); ++ const char *d2 = (NULL != d1) ? strchr(d1 + 1, '.') : NULL; ++ ck_assert_msg(NULL != d1 && NULL != d2, "malformed compact serialization"); ++ size_t n = (size_t)(d2 - (d1 + 1)); ++ ck_assert_msg(n > 0 && n < sizeof(ek[i]), "unexpected encrypted_key length"); ++ memcpy(ek[i], d1 + 1, n); ++ ek[i][n] = '\0'; ++ ++ cjose_get_dealloc()(cser); ++ cjose_jwe_release(jwe); ++ } ++ ++ ck_assert_msg(0 != strcmp(ek[0], ek[1]), ++ "AES-CBC-HMAC CEK is not random: identical encrypted_key across two encryptions (%s/%s)", alg, enc); ++ ++ cjose_header_release(hdr); ++ cjose_jwk_release(jwk); ++} ++ ++START_TEST(test_cjose_jwe_encrypt_cbc_cek_random) ++{ ++ _assert_cbc_cek_random(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A128CBC_HS256, JWK_OCT_16); ++ _assert_cbc_cek_random(CJOSE_HDR_ALG_A192KW, CJOSE_HDR_ENC_A192CBC_HS384, JWK_OCT_24); ++ _assert_cbc_cek_random(CJOSE_HDR_ALG_A256KW, CJOSE_HDR_ENC_A256CBC_HS512, JWK_OCT_32); ++} ++END_TEST ++ + Suite *cjose_jwe_suite(void) + { + Suite *suite = suite_create("jwe"); +@@ -1540,6 +1597,7 @@ Suite *cjose_jwe_suite(void) + tcase_add_test(tc_jwe, test_cjose_jwe_import_invalid_serialization); + tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_bad_params); + tcase_add_test(tc_jwe, test_cjose_jwe_multiple_recipients); ++ tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_cbc_cek_random); + suite_add_tcase(suite, tc_jwe); + + return suite; +-- +2.55.0 + diff -Nru cjose-0.6.2.3/debian/patches/series cjose-0.6.2.3/debian/patches/series --- cjose-0.6.2.3/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ cjose-0.6.2.3/debian/patches/series 2026-09-12 08:45:32.000000000 +0000 @@ -0,0 +1,2 @@ +fix-heap-buffer-overflow-in-AES-key-unwrap.patch +jwe-fix-all-zero-CEK-for-AES-CBC-HMAC-content-encryp.patch