From 26c4e186535a2dddbe757a0ee5941aaf0ea2be8b Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:44:46 -0400 Subject: [PATCH 01/12] Fix JWS import allocation handling --- src/jws.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/jws.c b/src/jws.c index d0cfa0d..22cebf3 100644 --- a/src/jws.c +++ b/src/jws.c @@ -749,9 +749,9 @@ bool cjose_jws_export(cjose_jws_t *jws, const char **compact, cjose_err *err) static bool _cjose_jws_strcpy(char **dst, const char *src, int len, cjose_err *err) { *dst = (char *)cjose_get_alloc()(len + 1); - if (NULL == dst) + if (NULL == *dst) { - CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); return false; } @@ -804,7 +804,11 @@ cjose_jws_t *cjose_jws_import(const char *cser, size_t cser_len, cjose_err *err) // copy and decode header b64u segment uint8_t *hdr_str = NULL; jws->hdr_b64u_len = d[0]; - _cjose_jws_strcpy(&jws->hdr_b64u, cser, jws->hdr_b64u_len, err); + if (!_cjose_jws_strcpy(&jws->hdr_b64u, cser, jws->hdr_b64u_len, err)) + { + cjose_jws_release(jws); + return NULL; + } if (!cjose_base64url_decode(jws->hdr_b64u, jws->hdr_b64u_len, &hdr_str, &len, err) || NULL == hdr_str) { cjose_jws_release(jws); @@ -843,7 +847,11 @@ cjose_jws_t *cjose_jws_import(const char *cser, size_t cser_len, cjose_err *err) // copy and b64u decode data segment jws->dat_b64u_len = d[1] - d[0] - 1; - _cjose_jws_strcpy(&jws->dat_b64u, cser + d[0] + 1, jws->dat_b64u_len, err); + if (!_cjose_jws_strcpy(&jws->dat_b64u, cser + d[0] + 1, jws->dat_b64u_len, err)) + { + cjose_jws_release(jws); + return NULL; + } if (!cjose_base64url_decode(jws->dat_b64u, jws->dat_b64u_len, &jws->dat, &jws->dat_len, err)) { cjose_jws_release(jws); @@ -852,7 +860,11 @@ cjose_jws_t *cjose_jws_import(const char *cser, size_t cser_len, cjose_err *err) // copy and b64u decode signature segment jws->sig_b64u_len = cser_len - d[1] - 1; - _cjose_jws_strcpy(&jws->sig_b64u, cser + d[1] + 1, jws->sig_b64u_len, err); + if (!_cjose_jws_strcpy(&jws->sig_b64u, cser + d[1] + 1, jws->sig_b64u_len, err)) + { + cjose_jws_release(jws); + return NULL; + } if (!cjose_base64url_decode(jws->sig_b64u, jws->sig_b64u_len, &jws->sig, &jws->sig_len, err)) { cjose_jws_release(jws); From 943cc9dacb34f2669fee8b5e7e81404f371d94bb Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:45:29 -0400 Subject: [PATCH 02/12] Validate EC inputs before key agreement --- src/jwe.c | 6 ++++++ src/jwk.c | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/jwe.c b/src/jwe.c index faed05b..7443ee0 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -860,6 +860,12 @@ static bool _cjose_jwe_decrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose goto cjose_decrypt_ek_ecdh_es_finish; } + if (cjose_jwk_EC_get_curve(jwk, err) != cjose_jwk_EC_get_curve(epk_jwk, err)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto cjose_decrypt_ek_ecdh_es_finish; + } + // perform ECDH (private=jwk, public=epk_jwk) if (!cjose_jwk_derive_ecdh_bits(jwk, epk_jwk, &secret, &secret_len, err)) { diff --git a/src/jwk.c b/src/jwk.c index 8dbdeec..9988706 100644 --- a/src/jwk.c +++ b/src/jwk.c @@ -944,7 +944,13 @@ cjose_jwk_t *cjose_jwk_create_EC_spec(const cjose_jwk_ec_keyspec *spec, cjose_er if (1 != EC_POINT_set_affine_coordinates_GFp(params, Q, bnX, bnY, NULL)) { - CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto create_EC_failed; + } + + if (1 != EC_POINT_is_on_curve(params, Q, NULL)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); goto create_EC_failed; } } @@ -952,7 +958,13 @@ cjose_jwk_t *cjose_jwk_create_EC_spec(const cjose_jwk_ec_keyspec *spec, cjose_er // always set the public key if (1 != EC_KEY_set_public_key(ec, Q)) { - CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto create_EC_failed; + } + + if (1 != EC_KEY_check_key(ec)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); goto create_EC_failed; } From f0064afd3d48951ae0467837a06f4f73ad9796d3 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:46:07 -0400 Subject: [PATCH 03/12] Use OpenSSL constant-time comparisons --- src/jwe.c | 3 ++- src/jws.c | 9 ++++++++- src/util.c | 8 +------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/jwe.c b/src/jwe.c index 7443ee0..5125a78 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1409,7 +1410,7 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) } // compare the provided Authentication Tag against our calculation - if ((tag_len != jwe->enc_auth_tag.raw_len) || (cjose_const_memcmp(tag, jwe->enc_auth_tag.raw, tag_len) != 0)) + if ((tag_len != jwe->enc_auth_tag.raw_len) || (CRYPTO_memcmp(tag, jwe->enc_auth_tag.raw, tag_len) != 0)) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); return false; diff --git a/src/jws.c b/src/jws.c index 22cebf3..0377604 100644 --- a/src/jws.c +++ b/src/jws.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -997,6 +998,7 @@ static bool _cjose_jws_verify_sig_rs(cjose_jws_t *jws, const cjose_jwk_t *jwk, c static bool _cjose_jws_verify_sig_hmac_sha(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) { bool retval = false; + int diff = 0; // ensure jwk is OCT if (jwk->kty != CJOSE_JWK_KTY_OCT) @@ -1006,7 +1008,12 @@ static bool _cjose_jws_verify_sig_hmac_sha(cjose_jws_t *jws, const cjose_jwk_t * } // verify decrypted digest matches computed digest - if ((cjose_const_memcmp(jws->dig, jws->sig, jws->dig_len) != 0) || (jws->sig_len != jws->dig_len)) + diff |= (jws->sig_len != jws->dig_len); + if (jws->sig_len == jws->dig_len) + { + diff |= CRYPTO_memcmp(jws->dig, jws->sig, jws->dig_len); + } + if (diff != 0) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jws_verify_sig_hmac_sha_cleanup; diff --git a/src/util.c b/src/util.c index a180d11..1d9bb86 100644 --- a/src/util.c +++ b/src/util.c @@ -94,13 +94,7 @@ cjose_dealloc3_fn_t cjose_get_dealloc3(void) { return (!_dealloc3) ? cjose_deall int cjose_const_memcmp(const uint8_t *a, const uint8_t *b, const size_t size) { - unsigned char result = 0; - for (size_t i = 0; i < size; i++) - { - result |= a[i] ^ b[i]; - } - - return result; + return CRYPTO_memcmp(a, b, size); } char *_cjose_strndup(const char *str, ssize_t len, cjose_err *err) From 1697242689274a1265ee4354ecf09ff9abf0ff32 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:46:49 -0400 Subject: [PATCH 04/12] Guard JWE buffer length calculations --- src/jwe.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/jwe.c b/src/jwe.c index 5125a78..781a08f 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -1135,7 +1136,16 @@ static bool _cjose_jwe_calc_auth_tag(const char *enc, cjose_jwe_t *jwe, uint8_t uint64_t al = jwe->enc_header.b64u_len * 8; // concatenate AAD + IV + ciphertext + AAD length field - int msg_len = jwe->enc_header.b64u_len + jwe->enc_iv.raw_len + jwe->enc_ct.raw_len + sizeof(uint64_t); + size_t msg_len = jwe->enc_header.b64u_len; + if (msg_len > SIZE_MAX - jwe->enc_iv.raw_len || msg_len + jwe->enc_iv.raw_len > SIZE_MAX - jwe->enc_ct.raw_len + || msg_len + jwe->enc_iv.raw_len + jwe->enc_ct.raw_len > SIZE_MAX - sizeof(uint64_t)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto _cjose_jwe_calc_auth_tag_end; + } + msg_len += jwe->enc_iv.raw_len; + msg_len += jwe->enc_ct.raw_len; + msg_len += sizeof(uint64_t); if (!_cjose_jwe_malloc(msg_len, false, &msg, err)) { goto _cjose_jwe_calc_auth_tag_end; @@ -1456,7 +1466,13 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) } // allocate buffer for the plaintext + one block padding - int p_len = jwe->enc_ct.raw_len, f_len = 0; + if (jwe->enc_ct.raw_len > INT_MAX || jwe->enc_ct.raw_len > SIZE_MAX - AES_BLOCK_SIZE) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + + int p_len = (int)jwe->enc_ct.raw_len, f_len = 0; cjose_get_dealloc()(jwe->dat); jwe->dat_len = p_len + AES_BLOCK_SIZE; if (!_cjose_jwe_malloc(jwe->dat_len, false, &jwe->dat, err)) From 782afe295b96fbbf1b3f41b5251ae754d4f4cdd4 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:48:39 -0400 Subject: [PATCH 05/12] Cleanse sensitive buffers before release --- src/concatkdf.c | 3 ++- src/include/util_int.h | 3 +++ src/jwe.c | 7 +++---- src/jwk.c | 14 ++++++++------ src/jws.c | 7 ++++--- src/util.c | 17 +++++++++++++++++ 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/concatkdf.c b/src/concatkdf.c index 44ced67..43f456e 100644 --- a/src/concatkdf.c +++ b/src/concatkdf.c @@ -6,6 +6,7 @@ */ #include "include/concatkdf_int.h" +#include "include/util_int.h" #ifdef _WIN32 #include @@ -149,7 +150,7 @@ uint8_t *cjose_concatkdf_derive(const size_t keylen, concatkdf_derive_finish: EVP_MD_CTX_destroy(ctx); - cjose_get_dealloc()(buffer); + _cjose_cleanse_dealloc(buffer, keylen); return derived; } diff --git a/src/include/util_int.h b/src/include/util_int.h index c8d059c..a550b93 100644 --- a/src/include/util_int.h +++ b/src/include/util_int.h @@ -24,4 +24,7 @@ void *cjose_alloc_wrapped(size_t n); void *cjose_realloc_wrapped(void *p, size_t n); void cjose_dealloc_wrapped(void *p); +void _cjose_cleanse(void *ptr, size_t len); +void _cjose_cleanse_dealloc(void *ptr, size_t len); + #endif // SRC_UTIL_INT_H diff --git a/src/jwe.c b/src/jwe.c index 781a08f..5fb1b03 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -79,8 +79,7 @@ static void _cjose_release_cek(uint8_t **cek, size_t cek_len) return; } - memset(*cek, 0, cek_len); - cjose_get_dealloc()(*cek); + _cjose_cleanse_dealloc(*cek, cek_len); *cek = 0; } @@ -827,7 +826,7 @@ static bool _cjose_jwe_encrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose cjose_jwk_release(epk_jwk); cjose_get_dealloc()(epk_json); - cjose_get_dealloc()(secret); + _cjose_cleanse_dealloc(secret, secret_len); cjose_get_dealloc()(otherinfo); return result; @@ -912,7 +911,7 @@ static bool _cjose_jwe_decrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose cjose_jwk_release(epk_jwk); cjose_get_dealloc()(epk_json); - cjose_get_dealloc()(secret); + _cjose_cleanse_dealloc(secret, secret_len); cjose_get_dealloc()(otherinfo); return result; diff --git a/src/jwk.c b/src/jwk.c index 9988706..8173773 100644 --- a/src/jwk.c +++ b/src/jwk.c @@ -381,7 +381,7 @@ static void _oct_free(cjose_jwk_t *jwk) jwk->keydata = NULL; if (buffer) { - cjose_get_dealloc()(buffer); + _cjose_cleanse_dealloc(buffer, jwk->keysize / 8); } cjose_get_dealloc()(jwk); } @@ -1746,8 +1746,8 @@ cjose_jwk_t *cjose_jwk_derive_ecdh_ephemeral_key( } // happy path - cjose_get_dealloc()(secret); - cjose_get_dealloc()(ephemeral_key); + _cjose_cleanse_dealloc(secret, secret_len); + _cjose_cleanse_dealloc(ephemeral_key, ephemeral_key_len); return jwk_ephemeral_key; @@ -1758,8 +1758,8 @@ cjose_jwk_t *cjose_jwk_derive_ecdh_ephemeral_key( { cjose_jwk_release(jwk_ephemeral_key); } - cjose_get_dealloc()(secret); - cjose_get_dealloc()(ephemeral_key); + _cjose_cleanse_dealloc(secret, secret_len); + _cjose_cleanse_dealloc(ephemeral_key, ephemeral_key_len); return NULL; } @@ -1852,7 +1852,7 @@ bool cjose_jwk_derive_ecdh_bits( { EVP_PKEY_free(pkey_peer); } - cjose_get_dealloc()(secret); + _cjose_cleanse_dealloc(secret, secret_len); return false; } @@ -1889,8 +1889,10 @@ bool cjose_jwk_hkdf(const EVP_MD *md, if (NULL == HMAC(md, prk, prk_len, t, sizeof(t), okm, NULL)) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + _cjose_cleanse(prk, sizeof(prk)); return false; } + _cjose_cleanse(prk, sizeof(prk)); return true; } diff --git a/src/jws.c b/src/jws.c index 0377604..361d6b8 100644 --- a/src/jws.c +++ b/src/jws.c @@ -23,6 +23,7 @@ #include "include/jwk_int.h" #include "include/header_int.h" #include "include/jws_int.h" +#include "include/util_int.h" //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_build_dig_sha(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err); @@ -175,7 +176,7 @@ static bool _cjose_jws_build_dig_sha(cjose_jws_t *jws, const cjose_jwk_t *jwk, c if (NULL != jws->dig) { - cjose_get_dealloc()(jws->dig); + _cjose_cleanse_dealloc(jws->dig, jws->dig_len); jws->dig = NULL; } @@ -721,8 +722,8 @@ void cjose_jws_release(cjose_jws_t *jws) cjose_get_dealloc()(jws->hdr_b64u); cjose_get_dealloc()(jws->dat); cjose_get_dealloc()(jws->dat_b64u); - cjose_get_dealloc()(jws->dig); - cjose_get_dealloc()(jws->sig); + _cjose_cleanse_dealloc(jws->dig, jws->dig_len); + _cjose_cleanse_dealloc(jws->sig, jws->sig_len); cjose_get_dealloc()(jws->sig_b64u); cjose_get_dealloc()(jws->cser); cjose_get_dealloc()(jws); diff --git a/src/util.c b/src/util.c index 1d9bb86..4c6bb79 100644 --- a/src/util.c +++ b/src/util.c @@ -92,6 +92,23 @@ cjose_realloc3_fn_t cjose_get_realloc3(void) { return (!_realloc3) ? cjose_reall cjose_dealloc_fn_t cjose_get_dealloc(void) { return (!_dealloc) ? free : _dealloc; } cjose_dealloc3_fn_t cjose_get_dealloc3(void) { return (!_dealloc3) ? cjose_dealloc3_default : _dealloc3; } +void _cjose_cleanse(void *ptr, size_t len) +{ + if (NULL != ptr && 0 != len) + { + OPENSSL_cleanse(ptr, len); + } +} + +void _cjose_cleanse_dealloc(void *ptr, size_t len) +{ + if (NULL != ptr) + { + _cjose_cleanse(ptr, len); + cjose_get_dealloc()(ptr); + } +} + int cjose_const_memcmp(const uint8_t *a, const uint8_t *b, const size_t size) { return CRYPTO_memcmp(a, b, size); From a9acdda1e6ad50bee05fc0c758c277a4ff020e27 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:49:48 -0400 Subject: [PATCH 06/12] Validate critical JOSE headers --- src/header.c | 54 ++++++++++++++++++++++++++++++++++++++++ src/include/header_int.h | 6 ++++- src/jwe.c | 18 ++++++++++++++ src/jws.c | 11 ++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/header.c b/src/header.c index 5a74797..728c4e1 100644 --- a/src/header.c +++ b/src/header.c @@ -6,6 +6,7 @@ */ #include +#include #include #include "cjose/header.h" #include "include/header_int.h" @@ -49,6 +50,59 @@ const char *CJOSE_HDR_EPK = "epk"; const char *CJOSE_HDR_APU = "apu"; const char *CJOSE_HDR_APV = "apv"; +static const char *CJOSE_HDR_CRIT = "crit"; + +//////////////////////////////////////////////////////////////////////////////// +bool _cjose_header_validate_crit(cjose_header_t *header, const char *const *supported, size_t supported_len, cjose_err *err) +{ + if (NULL == header) + { + return true; + } + + json_t *crit = json_object_get((json_t *)header, CJOSE_HDR_CRIT); + if (NULL == crit) + { + return true; + } + + if (!json_is_array(crit)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + size_t index = 0; + json_t *entry = NULL; + json_array_foreach(crit, index, entry) + { + if (!json_is_string(entry)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + const char *name = json_string_value(entry); + bool found = false; + for (size_t i = 0; i < supported_len; i++) + { + if (0 == strcmp(name, supported[i])) + { + found = true; + break; + } + } + + if (!found) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// cjose_header_t *cjose_header_new(cjose_err *err) { diff --git a/src/include/header_int.h b/src/include/header_int.h index 012a738..9695d15 100644 --- a/src/include/header_int.h +++ b/src/include/header_int.h @@ -8,6 +8,10 @@ #ifndef SRC_HEADER_INT_H #define SRC_HEADER_INT_H -// extern const char *CJOSE_HDR_ATTRS[]; +#include + +#include "cjose/header.h" + +bool _cjose_header_validate_crit(cjose_header_t *header, const char *const *supported, size_t supported_len, cjose_err *err); #endif // SRC_HEADER_INT_H diff --git a/src/jwe.c b/src/jwe.c index 5fb1b03..7fe14a5 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -314,6 +314,24 @@ static bool _cjose_jwe_validate_alg(cjose_header_t *protected_header, _jwe_int_recipient_t *recipient, cjose_err *err) { + static const char *const supported_crit_headers[] = { + "alg", + "enc", + "cty", + "epk", + "apu", + "apv" + }; + + if (!_cjose_header_validate_crit(protected_header, supported_crit_headers, + sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err) + || !_cjose_header_validate_crit(unprotected_header, supported_crit_headers, + sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err) + || !_cjose_header_validate_crit((cjose_header_t *)recipient->unprotected, supported_crit_headers, + sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err)) + { + return false; + } const char *alg = _cjose_jwe_get_from_headers(protected_header, unprotected_header, (cjose_header_t *)recipient->unprotected, CJOSE_HDR_ALG); diff --git a/src/jws.c b/src/jws.c index 361d6b8..d328d7e 100644 --- a/src/jws.c +++ b/src/jws.c @@ -73,6 +73,17 @@ static bool _cjose_jws_build_hdr(cjose_jws_t *jws, cjose_header_t *header, cjose //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_validate_hdr(cjose_jws_t *jws, cjose_err *err) { + static const char *const supported_crit_headers[] = { + "alg", + "cty" + }; + + if (!_cjose_header_validate_crit((cjose_header_t *)jws->hdr, supported_crit_headers, + sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err)) + { + return false; + } + // make sure we have an alg header json_t *alg_obj = json_object_get(jws->hdr, CJOSE_HDR_ALG); if ((NULL == alg_obj) || (!json_is_string(alg_obj))) From 6d9a10ffd9bb1f7e9ea522b2a13751d79af64254 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 11:58:35 -0400 Subject: [PATCH 07/12] Check JOSE algorithms against key types --- src/jwe.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/jws.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/jwe.c b/src/jwe.c index 7fe14a5..7aee0f5 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -71,6 +71,12 @@ static bool _cjose_jwe_decrypt_dat_aes_gcm(cjose_jwe_t *jwe, cjose_err *err); static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err); +static bool _cjose_jwe_validate_decrypt_key(_jwe_int_recipient_t *recipient, + cjose_header_t *protected_header, + cjose_header_t *shared_header, + const cjose_jwk_t *jwk, + cjose_err *err); + static void _cjose_release_cek(uint8_t **cek, size_t cek_len) { @@ -1524,6 +1530,43 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) return false; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_validate_decrypt_key(_jwe_int_recipient_t *recipient, + cjose_header_t *protected_header, + cjose_header_t *shared_header, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + const char *alg = _cjose_jwe_get_from_headers(protected_header, shared_header, (cjose_header_t *)recipient->unprotected, CJOSE_HDR_ALG); + if (NULL == alg) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if (((0 == strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP)) || (0 == strcmp(alg, CJOSE_HDR_ALG_RSA1_5))) && jwk->kty != CJOSE_JWK_KTY_RSA) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if (((0 == strcmp(alg, CJOSE_HDR_ALG_A128KW)) || (0 == strcmp(alg, CJOSE_HDR_ALG_A192KW)) || (0 == strcmp(alg, CJOSE_HDR_ALG_A256KW)) + || (0 == strcmp(alg, CJOSE_HDR_ALG_DIR))) + && jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if ((0 == strcmp(alg, CJOSE_HDR_ALG_ECDH_ES)) && jwk->kty != CJOSE_JWK_KTY_EC) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// cjose_jwe_t *cjose_jwe_encrypt_iv(const cjose_jwk_t *jwk, cjose_header_t *protected_header, @@ -2124,6 +2167,11 @@ uint8_t *cjose_jwe_decrypt_multi(cjose_jwe_t *jwe, cjose_key_locator key_locator continue; } + if (!_cjose_jwe_validate_decrypt_key(jwe->to + i, (cjose_header_t *)jwe->hdr, (cjose_header_t *)jwe->shared_hdr, key, err)) + { + goto _cjose_jwe_decrypt_multi_fail; + } + // decrypt JWE content-encryption key from encrypted key if (!jwe->to[i].fns.decrypt_ek(jwe->to + i, jwe, key, err)) { @@ -2187,6 +2235,11 @@ uint8_t *cjose_jwe_decrypt(cjose_jwe_t *jwe, const cjose_jwk_t *jwk, size_t *con return NULL; } + if (!_cjose_jwe_validate_decrypt_key(jwe->to, (cjose_header_t *)jwe->hdr, (cjose_header_t *)jwe->shared_hdr, jwk, err)) + { + return NULL; + } + // decrypt JWE content-encryption key from encrypted key if (!jwe->to[0].fns.decrypt_ek(jwe->to, jwe, jwk, err)) { diff --git a/src/jws.c b/src/jws.c index d328d7e..5664a2a 100644 --- a/src/jws.c +++ b/src/jws.c @@ -46,6 +46,8 @@ static bool _cjose_jws_build_sig_ec(cjose_jws_t *jws, const cjose_jwk_t *jwk, cj static bool _cjose_jws_verify_sig_ec(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err); +static bool _cjose_jws_validate_verify_key(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err); + //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_build_hdr(cjose_jws_t *jws, cjose_header_t *header, cjose_err *err) { @@ -1083,6 +1085,49 @@ static bool _cjose_jws_verify_sig_ec(cjose_jws_t *jws, const cjose_jwk_t *jwk, c return retval; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jws_validate_verify_key(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) +{ + json_t *alg_obj = json_object_get(jws->hdr, CJOSE_HDR_ALG); + if (NULL == alg_obj || !json_is_string(alg_obj)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + const char *alg = json_string_value(alg_obj); + if (0 == strcmp(alg, CJOSE_HDR_ALG_NONE)) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if (((0 == strcmp(alg, CJOSE_HDR_ALG_PS256)) || (0 == strcmp(alg, CJOSE_HDR_ALG_PS384)) || (0 == strcmp(alg, CJOSE_HDR_ALG_PS512)) + || (0 == strcmp(alg, CJOSE_HDR_ALG_RS256)) || (0 == strcmp(alg, CJOSE_HDR_ALG_RS384)) + || (0 == strcmp(alg, CJOSE_HDR_ALG_RS512))) + && jwk->kty != CJOSE_JWK_KTY_RSA) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if (((0 == strcmp(alg, CJOSE_HDR_ALG_HS256)) || (0 == strcmp(alg, CJOSE_HDR_ALG_HS384)) || (0 == strcmp(alg, CJOSE_HDR_ALG_HS512))) + && jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + if (((0 == strcmp(alg, CJOSE_HDR_ALG_ES256)) || (0 == strcmp(alg, CJOSE_HDR_ALG_ES384)) || (0 == strcmp(alg, CJOSE_HDR_ALG_ES512))) + && jwk->kty != CJOSE_JWK_KTY_EC) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// bool cjose_jws_verify(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) { @@ -1098,6 +1143,11 @@ bool cjose_jws_verify(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) return false; } + if (!_cjose_jws_validate_verify_key(jws, jwk, err)) + { + return false; + } + // build JWS digest from header and payload (hashed signing input value) if (!jws->fns.digest(jws, jwk, err)) { From a0c49c7614e534840ee3983f89ee2ac1cb77076d Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:51:15 -0400 Subject: [PATCH 08/12] Enforce JOSE IV lengths --- src/jwe.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/jwe.c b/src/jwe.c index 7aee0f5..9966f70 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -1359,6 +1359,12 @@ static bool _cjose_jwe_decrypt_dat_aes_gcm(cjose_jwe_t *jwe, cjose_err *err) } EVP_CIPHER_CTX_init(ctx); + if (jwe->enc_iv.raw_len != 12) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto _cjose_jwe_decrypt_dat_aes_gcm_fail; + } + // initialize context for decryption using AES GCM cipher and CEK and IV if (EVP_DecryptInit_ex(ctx, cipher, NULL, jwe->cek, jwe->enc_iv.raw) != 1) { @@ -1434,6 +1440,12 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) } const char *enc = json_string_value(enc_obj); + if (jwe->enc_iv.raw_len != AES_BLOCK_SIZE) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + // calculate Authentication Tag unsigned int tag_len = 0; uint8_t tag[EVP_MAX_MD_SIZE]; From e8c27b05d78c3fafdb763e956c33b5b58761780e Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:51:26 -0400 Subject: [PATCH 09/12] Guard JWK retain count overflow --- src/jwk.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jwk.c b/src/jwk.c index 8173773..22b56fd 100644 --- a/src/jwk.c +++ b/src/jwk.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -169,8 +170,13 @@ cjose_jwk_t *cjose_jwk_retain(cjose_jwk_t *jwk, cjose_err *err) return NULL; } + if (UINT_MAX == jwk->retained) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_STATE); + return NULL; + } + ++(jwk->retained); - // TODO: check for overflow return jwk; } From 71137b732f41f65ea69197f94e691ca80af0b126 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:51:42 -0400 Subject: [PATCH 10/12] Check base64 decode length bounds --- src/base64.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/base64.c b/src/base64.c index 9ffe14d..79dc78f 100644 --- a/src/base64.c +++ b/src/base64.c @@ -73,6 +73,12 @@ static inline bool _decode(const char *input, size_t inlen, uint8_t **output, si // rlen takes a best guess on size; // might be too large for base64url, but never too small. + if (inlen > SIZE_MAX / 3) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + size_t rlen = ((inlen * 3) >> 2) + 3; uint8_t *buffer = cjose_get_alloc()(sizeof(uint8_t) * rlen); if (NULL == buffer) From 36f39909caf3b658d5a7e7b2fffc2f2f8d9ce8b7 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Mon, 27 Apr 2026 10:51:55 -0400 Subject: [PATCH 11/12] Check ECDH secret allocation result --- src/jwk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jwk.c b/src/jwk.c index 22b56fd..b2068a8 100644 --- a/src/jwk.c +++ b/src/jwk.c @@ -1821,7 +1821,7 @@ bool cjose_jwk_derive_ecdh_bits( // allocate buffer for shared secret secret = (uint8_t *)cjose_get_alloc()(secret_len); - if (NULL == output) + if (NULL == secret) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _cjose_jwk_derive_bits_fail; From a78a72315154be49619e1fee6e66becb1cd86e89 Mon Sep 17 00:00:00 2001 From: YASUOKA Masahiko Date: Wed, 29 Apr 2026 10:28:26 +0900 Subject: [PATCH 12/12] Cleanup previous. - expand a TAB - Cisco implementation recently fixied the IV size. --- src/jwe.c | 2 +- src/jws.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jwe.c b/src/jwe.c index 9966f70..98ff157 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -975,7 +975,7 @@ static bool _cjose_jwe_set_iv_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) // And in the example in A.2.4 (https://tools.ietf.org/html/rfc7516#appendix-A.2.4) // they provide an example for AES128-CBC, which results (naturally) in the IV size of 128Bit. // - // The CISCO implementation chooses for the size of the IV the key size of the + // The CISCO implementation chose for the size of the IV the key size of the // cipher algorithm, which seems to be wrong. // // According to RFC 3602 section 3 (https://tools.ietf.org/html/rfc3602#section-3): diff --git a/src/jws.c b/src/jws.c index 5664a2a..13bbb31 100644 --- a/src/jws.c +++ b/src/jws.c @@ -189,7 +189,7 @@ static bool _cjose_jws_build_dig_sha(cjose_jws_t *jws, const cjose_jwk_t *jwk, c if (NULL != jws->dig) { - _cjose_cleanse_dealloc(jws->dig, jws->dig_len); + _cjose_cleanse_dealloc(jws->dig, jws->dig_len); jws->dig = NULL; }