jwt-common: Generate jwt-builder and jwt-checker

The way I was build this was causing some off issues, especially for
windows builds. The dll* attributes don't like playing these games.

In retrospect, autoconf/make would have handled this nicely. I could
have done a %.c:%.i make rule and built off of there, but cmake does not
appear to have anything that easy.

Oh well, builder and checker have way too much code in commong to split
them, so for now this is still the easier option.

Signed-off-by: Ben Collins <bcollins@libjwt.io>
This commit is contained in:
Ben Collins 2025-02-12 15:30:52 -05:00
parent 444ea08d0e
commit 734c0c5840
No known key found for this signature in database
GPG key ID: 5D5A57C7242B22CF
7 changed files with 680 additions and 40 deletions

View file

@ -66,20 +66,9 @@ set(JWT_SOURCES libjwt/base64.c
libjwt/jwt-setget.c
libjwt/jwt-crypto-ops.c
libjwt/jwt-encode.c
libjwt/jwt-verify.c)
add_library(builder OBJECT)
target_sources(builder PRIVATE libjwt/jwt-common.c)
target_compile_definitions(builder PRIVATE JWT_BUILDER)
set_property(TARGET builder PROPERTY POSITION_INDEPENDENT_CODE ON)
add_library(checker OBJECT)
target_sources(checker PRIVATE libjwt/jwt-common.c)
target_compile_definitions(checker PRIVATE JWT_CHECKER)
set_property(TARGET checker PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_libraries(jwt PRIVATE builder checker)
target_link_libraries(jwt_static PRIVATE builder checker)
libjwt/jwt-verify.c
libjwt/jwt-builder.c
libjwt/jwt-checker.c)
# Allow building without deprecated functions (suggested)
option(EXCLUDE_DEPRECATED
@ -95,8 +84,6 @@ include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}
target_link_libraries(jwt PUBLIC PkgConfig::JANSSON)
target_link_libraries(jwt_static PUBLIC PkgConfig::JANSSON)
target_link_libraries(builder PUBLIC PkgConfig::JANSSON)
target_link_libraries(checker PUBLIC PkgConfig::JANSSON)
# Process the detected packages
set(HAVE_CRYPTO FALSE)
@ -294,7 +281,7 @@ if (CHECK_FOUND)
include(CodeCoverage)
append_coverage_compiler_flags()
set(COVERAGE_LCOV_INCLUDES "${CMAKE_SOURCE_DIR}/libjwt/*.c")
set(COVERAGE_LCOV_INCLUDES "${CMAKE_SOURCE_DIR}/libjwt/")
setup_target_for_coverage_lcov(
NAME check-code-coverage
OUTPUT "${PROJECT_NAME}-${PROJECT_VERSION}-coverage"

6
libjwt/Makefile Normal file
View file

@ -0,0 +1,6 @@
FILES = jwt-builder.c jwt-checker.c
all: $(FILES)
jwt-%.c: %.sed jwt-common.c
sed -f $< jwt-common.c > $@

8
libjwt/builder.sed Normal file
View file

@ -0,0 +1,8 @@
s/FUNC(\([^)]*\))/jwt_builder_\1/
s/jwt_common_t/jwt_builder_t/g
s/CLAIMS_DEF/JWT_CLAIM_IAT/g
s/.*XXX.*/\/\* XXX This file is generated, do not edit! \*\//
s/__DISABLE/0/
/#ifdef JWT_CHECKER/,/#endif/d
/#ifdef/d
/#endif/d

8
libjwt/checker.sed Normal file
View file

@ -0,0 +1,8 @@
s/FUNC(\([^)]*\))/jwt_checker_\1/
s/jwt_common_t/jwt_checker_t/g
s/CLAIMS_DEF/(JWT_CLAIM_EXP\|JWT_CLAIM_NBF)/g
s/.*XXX.*/\/\* XXX This file is generated, do not edit! \*\//
s/__DISABLE/-1/
/#ifdef JWT_BUILDER/,/#endif/d
/#ifdef/d
/#endif/d

328
libjwt/jwt-builder.c Normal file
View file

@ -0,0 +1,328 @@
/* Copyright (C) 2015-2025 maClara, LLC <info@maclara-llc.com>
This file is part of the JWT C Library
SPDX-License-Identifier: MPL-2.0
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdlib.h>
#include <string.h>
#include <jwt.h>
#include "jwt-private.h"
/* XXX This file is generated, do not edit! */
void jwt_builder_free(jwt_builder_t *__cmd)
{
if (__cmd == NULL)
return;
json_decref(__cmd->c.payload);
json_decref(__cmd->c.headers);
memset(__cmd, 0, sizeof(*__cmd));
jwt_freemem(__cmd);
}
jwt_builder_t *jwt_builder_new(void)
{
jwt_builder_t *__cmd = jwt_malloc(sizeof(*__cmd));
if (__cmd == NULL)
return NULL; // LCOV_EXCL_LINE
memset(__cmd, 0, sizeof(*__cmd));
__cmd->c.payload = json_object();
__cmd->c.headers = json_object();
__cmd->c.claims = JWT_CLAIM_IAT;
if (!__cmd->c.payload || !__cmd->c.headers)
jwt_freemem(__cmd); // LCOV_EXCL_LINE
return __cmd;
}
static int __setkey_check(jwt_builder_t *__cmd, const jwt_alg_t alg,
const jwk_item_t *key)
{
if (__cmd == NULL)
return 1;
if (key && !key->is_private_key) {
jwt_write_error(__cmd, "Signing requires a private key");
return 1;
}
/* TODO: Check key_ops and use */
if (key == NULL) {
if (alg == JWT_ALG_NONE)
return 0;
jwt_write_error(__cmd, "Cannot set alg without a key");
} else if (key->alg == JWT_ALG_NONE) {
if (alg != JWT_ALG_NONE)
return 0;
jwt_write_error(__cmd, "Key provided, but could not find alg");
} else {
if (alg == JWT_ALG_NONE)
return 0;
if (alg == key->alg)
return 0;
jwt_write_error(__cmd, "Alg mismatch");
}
return 1;
}
int jwt_builder_setkey(jwt_builder_t *__cmd, const jwt_alg_t alg,
const jwk_item_t *key)
{
if (__setkey_check(__cmd, alg, key))
return 1;
__cmd->c.alg = alg;
__cmd->c.key = key;
return 0;
}
int jwt_builder_error(const jwt_builder_t *__cmd)
{
if (__cmd == NULL)
return 1;
return __cmd->error ? 1 : 0;
}
const char *jwt_builder_error_msg(const jwt_builder_t *__cmd)
{
if (__cmd == NULL)
return NULL;
return __cmd->error_msg;
}
void jwt_builder_error_clear(jwt_builder_t *__cmd)
{
if (__cmd == NULL)
return;
__cmd->error = 0;
__cmd->error_msg[0] = '\0';
}
int jwt_builder_enable_iat(jwt_builder_t *__cmd, int enable)
{
int orig;
if (!__cmd)
return -1;
orig = __cmd->c.claims & JWT_CLAIM_IAT ? 1 : 0;
if (enable)
__cmd->c.claims |= JWT_CLAIM_IAT;
else
__cmd->c.claims &= ~JWT_CLAIM_IAT;
return orig;
}
int jwt_builder_setcb(jwt_builder_t *__cmd, jwt_callback_t cb, void *ctx)
{
if (__cmd == NULL)
return 1;
if (cb == NULL && ctx != NULL) {
jwt_write_error(__cmd, "Setting ctx without a cb won't work");
return 1;
}
__cmd->c.cb = cb;
__cmd->c.cb_ctx = ctx;
return 0;
}
void *jwt_builder_getctx(jwt_builder_t *__cmd)
{
if (__cmd == NULL)
return NULL;
return __cmd->c.cb_ctx;
}
typedef enum {
__HEADER,
__CLAIM,
} _setget_type_t;
typedef jwt_value_error_t (*__doer_t)(json_t *, jwt_value_t *);
static jwt_value_error_t __run_it(jwt_builder_t *__cmd, _setget_type_t type,
jwt_value_t *value, __doer_t doer)
{
json_t *which = NULL;
if (!__cmd || !value) {
if (value)
return value->error = JWT_VALUE_ERR_INVALID;
return JWT_VALUE_ERR_INVALID;
}
switch (type) {
case __HEADER:
which = __cmd->c.headers;
break;
case __CLAIM:
which = __cmd->c.payload;
break;
default:
return value->error = JWT_VALUE_ERR_INVALID; // LCOV_EXCL_LINE
}
return doer(which, value);
}
/* Claims */
jwt_value_error_t jwt_builder_claim_get(jwt_builder_t *__cmd, jwt_value_t *value)
{
return __run_it(__cmd, __CLAIM, value, __getter);
}
jwt_value_error_t jwt_builder_claim_set(jwt_builder_t *__cmd, jwt_value_t *value)
{
return __run_it(__cmd, __CLAIM, value, __setter);
}
jwt_value_error_t jwt_builder_claim_del(jwt_builder_t *__cmd, const char *claim)
{
if (!__cmd)
return JWT_VALUE_ERR_INVALID;
return __deleter(__cmd->c.payload, claim);
}
/* Headers */
jwt_value_error_t jwt_builder_header_get(jwt_builder_t *__cmd, jwt_value_t *value)
{
return __run_it(__cmd, __HEADER, value, __getter);
}
jwt_value_error_t jwt_builder_header_set(jwt_builder_t *__cmd, jwt_value_t *value)
{
return __run_it(__cmd, __HEADER, value, __setter);
}
jwt_value_error_t jwt_builder_header_del(jwt_builder_t *__cmd, const char *header)
{
if (!__cmd)
return JWT_VALUE_ERR_INVALID;
return __deleter(__cmd->c.headers, header);
}
/* Time offsets */
int jwt_builder_time_offset(jwt_builder_t *__cmd, jwt_claims_t claim, time_t secs)
{
if (!__cmd)
return 1;
switch (claim) {
case JWT_CLAIM_EXP:
__cmd->c.exp = secs;
break;
case JWT_CLAIM_NBF:
__cmd->c.nbf = secs;
break;
default:
return 1;
}
if (secs <= 0)
__cmd->c.claims &= ~claim;
else
__cmd->c.claims |= claim;
return 0;
}
char *jwt_builder_generate(jwt_builder_t *__cmd)
{
JWT_CONFIG_DECLARE(config);
jwt_auto_t *jwt = NULL;
char *out = NULL;
jwt_value_t jval;
time_t tm = time(NULL);
if (__cmd == NULL)
return NULL;
jwt = jwt_malloc(sizeof(*jwt));
if (jwt == NULL)
return NULL; // LCOV_EXCL_LINE
memset(jwt, 0, sizeof(*jwt));
jwt->headers = json_deep_copy(__cmd->c.headers);
jwt->claims = json_deep_copy(__cmd->c.payload);
/* Our internal work first */
if (__cmd->c.claims & JWT_CLAIM_IAT) {
jwt_set_SET_INT(&jval, "iat", (long)tm);
jval.replace = 1;
jwt_claim_set(jwt, &jval);
}
if (__cmd->c.claims & JWT_CLAIM_NBF) {
jwt_set_SET_INT(&jval, "nbf", (long)(tm + __cmd->c.nbf));
jval.replace = 1;
jwt_claim_set(jwt, &jval);
}
if (__cmd->c.claims & JWT_CLAIM_EXP) {
jwt_set_SET_INT(&jval, "exp", (long)(tm + __cmd->c.exp));
jval.replace = 1;
jwt_claim_set(jwt, &jval);
}
/* Alg and key checks */
config.alg = __cmd->c.alg;
if (config.alg == JWT_ALG_NONE && __cmd->c.key)
config.alg = __cmd->c.key->alg;
config.key = __cmd->c.key;
config.ctx = __cmd->c.cb_ctx;
/* Let the callback do it's thing */
if (__cmd->c.cb && __cmd->c.cb(jwt, &config)) {
jwt_write_error(__cmd, "User callback returned error");
return NULL;
}
/* Callback may have changed this */
if (__setkey_check(__cmd, config.alg, config.key)) {
jwt_write_error(__cmd, "Algorithm and key returned by callback invalid");
return NULL;
}
jwt->alg = config.alg;
jwt->key = config.key;
if (jwt_head_setup(jwt))
return NULL;
out = jwt_encode_str(jwt);
jwt_copy_error(__cmd, jwt);
return out;
}

