blob: df1685b60175235fc6757dc88053874099922306 [file] [edit]
/* Public domain, no copyright. Use at your own risk. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jwt_tests.h"
static const char PT[] = "one plaintext, many recipients";
/* Decrypt @tok with (@alg, @key) from @keyfile and assert it returns PT. */
static void decrypt_ok(const char *tok, const char *keyfile,
jwe_key_alg_t alg, jwe_enc_t enc)
{
jwe_checker_auto_t *checker = NULL;
unsigned char *pt = NULL;
size_t pt_len = 0;
read_json(keyfile);
checker = jwe_checker_new();
ck_assert_int_eq(jwe_checker_setkey(checker, alg, enc, g_item), 0);
pt = jwe_checker_decrypt_all(checker, tok, &pt_len);
ck_assert_ptr_nonnull(pt);
ck_assert_int_eq(pt_len, strlen(PT));
ck_assert_mem_eq(pt, PT, pt_len);
free(pt);
free_key();
}
/* A token encrypted to three recipients (A256KW, RSA-OAEP-256, ECDH-ES+A128KW)
* decrypts with any one recipient's key. The content is encrypted once (one iv,
* one ciphertext, one tag) and each recipient gets its own encrypted_key. */
START_TEST(three_recipients)
{
jwe_builder_auto_t *builder = NULL;
jwe_recipient_t *r;
char_auto *tok = NULL;
jwk_set_auto_t *ks_oct = NULL, *ks_rsa = NULL, *ks_ec = NULL;
const jwk_item_t *k_oct, *k_rsa, *k_ec;
SET_OPS();
/* Recipient 0 via setkey (oct A256KW). The builder references the key, so
* it must outlive generate(); keep it in an auto-freed set, not the global
* read_json() slot that the decrypt_ok() calls below reuse. */
ks_oct = jwks_create_fromfile(KEYDIR "/oct_key_256_enc.json");
ck_assert_ptr_nonnull(ks_oct);
k_oct = jwks_item_get(ks_oct, 0);
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, k_oct), 0);
/* Recipients 1 and 2 via add_recipient. */
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
ck_assert_ptr_nonnull(ks_rsa);
k_rsa = jwks_item_get(ks_rsa, 0);
r = jwe_builder_add_recipient(builder, JWE_ALG_RSA_OAEP_256, k_rsa);
ck_assert_ptr_nonnull(r);
ks_ec = jwks_create_fromfile(KEYDIR "/ec_key_prime256v1_enc.json");
ck_assert_ptr_nonnull(ks_ec);
k_ec = jwks_item_get(ks_ec, 0);
r = jwe_builder_add_recipient(builder, JWE_ALG_ECDH_ES_A128KW, k_ec);
ck_assert_ptr_nonnull(r);
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
/* General form: a recipients array, exactly one iv/ciphertext/tag. */
ck_assert_int_eq(tok[0], '{');
ck_assert_ptr_nonnull(strstr(tok, "\"recipients\":"));
ck_assert_ptr_nonnull(strstr(tok, "\"ciphertext\":"));
/* Each recipient's key decrypts the same token. */
decrypt_ok(tok, "oct_key_256_enc.json", JWE_ALG_A256KW, JWE_ENC_A256GCM);
decrypt_ok(tok, "rsa_key_2048_enc.json", JWE_ALG_RSA_OAEP_256,
JWE_ENC_A256GCM);
decrypt_ok(tok, "ec_key_prime256v1_enc.json", JWE_ALG_ECDH_ES_A128KW,
JWE_ENC_A256GCM);
free_key();
}
END_TEST
/* The recipient that matches our key may be the second one in the array; it
* still decrypts (selection iterates all recipients). */
START_TEST(second_recipient_matches)
{
jwe_builder_auto_t *builder = NULL;
char_auto *tok = NULL;
jwk_set_auto_t *ks_oct = NULL, *ks_rsa = NULL;
const jwk_item_t *k_oct, *k_rsa;
SET_OPS();
ks_oct = jwks_create_fromfile(KEYDIR "/oct_key_256_enc.json");
ck_assert_ptr_nonnull(ks_oct);
k_oct = jwks_item_get(ks_oct, 0);
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, k_oct), 0);
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
ck_assert_ptr_nonnull(jwe_builder_add_recipient(builder,
JWE_ALG_RSA_OAEP_256, k_rsa));
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
/* Our key is recipient #2 (RSA), not #1 (A256KW). */
decrypt_ok(tok, "rsa_key_2048_enc.json", JWE_ALG_RSA_OAEP_256,
JWE_ENC_A256GCM);
}
END_TEST
/* Per-recipient partyinfo and an application header parameter survive to the
* per-recipient header. */
START_TEST(recipient_header_and_partyinfo)
{
jwe_builder_auto_t *builder = NULL;
jwe_recipient_t *r;
char_auto *tok = NULL;
jwk_set_auto_t *ks_oct = NULL, *ks_ec = NULL;
const jwk_item_t *k_oct, *k_ec;
SET_OPS();
ks_oct = jwks_create_fromfile(KEYDIR "/oct_key_256_enc.json");
ck_assert_ptr_nonnull(ks_oct);
k_oct = jwks_item_get(ks_oct, 0);
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, k_oct), 0);
ck_assert_int_eq(jwe_builder_set_format(builder,
JWE_FORMAT_JSON_GENERAL), 0);
/* setkey configures recipient 0 (no handle); add a second recipient
* whose handle we use for per-recipient partyinfo/header. Both keys are
* referenced by the builder until generate(), so keep them alive in
* auto-freed sets rather than the single global read_json() slot. */
ks_ec = jwks_create_fromfile(KEYDIR "/ec_key_secp521r1_enc.json");
ck_assert_ptr_nonnull(ks_ec);
k_ec = jwks_item_get(ks_ec, 0);
r = jwe_builder_add_recipient(builder, JWE_ALG_ECDH_ES_A256KW, k_ec);
ck_assert_ptr_nonnull(r);
ck_assert_int_eq(jwe_recipient_set_partyinfo(r,
(const unsigned char *)"Alice", 5,
(const unsigned char *)"Bob", 3), 0);
ck_assert_int_eq(jwe_recipient_add_header_json(r, "kid", "\"ec-1\""), 0);
/* Reserved names and NULLs are rejected. */
ck_assert_int_ne(jwe_recipient_add_header_json(r, "alg", "\"x\""), 0);
ck_assert_int_ne(jwe_recipient_add_header_json(r, "kid", "\"dup\""), 0);
ck_assert_int_ne(jwe_recipient_add_header_json(NULL, "a", "1"), 0);
ck_assert_int_ne(jwe_recipient_set_partyinfo(NULL, NULL, 0, NULL, 0), 0);
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
ck_assert_ptr_nonnull(strstr(tok, "\"kid\":\"ec-1\""));
ck_assert_ptr_nonnull(strstr(tok, "\"apu\":"));
/* Recipient 1 (the EC one) decrypts. */
decrypt_ok(tok, "ec_key_secp521r1_enc.json", JWE_ALG_ECDH_ES_A256KW,
JWE_ENC_A256GCM);
free_key();
}
END_TEST
/* add_recipient and the recipient setters validate their inputs. */
START_TEST(add_recipient_errors)
{
jwe_builder_auto_t *builder = NULL;
jwe_recipient_t *r;
SET_OPS();
read_json("oct_key_256_enc.json");
/* NULL builder. */
ck_assert_ptr_null(jwe_builder_add_recipient(NULL, JWE_ALG_A256KW,
g_item));
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, g_item), 0);
/* Invalid alg. */
ck_assert_ptr_null(jwe_builder_add_recipient(builder, JWE_ALG_NONE,
g_item));
jwe_builder_error_clear(builder);
ck_assert_ptr_null(jwe_builder_add_recipient(builder, JWE_ALG_INVAL,
g_item));
jwe_builder_error_clear(builder);
/* NULL key. */
ck_assert_ptr_null(jwe_builder_add_recipient(builder, JWE_ALG_A256KW,
NULL));
jwe_builder_error_clear(builder);
/* A signing key is rejected by the usage gate. */
free_key();
read_json("rsa_key_2048.json");
r = jwe_builder_add_recipient(builder, JWE_ALG_RSA_OAEP_256, g_item);
if (r == NULL)
ck_assert_int_eq(jwe_builder_error(builder), 1);
jwe_builder_error_clear(builder);
/* Recipient setter input validation. */
free_key();
read_json("oct_key_256_enc.json");
r = jwe_builder_add_recipient(builder, JWE_ALG_A256KW, g_item);
ck_assert_ptr_nonnull(r);
ck_assert_int_ne(jwe_recipient_add_header_json(r, NULL, "1"), 0);
ck_assert_int_ne(jwe_recipient_add_header_json(r, "x", NULL), 0);
ck_assert_int_ne(jwe_recipient_add_header_json(r, "x", "{bad json"), 0);
free_key();
}
END_TEST
/* dir cannot be combined with another recipient (both orderings). */
START_TEST(dir_rejects_multi)
{
jwe_builder_auto_t *b1 = NULL, *b2 = NULL;
jwk_set_auto_t *ks_rsa = NULL, *ks_dir = NULL;
const jwk_item_t *k_rsa, *k_dir;
SET_OPS();
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
ks_dir = jwks_create_fromfile(KEYDIR "/oct_dir_256.json");
ck_assert_ptr_nonnull(ks_dir);
k_dir = jwks_item_get(ks_dir, 0);
/* dir first, then add another recipient -> rejected. */
b1 = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(b1, JWE_ALG_DIR,
JWE_ENC_A256GCM, k_dir), 0);
ck_assert_ptr_null(jwe_builder_add_recipient(b1, JWE_ALG_RSA_OAEP_256,
k_rsa));
ck_assert_int_eq(jwe_builder_error(b1), 1);
/* Another recipient first, then dir -> rejected. */
b2 = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(b2, JWE_ALG_RSA_OAEP_256,
JWE_ENC_A256GCM, k_rsa), 0);
ck_assert_ptr_null(jwe_builder_add_recipient(b2, JWE_ALG_DIR, k_dir));
ck_assert_int_eq(jwe_builder_error(b2), 1);
}
END_TEST
/* ECDH-ES Direct also cannot be combined with another recipient. */
START_TEST(ecdh_direct_rejects_multi)
{
jwe_builder_auto_t *builder = NULL;
jwk_set_auto_t *ks_rsa = NULL;
const jwk_item_t *k_rsa;
SET_OPS();
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
read_json("ec_key_prime256v1_enc.json");
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_ECDH_ES,
JWE_ENC_A256GCM, g_item), 0);
ck_assert_ptr_null(jwe_builder_add_recipient(builder,
JWE_ALG_RSA_OAEP_256, k_rsa));
ck_assert_int_eq(jwe_builder_error(builder), 1);
free_key();
}
END_TEST
/* Flattened and Compact reject a second recipient at generate time. */
START_TEST(flat_compact_reject_multi)
{
jwe_builder_auto_t *bf = NULL, *bc = NULL;
jwk_set_auto_t *ks_rsa = NULL, *ks_oct = NULL;
const jwk_item_t *k_rsa, *k_oct;
char *tok;
SET_OPS();
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
ks_oct = jwks_create_fromfile(KEYDIR "/oct_key_256_enc.json");
ck_assert_ptr_nonnull(ks_oct);
k_oct = jwks_item_get(ks_oct, 0);
/* Flattened + 2 recipients -> generate fails. */
bf = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(bf, JWE_ALG_A256KW,
JWE_ENC_A256GCM, k_oct), 0);
ck_assert_ptr_nonnull(jwe_builder_add_recipient(bf, JWE_ALG_RSA_OAEP_256,
k_rsa));
/* add_recipient forced GENERAL; force it back to FLAT to trip the gate. */
ck_assert_int_eq(jwe_builder_set_format(bf, JWE_FORMAT_JSON_FLAT), 0);
tok = jwe_builder_generate(bf, (const unsigned char *)PT, strlen(PT));
ck_assert_ptr_null(tok);
ck_assert_int_eq(jwe_builder_error(bf), 1);
/* Compact + 2 recipients -> generate fails. */
bc = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(bc, JWE_ALG_A256KW,
JWE_ENC_A256GCM, k_oct), 0);
ck_assert_ptr_nonnull(jwe_builder_add_recipient(bc, JWE_ALG_RSA_OAEP_256,
k_rsa));
ck_assert_int_eq(jwe_builder_set_format(bc, JWE_FORMAT_COMPACT), 0);
tok = jwe_builder_generate(bc, (const unsigned char *)PT, strlen(PT));
ck_assert_ptr_null(tok);
ck_assert_int_eq(jwe_builder_error(bc), 1);
}
END_TEST
/* A checker whose key/alg matches no recipient fails with the generic auth
* error (RFC 7516 11.5: no oracle revealing which recipient, if any, matched). */
START_TEST(no_matching_recipient)
{
jwe_builder_auto_t *builder = NULL;
jwe_checker_auto_t *checker = NULL;
char_auto *tok = NULL;
jwk_set_auto_t *ks_rsa = NULL;
const jwk_item_t *k_rsa;
unsigned char *pt;
size_t pt_len = 0;
SET_OPS();
read_json("oct_key_256_enc.json");
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, g_item), 0);
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
ck_assert_ptr_nonnull(jwe_builder_add_recipient(builder,
JWE_ALG_RSA_OAEP_256, k_rsa));
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
free_key();
/* Checker configured for ECDH-ES, which no recipient uses. */
read_json("ec_key_prime256v1_enc.json");
checker = jwe_checker_new();
ck_assert_int_eq(jwe_checker_setkey(checker, JWE_ALG_ECDH_ES_A128KW,
JWE_ENC_A256GCM, g_item), 0);
pt = jwe_checker_decrypt_all(checker, tok, &pt_len);
ck_assert_ptr_null(pt);
ck_assert_int_eq(jwe_checker_error(checker), 1);
free_key();
}
END_TEST
/* The right key but wrong recipient's key still fails (wrong A256KW KEK while
* the matching alg recipient exists). */
START_TEST(matching_alg_wrong_key)
{
jwe_builder_auto_t *builder = NULL;
jwe_checker_auto_t *checker = NULL;
char_auto *tok = NULL;
jwk_set_auto_t *ks_rsa = NULL;
const jwk_item_t *k_rsa;
unsigned char *pt;
size_t pt_len = 0;
SET_OPS();
read_json("oct_key_256_enc.json");
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, g_item), 0);
ks_rsa = jwks_create_fromfile(KEYDIR "/rsa_key_2048_enc.json");
k_rsa = jwks_item_get(ks_rsa, 0);
ck_assert_ptr_nonnull(jwe_builder_add_recipient(builder,
JWE_ALG_RSA_OAEP_256, k_rsa));
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
free_key();
/* A256KW alg matches recipient #1, but a different 256-bit KEK. */
read_json("oct_dir_256.json");
checker = jwe_checker_new();
ck_assert_int_eq(jwe_checker_setkey(checker, JWE_ALG_A256KW,
JWE_ENC_A256GCM, g_item), 0);
pt = jwe_checker_decrypt_all(checker, tok, &pt_len);
ck_assert_ptr_null(pt);
ck_assert_int_eq(jwe_checker_error(checker), 1);
free_key();
}
END_TEST
/* No matching recipient AND an aad member present: the uniform-failure path
* must build (and free) the AAD too. */
START_TEST(no_match_with_aad)
{
jwe_builder_auto_t *builder = NULL;
jwe_checker_auto_t *checker = NULL;
char_auto *tok = NULL;
unsigned char *pt;
size_t pt_len = 0;
SET_OPS();
read_json("oct_key_256_enc.json");
builder = jwe_builder_new();
ck_assert_int_eq(jwe_builder_setkey(builder, JWE_ALG_A256KW,
JWE_ENC_A256GCM, g_item), 0);
ck_assert_int_eq(jwe_builder_set_format(builder,
JWE_FORMAT_JSON_GENERAL), 0);
ck_assert_int_eq(jwe_builder_set_aad(builder,
(const unsigned char *)"aad", 3), 0);
tok = jwe_builder_generate(builder, (const unsigned char *)PT,
strlen(PT));
ck_assert_ptr_nonnull(tok);
free_key();
/* Checker configured for an alg no recipient uses. */
read_json("rsa_key_2048_enc.json");
checker = jwe_checker_new();
ck_assert_int_eq(jwe_checker_setkey(checker, JWE_ALG_RSA_OAEP_256,
JWE_ENC_A256GCM, g_item), 0);
pt = jwe_checker_decrypt_all(checker, tok, &pt_len);
ck_assert_ptr_null(pt);
ck_assert_int_eq(jwe_checker_error(checker), 1);
/* No AAD surfaced on failure. */
ck_assert_ptr_null(jwe_checker_get_aad(checker, NULL));
free_key();
}
END_TEST
static Suite *libjwt_suite(const char *title)
{
Suite *s;
TCase *tc_core;
int i = ARRAY_SIZE(jwt_test_ops);
s = suite_create(title);
tc_core = tcase_create("JWE Multi-Recipient");
tcase_add_loop_test(tc_core, three_recipients, 0, i);
tcase_add_loop_test(tc_core, second_recipient_matches, 0, i);
tcase_add_loop_test(tc_core, recipient_header_and_partyinfo, 0, i);
tcase_add_loop_test(tc_core, add_recipient_errors, 0, i);
tcase_add_loop_test(tc_core, dir_rejects_multi, 0, i);
tcase_add_loop_test(tc_core, ecdh_direct_rejects_multi, 0, i);
tcase_add_loop_test(tc_core, flat_compact_reject_multi, 0, i);
tcase_add_loop_test(tc_core, no_matching_recipient, 0, i);
tcase_add_loop_test(tc_core, no_match_with_aad, 0, i);
tcase_add_loop_test(tc_core, matching_alg_wrong_key, 0, i);
tcase_set_timeout(tc_core, 30);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
JWT_TEST_MAIN("LibJWT JWE Multi-Recipient");
}