blob: e26351c7859ba39336f3bf5637da2bb895763356 [file]
/* Public domain, no copyright. Use at your own risk. */
/* Security regression tests for libjwt.
*
* These tests validate fixes for security vulnerabilities including:
* - Ill-structured JSON for JWT and JWKS parsing
* - Malformed base64 inputs
* - Missing or invalid header fields
* - Type confusion in JWK fields
* - Truncated and oversized tokens
* - NULL and empty input handling
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "jwt_tests.h"
/*
* === JWKS Ill-Structured JSON Tests ===
*
* These test malformed JWK/JWKS JSON documents to ensure the parser
* handles them gracefully without crashes or undefined behavior.
*/
/* JWK with "alg" as a non-string type (integer) - tests alg_str NULL deref fix */
START_TEST(test_jwks_rsa_alg_integer)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"e\":\"AQAB\","
"\"alg\":256}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
/* Should parse without crashing even though alg is not a string */
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
jwks_free(jwk_set);
}
END_TEST
/* JWK with "alg" as JSON null */
START_TEST(test_jwks_rsa_alg_null)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"e\":\"AQAB\","
"\"alg\":null}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
jwks_free(jwk_set);
}
END_TEST
/* JWK with "alg" as a boolean */
START_TEST(test_jwks_rsa_alg_boolean)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"e\":\"AQAB\","
"\"alg\":true}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
jwks_free(jwk_set);
}
END_TEST
/* JWK with "kty" as non-string type */
START_TEST(test_jwks_kty_integer)
{
const char *json = "{\"kty\":123}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWKS with empty "keys" array */
START_TEST(test_jwks_empty_keys_array)
{
const char *json = "{\"keys\":[]}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
ck_assert(!jwks_error(jwk_set));
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_null(item);
jwks_free(jwk_set);
}
END_TEST
/* JWKS with "keys" as a string instead of array */
START_TEST(test_jwks_keys_not_array)
{
const char *json = "{\"keys\":\"not an array\"}";
jwk_set_t *jwk_set = NULL;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
/* Should handle gracefully */
jwks_free(jwk_set);
}
END_TEST
/* JWK with missing required EC components */
START_TEST(test_jwks_ec_missing_x)
{
const char *json = "{\"kty\":\"EC\","
"\"crv\":\"P-256\","
"\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\","
"\"alg\":\"ES256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWK with missing required EC curve */
START_TEST(test_jwks_ec_missing_crv)
{
const char *json = "{\"kty\":\"EC\","
"\"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU\","
"\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\","
"\"alg\":\"ES256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWK with RSA missing 'n' component */
START_TEST(test_jwks_rsa_missing_n)
{
const char *json = "{\"kty\":\"RSA\","
"\"e\":\"AQAB\","
"\"alg\":\"RS256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWK with RSA missing 'e' component */
START_TEST(test_jwks_rsa_missing_e)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"alg\":\"RS256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* EdDSA JWK with missing 'x' (public key) */
START_TEST(test_jwks_eddsa_missing_x)
{
const char *json = "{\"kty\":\"OKP\","
"\"crv\":\"Ed25519\","
"\"alg\":\"EdDSA\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* OKP X25519 JWK with an oversized "x" (33 bytes instead of 32). The MbedTLS
* backend previously imported the raw little-endian value with no length check,
* accepting a malformed key that OpenSSL and GnuTLS reject. All backends must
* now reject it (the field width is fixed at 32 bytes for X25519). */
START_TEST(test_jwks_okp_x25519_bad_x_len)
{
const char *json = "{\"kty\":\"OKP\","
"\"crv\":\"X25519\","
/* 33-byte x (valid 32-byte value with a trailing zero byte). */
"\"x\":\"S46Jc4ib5np5zMd8F4xCJL3wLqM_mYlgGGhw0XQAsBUA\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* As above but for the private scalar "d": a valid 32-byte X25519 "x" with an
* oversized 33-byte "d" must also be rejected by the length check. */
START_TEST(test_jwks_okp_x25519_bad_d_len)
{
const char *json = "{\"kty\":\"OKP\","
"\"crv\":\"X25519\","
"\"x\":\"S46Jc4ib5np5zMd8F4xCJL3wLqM_mYlgGGhw0XQAsBU\","
"\"d\":\"8HS-8uSD0zo8pLVHOs3UQh6R57knGnjMgf7iCCknPGsA\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* RSA JWK with partial private key (some but not all components) */
START_TEST(test_jwks_rsa_partial_private_key)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"e\":\"AQAB\","
"\"d\":\"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYjqxn\","
"\"p\":\"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
/* Should detect missing q, dp, dq, qi */
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWK with values as wrong types (n as integer, e as array) */
START_TEST(test_jwks_rsa_wrong_value_types)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":12345,"
"\"e\":[1,2,3],"
"\"alg\":\"RS256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* Deeply nested JSON object */
START_TEST(test_jwks_deeply_nested)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":{\"a\":{\"b\":{\"c\":\"deep\"}}},"
"\"e\":\"AQAB\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* Empty JSON object */
START_TEST(test_jwks_empty_object)
{
const char *json = "{}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* Empty string as JWK */
START_TEST(test_jwks_empty_string)
{
const char *json = "";
jwk_set_t *jwk_set = NULL;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
ck_assert(jwks_error(jwk_set));
jwks_free(jwk_set);
}
END_TEST
/* JSON array at top level (not object) */
START_TEST(test_jwks_top_level_array)
{
const char *json = "[{\"kty\":\"RSA\",\"n\":\"abc\",\"e\":\"AQAB\"}]";
jwk_set_t *jwk_set = NULL;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
/* Should not crash regardless of how it handles this */
jwks_free(jwk_set);
}
END_TEST
/* JWKS with mixed valid and invalid keys */
START_TEST(test_jwks_mixed_valid_invalid)
{
const char *json = "{\"keys\":["
"{\"kty\":\"RSA\",\"n\":\"bad\"},"
"{\"kty\":\"INVALID\"},"
"{\"NOT-kty\":\"missing\"}"
"]}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
int i;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
/* All items should have errors but none should crash */
for (i = 0; (item = jwks_item_get(jwk_set, i)) != NULL; i++)
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* OCT key with invalid base64 in "k" */
START_TEST(test_jwks_oct_invalid_base64)
{
const char *json = "{\"kty\":\"oct\","
"\"k\":\"!!!not-valid-base64!!!\","
"\"alg\":\"HS256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
ck_assert_int_ne(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/* JWK with extra unexpected fields (should be ignored) */
START_TEST(test_jwks_extra_fields)
{
const char *json = "{\"kty\":\"oct\","
"\"k\":\"AyM32fcIOpOGAyXWTiHs\","
"\"alg\":\"HS256\","
"\"malicious_field\":\"should be ignored\","
"\"x5c\":[\"not a cert\"],"
"\"nested\":{\"deep\":true}}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
/* Key should parse; extra fields ignored */
jwks_free(jwk_set);
}
END_TEST
/*
* === JWT Parsing Security Tests ===
*
* These test malformed JWT token strings to ensure the parser
* handles them gracefully.
*/
/* Token with no dots at all */
START_TEST(test_jwt_no_dots)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ret = jwt_checker_verify(checker, "nodotshere");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with only one dot */
START_TEST(test_jwt_one_dot)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ret = jwt_checker_verify(checker, "one.dot");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Empty token */
START_TEST(test_jwt_empty_token)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ret = jwt_checker_verify(checker, "");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* NULL token */
START_TEST(test_jwt_null_token)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ret = jwt_checker_verify(checker, NULL);
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with empty header (..payload.sig) */
START_TEST(test_jwt_empty_header)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* Empty string before first dot */
ret = jwt_checker_verify(checker, ".eyJ0ZXN0IjoiMSJ9.");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with header that is valid base64 but not JSON */
START_TEST(test_jwt_header_not_json)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* "not json" in base64url = "bm90IGpzb24" */
ret = jwt_checker_verify(checker, "bm90IGpzb24.eyJ0ZXN0IjoiMSJ9.");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with header missing alg field */
START_TEST(test_jwt_header_missing_alg)
{
jwt_checker_auto_t *checker = NULL;
int ret;
const char *msg;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* {"typ":"JWT"} in base64url (no alg field) */
ret = jwt_checker_verify(checker,
"eyJ0eXAiOiJKV1QifQ.eyJ0ZXN0IjoiMSJ9.");
ck_assert_int_ne(ret, 0);
msg = jwt_checker_error_msg(checker);
ck_assert_ptr_nonnull(msg);
ck_assert(strlen(msg) > 0);
}
END_TEST
/* Token with invalid alg value */
START_TEST(test_jwt_header_invalid_alg)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* {"alg":"BOGUS"} in base64url */
ret = jwt_checker_verify(checker,
"eyJhbGciOiJCT0dVUyJ9.eyJ0ZXN0IjoiMSJ9.");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with alg as integer in header */
START_TEST(test_jwt_header_alg_integer)
{
jwt_checker_auto_t *checker = NULL;
int ret;
const char *msg;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* {"alg":256} in base64url */
ret = jwt_checker_verify(checker,
"eyJhbGciOjI1Nn0.eyJ0ZXN0IjoiMSJ9.");
ck_assert_int_ne(ret, 0);
msg = jwt_checker_error_msg(checker);
ck_assert_ptr_nonnull(msg);
ck_assert(strlen(msg) > 0);
}
END_TEST
/* Token with many dots */
START_TEST(test_jwt_many_dots)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ret = jwt_checker_verify(checker, "a.b.c.d.e.f");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with invalid base64 in payload */
START_TEST(test_jwt_invalid_base64_payload)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* Valid header {"alg":"none"}, garbage payload, empty sig */
ret = jwt_checker_verify(checker,
"eyJhbGciOiJub25lIn0.!!!invalid!!!.");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Token with empty payload and empty signature (alg:none) */
START_TEST(test_jwt_alg_none_empty)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
/* {"alg":"none"} . {} . (empty sig) */
ret = jwt_checker_verify(checker,
"eyJhbGciOiJub25lIn0.e30.");
ck_assert_int_eq(ret, 0);
}
END_TEST
/*
* === JWKS load_strn boundary tests ===
*/
/* Zero length string to jwks_load_strn */
START_TEST(test_jwks_load_strn_zero_len)
{
jwk_set_t *jwk_set = NULL;
SET_OPS();
jwk_set = jwks_create_strn("{}", 0);
ck_assert_ptr_nonnull(jwk_set);
jwks_free(jwk_set);
}
END_TEST
/* Truncated JSON to jwks_load_strn */
START_TEST(test_jwks_load_strn_truncated)
{
const char *json = "{\"kty\":\"RSA\",\"n\":\"abc\",\"e\":\"AQAB\"}";
jwk_set_t *jwk_set = NULL;
SET_OPS();
/* Pass only half the string */
jwk_set = jwks_create_strn(json, 10);
ck_assert_ptr_nonnull(jwk_set);
ck_assert(jwks_error(jwk_set));
jwks_free(jwk_set);
}
END_TEST
/*
* === JWKS NULL and edge case handling ===
*/
/* Multiple operations on NULL jwk_set */
START_TEST(test_jwks_null_operations)
{
SET_OPS();
/* These are documented as NULL-safe and must not crash */
ck_assert_ptr_null(jwks_item_get(NULL, 0));
ck_assert_int_eq(jwks_item_free(NULL, 0), 0);
ck_assert_int_eq(jwks_item_free_all(NULL), 0);
jwks_free(NULL);
ck_assert_ptr_null(jwks_create_strn(NULL, 0));
ck_assert_ptr_null(jwks_create_fromfile(NULL));
ck_assert_ptr_null(jwks_create_fromfp(NULL));
}
END_TEST
/* Checker NULL safety */
START_TEST(test_checker_null_safety)
{
int ret;
const char *msg;
SET_OPS();
jwt_checker_free(NULL);
ret = jwt_checker_error(NULL);
ck_assert_int_ne(ret, 0);
msg = jwt_checker_error_msg(NULL);
ck_assert_ptr_null(msg);
ret = jwt_checker_setkey(NULL, JWT_ALG_NONE, NULL);
ck_assert_int_ne(ret, 0);
ret = jwt_checker_verify(NULL, "test");
ck_assert_int_ne(ret, 0);
}
END_TEST
/* Builder NULL safety */
START_TEST(test_builder_null_safety)
{
char *out;
int ret;
const char *msg;
SET_OPS();
jwt_builder_free(NULL);
ret = jwt_builder_error(NULL);
ck_assert_int_ne(ret, 0);
msg = jwt_builder_error_msg(NULL);
ck_assert_ptr_null(msg);
ret = jwt_builder_setkey(NULL, JWT_ALG_NONE, NULL);
ck_assert_int_ne(ret, 0);
out = jwt_builder_generate(NULL);
ck_assert_ptr_null(out);
}
END_TEST
/*
* === JWK with PSS alg sniffing ===
*
* Tests the alg_str dereference fix specifically.
*/
START_TEST(test_jwks_rsa_pss_alg_string)
{
const char *json = "{\"kty\":\"RSA\","
"\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtV"
"T86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsG"
"Y4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqz"
"s8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQ"
"FH6wAvvL6F_\","
"\"e\":\"AQAB\","
"\"alg\":\"PS256\"}";
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
SET_OPS();
jwk_set = jwks_create(json);
ck_assert_ptr_nonnull(jwk_set);
item = jwks_item_get(jwk_set, 0);
ck_assert_ptr_nonnull(item);
/* Should parse successfully with PS256 */
ck_assert_int_eq(jwks_item_error(item), 0);
jwks_free(jwk_set);
}
END_TEST
/*
* === Algorithm Confusion Regression (GHSA-q843-6q5f-w55g) ===
*
* An RSA JWK with no "alg" hint must not be usable for any HMAC
* algorithm. Without the fix, libjwt would accept the RSA JWK for
* HS256/384/512 and run HMAC against a zero-length key
* (oct.key/oct.len read from the union shared with provider_data).
* That allowed an attacker who only knows the public JWKS to forge
* tokens that verify successfully.
*/
/* The exact PoC from the advisory: RSA public JWK without "alg",
* verifying an HS256 token whose signature is HMAC-SHA256("", header.payload).
* Must be rejected before the HMAC ever runs. */
START_TEST(test_alg_confusion_rsa_no_alg_hs256)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub_no_alg.json");
/* The setkey call itself must reject the kty/alg mismatch. */
ret = jwt_checker_setkey(checker, JWT_ALG_HS256, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* Same defense for HS384 and HS512. */
START_TEST(test_alg_confusion_rsa_no_alg_hs384)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub_no_alg.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS384, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
START_TEST(test_alg_confusion_rsa_no_alg_hs512)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub_no_alg.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS512, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* RSA JWK that DOES carry an "alg" hint of RS256 must still be rejected
* for HS256: the prior code allowed it whenever the JWK alg matched the
* caller alg, but here the kty would still be RSA. We assert the broader
* invariant: HS* requires kty=oct period. */
START_TEST(test_alg_confusion_rsa_with_alg_hs256)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS256, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* EC JWK must not be usable for an HMAC algorithm either. */
START_TEST(test_alg_confusion_ec_hs256)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("ec_key_prime256v1_pub.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS256, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* OKP/EdDSA JWK must not be usable for an HMAC algorithm either. */
START_TEST(test_alg_confusion_okp_hs256)
{
jwt_checker_auto_t *checker = NULL;
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("eddsa_key_ed25519_pub.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS256, g_item);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* The realistic application pattern: a JWKS callback that picks a key
* by "kid" and copies the JWT header alg into config->alg. The attacker
* controls the header alg. With the fix, the verify path's __setkey_check
* (run after the callback) rejects the kty/alg mismatch. */
static int alg_confusion_cb(jwt_t *jwt, jwt_config_t *config)
{
/* g_item is the RSA-no-alg JWK loaded by the test. */
config->key = g_item;
config->alg = jwt_get_alg(jwt);
return 0;
}
START_TEST(test_alg_confusion_callback_rsa_no_alg)
{
jwt_checker_auto_t *checker = NULL;
const char token[] =
"eyJhbGciOiJIUzI1NiIsImtpZCI6InJzYS1uby1hbGcifQ"
".eyJzdWIiOiJhZG1pbiJ9"
".I2Ey63EMS9lOFEL93tQM8eB8cCnH6QJy0rIe1HVEI3I";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub_no_alg.json");
ret = jwt_checker_setcb(checker, alg_confusion_cb, NULL);
ck_assert_int_eq(ret, 0);
/* The forged token must NOT verify. */
ret = jwt_checker_verify(checker, token);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match algorithm");
free_key();
}
END_TEST
/* Defense in depth: a malformed JWK whose "alg" hint disagrees with its
* "kty" (here kty=RSA, alg=HS256) must not be usable for HMAC. The caller
* sets alg=NONE so __setkey_check defers to the JWK's alg hint, but the
* verify path then double-checks kty against the bound algorithm. */
START_TEST(test_alg_confusion_malformed_jwk_kty_alg)
{
jwt_checker_auto_t *checker = NULL;
const char token[] =
"eyJhbGciOiJIUzI1NiIsImtpZCI6InJzYS1uby1hbGcifQ"
".eyJzdWIiOiJhZG1pbiJ9"
".I2Ey63EMS9lOFEL93tQM8eB8cCnH6QJy0rIe1HVEI3I";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub_alg_hs256.json");
/* alg=NONE: caller trusts whatever the JWK says. The JWK lies
* (kty=RSA, alg=HS256). __setkey_check accepts because alg=NONE,
* but the verify path's defensive kty switch must reject. */
ret = jwt_checker_setkey(checker, JWT_ALG_NONE, g_item);
ck_assert_int_eq(ret, 0);
ret = jwt_checker_verify(checker, token);
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(jwt_checker_error_msg(checker),
"Key type does not match JWT alg");
free_key();
}
END_TEST
/* A token with alg=RS256 but a signature segment far shorter than the RSA
* modulus must be rejected, not read past the end of the heap-allocated
* signature buffer. The MbedTLS RSA verify functions take no length argument
* and read mbedtls_rsa_get_len() bytes unconditionally; without an explicit
* size check the 3-byte signature here (base64url "AAAA") caused an ~253-byte
* out-of-bounds heap read on the MbedTLS backend. All backends must reject it.
*/
START_TEST(test_rsa_short_signature_oob)
{
jwt_checker_auto_t *checker = NULL;
const char token[] =
"eyJhbGciOiJSUzI1NiJ9"
".eyJzdWIiOiJhZG1pbiJ9"
".AAAA";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("rsa_key_2048_pub.json");
ret = jwt_checker_setkey(checker, JWT_ALG_RS256, g_item);
ck_assert_int_eq(ret, 0);
/* Must be rejected on every backend, and must not over-read. */
ret = jwt_checker_verify(checker, token);
ck_assert_int_ne(ret, 0);
free_key();
}
/* An out-of-range "exp" must not be silently accepted. json-c reports an
* integer larger than INT64_MAX as a normal integer and clamps it to
* INT64_MAX, which would make exp=99999999999999999999999999 verify as an
* effectively never-expiring token; Jansson rejects the whole token at parse.
* Both backends must reject the token (fail closed), and a valid far-future
* exp must still verify. Both tokens below are HS256-signed with the
* oct_key_256.json key. */
START_TEST(test_exp_out_of_range_int)
{
jwt_checker_auto_t *checker = NULL;
/* {"alg":"HS256"} . {"exp":99999999999999999999999999} . <hmac> */
const char tok_big[] =
"eyJhbGciOiJIUzI1NiJ9"
".eyJleHAiOjk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5fQ"
".LSbPILA_I3R_0saPbagko0aSRck-ZX9XZeRIduLgbvU";
/* {"alg":"HS256"} . {"exp":7999999999} . <hmac> (valid, far future) */
const char tok_ok[] =
"eyJhbGciOiJIUzI1NiJ9"
".eyJleHAiOjc5OTk5OTk5OTl9"
".IFlzCWYGNIBrNj3BL5lOsS4baJqdmWc-7LVIfaKf5mw";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("oct_key_256.json");
ret = jwt_checker_setkey(checker, JWT_ALG_HS256, g_item);
ck_assert_int_eq(ret, 0);
/* Out-of-range exp: rejected on both JSON backends. */
ret = jwt_checker_verify(checker, tok_big);
ck_assert_int_ne(ret, 0);
jwt_checker_error_clear(checker);
/* Control: a valid far-future exp still verifies. */
ret = jwt_checker_verify(checker, tok_ok);
ck_assert_int_eq(ret, 0);
free_key();
}
END_TEST
/* Fault-injecting allocator: returns NULL on the Nth allocation. Used to
* drive the out-of-memory error paths in JWKS parsing deterministically. */
static long g_alloc_fail_at = -1;
static long g_alloc_count;
static void *failing_malloc(size_t size)
{
if (++g_alloc_count == g_alloc_fail_at)
return NULL;
return malloc(size);
}
static void failing_free(void *ptr)
{
free(ptr);
}
/* Smoke test for the JWKS allocation-failure paths: parse a multi-key JWKS
* while failing each successive allocation in turn, and assert the library
* never crashes and teardown is clean.
*
* This exercises (under the Jansson backend, whose allocator this hook can
* override) the out-of-memory branches in jwk_process_one(), including the
* clone-failure path whose deallocator bug this change fixes. It is a
* robustness guard rather than a discriminating reproducer: the wrong-free
* corrupted a borrowed, refcounted JSON node, which json's refcounting does
* not surface as a deterministic fault here, and the json-c allocator hook is
* a no-op so that backend's internal clone cannot be failed this way. The
* actual fix (free the owned jwk_item_t, never the borrowed argument) is
* verified by inspection; this test ensures the OOM paths stay crash-free. */
START_TEST(test_jwks_oom_no_corruption)
{
const char *json = "{\"keys\":[{\"kty\":\"oct\",\"alg\":\"HS256\","
"\"k\":\"0gmNspkRljssLSrldySnYUS-zhtCo5sqeqo_yl7n2XA\"},"
"{\"kty\":\"oct\",\"alg\":\"HS384\","
"\"k\":\"YWFhYWJiYmJjY2NjZGRkZGVlZWVmZmZmZ2dnZ2hoaGg\"}]}";
long n;
SET_OPS();
for (n = 1; n <= 20; n++) {
jwk_set_t *jwk_set;
g_alloc_count = 0;
g_alloc_fail_at = n;
ck_assert_int_eq(jwt_set_alloc(failing_malloc, failing_free), 0);
jwk_set = jwks_create(json);
/* Restore the default allocator before any assertion can
* longjmp out, so a later test never runs with the failing one. */
g_alloc_fail_at = -1;
jwt_set_alloc(NULL, NULL);
/* Either the set failed to allocate, or it parsed; in both
* cases item access and teardown must be safe (no UAF / double
* free of the parsed tree). */
if (jwk_set != NULL) {
(void)jwks_item_get(jwk_set, 0);
(void)jwks_item_get(jwk_set, 1);
jwks_free(jwk_set);
}
}
/* A clean full load with the default allocator still works. */
g_alloc_fail_at = -1;
{
jwk_set_auto_t *ok = jwks_create(json);
ck_assert_ptr_nonnull(ok);
ck_assert_int_eq(jwks_error_any(ok), 0);
ck_assert_int_eq(jwks_item_count(ok), 2);
}
}
END_TEST
/* An ES256 token whose signature is a valid ECDSA size for a *different*
* algorithm (96 bytes, the ES384 size) but wrong for the bound ES256/P-256 key
* must be rejected. The MbedTLS backend previously accepted any of the three
* ECDSA sizes and split R||S accordingly before verifying; it now requires the
* exact size for the bound alg, matching OpenSSL. Rejection holds on all
* backends. */
START_TEST(test_es256_wrong_size_sig)
{
jwt_checker_auto_t *checker = NULL;
const char token[] =
"eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ4In0."
"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
read_json("ec_key_prime256v1_pub.json");
ret = jwt_checker_setkey(checker, JWT_ALG_ES256, g_item);
ck_assert_int_eq(ret, 0);
ret = jwt_checker_verify(checker, token);
ck_assert_int_ne(ret, 0);
free_key();
}
END_TEST
/* @rfc{8725,2.4} Duplicate members in the token header or payload must be
* rejected so a peer that picks a different occurrence cannot disagree with us
* about a claim/header. The Jansson backend rejects duplicates; json-c cannot
* (it keeps the last occurrence), a documented limitation, so there the token
* parses. */
START_TEST(test_dup_members_rejected)
{
jwt_checker_auto_t *checker = NULL;
/* {"alg":"none","alg":"none"} . {"iss":"x"} . */
const char dup_hdr[] =
"eyJhbGciOiJub25lIiwiYWxnIjoibm9uZSJ9.eyJpc3MiOiJ4In0.";
/* {"alg":"none"} . {"iss":"a","iss":"b"} . */
const char dup_pay[] =
"eyJhbGciOiJub25lIn0.eyJpc3MiOiJhIiwiaXNzIjoiYiJ9.";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_NONE, NULL), 0);
ret = jwt_checker_verify(checker, dup_hdr);
#ifdef HAVE_JSON_C
/* json-c keeps the last occurrence; the token still parses/verifies. */
ck_assert_int_eq(ret, 0);
#else
ck_assert_int_ne(ret, 0);
#endif
jwt_checker_error_clear(checker);
ret = jwt_checker_verify(checker, dup_pay);
#ifdef HAVE_JSON_C
ck_assert_int_eq(ret, 0);
#else
ck_assert_int_ne(ret, 0);
#endif
}
END_TEST
/* @rfc{7519,4.1.4} A token that omits "exp" passes a default checker even with
* the EXP check enabled (validate-if-present). Pin this documented behavior so
* it cannot change silently. */
START_TEST(test_missing_exp_passes)
{
jwt_checker_auto_t *checker = NULL;
/* {"alg":"none"} . {"iss":"x"} . — no exp/nbf at all. */
const char token[] = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJ4In0.";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_NONE, NULL), 0);
/* The default checker enables EXP/NBF, but a token lacking them is
* accepted (absence is not a failure). */
ret = jwt_checker_verify(checker, token);
ck_assert_int_eq(ret, 0);
}
END_TEST
/* base64url carries no padding, so a literal '=' inside a token segment is
* invalid. The decoder now rejects it outright rather than silently stopping
* at it and decoding only the prefix. (Downstream JSON/signature validation
* also rejects the resulting truncation, so this is a conformance guard that
* the malformed segment never decodes, not a bypass reproducer.) */
START_TEST(test_embedded_pad_rejected)
{
jwt_checker_auto_t *checker = NULL;
/* {"alg":"none"} . <payload with an embedded '='> . */
const char token[] = "eyJhbGciOiJub25lIn0.eyJp=c3MiOiJ4In0.";
int ret;
SET_OPS();
checker = jwt_checker_new();
ck_assert_ptr_nonnull(checker);
ck_assert_int_eq(jwt_checker_setkey(checker, JWT_ALG_NONE, NULL), 0);
ret = jwt_checker_verify(checker, token);
ck_assert_int_ne(ret, 0);
}
END_TEST
/*
* === Suite Setup ===
*/
static Suite *libjwt_suite(const char *title)
{
Suite *s;
TCase *tc_jwks_json;
TCase *tc_jwt_parse;
TCase *tc_null_safety;
TCase *tc_alg_confusion;
int i = ARRAY_SIZE(jwt_test_ops);
s = suite_create(title);
/* JWKS ill-structured JSON tests */
tc_jwks_json = tcase_create("jwks_malformed_json");
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_alg_integer, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_alg_null, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_alg_boolean, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_kty_integer, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_empty_keys_array, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_keys_not_array, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_ec_missing_x, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_ec_missing_crv, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_missing_n, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_missing_e, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_eddsa_missing_x, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_okp_x25519_bad_x_len, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_okp_x25519_bad_d_len, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_partial_private_key, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_wrong_value_types, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_deeply_nested, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_empty_object, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_empty_string, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_top_level_array, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_mixed_valid_invalid, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_oct_invalid_base64, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_extra_fields, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_rsa_pss_alg_string, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_load_strn_zero_len, 0, i);
tcase_add_loop_test(tc_jwks_json, test_jwks_load_strn_truncated, 0, i);
tcase_set_timeout(tc_jwks_json, 30);
suite_add_tcase(s, tc_jwks_json);
/* JWT token parsing tests */
tc_jwt_parse = tcase_create("jwt_malformed_tokens");
tcase_add_loop_test(tc_jwt_parse, test_jwt_no_dots, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_one_dot, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_empty_token, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_null_token, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_empty_header, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_header_not_json, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_header_missing_alg, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_header_invalid_alg, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_header_alg_integer, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_many_dots, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_invalid_base64_payload, 0, i);
tcase_add_loop_test(tc_jwt_parse, test_jwt_alg_none_empty, 0, i);
tcase_set_timeout(tc_jwt_parse, 30);
suite_add_tcase(s, tc_jwt_parse);
/* NULL safety tests */
tc_null_safety = tcase_create("null_safety");
tcase_add_loop_test(tc_null_safety, test_jwks_null_operations, 0, i);
tcase_add_loop_test(tc_null_safety, test_checker_null_safety, 0, i);
tcase_add_loop_test(tc_null_safety, test_builder_null_safety, 0, i);
tcase_set_timeout(tc_null_safety, 30);
suite_add_tcase(s, tc_null_safety);
/* Algorithm confusion regression (GHSA-q843-6q5f-w55g) */
tc_alg_confusion = tcase_create("alg_confusion");
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_rsa_no_alg_hs256, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_rsa_no_alg_hs384, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_rsa_no_alg_hs512, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_rsa_with_alg_hs256, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_ec_hs256, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_okp_hs256, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_callback_rsa_no_alg, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_alg_confusion_malformed_jwk_kty_alg, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_rsa_short_signature_oob, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_exp_out_of_range_int, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_jwks_oom_no_corruption, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_es256_wrong_size_sig, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_dup_members_rejected, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_missing_exp_passes, 0, i);
tcase_add_loop_test(tc_alg_confusion,
test_embedded_pad_rejected, 0, i);
tcase_set_timeout(tc_alg_confusion, 30);
suite_add_tcase(s, tc_alg_confusion);
return s;
}
int main(void)
{
JWT_TEST_MAIN("LibJWT Security Regression Tests");
}