323
libjwt/jwt-checker.c Normal file
View file

@ -0,0 +1,323 @@
/* Copyright (C) 2015-2025 maClara, LLC <info@maclara-llc.com>
This file is part of the JWT C Library
SPDX-License-Identifier: MPL-2.0
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdlib.h>
#include <string.h>
#include <jwt.h>
#include "jwt-private.h"
/* XXX This file is generated, do not edit! */
void jwt_checker_free(jwt_checker_t *__cmd)
{
if (__cmd == NULL)
return;
json_decref(__cmd->c.payload);
json_decref(__cmd->c.headers);
memset(__cmd, 0, sizeof(*__cmd));
jwt_freemem(__cmd);
}
jwt_checker_t *jwt_checker_new(void)
{
jwt_checker_t *__cmd = jwt_malloc(sizeof(*__cmd));
if (__cmd == NULL)
return NULL; // LCOV_EXCL_LINE
memset(__cmd, 0, sizeof(*__cmd));
__cmd->c.payload = json_object();
__cmd->c.headers = json_object();
__cmd->c.claims = (JWT_CLAIM_EXP|JWT_CLAIM_NBF);
if (!__cmd->c.payload || !__cmd->c.headers)
jwt_freemem(__cmd); // LCOV_EXCL_LINE
return __cmd;
}
static int __setkey_check(jwt_checker_t *__cmd, const jwt_alg_t alg,
const jwk_item_t *key)
{
if (__cmd == NULL)
return 1;
/* TODO: Check key_ops and use */
if (key == NULL) {
if (alg == JWT_ALG_NONE)
return 0;
jwt_write_error(__cmd, "Cannot set alg without a key");
} else if (key->alg == JWT_ALG_NONE) {
if (alg != JWT_ALG_NONE)
return 0;
jwt_write_error(__cmd, "Key provided, but could not find alg");
} else {
if (alg == JWT_ALG_NONE)
return 0;
if (alg == key->alg)
return 0;
jwt_write_error(__cmd, "Alg mismatch");
}
return 1;
}
int jwt_checker_setkey(jwt_checker_t *__cmd, const jwt_alg_t alg,
const jwk_item_t *key)
{
if (__setkey_check(__cmd, alg, key))
return 1;
__cmd->c.alg = alg;
__cmd->c.key = key;
return 0;
}
int jwt_checker_error(const jwt_checker_t *__cmd)
{
if (__cmd == NULL)
return 1;
return __cmd->error ? 1 : 0;
}
const char *jwt_checker_error_msg(const jwt_checker_t *__cmd)
{
if (__cmd == NULL)
return NULL;
return __cmd->error_msg;
}
void jwt_checker_error_clear(jwt_checker_t *__cmd)
{
if (__cmd == NULL)
return;
__cmd->error = 0;
__cmd->error_msg[0] = '\0';
}
int jwt_checker_setcb(jwt_checker_t *__cmd, jwt_callback_t cb, void *ctx)
{
if (__cmd == NULL)
return 1;
if (cb == NULL && ctx != NULL) {
jwt_write_error(__cmd, "Setting ctx without a cb won't work");
return 1;
}
__cmd->c.cb = cb;
__cmd->c.cb_ctx = ctx;
return 0;
}
void *jwt_checker_getctx(jwt_checker_t *__cmd)
{
if (__cmd == NULL)
return NULL;
return __cmd->c.cb_ctx;
}
typedef enum {
__HEADER,
__CLAIM,
} _setget_type_t;
typedef jwt_value_error_t (*__doer_t)(json_t *, jwt_value_t *);
static jwt_value_error_t __run_it(jwt_checker_t *__cmd, _setget_type_t type,
jwt_value_t *value, __doer_t doer)
{
json_t *which = NULL;
if (!__cmd || !value) {
if (value)
return value->error = JWT_VALUE_ERR_INVALID;
return JWT_VALUE_ERR_INVALID;
}
switch (type) {
case __HEADER:
which = __cmd->c.headers;
break;
case __CLAIM:
which = __cmd->c.payload;
break;
default:
return value->error = JWT_VALUE_ERR_INVALID; // LCOV_EXCL_LINE
}
return doer(which, value);
}
/* Just a few types of claims */
static const char *__get_name(jwt_claims_t type)
{
if (type == JWT_CLAIM_ISS)
return "iss";
else if (type == JWT_CLAIM_AUD)
return "aud";
else if (type == JWT_CLAIM_SUB)
return "sub";
return NULL;
}
const char *jwt_checker_claim_get(jwt_checker_t *__cmd, jwt_claims_t type)
{
const char *name = NULL;
jwt_value_t jval;
if (!__cmd)
return NULL;
name = __get_name(type);
if (name == NULL)
return NULL;
jwt_set_GET_STR(&jval, name);
__run_it(__cmd, __CLAIM, &jval, __getter);
/* Ignore errors, just return a string or NULL */
return jval.str_val;
}
int jwt_checker_claim_set(jwt_checker_t *__cmd, jwt_claims_t type, const char *value)
{
const char *name = NULL;
jwt_value_t jval;
if (!__cmd || !value)
return 1;
name = __get_name(type);
if (name == NULL)
return 1;
__cmd->c.claims |= type;
jwt_set_SET_STR(&jval, name, value);
jval.replace = 1;
return __run_it(__cmd, __CLAIM, &jval, __setter) ? 1 : 0;
}
int jwt_checker_claim_del(jwt_checker_t *__cmd, jwt_claims_t type)
{
const char *name = NULL;
if (!__cmd)
return 1;
name = __get_name(type);
if (name == NULL)
return 1;
__cmd->c.claims &= ~type;
return __deleter(__cmd->c.payload, name);
}
/* Time offsets */
int jwt_checker_time_leeway(jwt_checker_t *__cmd, jwt_claims_t claim, time_t secs)
{
if (!__cmd)
return 1;
switch (claim) {
case JWT_CLAIM_EXP:
__cmd->c.exp = secs;
break;
case JWT_CLAIM_NBF:
__cmd->c.nbf = secs;
break;
default:
return 1;
}
if (secs <= -1)
__cmd->c.claims &= ~claim;
else
__cmd->c.claims |= claim;
return 0;
}
int jwt_checker_verify(jwt_checker_t *__cmd, const char *token)
{
JWT_CONFIG_DECLARE(config);
unsigned int payload_len;
jwt_auto_t *jwt = NULL;
if (__cmd == NULL)
return 1;
if (token == NULL || !strlen(token)) {
jwt_write_error(__cmd, "Must pass a token");
return 1;
}
jwt = jwt_new();
if (jwt == NULL) {
// LCOV_EXCL_START
jwt_write_error(__cmd, "Could not allocate JWT object");
return 1;
// LCOV_EXCL_STOP
}
/* First parsing pass, error will be set for us */
if (jwt_parse(jwt, token, &payload_len)) {
jwt_copy_error(__cmd, jwt);
return 1;
};
config.key = __cmd->c.key;
config.alg = __cmd->c.alg;
config.ctx = __cmd->c.cb_ctx;
/* Let the user handle this and update config */
if (__cmd->c.cb && __cmd->c.cb(jwt, &config)) {
jwt_write_error(__cmd, "User callback returned error");
return 1;
}
/* Callback may have changed this */
if (__setkey_check(__cmd, config.alg, config.key))
return 1;
jwt->key = config.key;
jwt->checker = __cmd;
/* Finish it up */
jwt = jwt_verify_complete(jwt, &config, token, payload_len);
/* Copy any errors back */
jwt_copy_error(__cmd, jwt);
return __cmd->error;
}

