Skip to content

Commit b67c4b4

Browse files
jwe: fix the case when we have "zip" in the protected header
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 <[email protected]>
1 parent efb5cfa commit b67c4b4

File tree

8 files changed

+195
-26
lines changed

8 files changed

+195
-26
lines changed

lib/jwe.c

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -275,14 +275,8 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
275275
jose_io_t *next)
276276
{
277277
const jose_hook_alg_t *alg = NULL;
278-
jose_io_auto_t *zip = NULL;
279-
json_auto_t *prt = NULL;
280278
const char *h = NULL;
281279
const char *k = NULL;
282-
const char *z = NULL;
283-
284-
prt = jose_b64_dec_load(json_object_get(jwe, "protected"));
285-
(void) json_unpack(prt, "{s:s}", "zip", &z);
286280

287281
if (json_unpack(jwe, "{s?{s?s}}", "unprotected", "enc", &h) < 0)
288282
return NULL;
@@ -336,19 +330,7 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
336330
if (!encode_protected(jwe))
337331
return NULL;
338332

339-
if (z) {
340-
const jose_hook_alg_t *a = NULL;
341-
342-
a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z);
343-
if (!a)
344-
return NULL;
345-
346-
zip = a->comp.def(a, cfg, next);
347-
if (!zip)
348-
return NULL;
349-
}
350-
351-
return alg->encr.enc(alg, cfg, jwe, cek, zip ? zip : next);
333+
return alg->encr.enc(alg, cfg, jwe, cek, next);
352334
}
353335

354336
void *
@@ -463,6 +445,12 @@ jose_jwe_dec_cek(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek,
463445
o = jose_io_malloc(cfg, &pt, ptl);
464446
d = jose_jwe_dec_cek_io(cfg, jwe, cek, o);
465447
i = jose_b64_dec_io(d);
448+
449+
/* Here we make sure the ciphertext is not larger than our
450+
* compression limit. */
451+
if (zip_in_protected_header((json_t*)jwe) && ctl > MAX_COMPRESSED_SIZE)
452+
return false;
453+
466454
if (!o || !d || !i || !i->feed(i, ct, ctl) || !i->done(i))
467455
return NULL;
468456

lib/misc.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "misc.h"
1919
#include <jose/b64.h>
2020
#include <string.h>
21+
#include "hooks.h"
2122

2223
bool
2324
encode_protected(json_t *obj)
@@ -42,6 +43,65 @@ zero(void *mem, size_t len)
4243
memset(mem, 0, len);
4344
}
4445

46+
47+
bool
48+
handle_zip_enc(json_t *json, const void *in, size_t len, void **data, size_t *datalen)
49+
{
50+
json_t *prt = NULL;
51+
char *z = NULL;
52+
const jose_hook_alg_t *a = NULL;
53+
jose_io_auto_t *zip = NULL;
54+
jose_io_auto_t *zipdata = NULL;
55+
56+
prt = json_object_get(json, "protected");
57+
if (prt && json_is_string(prt))
58+
prt = jose_b64_dec_load(prt);
59+
60+
/* Check if we have "zip" in the protected header. */
61+
if (json_unpack(prt, "{s:s}", "zip", &z) == -1) {
62+
/* No zip. */
63+
*data = (void*)in;
64+
*datalen = len;
65+
return true;
66+
}
67+
68+
/* OK, we have "zip", so we should compress the payload before
69+
* the encryption takes place. */
70+
a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z);
71+
if (!a)
72+
return false;
73+
74+
zipdata = jose_io_malloc(NULL, data, datalen);
75+
if (!zipdata)
76+
return false;
77+
78+
zip = a->comp.def(a, NULL, zipdata);
79+
if (!zip || !zip->feed(zip, in, len) || !zip->done(zip))
80+
return false;
81+
82+
return true;
83+
}
84+
85+
bool
86+
zip_in_protected_header(json_t *json)
87+
{
88+
json_t *prt = NULL;
89+
char *z = NULL;
90+
const jose_hook_alg_t *a = NULL;
91+
92+
prt = json_object_get(json, "protected");
93+
if (prt && json_is_string(prt))
94+
prt = jose_b64_dec_load(prt);
95+
96+
/* Check if we have "zip" in the protected header. */
97+
if (json_unpack(prt, "{s:s}", "zip", &z) == -1)
98+
return false;
99+
100+
/* We have "zip", but let's validate the alg also. */
101+
a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z);
102+
return a != NULL;
103+
}
104+
45105
static void __attribute__((constructor))
46106
constructor(void)
47107
{

lib/misc.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ encode_protected(json_t *obj);
3030

3131
void
3232
zero(void *mem, size_t len);
33+
34+
bool
35+
handle_zip_enc(json_t *jwe, const void *in, size_t len, void **data, size_t *data_len);
36+
37+
bool
38+
zip_in_protected_header(json_t *jwe);

lib/openssl/aescbch.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "misc.h"
1919
#include <jose/b64.h>
2020
#include "../hooks.h"
21+
#include "../misc.h"
2122

2223
#include <openssl/rand.h>
2324
#include <openssl/sha.h>
@@ -155,9 +156,13 @@ enc_feed(jose_io_t *io, const void *in, size_t len)
155156
io_t *i = containerof(io, io_t, io);
156157

157158
uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
158-
const uint8_t *pt = in;
159+
uint8_t *pt = NULL;
160+
size_t ptlen = 0;
159161

160-
for (size_t j = 0; j < len; j++) {
162+
if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen))
163+
return false;
164+
165+
for (size_t j = 0; j < ptlen; j++) {
161166
int l = 0;
162167

163168
if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)

lib/openssl/aesgcm.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "misc.h"
1919
#include <jose/b64.h>
2020
#include "../hooks.h"
21+
#include "../misc.h"
2122

2223
#include <openssl/rand.h>
2324

@@ -103,10 +104,15 @@ static bool
103104
enc_feed(jose_io_t *io, const void *in, size_t len)
104105
{
105106
io_t *i = containerof(io, io_t, io);
106-
const uint8_t *pt = in;
107107
int l = 0;
108108

109-
for (size_t j = 0; j < len; j++) {
109+
uint8_t *pt = NULL;
110+
size_t ptlen = 0;
111+
112+
if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen))
113+
return false;
114+
115+
for (size_t j = 0; j < ptlen; j++) {
110116
uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
111117

112118
if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)

tests/alg_comp.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ static uint8_t* get_random_string(uint32_t length)
5353
{
5454
assert(length);
5555
uint8_t* c = (uint8_t*)malloc(length*sizeof(uint8_t));
56+
assert(c);
5657
for (uint32_t i=0; i<length; i++) {
5758
c[i] = 'A' + (random() % 26);
5859
}

tests/api_jwe.c

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
#include <assert.h>
2020
#include <string.h>
2121

22+
#include "../lib/hooks.h" /* for MAX_COMPRESSED_SIZE */
23+
2224
static bool
23-
dec(json_t *jwe, json_t *jwk)
25+
dec_cmp(json_t *jwe, json_t *jwk, const char* expected_data, size_t expected_len)
2426
{
2527
bool ret = false;
2628
char *pt = NULL;
@@ -30,10 +32,10 @@ dec(json_t *jwe, json_t *jwk)
3032
if (!pt)
3133
goto error;
3234

33-
if (ptl != 4)
35+
if (ptl != expected_len)
3436
goto error;
3537

36-
if (strcmp(pt, "foo") != 0)
38+
if (strcmp(pt, expected_data) != 0)
3739
goto error;
3840

3941
ret = true;
@@ -43,12 +45,40 @@ dec(json_t *jwe, json_t *jwk)
4345
return ret;
4446
}
4547

48+
static bool
49+
dec(json_t *jwe, json_t *jwk)
50+
{
51+
return dec_cmp(jwe, jwk, "foo", 4);
52+
}
53+
54+
struct zip_test_data_t {
55+
char* data;
56+
size_t datalen;
57+
bool expected;
58+
};
59+
60+
static char*
61+
make_data(size_t len)
62+
{
63+
assert(len > 0);
64+
65+
char *data = malloc(len);
66+
assert(data);
67+
68+
for (size_t i = 0; i < len; i++) {
69+
data[i] = 'A' + (random() % 26);
70+
}
71+
data[len-1] = '\0';
72+
return data;
73+
}
74+
4675
int
4776
main(int argc, char *argv[])
4877
{
4978
json_auto_t *jwke = json_pack("{s:s}", "alg", "ECDH-ES+A128KW");
5079
json_auto_t *jwkr = json_pack("{s:s}", "alg", "RSA1_5");
5180
json_auto_t *jwko = json_pack("{s:s}", "alg", "A128KW");
81+
json_auto_t *jwkz = json_pack("{s:s, s:i}", "kty", "oct", "bytes", 16);
5282
json_auto_t *set0 = json_pack("{s:[O,O]}", "keys", jwke, jwko);
5383
json_auto_t *set1 = json_pack("{s:[O,O]}", "keys", jwkr, jwko);
5484
json_auto_t *set2 = json_pack("{s:[O,O]}", "keys", jwke, jwkr);
@@ -57,6 +87,7 @@ main(int argc, char *argv[])
5787
assert(jose_jwk_gen(NULL, jwke));
5888
assert(jose_jwk_gen(NULL, jwkr));
5989
assert(jose_jwk_gen(NULL, jwko));
90+
assert(jose_jwk_gen(NULL, jwkz));
6091

6192
json_decref(jwe);
6293
assert((jwe = json_object()));
@@ -98,5 +129,67 @@ main(int argc, char *argv[])
98129
assert(dec(jwe, set1));
99130
assert(dec(jwe, set2));
100131

132+
133+
json_decref(jwe);
134+
assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF")));
135+
assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, "foo", 4));
136+
assert(dec(jwe, jwkz));
137+
assert(!dec(jwe, jwkr));
138+
assert(!dec(jwe, jwko));
139+
assert(!dec(jwe, set0));
140+
assert(!dec(jwe, set1));
141+
assert(!dec(jwe, set2));
142+
143+
/* Some tests with "zip": "DEF" */
144+
struct zip_test_data_t zip[] = {
145+
{
146+
.data = make_data(5),
147+
.datalen = 5,
148+
.expected = true,
149+
},
150+
{
151+
.data = make_data(50),
152+
.datalen = 50,
153+
.expected = true,
154+
},
155+
{
156+
.data = make_data(1000),
157+
.datalen = 1000,
158+
.expected = true,
159+
},
160+
{
161+
.data = make_data(10000000),
162+
.datalen = 10000000,
163+
.expected = false, /* compressed len will be ~8000000+
164+
* (i.e. > MAX_COMPRESSED_SIZE)
165+
*/
166+
},
167+
{
168+
.data = make_data(50000),
169+
.datalen = 50000,
170+
.expected = true
171+
},
172+
{
173+
174+
.data = NULL
175+
}
176+
};
177+
178+
for (size_t i = 0; zip[i].data != NULL; i++) {
179+
json_decref(jwe);
180+
assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF")));
181+
assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, zip[i].data, zip[i].datalen));
182+
183+
/* Now let's get the ciphertext compressed len. */
184+
char *ct = NULL;
185+
size_t ctl = 0;
186+
assert(json_unpack(jwe, "{s:s%}", "ciphertext", &ct, & ctl) != -1);
187+
/* And check our expectation is correct. */
188+
assert(zip[i].expected == (ctl < MAX_COMPRESSED_SIZE));
189+
190+
assert(dec_cmp(jwe, jwkz, zip[i].data, zip[i].datalen) == zip[i].expected);
191+
free(zip[i].data);
192+
zip[i].data = NULL;
193+
}
101194
return EXIT_SUCCESS;
102195
}

tests/jose-jwe-enc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,14 @@ for msg in "hi" "this is a longer message that is more than one block"; do
7474
printf '%s' "$msg" | jose jwe enc -I- -k $jwk -o $jwe
7575
[ "`jose jwe dec -i $jwe -k $jwk -O-`" = "$msg" ]
7676
done
77+
78+
# "zip": "DEF"
79+
tmpl='{"kty":"oct","bytes":32}'
80+
for enc in A128CBC-HS256 A192CBC-HS384 A256CBC-HS512 A128GCM A192GCM A256GCM; do
81+
zip="$(printf '{"alg":"A128KW","enc":"%s","zip":"DEF"}' "${enc}")"
82+
83+
jose jwk gen -i "${tmpl}" -o "${jwk}"
84+
printf '%s' "${msg}" | jose jwe enc -i "${zip}" -I- -k "${jwk}" -o "${jwe}"
85+
[ "$(jose jwe dec -i "${jwe}" -k "${jwk}" -O-)" = "${msg}" ]
86+
done
7787
done

0 commit comments

Comments
 (0)