From a421f2d09a45d8b9ab8001bea251c1a0760b6890 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Wed, 14 Aug 2024 08:32:41 +0000 Subject: [PATCH] Import from CS git --- ...rivate-exponent-when-converting-jwk-.patch | 187 ++++++++ ...-potential-DoS-issue-with-p2c-header.patch | 112 +++++ ...-alg_comp-test-to-different-zlib-142.patch | 111 +++++ ...l-DoS-with-high-decompression-chunks.patch | 135 ++++++ ...-when-we-have-zip-in-the-protected-h.patch | 413 ++++++++++++++++++ SPECS/jose.spec | 23 +- 6 files changed, 979 insertions(+), 2 deletions(-) create mode 100644 SOURCES/0001-openssl-decode-private-exponent-when-converting-jwk-.patch create mode 100644 SOURCES/0002-Fix-potential-DoS-issue-with-p2c-header.patch create mode 100644 SOURCES/0003-Adapt-alg_comp-test-to-different-zlib-142.patch create mode 100644 SOURCES/0004-Avoid-potential-DoS-with-high-decompression-chunks.patch create mode 100644 SOURCES/0005-jwe-fix-the-case-when-we-have-zip-in-the-protected-h.patch diff --git a/SOURCES/0001-openssl-decode-private-exponent-when-converting-jwk-.patch b/SOURCES/0001-openssl-decode-private-exponent-when-converting-jwk-.patch new file mode 100644 index 0000000..b09fff0 --- /dev/null +++ b/SOURCES/0001-openssl-decode-private-exponent-when-converting-jwk-.patch @@ -0,0 +1,187 @@ +From 70cebe8c2f14ad9e7b8359ed53188bbd8ac3cce5 Mon Sep 17 00:00:00 2001 +From: Sergio Correia +Date: Thu, 23 May 2024 12:34:01 -0400 +Subject: [PATCH] openssl: decode private exponent when converting jwk -> RSA + +We were not decoding the private exponent, and thus always passing +NULL to RSA_set0_key() in jose_openssl_jwk_to_RSA(). + +Fixes: #75 + +This is a backport of https://github.com/latchset/jose/pull/76, with +some changes to adapt the test to run during the EL8 build, which +still uses autotools instead of meson. + +Signed-off-by: Sergio Correia +--- + configure.ac | 1 + + lib/openssl/jwk.c | 1 + + tests/Makefile.am | 7 +-- + tests/issue-75/issue-75.c | 89 +++++++++++++++++++++++++++++++++++++++ + tests/issue-75/rsa512.pem | 9 ++++ + 5 files changed, 104 insertions(+), 3 deletions(-) + create mode 100644 tests/issue-75/issue-75.c + create mode 100644 tests/issue-75/rsa512.pem + +diff --git a/configure.ac b/configure.ac +index cf8c9a6..fb9c7b7 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -14,6 +14,7 @@ PKG_PROG_PKG_CONFIG([0.25]) + PKG_CHECK_MODULES([zlib], [zlib]) + PKG_CHECK_MODULES([jansson], [jansson >= 2.10]) + PKG_CHECK_MODULES([libcrypto], [libcrypto >= 1.0.2]) ++PKG_CHECK_MODULES([libssl], [libssl >= 1.0.2]) + + AC_OPENMP + AC_SUBST([OPENMP_CFLAGS]) +diff --git a/lib/openssl/jwk.c b/lib/openssl/jwk.c +index 83be3a5..8fc1dd7 100644 +--- a/lib/openssl/jwk.c ++++ b/lib/openssl/jwk.c +@@ -305,6 +305,7 @@ jose_openssl_jwk_to_RSA(jose_cfg_t *cfg, const json_t *jwk) + + N = bn_decode_json(n); + E = bn_decode_json(e); ++ D = bn_decode_json(d); + P = bn_decode_json(p); + Q = bn_decode_json(q); + DP = bn_decode_json(dp); +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 282463e..cd330d7 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1,8 +1,8 @@ + AM_CFLAGS = @JOSE_CFLAGS@ @OPENMP_CFLAGS@ @jansson_CFLAGS@ -I$(top_srcdir) -I$(top_builddir) +-LDFLAGS += $(top_builddir)/lib/libjose.la @jansson_LIBS@ ++LDFLAGS += $(top_builddir)/lib/libjose.la @jansson_LIBS@ @libcrypto_LIBS@ @libssl_LIBS@ + EXTRA_DIST = vectors + +-AM_TESTS_ENVIRONMENT=PATH=$(top_builddir)/cmd:$(PATH) VECTORS=$(top_srcdir)/tests/vectors ++AM_TESTS_ENVIRONMENT=PATH=$(top_builddir)/cmd:$(PATH) VECTORS=$(top_srcdir)/tests/vectors ISSUE_75_DATADIR=$(top_srcdir)/tests/issue-75 + TESTS = $(dist_check_SCRIPTS) $(check_PROGRAMS) + + check_PROGRAMS = \ +@@ -13,7 +13,8 @@ check_PROGRAMS = \ + alg_comp \ + api_b64 \ + api_jws \ +- api_jwe ++ api_jwe \ ++ issue-75/issue-75 + + dist_check_SCRIPTS = \ + jose-alg \ +diff --git a/tests/issue-75/issue-75.c b/tests/issue-75/issue-75.c +new file mode 100644 +index 0000000..6e266df +--- /dev/null ++++ b/tests/issue-75/issue-75.c +@@ -0,0 +1,89 @@ ++/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ ++/* ++ * Copyright 2020 Red Hat, Inc. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++/* ++ * In this test we load a (RSA, 512-bit) PEM file asa n EVP_PKEY*, then ++ * convert it to JWK with jose_openssl_jwk_from_EVP_PKEY(). ++ * ++ * Afterwards, we convert this JWK to EVP_PKEY* again, with ++ * jose_openssl_jwk_to_EVP_PKEY(), and once more convert the ++ * resulting EVP_PKEY* back to JWK with jose_openssl_jwk_from_EVP_PKEY(). ++ * ++ * We then compare the two JWKs, and they should be equal. ++ */ ++ ++int ++main(int argc, char *argv[]) ++{ ++#if OPENSSL_VERSION_NUMBER < 0x10100000L ++ SSL_library_init(); ++#else ++ OPENSSL_init_ssl(0, NULL); ++#endif ++ ++ const char *issue_75_data_dir = getenv("ISSUE_75_DATADIR"); ++ if (!issue_75_data_dir) { ++ fprintf(stderr, "%s: ERROR: please, specify the ISSUE_75_DATADIR env variable, pointing out to the directory where the rsa512.pem used in the issue-75 test is located\n", argv[0]); ++ exit(1); ++ } ++ ++ const char *datafile = "rsa512.pem"; ++ if (strlen(datafile) + strlen(issue_75_data_dir + 1 /* path separator */) > PATH_MAX) { ++ fprintf(stderr, "%s: ERROR: invalid path to issue-75 data file: ISSUE_75_DATADIR is too large\n", argv[0]); ++ exit(1); ++ } ++ ++ char pemfile[PATH_MAX + 1] = {}; ++ snprintf(pemfile, PATH_MAX, "%s/%s", issue_75_data_dir, datafile); ++ ++ struct stat s_buffer; ++ if (stat(pemfile, &s_buffer) != 0) { ++ fprintf(stderr, "%s: ERROR: data file '%s' does not seem to exist; please make sure ISSUE_75_DATADIR is correctly set\n", argv[0], pemfile); ++ exit(1); ++ } ++ ++ BIO* pfile = BIO_new_file(pemfile, "r"); ++ assert(pfile); ++ ++ EVP_PKEY* pkey = PEM_read_bio_PrivateKey(pfile, NULL, 0, NULL); ++ assert(pkey); ++ BIO_free(pfile); ++ ++ json_auto_t* jwk = jose_openssl_jwk_from_EVP_PKEY(NULL, pkey); ++ assert(jwk); ++ ++ EVP_PKEY* from_jwk = jose_openssl_jwk_to_EVP_PKEY(NULL, jwk); ++ assert(from_jwk); ++ ++ json_auto_t* converted_jwk = jose_openssl_jwk_from_EVP_PKEY(NULL, from_jwk); ++ assert(converted_jwk); ++ ++ EVP_PKEY_free(pkey); ++ EVP_PKEY_free(from_jwk); ++ ++ assert(json_equal(jwk, converted_jwk)); ++ return EXIT_SUCCESS; ++} +diff --git a/tests/issue-75/rsa512.pem b/tests/issue-75/rsa512.pem +new file mode 100644 +index 0000000..961ec32 +--- /dev/null ++++ b/tests/issue-75/rsa512.pem +@@ -0,0 +1,9 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIBOgIBAAJBAMm/50Zj7HgDGBzTy6tmgeBq4jVVpbWA86ZBFgQpwOmXsRToQpCA ++K56DNzDBMOt4SIA7pF2uf9VBF3EQ7rg8H88CAwEAAQJAYK/HdsSMnzdcZvRZt1r5 ++A0Q2BLl3IPUbz6GBm50nBssB2lYZqxpOL0i5MO5wt7DgPzrbwjugjUvhkSwdy+Wo ++gQIhAO1KoRRDaUufWNkzLjx+1XbZFnZRw+xN4Nz2P0JrVRO9AiEA2afqKfzaaxGg ++tnZGINhYBx8Iym9cZ2BpXdh5ZGCydHsCIBIcYhLz2jOFY/if6WWAoLZDd21sbDG6 ++9/ClcsqU+pdZAiEA1zLDPkJnPidOrDjie4UL+/Z+PZC/XuKfKw9mbo2Aw9MCIB2E ++LzXkdu8W3g3ORa4jkV3na49Jiyg0VGeaAoauebo5 ++-----END RSA PRIVATE KEY----- +-- +2.44.0 + diff --git a/SOURCES/0002-Fix-potential-DoS-issue-with-p2c-header.patch b/SOURCES/0002-Fix-potential-DoS-issue-with-p2c-header.patch new file mode 100644 index 0000000..249c9df --- /dev/null +++ b/SOURCES/0002-Fix-potential-DoS-issue-with-p2c-header.patch @@ -0,0 +1,112 @@ +From 6825e070ef5cdcaf815bbd99089a3a6ef8b785d7 Mon Sep 17 00:00:00 2001 +From: Sergio Correia +Date: Thu, 23 May 2024 13:56:43 -0400 +Subject: [PATCH 2/2] Fix potential DoS issue with p2c header + +Unbounded p2c headers may be used to cause an application that accept +PBES algorithms to spend a lot of resources running PBKDF2 with a very +high number of iterations. + +Limit the maximum number of iterations to to 32768. + +Fixes: CVE-2023-50967 + +This is a backport of https://github.com/latchset/jose/pull/154, with +some changes to adapt the test to run during the EL8 build, which +still uses autotools instead of meson. + +Signed-off-by: Sergio Correia +--- + lib/openssl/pbes2.c | 9 +++++++-- + tests/Makefile.am | 2 +- + tests/cve-2023-50967/cve-2023-50967.jwe | 1 + + tests/cve-2023-50967/cve-2023-50967.jwk | 1 + + tests/jose-jwe-dec | 5 +++++ + 5 files changed, 15 insertions(+), 3 deletions(-) + create mode 100644 tests/cve-2023-50967/cve-2023-50967.jwe + create mode 100644 tests/cve-2023-50967/cve-2023-50967.jwk + +diff --git a/lib/openssl/pbes2.c b/lib/openssl/pbes2.c +index 0a2756e..b399c5d 100644 +--- a/lib/openssl/pbes2.c ++++ b/lib/openssl/pbes2.c +@@ -25,6 +25,8 @@ + #include + + #define NAMES "PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW" ++#define P2C_MIN_ITERATIONS 1000 ++#define P2C_MAX_ITERATIONS 32768 + + static json_t * + pbkdf2(const char *alg, jose_cfg_t *cfg, const json_t *jwk, int iter, +@@ -170,7 +172,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, + json_auto_t *hdr = NULL; + const char *aes = NULL; + json_t *h = NULL; +- int p2c = 10000; ++ int p2c = P2C_MAX_ITERATIONS; + size_t stl = 0; + + if (!json_object_get(cek, "k") && !jose_jwk_gen(cfg, cek)) +@@ -203,7 +205,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, + json_object_set_new(h, "p2c", json_integer(p2c)) < 0) + return false; + +- if (p2c < 1000) ++ if (p2c < P2C_MIN_ITERATIONS || p2c > P2C_MAX_ITERATIONS) + return false; + + if (json_object_set_new(h, "p2s", jose_b64_enc(st, stl)) == -1) +@@ -245,6 +247,9 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, + if (json_unpack(hdr, "{s:I}", "p2c", &p2c) == -1) + return false; + ++ if (p2c > P2C_MAX_ITERATIONS) ++ return false; ++ + stl = jose_b64_dec(json_object_get(hdr, "p2s"), NULL, 0); + if (stl < 8 || stl > sizeof(st)) + return false; +diff --git a/tests/Makefile.am b/tests/Makefile.am +index cd330d7..4a3651a 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -2,7 +2,7 @@ AM_CFLAGS = @JOSE_CFLAGS@ @OPENMP_CFLAGS@ @jansson_CFLAGS@ -I$(top_srcdir) -I$(t + LDFLAGS += $(top_builddir)/lib/libjose.la @jansson_LIBS@ @libcrypto_LIBS@ @libssl_LIBS@ + EXTRA_DIST = vectors + +-AM_TESTS_ENVIRONMENT=PATH=$(top_builddir)/cmd:$(PATH) VECTORS=$(top_srcdir)/tests/vectors ISSUE_75_DATADIR=$(top_srcdir)/tests/issue-75 ++AM_TESTS_ENVIRONMENT=PATH=$(top_builddir)/cmd:$(PATH) VECTORS=$(top_srcdir)/tests/vectors ISSUE_75_DATADIR=$(top_srcdir)/tests/issue-75 CVE_2023_50967=$(top_srcdir)/tests/cve-2023-50967 + TESTS = $(dist_check_SCRIPTS) $(check_PROGRAMS) + + check_PROGRAMS = \ +diff --git a/tests/cve-2023-50967/cve-2023-50967.jwe b/tests/cve-2023-50967/cve-2023-50967.jwe +new file mode 100644 +index 0000000..70bfc42 +--- /dev/null ++++ b/tests/cve-2023-50967/cve-2023-50967.jwe +@@ -0,0 +1 @@ ++{"ciphertext":"aaPb-JYGACs-loPwJkZewg","encrypted_key":"P1h8q8wLVxqYsZUuw6iEQTzgXVZHCsu8Eik-oqbE4AJGIDto3gb3SA","header":{"alg":"PBES2-HS256+A128KW","p2c":1000000000,"p2s":"qUQQWWkyyIqculSiC93mlg"},"iv":"Clg3JX9oNl_ck3sLSGrlgg","protected":"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"i7vga9tJkwRswFd7HlyD_A"} +diff --git a/tests/cve-2023-50967/cve-2023-50967.jwk b/tests/cve-2023-50967/cve-2023-50967.jwk +new file mode 100644 +index 0000000..d7fb1be +--- /dev/null ++++ b/tests/cve-2023-50967/cve-2023-50967.jwk +@@ -0,0 +1 @@ ++{"alg":"PBES2-HS256+A128KW","k":"VHBLJ4-PmnqELoKbQoXuRA","key_ops":["wrapKey","unwrapKey"],"kty":"oct"} +diff --git a/tests/jose-jwe-dec b/tests/jose-jwe-dec +index 9b2143c..b5b4995 100755 +--- a/tests/jose-jwe-dec ++++ b/tests/jose-jwe-dec +@@ -53,3 +53,8 @@ test "`jose jwe dec -i $prfx.12.jweg -k $prfx.12.jwk`" == "`cat $prfx.12.pt`" + test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.1.jwk`" == "`cat $prfx.13.pt`" + test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.2.jwk`" == "`cat $prfx.13.pt`" + test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.3.jwk`" == "`cat $prfx.13.pt`" ++ ++# CVE-2023-50967 - test originally from https://github.com/P3ngu1nW/CVE_Request/blob/main/latch-jose.md ++# This test is expected to fail quickly on patched systems. ++prfx="${CVE_2023_50967}/cve-2023-50967" ++! test "$(jose jwe dec -i $prfx.jwe -k $prfx.jwk)" +-- +2.43.0 + diff --git a/SOURCES/0003-Adapt-alg_comp-test-to-different-zlib-142.patch b/SOURCES/0003-Adapt-alg_comp-test-to-different-zlib-142.patch new file mode 100644 index 0000000..d1b6f05 --- /dev/null +++ b/SOURCES/0003-Adapt-alg_comp-test-to-different-zlib-142.patch @@ -0,0 +1,111 @@ +From 4878253a04be8d5b60a3b33262f60dac76eed3ec Mon Sep 17 00:00:00 2001 +From: Tulio Magno Quites Machado Filho +Date: Wed, 6 Dec 2023 19:12:13 -0300 +Subject: [PATCH] Adapt alg_comp test to different zlib (#142) + +Different zlib implementations such as zlib-ng, QATzip and libnxz +provide API and ABI compatibility with madler's zlib. However, they do +not guarantee identical output. +This makes it very hard to compare the length or binary output of a +compressed stream. + +Instead of doing that, this patch aims to compare the input against the +output of a compression and decompression cycle. + +Fixes #142. +--- + tests/alg_comp.c | 55 ++++++++++++++++++++++++++++-------------------- + 1 file changed, 32 insertions(+), 23 deletions(-) + +diff --git a/tests/alg_comp.c b/tests/alg_comp.c +index c9bef75..fcd305c 100644 +--- a/tests/alg_comp.c ++++ b/tests/alg_comp.c +@@ -41,22 +41,23 @@ const struct { + {} + }; + +-typedef typeof(((jose_hook_alg_t *) NULL)->comp.inf) comp_func_t; +- + static void +-test(const jose_hook_alg_t *a, comp_func_t func, bool iter, +- const uint8_t *i, size_t il, +- const uint8_t *o, size_t ol) ++test(const jose_hook_alg_t *a, bool iter, ++ const uint8_t *i, size_t il) + { + jose_io_auto_t *b = NULL; ++ jose_io_auto_t *c = NULL; + jose_io_auto_t *z = NULL; +- void *buf = NULL; +- size_t len = 0; ++ void *buf1 = NULL; ++ void *buf2 = NULL; ++ size_t blen = 0; ++ size_t clen = 0; + +- b = jose_io_malloc(NULL, &buf, &len); ++ /* Test compression first. */ ++ b = jose_io_malloc(NULL, &buf1, &blen); + assert(b); + +- z = func(a, NULL, b); ++ z = a->comp.def(a, NULL, b); + assert(z); + + if (iter) { +@@ -68,8 +69,26 @@ test(const jose_hook_alg_t *a, comp_func_t func, bool iter, + + assert(z->done(z)); + +- assert(len == ol); +- assert(memcmp(buf, o, ol) == 0); ++ /* Test decompression now. */ ++ c = jose_io_malloc(NULL, &buf2, &clen); ++ assert(b); ++ ++ z = a->comp.inf(a, NULL, c); ++ assert(z); ++ ++ if (iter) { ++ uint8_t *m = buf1; ++ for (size_t j = 0; j < blen; j++) ++ assert(z->feed(z, &m[j], 1)); ++ } else { ++ assert(z->feed(z, buf1, blen)); ++ } ++ ++ assert(z->done(z)); ++ ++ /* Compare the final output with the original input. */ ++ assert(clen == il); ++ assert(memcmp(buf2, i, il) == 0); + } + + int +@@ -93,20 +112,10 @@ main(int argc, char *argv[]) + assert(jose_b64_dec_buf(tests[i].def, strlen(tests[i].def), + tst_def, sizeof(tst_def)) == sizeof(tst_def)); + +- test(a, a->comp.def, false, +- tst_inf, sizeof(tst_inf), +- tst_def, sizeof(tst_def)); +- +- test(a, a->comp.inf, false, +- tst_def, sizeof(tst_def), ++ test(a, false, + tst_inf, sizeof(tst_inf)); + +- test(a, a->comp.def, true, +- tst_inf, sizeof(tst_inf), +- tst_def, sizeof(tst_def)); +- +- test(a, a->comp.inf, true, +- tst_def, sizeof(tst_def), ++ test(a, true, + tst_inf, sizeof(tst_inf)); + } + +-- +2.43.0 + diff --git a/SOURCES/0004-Avoid-potential-DoS-with-high-decompression-chunks.patch b/SOURCES/0004-Avoid-potential-DoS-with-high-decompression-chunks.patch new file mode 100644 index 0000000..a6c8665 --- /dev/null +++ b/SOURCES/0004-Avoid-potential-DoS-with-high-decompression-chunks.patch @@ -0,0 +1,135 @@ +From d2917d639717a9eaf401d87844ea2b78d597d917 Mon Sep 17 00:00:00 2001 +From: Sergio Arroutbi +Date: Mon, 1 Jul 2024 10:28:40 -0400 +Subject: [PATCH] Avoid potential DoS with high decompression chunks + +Backported from https://github.com/latchset/jose/pull/157 + +Signed-off-by: Sergio Arroutbi +--- + lib/hooks.h | 2 ++ + lib/zlib/deflate.c | 3 +++ + tests/alg_comp.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 72 insertions(+) + +diff --git a/lib/hooks.h b/lib/hooks.h +index 9da7b40..e9c78f4 100644 +--- a/lib/hooks.h ++++ b/lib/hooks.h +@@ -20,6 +20,8 @@ + #include + #include + ++#define MAX_COMPRESSED_SIZE (256*1024) ++ + typedef enum { + JOSE_HOOK_JWK_KIND_NONE = 0, + JOSE_HOOK_JWK_KIND_TYPE, +diff --git a/lib/zlib/deflate.c b/lib/zlib/deflate.c +index 07eca0c..04ded33 100644 +--- a/lib/zlib/deflate.c ++++ b/lib/zlib/deflate.c +@@ -113,6 +113,9 @@ def_free(jose_io_t *io) + static bool + inf_feed(jose_io_t *io, const void *in, size_t len) + { ++ if (len > MAX_COMPRESSED_SIZE) { ++ return false; ++ } + return feed(io, in, len, inflate); + } + +diff --git a/tests/alg_comp.c b/tests/alg_comp.c +index fcd305c..753566b 100644 +--- a/tests/alg_comp.c ++++ b/tests/alg_comp.c +@@ -19,6 +19,10 @@ + #include + #include + #include ++#include ++ ++static int g_high_compression_tested = 0; ++static int g_low_compression_tested = 0; + + const struct { + const char *alg; +@@ -41,6 +45,62 @@ const struct { + {} + }; + ++const uint32_t long_string_tests[] = { ++ 2000, 200000, 10000000, 0 ++}; ++ ++static uint8_t* get_random_string(uint32_t length) ++{ ++ assert(length); ++ uint8_t* c = (uint8_t*)malloc(length*sizeof(uint8_t)); ++ for (uint32_t i=0; icomp.def(a, NULL, b); ++ assert(z); ++ ++ assert(z->feed(z, str, inputlen)); ++ assert(z->done(z)); ++ ++ /* Test decompression now */ ++ c = jose_io_malloc(NULL, &buf2, &clen); ++ assert(b); ++ z = a->comp.inf(a, NULL, c); ++ assert(z); ++ ++ /* If length>MAX_COMPRESSED_SIZE, it must fail due to high decompression size */ ++ if(blen > MAX_COMPRESSED_SIZE) { ++ assert(!z->feed(z, buf1, blen)); ++ g_high_compression_tested = 1; ++ } else { ++ assert(z->feed(z, buf1, blen)); ++ g_low_compression_tested = 1; ++ /* Compare the final output with the original input. */ ++ assert(clen == inputlen); ++ assert(memcmp(buf2, str, inputlen) == 0); ++ } ++ assert(z->done(z)); ++ free(str); ++} ++ + static void + test(const jose_hook_alg_t *a, bool iter, + const uint8_t *i, size_t il) +@@ -119,5 +179,12 @@ main(int argc, char *argv[]) + tst_inf, sizeof(tst_inf)); + } + ++ for (size_t i = 0; long_string_tests[i]; i++) { ++ test_long_string(long_string_tests[i]); ++ } ++ ++ assert(1 == g_high_compression_tested); ++ assert(1 == g_low_compression_tested); ++ + return EXIT_SUCCESS; + } +-- +2.43.0 + diff --git a/SOURCES/0005-jwe-fix-the-case-when-we-have-zip-in-the-protected-h.patch b/SOURCES/0005-jwe-fix-the-case-when-we-have-zip-in-the-protected-h.patch new file mode 100644 index 0000000..c5e8043 --- /dev/null +++ b/SOURCES/0005-jwe-fix-the-case-when-we-have-zip-in-the-protected-h.patch @@ -0,0 +1,413 @@ +From c2f52c8a063aac43161b030c36f43587985e8cf7 Mon Sep 17 00:00:00 2001 +From: Sergio Correia +Date: Mon, 1 Jul 2024 10:30:39 -0400 +Subject: [PATCH] jwe: fix the case when we have "zip" in the protected header + +Backport from https://github.com/latchset/jose/pull/161 + +When we have "zip" in the protected header, e.g.: "zip": "DEF", +we should compress the payload before the encryption. + +However, as it stands, we are doing the compression after the +encryption, which results in the jose_jwe_enc* functions +producing JWEs that we are unable to decrypt afterwards. + +For the "zip" case, we do the compression now before the +encryption, to fix this behavior. + +Also add some tests to exercise these scenarios, both using +the jose_jwe_enc*/jose_jwe_dec* functions, as well as the +command line utilities jose jwe enc / jose jwe dec. + +Signed-off-by: Sergio Correia +--- + lib/jwe.c | 26 +++--------- + lib/misc.c | 58 +++++++++++++++++++++++++ + lib/misc.h | 6 +++ + lib/openssl/aescbch.c | 9 +++- + lib/openssl/aesgcm.c | 10 ++++- + tests/alg_comp.c | 1 + + tests/api_jwe.c | 99 +++++++++++++++++++++++++++++++++++++++++-- + tests/jose-jwe-enc | 9 ++++ + 8 files changed, 192 insertions(+), 26 deletions(-) + +diff --git a/lib/jwe.c b/lib/jwe.c +index 516245b..55b4333 100644 +--- a/lib/jwe.c ++++ b/lib/jwe.c +@@ -275,14 +275,8 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek, + jose_io_t *next) + { + const jose_hook_alg_t *alg = NULL; +- jose_io_auto_t *zip = NULL; +- json_auto_t *prt = NULL; + const char *h = NULL; + const char *k = NULL; +- const char *z = NULL; +- +- prt = jose_b64_dec_load(json_object_get(jwe, "protected")); +- (void) json_unpack(prt, "{s:s}", "zip", &z); + + if (json_unpack(jwe, "{s?{s?s}}", "unprotected", "enc", &h) < 0) + return NULL; +@@ -336,19 +330,7 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek, + if (!encode_protected(jwe)) + return NULL; + +- if (z) { +- const jose_hook_alg_t *a = NULL; +- +- a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z); +- if (!a) +- return NULL; +- +- zip = a->comp.def(a, cfg, next); +- if (!zip) +- return NULL; +- } +- +- return alg->encr.enc(alg, cfg, jwe, cek, zip ? zip : next); ++ return alg->encr.enc(alg, cfg, jwe, cek, next); + } + + void * +@@ -463,6 +445,12 @@ jose_jwe_dec_cek(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek, + o = jose_io_malloc(cfg, &pt, ptl); + d = jose_jwe_dec_cek_io(cfg, jwe, cek, o); + i = jose_b64_dec_io(d); ++ ++ /* Here we make sure the ciphertext is not larger than our ++ * compression limit. */ ++ if (zip_in_protected_header((json_t*)jwe) && ctl > MAX_COMPRESSED_SIZE) ++ return false; ++ + if (!o || !d || !i || !i->feed(i, ct, ctl) || !i->done(i)) + return NULL; + +diff --git a/lib/misc.c b/lib/misc.c +index 465cd0d..1015ce4 100644 +--- a/lib/misc.c ++++ b/lib/misc.c +@@ -18,6 +18,7 @@ + #include "misc.h" + #include + #include ++#include "hooks.h" + + bool + encode_protected(json_t *obj) +@@ -42,6 +43,63 @@ zero(void *mem, size_t len) + memset(mem, 0, len); + } + ++ ++bool ++handle_zip_enc(json_t *json, const void *in, size_t len, void **data, size_t *datalen) ++{ ++ json_t *prt = NULL; ++ char *z = NULL; ++ const jose_hook_alg_t *a = NULL; ++ jose_io_auto_t *zip = NULL; ++ jose_io_auto_t *zipdata = NULL; ++ ++ prt = json_object_get(json, "protected"); ++ if (prt && json_is_string(prt)) ++ prt = jose_b64_dec_load(prt); ++ ++ /* Check if we have "zip" in the protected header. */ ++ if (json_unpack(prt, "{s:s}", "zip", &z) == -1) { ++ /* No zip. */ ++ *data = (void*)in; ++ *datalen = len; ++ return true; ++ } ++ ++ /* OK, we have "zip", so we should compress the payload before ++ * the encryption takes place. */ ++ a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z); ++ if (!a) ++ return false; ++ ++ zipdata = jose_io_malloc(NULL, data, datalen); ++ if (!zipdata) ++ return false; ++ ++ zip = a->comp.def(a, NULL, zipdata); ++ if (!zip || !zip->feed(zip, in, len) || !zip->done(zip)) ++ return false; ++ ++ return true; ++} ++ ++bool ++zip_in_protected_header(json_t *json) ++{ ++ json_t *prt = NULL; ++ char *z = NULL; ++ ++ prt = json_object_get(json, "protected"); ++ if (prt && json_is_string(prt)) ++ prt = jose_b64_dec_load(prt); ++ ++ /* Check if we have "zip" in the protected header. */ ++ if (json_unpack(prt, "{s:s}", "zip", &z) == -1) ++ return false; ++ ++ /* We have "zip", but let's validate the alg also. */ ++ return jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z) != NULL; ++} ++ + static void __attribute__((constructor)) + constructor(void) + { +diff --git a/lib/misc.h b/lib/misc.h +index d479d53..18e7710 100644 +--- a/lib/misc.h ++++ b/lib/misc.h +@@ -30,3 +30,9 @@ encode_protected(json_t *obj); + + void + zero(void *mem, size_t len); ++ ++bool ++handle_zip_enc(json_t *jwe, const void *in, size_t len, void **data, size_t *data_len); ++ ++bool ++zip_in_protected_header(json_t *jwe); +diff --git a/lib/openssl/aescbch.c b/lib/openssl/aescbch.c +index ce8073d..b0e6419 100644 +--- a/lib/openssl/aescbch.c ++++ b/lib/openssl/aescbch.c +@@ -18,6 +18,7 @@ + #include "misc.h" + #include + #include "../hooks.h" ++#include "../misc.h" + + #include + #include +@@ -132,9 +133,13 @@ enc_feed(jose_io_t *io, const void *in, size_t len) + io_t *i = containerof(io, io_t, io); + + uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1]; +- const uint8_t *pt = in; ++ uint8_t *pt = NULL; ++ size_t ptlen = 0; + +- for (size_t j = 0; j < len; j++) { ++ if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen)) ++ return false; ++ ++ for (size_t j = 0; j < ptlen; j++) { + int l = 0; + + if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0) +diff --git a/lib/openssl/aesgcm.c b/lib/openssl/aesgcm.c +index 190b469..1a88719 100644 +--- a/lib/openssl/aesgcm.c ++++ b/lib/openssl/aesgcm.c +@@ -18,6 +18,7 @@ + #include "misc.h" + #include + #include "../hooks.h" ++#include "../misc.h" + + #include + +@@ -103,10 +104,15 @@ static bool + enc_feed(jose_io_t *io, const void *in, size_t len) + { + io_t *i = containerof(io, io_t, io); +- const uint8_t *pt = in; + int l = 0; + +- for (size_t j = 0; j < len; j++) { ++ uint8_t *pt = NULL; ++ size_t ptlen = 0; ++ ++ if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen)) ++ return false; ++ ++ for (size_t j = 0; j < ptlen; j++) { + uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1]; + + if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0) +diff --git a/tests/alg_comp.c b/tests/alg_comp.c +index 753566b..33dc32e 100644 +--- a/tests/alg_comp.c ++++ b/tests/alg_comp.c +@@ -53,6 +53,7 @@ static uint8_t* get_random_string(uint32_t length) + { + assert(length); + uint8_t* c = (uint8_t*)malloc(length*sizeof(uint8_t)); ++ assert(c); + for (uint32_t i=0; i + #include + ++#include "../lib/hooks.h" /* for MAX_COMPRESSED_SIZE */ ++ + static bool +-dec(json_t *jwe, json_t *jwk) ++dec_cmp(json_t *jwe, json_t *jwk, const char* expected_data, size_t expected_len) + { + bool ret = false; + char *pt = NULL; +@@ -30,10 +32,10 @@ dec(json_t *jwe, json_t *jwk) + if (!pt) + goto error; + +- if (ptl != 4) ++ if (ptl != expected_len) + goto error; + +- if (strcmp(pt, "foo") != 0) ++ if (strcmp(pt, expected_data) != 0) + goto error; + + ret = true; +@@ -43,12 +45,40 @@ error: + return ret; + } + ++static bool ++dec(json_t *jwe, json_t *jwk) ++{ ++ return dec_cmp(jwe, jwk, "foo", 4); ++} ++ ++struct zip_test_data_t { ++ char* data; ++ size_t datalen; ++ bool expected; ++}; ++ ++static char* ++make_data(size_t len) ++{ ++ assert(len > 0); ++ ++ char *data = malloc(len); ++ assert(data); ++ ++ for (size_t i = 0; i < len; i++) { ++ data[i] = 'A' + (random() % 26); ++ } ++ data[len-1] = '\0'; ++ return data; ++} ++ + int + main(int argc, char *argv[]) + { + json_auto_t *jwke = json_pack("{s:s}", "alg", "ECDH-ES+A128KW"); + json_auto_t *jwkr = json_pack("{s:s}", "alg", "RSA1_5"); + json_auto_t *jwko = json_pack("{s:s}", "alg", "A128KW"); ++ json_auto_t *jwkz = json_pack("{s:s, s:i}", "kty", "oct", "bytes", 16); + json_auto_t *set0 = json_pack("{s:[O,O]}", "keys", jwke, jwko); + json_auto_t *set1 = json_pack("{s:[O,O]}", "keys", jwkr, jwko); + json_auto_t *set2 = json_pack("{s:[O,O]}", "keys", jwke, jwkr); +@@ -57,6 +87,7 @@ main(int argc, char *argv[]) + assert(jose_jwk_gen(NULL, jwke)); + assert(jose_jwk_gen(NULL, jwkr)); + assert(jose_jwk_gen(NULL, jwko)); ++ assert(jose_jwk_gen(NULL, jwkz)); + + json_decref(jwe); + assert((jwe = json_object())); +@@ -98,5 +129,67 @@ main(int argc, char *argv[]) + assert(dec(jwe, set1)); + assert(dec(jwe, set2)); + ++ ++ json_decref(jwe); ++ assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF"))); ++ assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, "foo", 4)); ++ assert(dec(jwe, jwkz)); ++ assert(!dec(jwe, jwkr)); ++ assert(!dec(jwe, jwko)); ++ assert(!dec(jwe, set0)); ++ assert(!dec(jwe, set1)); ++ assert(!dec(jwe, set2)); ++ ++ /* Some tests with "zip": "DEF" */ ++ struct zip_test_data_t zip[] = { ++ { ++ .data = make_data(5), ++ .datalen = 5, ++ .expected = true, ++ }, ++ { ++ .data = make_data(50), ++ .datalen = 50, ++ .expected = true, ++ }, ++ { ++ .data = make_data(1000), ++ .datalen = 1000, ++ .expected = true, ++ }, ++ { ++ .data = make_data(10000000), ++ .datalen = 10000000, ++ .expected = false, /* compressed len will be ~8000000+ ++ * (i.e. > MAX_COMPRESSED_SIZE) ++ */ ++ }, ++ { ++ .data = make_data(50000), ++ .datalen = 50000, ++ .expected = true ++ }, ++ { ++ ++ .data = NULL ++ } ++ }; ++ ++ for (size_t i = 0; zip[i].data != NULL; i++) { ++ json_decref(jwe); ++ assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF"))); ++ assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, zip[i].data, zip[i].datalen)); ++ ++ /* Now let's get the ciphertext compressed len. */ ++ char *ct = NULL; ++ size_t ctl = 0; ++ assert(json_unpack(jwe, "{s:s%}", "ciphertext", &ct, &ctl) != -1); ++ /* And check our expectation is correct. */ ++ assert(zip[i].expected == (ctl < MAX_COMPRESSED_SIZE)); ++ ++ assert(dec_cmp(jwe, jwkz, zip[i].data, zip[i].datalen) == zip[i].expected); ++ free(zip[i].data); ++ zip[i].data = NULL; ++ } + return EXIT_SUCCESS; + } +diff --git a/tests/jose-jwe-enc b/tests/jose-jwe-enc +index 4644aee..0091c4b 100755 +--- a/tests/jose-jwe-enc ++++ b/tests/jose-jwe-enc +@@ -75,4 +75,13 @@ for msg in "hi" "this is a longer message that is more than one block"; do + echo -n "$msg" | jose jwe enc -I- -k $jwk -o $jwe + [ "`jose jwe dec -i $jwe -k $jwk -O-`" == "$msg" ] + done ++ ++ # "zip": "DEF" ++ tmpl='{"kty":"oct","bytes":32}' ++ for enc in A128CBC-HS256 A192CBC-HS384 A256CBC-HS512 A128GCM A192GCM A256GCM; do ++ jose jwk gen -i "${tmpl}" -o "${jwk}" ++ zip="$(printf '{"alg":"A128KW","enc":"%s","zip":"DEF"}' "${enc}")" ++ printf '%s' "${msg}" | jose jwe enc -i "${zip}" -I- -k "${jwk}" -o "${jwe}" ++ [ "$(jose jwe dec -i "${jwe}" -k "${jwk}" -O-)" = "${msg}" ] ++ done + done +-- +2.43.0 + diff --git a/SPECS/jose.spec b/SPECS/jose.spec index dfb13ed..942dbc0 100644 --- a/SPECS/jose.spec +++ b/SPECS/jose.spec @@ -1,16 +1,23 @@ Name: jose Version: 10 -Release: 2%{?dist} +Release: 2%{?dist}.3 Summary: Tools for JSON Object Signing and Encryption (JOSE) License: ASL 2.0 URL: https://github.com/latchset/%{name} Source0: https://github.com/latchset/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.bz2 +Patch1: 0001-openssl-decode-private-exponent-when-converting-jwk-.patch +Patch2: 0002-Fix-potential-DoS-issue-with-p2c-header.patch +Patch3: 0003-Adapt-alg_comp-test-to-different-zlib-142.patch +Patch4: 0004-Avoid-potential-DoS-with-high-decompression-chunks.patch +Patch5: 0005-jwe-fix-the-case-when-we-have-zip-in-the-protected-h.patch + BuildRequires: pkgconfig BuildRequires: jansson-devel >= 2.10 BuildRequires: openssl-devel BuildRequires: zlib-devel +BuildRequires: autoconf automake libtool Requires: lib%{name}%{?_isa} = %{version}-%{release} %description @@ -43,7 +50,8 @@ Obsoletes: lib%{name}-zlib-devel < %{version}-%{release} This package contains development files for lib%{name}. %prep -%setup -q +%autosetup -p1 +autoreconf -fv --install %build %if 0%{?rhel} @@ -79,6 +87,17 @@ make %{?_smp_mflags} check %{_mandir}/man3/jose*.3* %changelog +* Mon Jul 01 2024 Sergio Correia - 10-2.3 +- Backport fix for CVE-2024-28176 + Resolves: RHEL-28719 + +* Mon Jul 01 2024 Sergio Correia - 10-2.2 +- Fix tests on s390x + Related: RHEL-29857 + +* Sun Jun 30 2024 Sergio Correia - 10-2.1 +- Fixes CVE-2023-50967 + * Wed Feb 07 2018 Fedora Release Engineering - 10-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild