Skip to content

Commit 8eb61f2

Browse files
committed
test: add jose.c coverage tests
Signed-off-by: Hans Zandbelt <[email protected]>
1 parent f901ab6 commit 8eb61f2

File tree

4 files changed

+353
-2
lines changed

4 files changed

+353
-2
lines changed

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
11/05/2025
2+
- test: add jose.c coverage tests
3+
14
10/24/2025
25
- code: refactor util/random.c
36

src/jose.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,8 @@ apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *clai
775775
/*
776776
* parse (optional) timestamp from payload
777777
*/
778-
static apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name,
779-
apr_byte_t is_mandatory, double *result, oidc_jose_error_t *err) {
778+
apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory,
779+
double *result, oidc_jose_error_t *err) {
780780
*result = OIDC_JWT_CLAIM_TIME_EMPTY;
781781
json_t *v = json_object_get(json, claim_name);
782782
if (v != NULL) {

src/jose.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *ope
134134
/* return a string claim value from a JSON object */
135135
apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory,
136136
char **result, oidc_jose_error_t *err);
137+
apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory,
138+
double *result, oidc_jose_error_t *err);
137139

138140
apr_byte_t oidc_jose_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len,
139141
oidc_jose_error_t *err);

test/test_jose.c

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,338 @@ START_TEST(test_jose_jwe_encryption_is_supported) {
9898
}
9999
END_TEST
100100

101+
START_TEST(test_jose_hash_and_base64_and_length) {
102+
apr_pool_t *pool = oidc_test_pool_get();
103+
oidc_jose_error_t err;
104+
char *hash = NULL;
105+
unsigned int hash_len = 0;
106+
107+
ck_assert_msg(oidc_jose_hash_string(pool, CJOSE_HDR_ALG_RS256, "hello", &hash, &hash_len, &err) == TRUE,
108+
"oidc_jose_hash_string failed");
109+
ck_assert_msg(hash != NULL, "hash is NULL");
110+
ck_assert_msg((int)hash_len == oidc_jose_hash_length(CJOSE_HDR_ALG_RS256), "hash length mismatch");
111+
112+
char *b64 = NULL;
113+
ck_assert_msg(oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, "hello", 5, &b64, &err) == TRUE,
114+
"oidc_jose_hash_and_base64url_encode failed");
115+
ck_assert_msg(b64 != NULL, "base64url output is NULL");
116+
ck_assert_str_eq(b64, "LPJNul-wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ");
117+
}
118+
END_TEST
119+
120+
START_TEST(test_jose_get_string_and_timestamps) {
121+
apr_pool_t *pool = oidc_test_pool_get();
122+
json_t *j = json_object();
123+
json_object_set_new(j, "k1", json_string("v1"));
124+
json_object_set_new(j, "num", json_integer(123));
125+
json_object_set_new(j, "t1", json_real(4.5));
126+
char *s = NULL;
127+
oidc_jose_error_t err;
128+
129+
ck_assert_msg(oidc_jose_get_string(pool, j, "k1", TRUE, &s, &err) == TRUE,
130+
"get string failed for existing key");
131+
ck_assert_msg(_oidc_strcmp(s, "v1") == 0, "unexpected value");
132+
133+
char *s2 = NULL;
134+
ck_assert_msg(oidc_jose_get_string(pool, j, "missing", TRUE, &s2, &err) == FALSE,
135+
"get string should have failed for missing mandatory key");
136+
137+
double ts = 0;
138+
ck_assert_msg(oidc_jose_get_timestamp(pool, j, "t1", TRUE, &ts, &err) == TRUE,
139+
"get timestamp failed for existing key");
140+
ck_assert_msg(ts == 4.5, "unexpected value");
141+
142+
double ts2 = 0;
143+
ck_assert_msg(oidc_jose_get_timestamp(pool, j, "tsmissing", TRUE, &ts2, &err) == FALSE,
144+
"get timestamp should have failed for missing mandatory key");
145+
146+
json_decref(j);
147+
}
148+
END_TEST
149+
150+
START_TEST(test_jose_compress_uncompress) {
151+
apr_pool_t *pool = oidc_test_pool_get();
152+
const char *input = "the quick brown fox jumps over the lazy dog";
153+
int input_len = (int)_oidc_strlen(input);
154+
char *out = NULL;
155+
int out_len = 0;
156+
oidc_jose_error_t err;
157+
ck_assert_msg(oidc_jose_compress(pool, input, input_len, &out, &out_len, &err) == TRUE, "compress failed");
158+
ck_assert_msg(out != NULL, "compress returned NULL output");
159+
160+
char *un = NULL;
161+
int un_len = 0;
162+
ck_assert_msg(oidc_jose_uncompress(pool, out, out_len, &un, &un_len, &err) == TRUE, "uncompress failed");
163+
ck_assert_msg(un != NULL, "uncompress returned NULL output");
164+
ck_assert_msg(un_len == input_len, "uncompressed length mismatch");
165+
ck_assert_msg(memcmp(un, input, input_len) == 0, "uncompressed data differs from original");
166+
}
167+
END_TEST
168+
169+
START_TEST(test_jose_jwk_and_json_and_copy_lists) {
170+
apr_pool_t *pool = oidc_test_pool_get();
171+
oidc_jose_error_t err;
172+
const char *src_file = __FILE__;
173+
char *dir = NULL;
174+
char *slash = strrchr(src_file, '/');
175+
if (slash)
176+
dir = apr_pstrndup(pool, src_file, (int)(slash - src_file));
177+
else
178+
dir = apr_pstrdup(pool, ".");
179+
char *pub_path = apr_psprintf(pool, "%s/public.pem", dir);
180+
181+
oidc_jwk_t *pub = NULL;
182+
if (oidc_jwk_parse_pem_public_key(pool, NULL, pub_path, &pub, &err) != TRUE) {
183+
char *e = oidc_jose_e2s(pool, err);
184+
ck_abort_msg("oidc_jwk_parse_pem_public_key failed: %s", e);
185+
}
186+
ck_assert_ptr_nonnull(pub);
187+
ck_assert_ptr_nonnull(pub->kid);
188+
189+
char *s_json = NULL;
190+
ck_assert_msg(oidc_jwk_to_json(pool, pub, &s_json, &err) == TRUE, "oidc_jwk_to_json failed");
191+
ck_assert_ptr_nonnull(s_json);
192+
json_error_t je;
193+
json_t *j = json_loads(s_json, 0, &je);
194+
ck_assert_ptr_nonnull(j);
195+
json_t *kty = json_object_get(j, "kty");
196+
ck_assert_ptr_nonnull(kty);
197+
json_decref(j);
198+
199+
unsigned char key[32];
200+
for (int i = 0; i < 32; i++)
201+
key[i] = (unsigned char)i;
202+
oidc_jwk_t *sym = oidc_jwk_create_symmetric_key(pool, "symkid", key, 32, TRUE, &err);
203+
ck_assert_ptr_nonnull(sym);
204+
ck_assert_ptr_nonnull(sym->kid);
205+
206+
oidc_jwk_t *sym_copy = oidc_jwk_copy(pool, sym);
207+
ck_assert_ptr_nonnull(sym_copy);
208+
ck_assert_ptr_nonnull(sym_copy->kid);
209+
210+
apr_array_header_t *arr = apr_array_make(pool, 1, sizeof(const oidc_jwk_t *));
211+
APR_ARRAY_PUSH(arr, const oidc_jwk_t *) = sym;
212+
apr_array_header_t *arr_copy = oidc_jwk_list_copy(pool, arr);
213+
ck_assert_msg(arr_copy != NULL && arr_copy->nelts == 1, "oidc_jwk_list_copy failed");
214+
215+
apr_hash_t *h = apr_hash_make(pool);
216+
apr_hash_set(h, sym->kid, APR_HASH_KEY_STRING, sym);
217+
oidc_jwk_list_destroy_hash(h);
218+
ck_assert_int_eq(apr_hash_count(h), 0);
219+
}
220+
END_TEST
221+
222+
START_TEST(test_jose_jwe_decrypt_plaintext) {
223+
apr_pool_t *pool = oidc_test_pool_get();
224+
char *plaintext = NULL;
225+
int plaintext_len = 0;
226+
oidc_jose_error_t err;
227+
ck_assert_msg(oidc_jwe_decrypt(pool, "plain-text-data", NULL, &plaintext, &plaintext_len, &err, FALSE) == TRUE,
228+
"oidc_jwe_decrypt passthrough failed");
229+
ck_assert_ptr_nonnull(plaintext);
230+
ck_assert_msg(plaintext_len == (int)_oidc_strlen("plain-text-data"), "plaintext len mismatch");
231+
ck_assert_msg(_oidc_strcmp(plaintext, "plain-text-data") == 0, "plaintext content mismatch");
232+
}
233+
END_TEST
234+
235+
START_TEST(test_jwt_sign_verify_and_encrypt_decrypt) {
236+
apr_pool_t *pool = oidc_test_pool_get();
237+
oidc_jose_error_t err;
238+
unsigned char key[32];
239+
for (int i = 0; i < 32; i++)
240+
key[i] = (unsigned char)(i + 1);
241+
oidc_jwk_t *sym = oidc_jwk_create_symmetric_key(pool, "hskid", key, 32, TRUE, &err);
242+
ck_assert_ptr_nonnull(sym);
243+
244+
oidc_jwt_t *jwt = oidc_jwt_new(pool, 1, 1);
245+
json_object_set_new(jwt->payload.value.json, "iss", json_string("unit-test"));
246+
jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_HS256);
247+
248+
ck_assert_msg(oidc_jwt_sign(pool, jwt, sym, FALSE, &err) == TRUE, "oidc_jwt_sign failed");
249+
ck_assert_ptr_nonnull(jwt->cjose_jws);
250+
251+
apr_hash_t *keys = apr_hash_make(pool);
252+
apr_hash_set(keys, sym->kid, APR_HASH_KEY_STRING, sym);
253+
ck_assert_msg(oidc_jwt_verify(pool, jwt, keys, &err) == TRUE, "oidc_jwt_verify failed");
254+
255+
oidc_jwt_t *none_jwt = oidc_jwt_new(pool, 1, 1);
256+
json_object_set_new(none_jwt->payload.value.json, "hello", json_string("world"));
257+
none_jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_NONE);
258+
char *s = oidc_jose_jwt_serialize(pool, none_jwt, &err);
259+
ck_assert_ptr_nonnull(s);
260+
261+
char *dot = strchr(s, '.');
262+
ck_assert_ptr_nonnull(dot);
263+
int hdr_len = (int)(dot - s);
264+
char *hdr_b64 = apr_pstrndup(pool, s, hdr_len);
265+
unsigned char *decoded = NULL;
266+
size_t decoded_len = 0;
267+
cjose_err cjose_err_local;
268+
if (cjose_base64url_decode(hdr_b64, _oidc_strlen(hdr_b64), &decoded, &decoded_len, &cjose_err_local) == FALSE) {
269+
ck_abort_msg("cjose_base64url_decode failed: %s", oidc_cjose_e2s(pool, cjose_err_local));
270+
}
271+
char *hdr_json = apr_pstrmemdup(pool, (const char *)decoded, decoded_len);
272+
cjose_get_dealloc()(decoded);
273+
json_error_t jerr;
274+
json_t *hdr_obj = json_loads(hdr_json, 0, &jerr);
275+
ck_assert_ptr_nonnull(hdr_obj);
276+
json_t *alg = json_object_get(hdr_obj, "alg");
277+
ck_assert_ptr_nonnull(alg);
278+
ck_assert_msg(json_is_string(alg) && _oidc_strcmp(json_string_value(alg), "none") == 0, "alg is not 'none'");
279+
json_decref(hdr_obj);
280+
281+
const char *src_file = __FILE__;
282+
char *dir = NULL;
283+
char *slash = strrchr(src_file, '/');
284+
if (slash)
285+
dir = apr_pstrndup(pool, src_file, (int)(slash - src_file));
286+
else
287+
dir = apr_pstrdup(pool, ".");
288+
char *pub_path = apr_psprintf(pool, "%s/public.pem", dir);
289+
char *priv_path = apr_psprintf(pool, "%s/private.pem", dir);
290+
291+
oidc_jwk_t *pub = NULL;
292+
if (oidc_jwk_parse_pem_public_key(pool, NULL, pub_path, &pub, &err) != TRUE) {
293+
char *e = oidc_jose_e2s(pool, err);
294+
ck_abort_msg("parse public pem failed: %s", e);
295+
}
296+
ck_assert_ptr_nonnull(pub);
297+
oidc_jwk_t *priv = NULL;
298+
if (oidc_jwk_parse_pem_private_key(pool, NULL, priv_path, &priv, &err) != TRUE) {
299+
char *e = oidc_jose_e2s(pool, err);
300+
ck_abort_msg("parse private pem failed: %s", e);
301+
}
302+
ck_assert_ptr_nonnull(priv);
303+
304+
oidc_jwt_t *jwe = oidc_jwt_new(pool, 1, 0);
305+
jwe->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_RSA_OAEP);
306+
jwe->header.enc = apr_pstrdup(pool, CJOSE_HDR_ENC_A128CBC_HS256);
307+
jwe->header.kid = apr_pstrdup(pool, pub->kid);
308+
309+
char *serialized = NULL;
310+
const char *payload = "this is a secret";
311+
ck_assert_msg(oidc_jwt_encrypt(pool, jwe, pub, payload, (int)_oidc_strlen(payload), &serialized, &err) == TRUE,
312+
"oidc_jwt_encrypt failed");
313+
ck_assert_ptr_nonnull(serialized);
314+
315+
apr_hash_t *dec_keys = apr_hash_make(pool);
316+
apr_hash_set(dec_keys, priv->kid, APR_HASH_KEY_STRING, priv);
317+
char *dec_plain = NULL;
318+
int dec_plain_len = 0;
319+
ck_assert_msg(oidc_jwe_decrypt(pool, serialized, dec_keys, &dec_plain, &dec_plain_len, &err, TRUE) == TRUE,
320+
"oidc_jwe_decrypt failed");
321+
ck_assert_msg(dec_plain_len == (int)_oidc_strlen(payload), "decrypted length mismatch");
322+
ck_assert_msg(memcmp(dec_plain, payload, dec_plain_len) == 0, "decrypted plaintext mismatch");
323+
}
324+
END_TEST
325+
326+
START_TEST(test_jose_hash_bytes) {
327+
apr_pool_t *pool = oidc_test_pool_get();
328+
unsigned char *out = NULL;
329+
unsigned int out_len = 0;
330+
oidc_jose_error_t err;
331+
332+
ck_assert_msg(oidc_jose_hash_bytes(pool, OIDC_JOSE_ALG_SHA256, (const unsigned char *)"abc", 3, &out, &out_len,
333+
&err) == TRUE,
334+
"oidc_jose_hash_bytes failed");
335+
ck_assert_msg(out != NULL, "hash output is NULL");
336+
ck_assert_msg((int)out_len == oidc_jose_hash_length(CJOSE_HDR_ALG_RS256), "hash length mismatch");
337+
}
338+
END_TEST
339+
340+
START_TEST(test_jwk_json_parse_and_jwks) {
341+
apr_pool_t *pool = oidc_test_pool_get();
342+
oidc_jose_error_t err;
343+
const char *src_file = __FILE__;
344+
char *dir = NULL;
345+
char *slash = strrchr(src_file, '/');
346+
if (slash)
347+
dir = apr_pstrndup(pool, src_file, (int)(slash - src_file));
348+
else
349+
dir = apr_pstrdup(pool, ".");
350+
char *pub_path = apr_psprintf(pool, "%s/public.pem", dir);
351+
352+
oidc_jwk_t *pub = NULL;
353+
if (oidc_jwk_parse_pem_public_key(pool, NULL, pub_path, &pub, &err) != TRUE) {
354+
char *e = oidc_jose_e2s(pool, err);
355+
ck_abort_msg("oidc_jwk_parse_pem_public_key failed: %s", e);
356+
}
357+
ck_assert_ptr_nonnull(pub);
358+
359+
char *s_json = NULL;
360+
ck_assert_msg(oidc_jwk_to_json(pool, pub, &s_json, &err) == TRUE, "oidc_jwk_to_json failed");
361+
json_error_t je;
362+
json_t *j = json_loads(s_json, 0, &je);
363+
ck_assert_ptr_nonnull(j);
364+
365+
oidc_jwk_t *parsed = oidc_jwk_parse(pool, j, &err);
366+
ck_assert_ptr_nonnull(parsed);
367+
ck_assert_ptr_nonnull(parsed->kid);
368+
369+
ck_assert_msg(oidc_is_jwk(j) == TRUE, "oidc_is_jwk false for JWK json");
370+
371+
json_t *jwks = json_object();
372+
json_t *arr = json_array();
373+
json_array_append_new(arr, json_deep_copy(j));
374+
json_object_set_new(jwks, "keys", arr);
375+
apr_array_header_t *jwk_list = NULL;
376+
ck_assert_msg(oidc_jwks_parse_json(pool, jwks, &jwk_list, &err) == TRUE, "oidc_jwks_parse_json failed");
377+
ck_assert_msg(jwk_list != NULL && jwk_list->nelts == 1, "jwks parse returned wrong number of keys");
378+
ck_assert_msg(oidc_is_jwks(jwks) == TRUE, "oidc_is_jwks false for jwks json");
379+
380+
json_decref(j);
381+
json_decref(jwks);
382+
}
383+
END_TEST
384+
385+
START_TEST(test_jwk_list_destroy) {
386+
apr_pool_t *pool = oidc_test_pool_get();
387+
apr_array_header_t *arr = apr_array_make(pool, 2, sizeof(const oidc_jwk_t *));
388+
oidc_jose_error_t err;
389+
unsigned char key[16] = {0};
390+
for (int i = 0; i < 2; i++) {
391+
char kid[8];
392+
snprintf(kid, sizeof(kid), "k%02d", i);
393+
oidc_jwk_t *sym = oidc_jwk_create_symmetric_key(pool, kid, key, 16, TRUE, &err);
394+
APR_ARRAY_PUSH(arr, const oidc_jwk_t *) = sym;
395+
}
396+
oidc_jwk_list_destroy(arr);
397+
ck_assert_int_eq(arr->nelts, 0);
398+
}
399+
END_TEST
400+
401+
START_TEST(test_alg2keysize_and_hdr_get_and_jwt_parse) {
402+
apr_pool_t *pool = oidc_test_pool_get();
403+
oidc_jose_error_t err;
404+
ck_assert_msg(oidc_alg2keysize(CJOSE_HDR_ALG_A128KW) == 16, "A128KW keysize wrong");
405+
ck_assert_msg(oidc_alg2keysize(CJOSE_HDR_ALG_A256KW) == 32, "A256KW keysize wrong");
406+
ck_assert_msg(oidc_alg2keysize(CJOSE_HDR_ALG_RS256) == 32, "RS256 keysize wrong");
407+
408+
unsigned char key[32];
409+
for (int i = 0; i < 32; i++)
410+
key[i] = (unsigned char)(i + 2);
411+
oidc_jwk_t *sym = oidc_jwk_create_symmetric_key(pool, "parsekid", key, 32, TRUE, &err);
412+
ck_assert_ptr_nonnull(sym);
413+
414+
oidc_jwt_t *jwt = oidc_jwt_new(pool, 1, 1);
415+
json_object_set_new(jwt->payload.value.json, "sub", json_string("subject"));
416+
jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_HS256);
417+
ck_assert_msg(oidc_jwt_sign(pool, jwt, sym, FALSE, &err) == TRUE, "oidc_jwt_sign failed");
418+
char *s = oidc_jose_jwt_serialize(pool, jwt, &err);
419+
ck_assert_ptr_nonnull(s);
420+
421+
apr_hash_t *keys = apr_hash_make(pool);
422+
apr_hash_set(keys, sym->kid, APR_HASH_KEY_STRING, sym);
423+
oidc_jwt_t *parsed = NULL;
424+
ck_assert_msg(oidc_jwt_parse(pool, s, &parsed, keys, FALSE, &err) == TRUE, "oidc_jwt_parse failed");
425+
ck_assert_ptr_nonnull(parsed);
426+
427+
const char *alg = oidc_jwt_hdr_get(parsed, "alg");
428+
ck_assert_ptr_nonnull(alg);
429+
ck_assert_msg(_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0, "parsed alg mismatch");
430+
}
431+
END_TEST
432+
101433
int main(void) {
102434
TCase *sup = tcase_create("supported");
103435
tcase_add_checked_fixture(sup, oidc_test_setup, oidc_test_teardown);
@@ -109,8 +441,22 @@ int main(void) {
109441
tcase_add_test(sup, test_jose_jwe_supported_encryptions);
110442
tcase_add_test(sup, test_jose_jwe_encryption_is_supported);
111443

444+
TCase *core = tcase_create("core");
445+
tcase_add_checked_fixture(core, oidc_test_setup, oidc_test_teardown);
446+
tcase_add_test(core, test_jose_hash_and_base64_and_length);
447+
tcase_add_test(core, test_jose_get_string_and_timestamps);
448+
tcase_add_test(core, test_jose_compress_uncompress);
449+
tcase_add_test(core, test_jose_jwk_and_json_and_copy_lists);
450+
tcase_add_test(core, test_jose_jwe_decrypt_plaintext);
451+
tcase_add_test(core, test_jwt_sign_verify_and_encrypt_decrypt);
452+
tcase_add_test(core, test_jose_hash_bytes);
453+
tcase_add_test(core, test_jwk_json_parse_and_jwks);
454+
tcase_add_test(core, test_jwk_list_destroy);
455+
tcase_add_test(core, test_alg2keysize_and_hdr_get_and_jwt_parse);
456+
112457
Suite *s = suite_create("jose");
113458
suite_add_tcase(s, sup);
459+
suite_add_tcase(s, core);
114460

115461
return oidc_test_suite_run(s);
116462
}

0 commit comments

Comments
 (0)