Compare commits

..

No commits in common. "c9" and "c8" have entirely different histories.
c9 ... c8

8 changed files with 989 additions and 52 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/jose-11.tar.xz SOURCES/jose-10.tar.bz2

View File

@ -1 +1 @@
4aef660fa140c8897d76039b651cd093207ad456 SOURCES/jose-11.tar.xz def05b305a862e23910c3dfc6cacb511b935ab65 SOURCES/jose-10.tar.bz2

View File

@ -0,0 +1,187 @@
From 70cebe8c2f14ad9e7b8359ed53188bbd8ac3cce5 Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
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 <scorreia@redhat.com>
---
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 <jose/jose.h>
+#include <jose/openssl.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+
+/*
+ * 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

View File

@ -0,0 +1,112 @@
From 6825e070ef5cdcaf815bbd99089a3a6ef8b785d7 Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
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 <scorreia@redhat.com>
---
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 <string.h>
#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

View File

@ -0,0 +1,111 @@
From 4878253a04be8d5b60a3b33262f60dac76eed3ec Mon Sep 17 00:00:00 2001
From: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
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

View File

@ -0,0 +1,135 @@
From d2917d639717a9eaf401d87844ea2b78d597d917 Mon Sep 17 00:00:00 2001
From: Sergio Arroutbi <sarroutb@redhat.com>
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 <sarroutb@redhat.com>
---
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 <jose/jws.h>
#include <jose/jwe.h>
+#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 <jose/jose.h>
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+
+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; i<length; i++) {
+ c[i] = 'A' + (random() % 26);
+ }
+ return c;
+}
+
+static void
+test_long_string(size_t inputlen) {
+ jose_io_auto_t *b = NULL;
+ jose_io_auto_t *c = NULL;
+ jose_io_auto_t *z = NULL;
+ void *buf1 = NULL;
+ void *buf2 = NULL;
+ size_t blen = 0;
+ size_t clen = 0;
+ const jose_hook_alg_t *a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, "DEF");
+ uint8_t* str = get_random_string(inputlen);
+
+ /* Test compression first. */
+ b = jose_io_malloc(NULL, &buf1, &blen);
+ assert(b);
+ z = a->comp.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

View File

@ -0,0 +1,413 @@
From c2f52c8a063aac43161b030c36f43587985e8cf7 Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
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 <scorreia@redhat.com>
---
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 <jose/b64.h>
#include <string.h>
+#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 <jose/b64.h>
#include "../hooks.h"
+#include "../misc.h"
#include <openssl/rand.h>
#include <openssl/sha.h>
@@ -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 <jose/b64.h>
#include "../hooks.h"
+#include "../misc.h"
#include <openssl/rand.h>
@@ -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<length; i++) {
c[i] = 'A' + (random() % 26);
}
diff --git a/tests/api_jwe.c b/tests/api_jwe.c
index f1d7a48..5fa4e10 100644
--- a/tests/api_jwe.c
+++ b/tests/api_jwe.c
@@ -19,8 +19,10 @@
#include <assert.h>
#include <string.h>
+#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

View File

@ -1,21 +1,23 @@
Name: jose Name: jose
Version: 11 Version: 10
Release: 3%{?dist} Release: 2%{?dist}.3
Summary: Tools for JSON Object Signing and Encryption (JOSE) Summary: Tools for JSON Object Signing and Encryption (JOSE)
License: ASL 2.0 License: ASL 2.0
URL: https://github.com/latchset/%{name} URL: https://github.com/latchset/%{name}
Source0: https://github.com/latchset/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.xz 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: gcc
BuildRequires: pkgconfig BuildRequires: pkgconfig
BuildRequires: jansson-devel >= 2.10 BuildRequires: jansson-devel >= 2.10
BuildRequires: openssl-devel BuildRequires: openssl-devel
BuildRequires: zlib-devel BuildRequires: zlib-devel
BuildRequires: git-core BuildRequires: autoconf automake libtool
BuildRequires: meson
BuildRequires: ninja-build
BuildRequires: asciidoc
Requires: lib%{name}%{?_isa} = %{version}-%{release} Requires: lib%{name}%{?_isa} = %{version}-%{release}
%description %description
@ -48,26 +50,30 @@ Obsoletes: lib%{name}-zlib-devel < %{version}-%{release}
This package contains development files for lib%{name}. This package contains development files for lib%{name}.
%prep %prep
%autosetup -S git %autosetup -p1
autoreconf -fv --install
%build %build
%meson %if 0%{?rhel}
%meson_build %__sed -i 's|libcrypto >= 1\.0\.2|libcrypto >= 1\.0\.1|' configure
%endif
%configure --disable-openmp
make %{?_smp_mflags}
%install %install
rm -rf %{buildroot} rm -rf %{buildroot}
%meson_install %make_install
rm -rf %{buildroot}/%{_libdir}/lib%{name}.la rm -rf %{buildroot}/%{_libdir}/lib%{name}.la
%check %check
%meson_test make %{?_smp_mflags} check
%ldconfig_scriptlets -n lib%{name} %post -n lib%{name} -p /sbin/ldconfig
%postun -n lib%{name} -p /sbin/ldconfig
%files %files
%{_bindir}/%{name} %{_bindir}/%{name}
%{_mandir}/man1/jose*.1* %{_mandir}/man1/jose*.1*
%license COPYING
%files -n lib%{name} %files -n lib%{name}
%license COPYING %license COPYING
@ -81,43 +87,16 @@ rm -rf %{buildroot}/%{_libdir}/lib%{name}.la
%{_mandir}/man3/jose*.3* %{_mandir}/man3/jose*.3*
%changelog %changelog
* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 11-3 * Mon Jul 01 2024 Sergio Correia <scorreia@redhat.com> - 10-2.3
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags - Backport fix for CVE-2024-28176
Related: rhbz#1991688 Resolves: RHEL-28719
* Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 11-2 * Mon Jul 01 2024 Sergio Correia <scorreia@redhat.com> - 10-2.2
- Rebuilt for RHEL 9 BETA for openssl 3.0 - Fix tests on s390x
Related: rhbz#1971065 Related: RHEL-29857
* Fri May 07 2021 Sergio Correia <scorreia@redhat.com> - 11-1 * Sun Jun 30 2024 Sergio Correia <scorreia@redhat.com> - 10-2.1
- Update to new jose upstream release, v11. - Fixes CVE-2023-50967
Resolves: rhbz#1935983
Resolves: rhbz#1958067
* Fri Apr 16 2021 Mohan Boddu <mboddu@redhat.com> - 10-10
- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 10-9
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Tue Jul 28 2020 Tom Stellard <tstellar@redhat.com> - 10-8
- Use make macros
- https://fedoraproject.org/wiki/Changes/UseMakeBuildInstallMacro
* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 10-7
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 10-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 10-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 10-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 10-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 10-2 * Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 10-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild - Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild