blob: 785e869d3072094efa65acd4482020659c95348a [file] [edit]
/* Public domain, no copyright. Use at your own risk. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jwt_tests.h"
/* Tests for the RFC 7800 "cnf" (confirmation) claim helpers:
* jwt_builder_setcnf_jkt() / _jwk() / jwt_builder_setcnf() and jwt_get_cnf().
* The token is signed and verified with one EC P-256 key (ES256 works on every
* backend); the same key's public thumbprint is the proof-of-possession value.
*
* NOTE: SET_OPS() is called BEFORE loading any key. A jwk_item is bound to the
* crypto backend that parsed it, so a key must be loaded under the backend it
* will be used with (see jwt_flipflop.c, and issue about per-key backend
* affinity). Loading first and switching backends after would bind the key to
* the wrong backend and fail signing on MbedTLS. */
static jwk_set_t *load_key(void)
{
char *path;
jwk_set_t *set;
int ret;
ret = asprintf(&path, KEYDIR "/ec_key_prime256v1.json");
ck_assert_int_gt(ret, 0);
set = jwks_create_fromfile(path);
free(path);
ck_assert_ptr_nonnull(set);
return set;
}
/* Get the builder's current "cnf" claim as a serialized JSON string. */
static char *get_builder_cnf(jwt_builder_t *builder)
{
jwt_value_t jval;
jwt_set_GET_JSON(&jval, "cnf");
if (jwt_builder_claim_get(builder, &jval) != JWT_VALUE_ERR_NONE)
return NULL;
return jval.json_val;
}
START_TEST(test_setcnf_jkt)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
char_auto *tp = NULL;
char_auto *cnf = NULL;
char expected[128];
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
tp = jwks_item_thumbprint(key, JWK_THUMBPRINT_SHA256);
ck_assert_ptr_nonnull(tp);
snprintf(expected, sizeof(expected), "{\"jkt\":\"%s\"}", tp);
builder = jwt_builder_new();
ck_assert_ptr_nonnull(builder);
ck_assert_int_eq(jwt_builder_setcnf_jkt(builder, key), 0);
cnf = get_builder_cnf(builder);
ck_assert_ptr_nonnull(cnf);
ck_assert_str_eq(cnf, expected);
jwks_free(set);
}
END_TEST
START_TEST(test_setcnf_generic_and_single_member)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
char_auto *c1 = NULL, *c2 = NULL, *c3 = NULL;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
builder = jwt_builder_new();
ck_assert_ptr_nonnull(builder);
/* A string member (e.g. the RFC 8705 mTLS confirmation). */
ck_assert_int_eq(jwt_builder_setcnf(builder, "x5t#S256", "abc123"), 0);
c1 = get_builder_cnf(builder);
ck_assert_str_eq(c1, "{\"x5t#S256\":\"abc123\"}");
/* A second setter REPLACES cnf: it must still carry exactly one member. */
ck_assert_int_eq(jwt_builder_setcnf(builder, "kid", "key-1"), 0);
c2 = get_builder_cnf(builder);
ck_assert_str_eq(c2, "{\"kid\":\"key-1\"}");
/* jkt likewise replaces. */
ck_assert_int_eq(jwt_builder_setcnf_jkt(builder, key), 0);
c3 = get_builder_cnf(builder);
ck_assert(!strncmp(c3, "{\"jkt\":\"", 8));
/* exactly one member: one ':' and no ',' */
ck_assert_ptr_null(strchr(c3, ','));
jwks_free(set);
}
END_TEST
START_TEST(test_setcnf_jwk)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
char_auto *cnf = NULL;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
builder = jwt_builder_new();
ck_assert_ptr_nonnull(builder);
ck_assert_int_eq(jwt_builder_setcnf_jwk(builder, key), 0);
cnf = get_builder_cnf(builder);
ck_assert_ptr_nonnull(cnf);
/* cnf = {"jwk": <public EC JWK>} : has the public members, not "d". */
ck_assert(strstr(cnf, "\"jwk\":{") != NULL);
ck_assert(strstr(cnf, "\"kty\":\"EC\"") != NULL);
ck_assert(strstr(cnf, "\"crv\":\"P-256\"") != NULL);
ck_assert(strstr(cnf, "\"x\":") != NULL);
ck_assert(strstr(cnf, "\"d\":") == NULL);
jwks_free(set);
}
END_TEST
/* Captures what the verify callback reads from the token's cnf. */
struct cnf_seen {
char *jkt; /* cnf.jkt value (must match) */
int missing_is_null; /* jwt_get_cnf(absent) == NULL */
int object_is_null; /* jwt_get_cnf("jwk" object) == NULL when present */
int ran;
};
static int verify_jkt_cb(jwt_t *jwt, jwt_config_t *config)
{
struct cnf_seen *s = config->ctx;
s->ran = 1;
s->jkt = jwt_get_cnf(jwt, "jkt");
s->missing_is_null = (jwt_get_cnf(jwt, "nope") == NULL);
return 0;
}
static int verify_jwk_cb(jwt_t *jwt, jwt_config_t *config)
{
struct cnf_seen *s = config->ctx;
s->ran = 1;
/* "jwk" is an object, not a string -> the string getter returns NULL. */
s->object_is_null = (jwt_get_cnf(jwt, "jwk") == NULL);
return 0;
}
static int verify_nocnf_cb(jwt_t *jwt, jwt_config_t *config)
{
struct cnf_seen *s = config->ctx;
s->ran = 1;
/* No "cnf" claim at all -> NULL. */
s->missing_is_null = (jwt_get_cnf(jwt, "jkt") == NULL);
return 0;
}
START_TEST(test_get_cnf_roundtrip)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
jwt_checker_auto_t *checker = NULL;
char_auto *tp = NULL;
char_auto *token = NULL;
struct cnf_seen seen;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
tp = jwks_item_thumbprint(key, JWK_THUMBPRINT_SHA256);
ck_assert_ptr_nonnull(tp);
/* Issue a token bound to the key via cnf.jkt. */
builder = jwt_builder_new();
ck_assert_int_eq(jwt_builder_setkey(builder, JWT_ALG_ES256, key), 0);
ck_assert_int_eq(jwt_builder_setcnf_jkt(builder, key), 0);
token = jwt_builder_generate(builder);
ck_assert_ptr_nonnull(token);
/* Verify and read cnf.jkt from the callback's jwt_t. */
memset(&seen, 0, sizeof(seen));
checker = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_ES256, key), 0);
ck_assert_int_eq(jwt_checker_setcb(checker, verify_jkt_cb, &seen), 0);
ck_assert_int_eq(jwt_checker_verify(checker, token), 0);
ck_assert_int_eq(seen.ran, 1);
ck_assert_ptr_nonnull(seen.jkt);
ck_assert_str_eq(seen.jkt, tp);
ck_assert_int_eq(seen.missing_is_null, 1);
free(seen.jkt);
jwks_free(set);
}
END_TEST
START_TEST(test_get_cnf_object_member)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
jwt_checker_auto_t *checker = NULL;
char_auto *token = NULL;
struct cnf_seen seen;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
builder = jwt_builder_new();
ck_assert_int_eq(jwt_builder_setkey(builder, JWT_ALG_ES256, key), 0);
ck_assert_int_eq(jwt_builder_setcnf_jwk(builder, key), 0);
token = jwt_builder_generate(builder);
ck_assert_ptr_nonnull(token);
memset(&seen, 0, sizeof(seen));
checker = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_ES256, key), 0);
ck_assert_int_eq(jwt_checker_setcb(checker, verify_jwk_cb, &seen), 0);
ck_assert_int_eq(jwt_checker_verify(checker, token), 0);
ck_assert_int_eq(seen.ran, 1);
ck_assert_int_eq(seen.object_is_null, 1);
jwks_free(set);
}
END_TEST
START_TEST(test_get_cnf_absent)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
jwt_checker_auto_t *checker = NULL;
char_auto *token = NULL;
struct cnf_seen seen;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
/* A token with no "cnf" at all. */
builder = jwt_builder_new();
ck_assert_int_eq(jwt_builder_setkey(builder, JWT_ALG_ES256, key), 0);
token = jwt_builder_generate(builder);
ck_assert_ptr_nonnull(token);
memset(&seen, 0, sizeof(seen));
checker = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_ES256, key), 0);
ck_assert_int_eq(jwt_checker_setcb(checker, verify_nocnf_cb, &seen), 0);
ck_assert_int_eq(jwt_checker_verify(checker, token), 0);
ck_assert_int_eq(seen.ran, 1);
ck_assert_int_eq(seen.missing_is_null, 1);
jwks_free(set);
}
END_TEST
START_TEST(test_errors)
{
jwk_set_t *set;
const jwk_item_t *key;
jwt_builder_auto_t *builder = NULL;
jwk_set_t *seed_set;
const jwk_item_t *seed_key;
char *p;
int ret;
SET_OPS();
set = load_key();
key = jwks_item_get(set, 0);
builder = jwt_builder_new();
ck_assert_ptr_nonnull(builder);
/* NULL / empty arguments. */
ck_assert_int_ne(jwt_builder_setcnf_jkt(NULL, key), 0);
ck_assert_int_ne(jwt_builder_setcnf_jkt(builder, NULL), 0);
ck_assert_int_ne(jwt_builder_setcnf_jwk(NULL, key), 0);
ck_assert_int_ne(jwt_builder_setcnf_jwk(builder, NULL), 0);
ck_assert_int_ne(jwt_builder_setcnf(NULL, "kid", "v"), 0);
ck_assert_int_ne(jwt_builder_setcnf(builder, NULL, "v"), 0);
ck_assert_int_ne(jwt_builder_setcnf(builder, "", "v"), 0);
ck_assert_int_ne(jwt_builder_setcnf(builder, "kid", NULL), 0);
ck_assert_ptr_null(jwt_get_cnf(NULL, "jkt"));
/* A seed-only OKP key (Ed448, "d" with no "x") can't be thumbprinted,
* so jkt binding fails cleanly. */
ret = asprintf(&p, KEYDIR "/eddsa_key_ed448.json");
ck_assert_int_gt(ret, 0);
seed_set = jwks_create_fromfile(p);
free(p);
ck_assert_ptr_nonnull(seed_set);
seed_key = jwks_item_get(seed_set, 0);
ck_assert_ptr_nonnull(seed_key);
ck_assert_int_ne(jwt_builder_setcnf_jkt(builder, seed_key), 0);
jwks_free(seed_set);
jwks_free(set);
}
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("jwt_cnf");
tcase_add_loop_test(tc_core, test_setcnf_jkt, 0, i);
tcase_add_loop_test(tc_core, test_setcnf_generic_and_single_member, 0, i);
tcase_add_loop_test(tc_core, test_setcnf_jwk, 0, i);
tcase_add_loop_test(tc_core, test_get_cnf_roundtrip, 0, i);
tcase_add_loop_test(tc_core, test_get_cnf_object_member, 0, i);
tcase_add_loop_test(tc_core, test_get_cnf_absent, 0, i);
tcase_add_loop_test(tc_core, test_errors, 0, i);
tcase_set_timeout(tc_core, 30);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
JWT_TEST_MAIN("LibJWT cnf (RFC 7800) Confirmation Claim");
}