Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
steps:
- uses: actions/checkout@v5
- name: Cache vcpkg binary archives
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: ${{ env.LOCALAPPDATA }}\vcpkg\archives
key: vcpkg-archives-${{ runner.os }}-x64-windows-openssl-jansson-check
Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/openssl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: OpenSSL

on:
pull_request:
push:
branches: [master]

jobs:
format:
uses: ./.github/workflows/format.yml

build:
needs: format
runs-on: ubuntu-latest
name: OpenSSL ${{ matrix.openssl }}

# The Build and Archs workflows test against the OpenSSL that the platform
# packages provide, which is a recent release. README.md promises OpenSSL
# 1.0.1h or newer, so this workflow builds the last release of every
# release series from source and tests against it, plus 3.0.7: the release
# RHEL 9 is based on and the last 3.0.x whose EdDSA signing dereferences a
# missing private key instead of failing (guarded in 3.0.8).
strategy:
fail-fast: false
matrix:
include:
- { openssl: 1.0.1u, tag: OpenSSL_1_0_1u, sha256: 4312b4ca1215b6f2c97007503d80db80d5157f76f8f7d3febbe6b4c56ff26739 }
- { openssl: 1.0.2u, tag: OpenSSL_1_0_2u, sha256: ecd0c6ffb493dd06707d38b14bb4d8c2288bb7033735606569d8f90f89669d16 }
- { openssl: 1.1.0l, tag: OpenSSL_1_1_0l, sha256: 74a2f756c64fd7386a29184dc0344f4831192d61dc2481a93a4c5dd727f41148 }
- { openssl: 1.1.1w, tag: OpenSSL_1_1_1w, sha256: cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8 }
- { openssl: 3.0.7, tag: openssl-3.0.7, sha256: 83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e }
- { openssl: 3.0.22, tag: openssl-3.0.22, sha256: 67ebca7e50d17383028045486653492195b83db95f8558709701bb47b5c1ef81 }
- { openssl: 3.1.8, tag: openssl-3.1.8, sha256: d319da6aecde3aa6f426b44bbf997406d95275c5c59ab6f6ef53caaa079f456f }
- { openssl: 3.2.6, tag: openssl-3.2.6, sha256: 89681a9ddaa9ed7cf25ea8ef61338db805200bae47d00510490623547380c148 }
- { openssl: 3.3.7, tag: openssl-3.3.7, sha256: 4900be54e81c4dfe00bb1a10dad33fd8414833573c40d0e9e3274d4ed32e53a2 }
- { openssl: 3.4.7, tag: openssl-3.4.7, sha256: 7261c348e6e59bc7b635a30f98072e8817a1c60a7033bb570737394a3a061842 }
- { openssl: 3.5.8, tag: openssl-3.5.8, sha256: a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2 }
- { openssl: 3.6.4, tag: openssl-3.6.4, sha256: 9bffaa1ad1e07b354c21bd3324ec02fa15579f45a7d0494b3e74bc449b7333ef }
- { openssl: 4.0.2, tag: openssl-4.0.2, sha256: 736b467530f916737b7031310ccb21d8218c6229e61e8e160cd1d3458cd543a8 }

steps:
- uses: actions/checkout@v5
- name: Dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y cmake pkg-config gcc libjansson-dev check
echo "OPENSSL_PREFIX=$HOME/openssl-${{ matrix.openssl }}" >> "$GITHUB_ENV"
- name: Cache OpenSSL ${{ matrix.openssl }}
id: cache-openssl
uses: actions/cache@v6
with:
path: ~/openssl-${{ matrix.openssl }}
key: openssl-${{ matrix.openssl }}-${{ runner.os }}-${{ runner.arch }}
- name: Build OpenSSL ${{ matrix.openssl }}
if: steps.cache-openssl.outputs.cache-hit != 'true'
working-directory: ${{ runner.temp }}
run: |
curl -fsSLO "https://github.com/openssl/openssl/releases/download/${{ matrix.tag }}/openssl-${{ matrix.openssl }}.tar.gz"
echo "${{ matrix.sha256 }} openssl-${{ matrix.openssl }}.tar.gz" | sha256sum -c -
tar xzf "openssl-${{ matrix.openssl }}.tar.gz"
cd "openssl-${{ matrix.openssl }}"
case "${{ matrix.openssl }}" in
1.0.*)
# static by default, no --libdir, needs 'make depend', parallel make is unreliable
./config --prefix="$OPENSSL_PREFIX" shared
make depend
make
;;
1.1.0*)
# has --libdir but not no-tests
./config --prefix="$OPENSSL_PREFIX" --libdir=lib
make -j"$(nproc)"
;;
*)
./config --prefix="$OPENSSL_PREFIX" --libdir=lib no-tests
make -j"$(nproc)"
;;
esac
make install_sw
- name: Configure
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DOPENSSL_ROOT_DIR="$OPENSSL_PREFIX"
- name: Build
run: cmake --build build --parallel
- name: Check which OpenSSL the tests load
run: |
LD_LIBRARY_PATH="$OPENSSL_PREFIX/lib" "$OPENSSL_PREFIX/bin/openssl" version
ldd build/check_cjose | grep -F "$OPENSSL_PREFIX/lib/libcrypto"
- name: Test
run: ctest --test-dir build --output-on-failure
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release Notes #

