blob: b09d9bef0cbfa8226afb6611c540b812c8d43f84 [file] [edit]
/* Public domain, no copyright. Use at your own risk. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jwt_tests.h"
/* @rfc{7797} JWS unencoded ("b64":false) + detached payloads (issue #315).
* Runs under each compiled crypto backend (SET_OPS loop) and both JSON
* backends. The Appendix A payload "$.02" is non-JSON on purpose. */
static const unsigned char RAW[] = "$.02";
#define RAW_LEN 4
static jwk_set_t *load_one(const char *file)
{
char *path;
jwk_set_t *set;
int ret;
ret = asprintf(&path, KEYDIR "/%s", file);
ck_assert_int_gt(ret, 0);
set = jwks_create_fromfile(path);
free(path);
ck_assert_ptr_nonnull(set);
return set;
}
static const jwt_serialization_t formats[] = {
JWT_FORMAT_COMPACT, JWT_FORMAT_JSON_FLAT, JWT_FORMAT_JSON_GENERAL,
};
/* ---- b64=false, attached, every serialization ---- */
START_TEST(test_b64_false_attached)
{
jwk_set_t *ks;
const jwk_item_t *ec;
size_t f;
SET_OPS();
ks = load_one("ec_key_prime256v1.json");
ec = jwks_item_get(ks, 0);
for (f = 0; f < ARRAY_SIZE(formats); f++) {
jwt_builder_auto_t *b = jwt_builder_new();
jwt_checker_auto_t *c = jwt_checker_new();
char_auto *tok = NULL;
ck_assert_int_eq(jwt_builder_setkey(b, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_builder_setpayload(b, RAW, RAW_LEN), 0);
ck_assert_int_eq(jwt_builder_setb64(b, 0), 0);
ck_assert_int_eq(jwt_builder_set_format(b, formats[f]), 0);
tok = jwt_builder_generate(b);
ck_assert_ptr_nonnull(tok);
ck_assert_int_eq(jwt_checker_setkey(c, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_checker_verify(c, tok), 0);
}
jwks_free(ks);
}
END_TEST
/* ---- b64=false, detached, every serialization ---- */
START_TEST(test_b64_false_detached)
{
jwk_set_t *ks;
const jwk_item_t *ec;
size_t f;
SET_OPS();
ks = load_one("ec_key_prime256v1.json");
ec = jwks_item_get(ks, 0);
for (f = 0; f < ARRAY_SIZE(formats); f++) {
jwt_builder_auto_t *b = jwt_builder_new();
jwt_checker_auto_t *c = jwt_checker_new();
char_auto *tok = NULL;
ck_assert_int_eq(jwt_builder_setkey(b, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_builder_setpayload(b, RAW, RAW_LEN), 0);
ck_assert_int_eq(jwt_builder_setb64(b, 0), 0);
ck_assert_int_eq(jwt_builder_set_detached(b, 1), 0);
ck_assert_int_eq(jwt_builder_set_format(b, formats[f]), 0);
tok = jwt_builder_generate(b);
ck_assert_ptr_nonnull(tok);
ck_assert_int_eq(jwt_checker_setkey(c, JWT_ALG_ES256, ec), 0);
/* Correct out-of-band payload verifies; a wrong one is rejected. */
ck_assert_int_eq(jwt_checker_verify_detached(c, tok, RAW, RAW_LEN), 0);
ck_assert_int_ne(jwt_checker_verify_detached(c, tok,
(const unsigned char *)"x.99", 4), 0);
}
jwks_free(ks);
}
END_TEST
/* ---- b64=true detached JSON claims (a detached JWT, RFC 7515 App F) ---- */
START_TEST(test_b64_true_detached_jwt)
{
static const unsigned char JSON[] = "{\"sub\":\"alice\"}";
jwk_set_t *ks;
const jwk_item_t *ec;
jwt_builder_auto_t *b = NULL;
jwt_checker_auto_t *c = NULL;
char_auto *tok = NULL;
SET_OPS();
ks = load_one("ec_key_prime256v1.json");
ec = jwks_item_get(ks, 0);
b = jwt_builder_new();
ck_assert_int_eq(jwt_builder_setkey(b, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_builder_setpayload(b, JSON, strlen((char *)JSON)), 0);
ck_assert_int_eq(jwt_builder_set_detached(b, 1), 0);
tok = jwt_builder_generate(b);
ck_assert_ptr_nonnull(tok);
c = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(c, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_checker_verify_detached(c, tok, JSON,
strlen((char *)JSON)), 0);
jwks_free(ks);
}
END_TEST
/* ---- RFC 7797 Appendix A.4: HS256, detached, unencoded "$.02" ---- */
START_TEST(test_rfc7797_appendix_a)
{
/* The HMAC key from RFC 7515 Appendix A.1. */
static const char *KEY =
"{\"kty\":\"oct\",\"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ"
"-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}";
/* The detached JWS from RFC 7797 Appendix A.4. */
static const char *TOKEN =
"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19"
"..A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY";
jwk_set_t *ks;
const jwk_item_t *k;
jwt_checker_auto_t *c = NULL, *c2 = NULL;
SET_OPS();
ks = jwks_create(NULL);
ck_assert_ptr_nonnull(ks);
ck_assert_ptr_nonnull(jwks_load(ks, KEY));
k = jwks_item_get(ks, 0);
ck_assert_ptr_nonnull(k);
c = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(c, JWT_ALG_HS256, k), 0);
ck_assert_int_eq(jwt_checker_verify_detached(c, TOKEN, RAW, RAW_LEN), 0);
/* A different payload must not verify against the RFC signature. */
c2 = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(c2, JWT_ALG_HS256, k), 0);
ck_assert_int_ne(jwt_checker_verify_detached(c2, TOKEN,
(const unsigned char *)"$.03", 4), 0);
jwks_free(ks);
}
END_TEST
/* ---- Security + builder guards ---- */
START_TEST(test_b64_security)
{
jwk_set_t *ks;
const jwk_item_t *ec;
jwt_builder_auto_t *b = NULL;
jwt_checker_auto_t *c = NULL;
/* base64url({"alg":"ES256","b64":false}) -- b64 NOT in crit. */
static const char *NOCRIT =
"eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2V9.$.02.AAAA";
SET_OPS();
ks = load_one("ec_key_prime256v1.json");
ec = jwks_item_get(ks, 0);
/* @rfc{7797,6} b64=false without "b64" in "crit" is rejected. */
c = jwt_checker_new();
ck_assert_int_eq(jwt_checker_setkey(c, JWT_ALG_ES256, ec), 0);
ck_assert_int_ne(jwt_checker_verify(c, NOCRIT), 0);
/* The builder refuses b64=false without a raw payload (would be a JWT). */
b = jwt_builder_new();
ck_assert_int_eq(jwt_builder_setkey(b, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_builder_setb64(b, 0), 0);
ck_assert_ptr_null(jwt_builder_generate(b));
/* An unencoded payload with an embedded NUL cannot be serialized verbatim,
* so it is rejected rather than silently truncated. */
{
jwt_builder_auto_t *bn = jwt_builder_new();
static const unsigned char NUL_PT[] = { 'a', '\0', 'b' };
ck_assert_int_eq(jwt_builder_setkey(bn, JWT_ALG_ES256, ec), 0);
ck_assert_int_eq(jwt_builder_setpayload(bn, NUL_PT, 3), 0);
ck_assert_int_eq(jwt_builder_setb64(bn, 0), 0);
ck_assert_ptr_null(jwt_builder_generate(bn));
}
jwks_free(ks);
}
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("jws_b64");
tcase_add_loop_test(tc_core, test_b64_false_attached, 0, i);
tcase_add_loop_test(tc_core, test_b64_false_detached, 0, i);
tcase_add_loop_test(tc_core, test_b64_true_detached_jwt, 0, i);
tcase_add_loop_test(tc_core, test_rfc7797_appendix_a, 0, i);
tcase_add_loop_test(tc_core, test_b64_security, 0, i);
tcase_set_timeout(tc_core, 60);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
JWT_TEST_MAIN("LibJWT RFC 7797 unencoded/detached payload (#315)");
}