View file

@ -8,28 +8,12 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jwt.h>
#include "base64.h"
#include "jwt-private.h"
#ifdef JWT_BUILDER
#define jwt_common_t jwt_builder_t
#define FUNC(__x) jwt_builder_##__x
#define CLAIMS_DEF JWT_CLAIM_IAT
#endif
#ifdef JWT_CHECKER
#define jwt_common_t jwt_checker_t
#define FUNC(__x) jwt_checker_##__x
#define CLAIMS_DEF (JWT_CLAIM_EXP | JWT_CLAIM_NBF)
#endif
#ifndef jwt_common_t
#error Must have target defined
#endif
/* XXX This file is used to generate jwt-builder.c and jwt-checker.c */
void FUNC(free)(jwt_common_t *__cmd)
{
@ -321,13 +305,9 @@ int FUNC(claim_del)(jwt_common_t *__cmd, jwt_claims_t type)
/* Time offsets */
#ifdef JWT_BUILDER
#define __DISABLE 0
#else
#define __DISABLE -1
#endif
#ifdef JWT_BUILDER
int FUNC(time_offset)(jwt_common_t *__cmd, jwt_claims_t claim, time_t secs)
#else
#endif
#ifdef JWT_CHECKER
int FUNC(time_leeway)(jwt_common_t *__cmd, jwt_claims_t claim, time_t secs)
#endif
{