<a name="unreleased"></a>
## [Unreleased](https://github.com/cisco/cjose/0.7.0..master)

### Fix

* Check the key type before the HMAC digest step: cjose_jws_sign with an HS256, HS384 or HS512 header and an EC or RSA key ran the HMAC over the key structure and past the end of its allocation before failing with CJOSE_ERR_INVALID_ARG

<a name="0.7.0"></a>
## [0.7.0](https://github.com/cisco/cjose/0.6.3..0.7.0) (2026-09-09)

Expand Down
4 changes: 0 additions & 4 deletions src/jwe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1354,7 +1354,6 @@ static bool _cjose_jwe_encrypt_dat_aes_gcm(cjose_jwe_t *jwe, const uint8_t *plai
CJOSE_ERROR(err, CJOSE_ERR_CRYPTO);
goto _cjose_jwe_encrypt_dat_fail;
}
EVP_CIPHER_CTX_init(ctx);

// initialize context for encryption using the AES GCM cipher and CEK and IV
if (EVP_EncryptInit_ex(ctx, cipher, NULL, jwe->cek, jwe->enc_iv.raw) != 1)
Expand Down Expand Up @@ -1558,7 +1557,6 @@ static bool _cjose_jwe_encrypt_dat_aes_cbc(cjose_jwe_t *jwe, const uint8_t *plai
CJOSE_ERROR(err, CJOSE_ERR_CRYPTO);
goto _cjose_jwe_encrypt_dat_aes_cbc_fail;
}
EVP_CIPHER_CTX_init(ctx);

// initialize context for decryption using the cipher, the 2nd half of the CEK and the IV
if (EVP_EncryptInit_ex(ctx, cipher, NULL, jwe->cek + jwe->cek_len / 2, jwe->enc_iv.raw) != 1)
Expand Down Expand Up @@ -1664,7 +1662,6 @@ static bool _cjose_jwe_decrypt_dat_aes_gcm(cjose_jwe_t *jwe, cjose_err *err)
CJOSE_ERROR(err, CJOSE_ERR_CRYPTO);
goto _cjose_jwe_decrypt_dat_aes_gcm_fail;
}
EVP_CIPHER_CTX_init(ctx);

if (jwe->enc_iv.raw_len != 12)
{
Expand Down Expand Up @@ -1798,7 +1795,6 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
CJOSE_ERROR(err, CJOSE_ERR_CRYPTO);
goto _cjose_jwe_decrypt_dat_aes_cbc_fail;
}
EVP_CIPHER_CTX_init(ctx);

// initialize context for decryption using the cipher, the 2nd half of the CEK and the IV
if (EVP_DecryptInit_ex(ctx, cipher, NULL, jwe->cek + jwe->cek_len / 2, jwe->enc_iv.raw) != 1)
Expand Down
8 changes: 8 additions & 0 deletions src/jws.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/hmac.h>

Expand Down Expand Up @@ -253,6 +254,13 @@ static bool _cjose_jws_build_dig_hmac_sha(cjose_jws_t *jws, const cjose_jwk_t *j
bool retval = false;
HMAC_CTX *ctx = NULL;

// ensure jwk is OCT: only then is keydata the raw key material
if (jwk->kty != CJOSE_JWK_KTY_OCT)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
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)
Expand Down
9 changes: 7 additions & 2 deletions test/check_cjose.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdlib.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/opensslv.h>

Suite *cjose_suite(void)
{
Expand All @@ -17,9 +18,11 @@ Suite *cjose_suite(void)

int main(void)
{
// initialize "OpenSSL" crypto
// initialize "OpenSSL" crypto (automatic since OpenSSL 1.1.0)
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
#endif

// setup suites
SRunner *runner = srunner_create(cjose_suite());
Expand All @@ -39,9 +42,11 @@ int main(void)
int failed = srunner_ntests_failed(runner);
srunner_free(runner);

// cleanup "OpenSSL" crypto
// cleanup "OpenSSL" crypto (automatic since OpenSSL 1.1.0)
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_cleanup();
ERR_free_strings();
#endif

return (0 == failed) ? EXIT_SUCCESS : EXIT_FAILURE;
}
38 changes: 38 additions & 0 deletions test/check_jws.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,43 @@ START_TEST(test_cjose_jws_sign_with_bad_key)
}
END_TEST

START_TEST(test_cjose_jws_sign_hmac_with_non_oct_key)
{
cjose_err err;
static const uint8_t plain[] = "The mind is everything. What you think you become.";
size_t plain_len = sizeof(plain) - 1;

// HS256/HS384/HS512 take an oct key; for any other key type keydata is a
// key structure, not raw key material, and must not be fed to the HMAC.
// Before the fix the digest step ran the HMAC over keysize/8 octets of
// jwk->keydata and only the signing step rejected the key type: an
// over-read of the (much smaller) EC or RSA key structure that valgrind
// flags. The P-521 key reads 65 octets from a 16 octet allocation.
cjose_jwk_t *keys[3] = { NULL, NULL, NULL };
keys[0] = cjose_jwk_import(JWK_COMMON_EC, strlen(JWK_COMMON_EC), &err);
keys[1] = cjose_jwk_create_EC_random(CJOSE_JWK_EC_P_521, &err);
keys[2] = cjose_jwk_import(JWK_COMMON, strlen(JWK_COMMON), &err); // RSA
static const char *ALGS[] = { CJOSE_HDR_ALG_HS256, CJOSE_HDR_ALG_HS384, CJOSE_HDR_ALG_HS512 };

cjose_header_t *hdr = cjose_header_new(&err);
ck_assert(NULL != hdr);
for (size_t k = 0; k < sizeof(keys) / sizeof(keys[0]); k++)
{
ck_assert_msg(NULL != keys[k], "creating key %zu failed: %s", k, err.message);
for (size_t a = 0; a < sizeof(ALGS) / sizeof(ALGS[0]); a++)
{
ck_assert(cjose_header_set(hdr, CJOSE_HDR_ALG, ALGS[a], &err));
cjose_jws_t *jws = cjose_jws_sign(keys[k], hdr, plain, plain_len, &err);
ck_assert_msg(NULL == jws, "cjose_jws_sign [%s] accepted a non-oct key (key %zu)", ALGS[a], k);
ck_assert_msg(err.code == CJOSE_ERR_INVALID_ARG, "cjose_jws_sign [%s] returned wrong err.code (%u:%s)", ALGS[a],
err.code, err.message);
}
cjose_jwk_release(keys[k]);
}
cjose_header_release(hdr);
}
END_TEST

START_TEST(test_cjose_jws_sign_with_bad_content)
{
cjose_err err;
Expand Down Expand Up @@ -1199,6 +1236,7 @@ Suite *cjose_jws_suite(void)
tcase_add_test(tc_jws, test_cjose_jws_es256k_rejects_wrong_curve);
tcase_add_test(tc_jws, test_cjose_jws_sign_with_bad_header);
tcase_add_test(tc_jws, test_cjose_jws_sign_with_bad_key);
tcase_add_test(tc_jws, test_cjose_jws_sign_hmac_with_non_oct_key);
tcase_add_test(tc_jws, test_cjose_jws_sign_with_bad_content);
tcase_add_test(tc_jws, test_cjose_jws_import_export_compare);
tcase_add_test(tc_jws, test_cjose_jws_import_invalid_serialization);
Expand Down
Loading