From 46b4a515e609e94341caef0351b3b901276e4fa8 Mon Sep 17 00:00:00 2001 From: Ben Collins Date: Mon, 23 Dec 2024 21:10:50 -0500 Subject: [PATCH] source: Split jwt.c into logical files Signed-off-by: Ben Collins --- CMakeLists.txt | 22 +- libjwt/Makefile.am | 10 +- libjwt/{jwks-gnutls.c => gnutls/jwk-parse.c} | 0 libjwt/{jwt-gnutls.c => gnutls/sign-verify.c} | 0 libjwt/jwt-crypto-ops.c | 112 ++ libjwt/jwt-encode.c | 272 ++++ libjwt/jwt-memory.c | 120 ++ libjwt/jwt-private.h | 16 + libjwt/jwt-setget.c | 276 ++++ libjwt/jwt-validate.c | 364 +++++ libjwt/jwt-verify.c | 315 ++++ libjwt/jwt.c | 1407 +---------------- .../{jwks-mbedtls.c => mbedtls/jwk-parse.c} | 0 .../{jwt-mbedtls.c => mbedtls/sign-verify.c} | 0 .../{jwks-openssl.c => openssl/jwk-parse.c} | 0 .../{jwt-openssl.c => openssl/sign-verify.c} | 0 .../sign-verify.c} | 0 17 files changed, 1522 insertions(+), 1392 deletions(-) rename libjwt/{jwks-gnutls.c => gnutls/jwk-parse.c} (100%) rename libjwt/{jwt-gnutls.c => gnutls/sign-verify.c} (100%) create mode 100644 libjwt/jwt-crypto-ops.c create mode 100644 libjwt/jwt-encode.c create mode 100644 libjwt/jwt-memory.c create mode 100644 libjwt/jwt-setget.c create mode 100644 libjwt/jwt-validate.c create mode 100644 libjwt/jwt-verify.c rename libjwt/{jwks-mbedtls.c => mbedtls/jwk-parse.c} (100%) rename libjwt/{jwt-mbedtls.c => mbedtls/sign-verify.c} (100%) rename libjwt/{jwks-openssl.c => openssl/jwk-parse.c} (100%) rename libjwt/{jwt-openssl.c => openssl/sign-verify.c} (100%) rename libjwt/{jwt-wincrypt.c => wincrypt/sign-verify.c} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36c02560..df0b1f5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,16 @@ add_library(jwt SHARED) add_library(jwt_static STATIC) set_target_properties(jwt_static PROPERTIES OUTPUT_NAME jwt) -target_sources(jwt PRIVATE libjwt/jwt.c libjwt/jwks.c libjwt/base64.c) +target_sources(jwt PRIVATE + libjwt/base64.c + libjwt/jwt-memory.c + libjwt/jwt.c + libjwt/jwks.c + libjwt/jwt-setget.c + libjwt/jwt-crypto-ops.c + libjwt/jwt-validate.c + libjwt/jwt-encode.c + libjwt/jwt-verify.c) set(COMPILER_CONSTRUCTOR "__attribute__((constructor))") _check_c_compiler_attribute(${COMPILER_CONSTRUCTOR} COMPILER_HAS_CONSTRUCTOR) @@ -69,7 +78,8 @@ endif() generate_export_header(jwt CUSTOM_CONTENT_FROM_VARIABLE JWT_CUSTOM_CONTENT) -include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/libjwt) target_link_libraries(jwt PUBLIC ${JANSSON_LINK_LIBRARIES}) target_include_directories(jwt PUBLIC @@ -83,7 +93,9 @@ if (GNUTLS_FOUND) target_link_libraries(jwt PUBLIC ${GNUTLS_LINK_LIBRARIES}) target_include_directories(jwt PUBLIC ${GNUTLS_INCLUDE_DIRS}) - target_sources(jwt PRIVATE libjwt/jwt-gnutls.c libjwt/jwks-gnutls.c) + target_sources(jwt PRIVATE + libjwt/gnutls/jwk-parse.c + libjwt/gnutls/sign-verify.c) endif() if (OPENSSL_FOUND) @@ -92,7 +104,9 @@ if (OPENSSL_FOUND) target_link_libraries(jwt PUBLIC ${OPENSSL_LINK_LIBRARIES}) target_include_directories(jwt PUBLIC ${OPENSSL_INCLUDE_DIRS}) - target_sources(jwt PRIVATE libjwt/jwt-openssl.c libjwt/jwks-openssl.c) + target_sources(jwt PRIVATE + libjwt/openssl/jwk-parse.c + libjwt/openssl/sign-verify.c) endif() # We need one of the things above to even work diff --git a/libjwt/Makefile.am b/libjwt/Makefile.am index 98206d9e..774861bb 100644 --- a/libjwt/Makefile.am +++ b/libjwt/Makefile.am @@ -10,7 +10,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include lib_LTLIBRARIES = libjwt.la -libjwt_la_SOURCES = jwt.c jwks.c base64.c jwt-private.h base64.h ll.h +libjwt_la_SOURCES = jwt.c jwt-verify.c jwt-encode.c jwks.c \ + jwt-crypto-ops.c jwt-memory.c jwt-setget.c ../libjwt/jwt-validate.c \ + base64.c jwt-private.h base64.h ll.h libjwt_la_LDFLAGS = -version-info $(LIBJWT_VERSION_INFO) $(OPENSSL_LDFLAGS) \ $(GNUTLS_LDFLAGS) $(MBEDTLS_LIBS) $(JANSSON_LDFLAGS) -no-undefined @@ -25,15 +27,15 @@ libjwt_la_LIBADD = $(JANSSON_LIBS) $(OPENSSL_LIBS) $(GNUTLS_LIBS) \ $(CODE_COVERAGE_LDFLAGS) if HAVE_OPENSSL -libjwt_la_SOURCES += jwt-openssl.c jwks-openssl.c +libjwt_la_SOURCES += openssl/jwk-parse.c openssl/sign-verify.c endif if HAVE_GNUTLS -libjwt_la_SOURCES += jwt-gnutls.c jwks-gnutls.c +libjwt_la_SOURCES += gnutls/jwk-parse.c gnutls/sign-verify.c endif if HAVE_MBEDTLS -libjwt_la_SOURCES += jwt-mbedtls.c jwks-mbedtls.c +libjwt_la_SOURCES += mbedtls/jwk-parse.c mbedtls/sign-verify.c endif pkgconfiglibdir = $(libdir)/pkgconfig diff --git a/libjwt/jwks-gnutls.c b/libjwt/gnutls/jwk-parse.c similarity index 100% rename from libjwt/jwks-gnutls.c rename to libjwt/gnutls/jwk-parse.c diff --git a/libjwt/jwt-gnutls.c b/libjwt/gnutls/sign-verify.c similarity index 100% rename from libjwt/jwt-gnutls.c rename to libjwt/gnutls/sign-verify.c diff --git a/libjwt/jwt-crypto-ops.c b/libjwt/jwt-crypto-ops.c new file mode 100644 index 00000000..ea185d1b --- /dev/null +++ b/libjwt/jwt-crypto-ops.c @@ -0,0 +1,112 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include +#include + +#include + +#include "jwt-private.h" + +/* Library init functionality */ +static struct jwt_crypto_ops *jwt_ops_available[] = { +#ifdef HAVE_OPENSSL + &jwt_openssl_ops, +#endif +#ifdef HAVE_GNUTLS + &jwt_gnutls_ops, +#endif +#ifdef HAVE_MBEDTLS + &jwt_mbedtls_ops, +#endif + NULL, +}; + +#if defined HAVE_OPENSSL +struct jwt_crypto_ops *jwt_ops = &jwt_openssl_ops; +#elif defined HAVE_GNUTLS +struct jwt_crypto_ops *jwt_ops = &jwt_gnutls_ops; +#elif defined HAVE_MBEDTLS +struct jwt_crypto_ops *jwt_ops = &jwt_mbedtls_ops; +#else +#error No crypto ops providers are enabled +#endif + +const char *jwt_get_crypto_ops(void) +{ + if (jwt_ops == NULL) + return "(unknown)"; // LCOV_EXCL_LINE + + return jwt_ops->name; +} + +jwt_crypto_provider_t jwt_get_crypto_ops_t(void) +{ + if (jwt_ops == NULL) + return JWT_CRYPTO_OPS_NONE; // LCOV_EXCL_LINE + + return jwt_ops->provider; +} + +int jwt_set_crypto_ops_t(jwt_crypto_provider_t opname) +{ + int i; + + /* The user asked for something, let's give it a try */ + for (i = 0; jwt_ops_available[i] != NULL; i++) { + if (jwt_ops_available[i]->provider != opname) + continue; + + jwt_ops = jwt_ops_available[i]; + return 0; + } + + return EINVAL; +} + +int jwt_set_crypto_ops(const char *opname) +{ + int i; + + /* The user asked for something, let's give it a try */ + for (i = 0; jwt_ops_available[i] != NULL; i++) { + if (jwt_strcmp(jwt_ops_available[i]->name, opname)) + continue; + + jwt_ops = jwt_ops_available[i]; + return 0; + } + + return EINVAL; +} + +int jwt_crypto_ops_supports_jwk(void) +{ + return jwt_ops->jwk_implemented ? 1 : 0; +} + +JWT_CONSTRUCTOR +void jwt_init() +{ + const char *opname = getenv("JWT_CRYPTO"); + + /* By default, we choose the top spot */ + if (opname == NULL || opname[0] == '\0') { + jwt_ops = jwt_ops_available[0]; + return; + } + + /* Attempt to set ops */ + if (jwt_set_crypto_ops(opname)) { + jwt_ops = jwt_ops_available[0]; + fprintf(stderr, "LibJWT: No such crypto ops [%s], falling back to [%s]\n", + opname, jwt_ops->name); + } +} diff --git a/libjwt/jwt-encode.c b/libjwt/jwt-encode.c new file mode 100644 index 00000000..f39f6113 --- /dev/null +++ b/libjwt/jwt-encode.c @@ -0,0 +1,272 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include +#include + +#include + +/* https://github.com/zhicheng/base64 */ +#include "base64.h" + +#include "jwt-private.h" + +#define APPEND_STR(__buf, __str) do { \ + if (__append_str(__buf, __str)) \ + return ENOMEM; \ +} while (0) + +static int write_js(const json_t *js, char **buf, int pretty) +{ + /* Sort keys for repeatability */ + size_t flags = JSON_SORT_KEYS; + char *serial; + + if (pretty) { + APPEND_STR(buf, "\n"); + flags |= JSON_INDENT(4); + } else { + flags |= JSON_COMPACT; + } + + serial = json_dumps(js, flags); + + APPEND_STR(buf, serial); + + jwt_freemem(serial); + + if (pretty) + APPEND_STR(buf, "\n"); + + return 0; +} + +static int jwt_write_head(jwt_t *jwt, char **buf, int pretty) +{ + int ret = 0; + + if (jwt->alg != JWT_ALG_NONE) { + /* Only add default 'typ' header if it has not been defined, + * allowing for any value of it. This allows for signaling + * of application specific extensions to JWT, such as PASSporT, + * RFC 8225. */ + if ((ret = jwt_add_header(jwt, "typ", "JWT"))) { + if (ret != EEXIST) + return ret; + } + } + + if ((ret = jwt_del_headers(jwt, "alg"))) + return ret; + + if ((ret = jwt_add_header(jwt, "alg", jwt_alg_str(jwt->alg)))) + return ret; + + return write_js(jwt->headers, buf, pretty); +} + +static int jwt_write_body(jwt_t *jwt, char **buf, int pretty) +{ + return write_js(jwt->grants, buf, pretty); +} + +static int jwt_dump(jwt_t *jwt, char **buf, int pretty) +{ + int ret; + + ret = jwt_write_head(jwt, buf, pretty); + + if (ret == 0) + ret = __append_str(buf, "."); + + if (ret == 0) + ret = jwt_write_body(jwt, buf, pretty); + + return ret; +} + +char *jwt_dump_grants_str(jwt_t *jwt, int pretty) +{ + char *out = NULL; + int err; + + errno = 0; + + err = jwt_write_body(jwt, &out, pretty); + + if (err) { + errno = err; + if (out) + jwt_freemem(out); + out = NULL; + } + + return out; +} + +int jwt_dump_fp(jwt_t *jwt, FILE *fp, int pretty) +{ + char *out = NULL; + int ret = 0; + + ret = jwt_dump(jwt, &out, pretty); + + if (ret == 0) + fputs(out, fp); + + if (out) + jwt_freemem(out); + + return ret; +} + +char *jwt_dump_str(jwt_t *jwt, int pretty) +{ + char *out = NULL; + + errno = jwt_dump(jwt, &out, pretty); + + if (errno) + jwt_freemem(out); + + return out; +} + +static int jwt_encode(jwt_t *jwt, char **out) +{ + char *buf = NULL, *head = NULL, *body = NULL, *sig = NULL; + int ret, head_len, body_len; + unsigned int sig_len; + + if (out == NULL) + return EINVAL; + *out = NULL; + + /* First the header. */ + ret = jwt_write_head(jwt, &buf, 0); + if (ret) + return ret; + /* Encode it */ + head_len = jwt_base64uri_encode(&head, buf, (int)strlen(buf)); + jwt_freemem(buf); + + if (head_len <= 0) + return -head_len; + + /* Now the body. */ + ret = jwt_write_body(jwt, &buf, 0); + if (ret) { + jwt_freemem(head); + return ret; + } + + body_len = jwt_base64uri_encode(&body, buf, (int)strlen(buf)); + jwt_freemem(buf); + + if (body_len <= 0) { + jwt_freemem(head); + return -body_len; + } + + /* The part we need to sign, but add space for 3 dots and a nil */ + buf = jwt_malloc(head_len + body_len + 4); + if (buf == NULL) { + jwt_freemem(head); + jwt_freemem(body); + return ENOMEM; + } + + strcpy(buf, head); + strcat(buf, "."); + strcat(buf, body); + + if (jwt->alg == JWT_ALG_NONE) { + jwt_freemem(head); + jwt_freemem(body); + + /* Add the trailing dot, and send it back */ + strcat(buf, "."); + *out = buf; + return 0; + } + + /* At this point buf has "head.body" */ + + /* Now the signature. */ + ret = jwt_sign(jwt, &sig, &sig_len, buf, strlen(buf)); + jwt_freemem(buf); + if (ret) { + jwt_freemem(head); + jwt_freemem(body); + return ret; + } + + ret = jwt_base64uri_encode(&buf, sig, sig_len); + jwt_freemem(sig); + /* At this point buf has b64 of sig and ret is size of it */ + + if (ret < 0) { + jwt_freemem(head); + jwt_freemem(body); + jwt_freemem(buf); + return ENOMEM; + } + + /* plus 2 dots and a nil */ + ret = head_len + body_len + ret + 3; + + /* We're good, so let's get it all together */ + *out = jwt_malloc(ret); + if (*out == NULL) { + ret = ENOMEM; + } else { + strcpy(*out, head); + strcat(*out, "."); + strcat(*out, body); + strcat(*out, "."); + strcat(*out, buf); + ret = 0; + } + + jwt_freemem(head); + jwt_freemem(body); + jwt_freemem(buf); + + return ret; +} + +int jwt_encode_fp(jwt_t *jwt, FILE *fp) +{ + char *str = NULL; + int ret; + + ret = jwt_encode(jwt, &str); + if (ret) { + if (str) + jwt_freemem(str); + return ret; + } + + fputs(str, fp); + jwt_freemem(str); + + return 0; +} + +char *jwt_encode_str(jwt_t *jwt) +{ + char *str = NULL; + + errno = jwt_encode(jwt, &str); + if (errno) + jwt_freemem(str); + + return str; +} diff --git a/libjwt/jwt-memory.c b/libjwt/jwt-memory.c new file mode 100644 index 00000000..657477c1 --- /dev/null +++ b/libjwt/jwt-memory.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include +#include + +#include + +#include "jwt-private.h" + +static jwt_malloc_t pfn_malloc; +static jwt_realloc_t pfn_realloc; +static jwt_free_t pfn_free; + +void *jwt_malloc(size_t size) +{ + if (pfn_malloc) + return pfn_malloc(size); + + return malloc(size); +} + +void *jwt_realloc(void *ptr, size_t size) +{ + if (pfn_realloc) + return pfn_realloc(ptr, size); + + return realloc(ptr, size); +} + +void jwt_free_str(char *str) +{ + jwt_freemem(str); +} + +int jwt_set_alloc(jwt_malloc_t pmalloc, jwt_realloc_t prealloc, jwt_free_t pfree) +{ + /* Set allocator functions for LibJWT. */ + pfn_malloc = pmalloc; + pfn_realloc = prealloc; + pfn_free = pfree; + + /* Set same allocator functions for Jansson. */ + json_set_alloc_funcs(jwt_malloc, __jwt_freemem); + + return 0; +} + +void jwt_get_alloc(jwt_malloc_t *pmalloc, jwt_realloc_t *prealloc, + jwt_free_t *pfree) +{ + if (pmalloc) + *pmalloc = pfn_malloc; + + if (prealloc) + *prealloc = pfn_realloc; + + if (pfree) + *pfree = pfn_free; +} + +/* Should call the macros instead */ +void __jwt_freemem(void *ptr) +{ + if (pfn_free) + pfn_free(ptr); + else + free(ptr); +} + +char *jwt_strdup(const char *str) +{ + size_t len; + char *result; + + len = strlen(str); + result = (char *)jwt_malloc(len + 1); + if (!result) + return NULL; // LCOV_EXCL_LINE + + memcpy(result, str, len); + result[len] = '\0'; + return result; +} + +/* A time-safe strcmp function */ +int jwt_strcmp(const char *str1, const char *str2) +{ + /* Get the LONGEST length */ + int len1 = strlen(str1); + int len2 = strlen(str2); + int len_max = len1 >= len2 ? len1 : len2; + + int i, ret = 0; + + /* Iterate the entire longest string no matter what. Only testing + * the shortest string would still allow attacks for + * "a" == "aKJSDHkjashaaHJASJ", adding a character each time one + * is found. */ + for (i = 0; i < len_max; i++) { + char c1, c2; + + c1 = (i < len1) ? str1[i] : 0; + c2 = (i < len2) ? str2[i] : 0; + + ret |= c1 ^ c2; + } + + /* Don't forget to check length */ + ret |= len1 ^ len2; + + return ret; +} diff --git a/libjwt/jwt-private.h b/libjwt/jwt-private.h index bac0cd8f..68465b10 100644 --- a/libjwt/jwt-private.h +++ b/libjwt/jwt-private.h @@ -118,7 +118,23 @@ void *jwt_realloc(void *ptr, size_t size); int jwt_base64uri_encode(char **_dst, const char *plain, int plain_len); void *jwt_base64uri_decode(const char *src, int *ret_len); +/* JSON stuff */ +const char *get_js_string(json_t *js, const char *key); +long get_js_int(json_t *js, const char *key); +int get_js_bool(json_t *js, const char *key); + /* A time-safe strcmp function */ int jwt_strcmp(const char *str1, const char *str2); +char *jwt_strdup(const char *str); + +void jwt_scrub_key(jwt_t *jwt); + +int jwt_verify_sig(jwt_t *jwt, const char *head, unsigned int head_len, + const char *sig); +int jwt_sign(jwt_t *jwt, char **out, unsigned int *len, const char *str, + unsigned int str_len); + +int __append_str(char **buf, const char *str); + #endif /* JWT_PRIVATE_H */ diff --git a/libjwt/jwt-setget.c b/libjwt/jwt-setget.c new file mode 100644 index 00000000..48f1da12 --- /dev/null +++ b/libjwt/jwt-setget.c @@ -0,0 +1,276 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include + +#include + +#include "jwt-private.h" + +const char *jwt_get_grant(jwt_t *jwt, const char *grant) +{ + if (!jwt || !grant || !strlen(grant)) { + errno = EINVAL; + return NULL; + } + + errno = 0; + + return get_js_string(jwt->grants, grant); +} + +long jwt_get_grant_int(jwt_t *jwt, const char *grant) +{ + if (!jwt || !grant || !strlen(grant)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_int(jwt->grants, grant); +} + +int jwt_get_grant_bool(jwt_t *jwt, const char *grant) +{ + if (!jwt || !grant || !strlen(grant)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_bool(jwt->grants, grant); +} + +char *jwt_get_grants_json(jwt_t *jwt, const char *grant) +{ + json_t *js_val = NULL; + + if (!jwt) { + errno = EINVAL; + return NULL; + } + + if (grant && strlen(grant)) + js_val = json_object_get(jwt->grants, grant); + else + js_val = jwt->grants; + + if (js_val == NULL) { + errno = ENOENT; + return NULL; + } + + errno = 0; + + return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | + JSON_ENCODE_ANY); +} + +int jwt_add_grant(jwt_t *jwt, const char *grant, const char *val) +{ + if (!jwt || !grant || !strlen(grant) || !val) + return EINVAL; + + if (get_js_string(jwt->grants, grant) != NULL) + return EEXIST; + + if (json_object_set_new(jwt->grants, grant, json_string(val))) + return EINVAL; + + return 0; +} + +int jwt_add_grant_int(jwt_t *jwt, const char *grant, long val) +{ + if (!jwt || !grant || !strlen(grant)) + return EINVAL; + + if (get_js_int(jwt->grants, grant) != -1) + return EEXIST; + + if (json_object_set_new(jwt->grants, grant, json_integer((json_int_t)val))) + return EINVAL; + + return 0; +} + +int jwt_add_grant_bool(jwt_t *jwt, const char *grant, int val) +{ + if (!jwt || !grant || !strlen(grant)) + return EINVAL; + + if (get_js_int(jwt->grants, grant) != -1) + return EEXIST; + + if (json_object_set_new(jwt->grants, grant, json_boolean(val))) + return EINVAL; + + return 0; +} + +int jwt_add_grants_json(jwt_t *jwt, const char *json) +{ + json_auto_t *js_val; + int ret = -1; + + if (!jwt) + return EINVAL; + + js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); + + if (json_is_object(js_val)) + ret = json_object_update(jwt->grants, js_val); + + return ret ? EINVAL : 0; +} + +int jwt_del_grants(jwt_t *jwt, const char *grant) +{ + if (!jwt) + return EINVAL; + + if (grant == NULL || !strlen(grant)) + json_object_clear(jwt->grants); + else + json_object_del(jwt->grants, grant); + + return 0; +} + +const char *jwt_get_header(jwt_t *jwt, const char *header) +{ + if (!jwt || !header || !strlen(header)) { + errno = EINVAL; + return NULL; + } + + errno = 0; + + return get_js_string(jwt->headers, header); +} + +long jwt_get_header_int(jwt_t *jwt, const char *header) +{ + if (!jwt || !header || !strlen(header)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_int(jwt->headers, header); +} + +int jwt_get_header_bool(jwt_t *jwt, const char *header) +{ + if (!jwt || !header || !strlen(header)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_bool(jwt->headers, header); +} + +char *jwt_get_headers_json(jwt_t *jwt, const char *header) +{ + json_t *js_val = NULL; + + errno = EINVAL; + + if (!jwt) + return NULL; + + if (header && strlen(header)) + js_val = json_object_get(jwt->headers, header); + else + js_val = jwt->headers; + + if (js_val == NULL) + return NULL; + + errno = 0; + + return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | JSON_ENCODE_ANY); +} + +int jwt_add_header(jwt_t *jwt, const char *header, const char *val) +{ + if (!jwt || !header || !strlen(header) || !val) + return EINVAL; + + if (get_js_string(jwt->headers, header) != NULL) + return EEXIST; + + if (json_object_set_new(jwt->headers, header, json_string(val))) + return EINVAL; + + return 0; +} + +int jwt_add_header_int(jwt_t *jwt, const char *header, long val) +{ + if (!jwt || !header || !strlen(header)) + return EINVAL; + + if (get_js_int(jwt->headers, header) != -1) + return EEXIST; + + if (json_object_set_new(jwt->headers, header, json_integer((json_int_t)val))) + return EINVAL; + + return 0; +} + +int jwt_add_header_bool(jwt_t *jwt, const char *header, int val) +{ + if (!jwt || !header || !strlen(header)) + return EINVAL; + + if (get_js_int(jwt->headers, header) != -1) + return EEXIST; + + if (json_object_set_new(jwt->headers, header, json_boolean(val))) + return EINVAL; + + return 0; +} + +int jwt_add_headers_json(jwt_t *jwt, const char *json) +{ + json_auto_t *js_val; + int ret = -1; + + if (!jwt) + return EINVAL; + + js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); + + if (json_is_object(js_val)) + ret = json_object_update(jwt->headers, js_val); + + return ret ? EINVAL : 0; +} + +int jwt_del_headers(jwt_t *jwt, const char *header) +{ + if (!jwt) + return EINVAL; + + if (header == NULL || !strlen(header)) + json_object_clear(jwt->headers); + else + json_object_del(jwt->headers, header); + + return 0; +} diff --git a/libjwt/jwt-validate.c b/libjwt/jwt-validate.c new file mode 100644 index 00000000..5edeba24 --- /dev/null +++ b/libjwt/jwt-validate.c @@ -0,0 +1,364 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include + +#include + +#include "jwt-private.h" + +int jwt_valid_new(jwt_valid_t **jwt_valid, jwt_alg_t alg) +{ + if (!jwt_valid) + return EINVAL; + + *jwt_valid = jwt_malloc(sizeof(jwt_valid_t)); + if (!*jwt_valid) + return ENOMEM; // LCOV_EXCL_LINE + + memset(*jwt_valid, 0, sizeof(jwt_valid_t)); + (*jwt_valid)->alg = alg; + + (*jwt_valid)->status = JWT_VALIDATION_ERROR; + + (*jwt_valid)->nbf_leeway = 0; + (*jwt_valid)->exp_leeway = 0; + + (*jwt_valid)->req_grants = json_object(); + if (!(*jwt_valid)->req_grants) { + jwt_freemem(*jwt_valid); + return ENOMEM; + } + + return 0; +} + +void jwt_valid_free(jwt_valid_t *jwt_valid) +{ + if (!jwt_valid) + return; + + json_decref(jwt_valid->req_grants); + + jwt_freemem(jwt_valid); +} + +jwt_valid_exception_t jwt_valid_get_status(jwt_valid_t *jwt_valid) +{ + if (!jwt_valid) + return JWT_VALIDATION_ERROR; + + return jwt_valid->status; +} + +time_t jwt_valid_get_nbf_leeway(jwt_valid_t *jwt_valid) +{ + if (!jwt_valid) + return EINVAL; + + return jwt_valid->nbf_leeway; +} + +time_t jwt_valid_get_exp_leeway(jwt_valid_t *jwt_valid) +{ + if (!jwt_valid) + return EINVAL; + + return jwt_valid->exp_leeway; +} + +int jwt_valid_add_grant(jwt_valid_t *jwt_valid, const char *grant, const char *val) +{ + if (!jwt_valid || !grant || !strlen(grant) || !val) + return EINVAL; + + if (get_js_string(jwt_valid->req_grants, grant) != NULL) + return EEXIST; + + if (json_object_set_new(jwt_valid->req_grants, grant, json_string(val))) + return EINVAL; + + return 0; +} + +int jwt_valid_add_grant_int(jwt_valid_t *jwt_valid, const char *grant, long val) +{ + if (!jwt_valid || !grant || !strlen(grant)) + return EINVAL; + + if (get_js_int(jwt_valid->req_grants, grant) != -1) + return EEXIST; + + if (json_object_set_new(jwt_valid->req_grants, grant, json_integer((json_int_t)val))) + return EINVAL; + + return 0; +} + +int jwt_valid_add_grant_bool(jwt_valid_t *jwt_valid, const char *grant, int val) +{ + if (!jwt_valid || !grant || !strlen(grant)) + return EINVAL; + + if (get_js_bool(jwt_valid->req_grants, grant) != -1) + return EEXIST; + + if (json_object_set_new(jwt_valid->req_grants, grant, json_boolean(val))) + return EINVAL; + + return 0; +} + +int jwt_valid_add_grants_json(jwt_valid_t *jwt_valid, const char *json) +{ + json_auto_t *js_val; + int ret = -1; + + if (!jwt_valid) + return EINVAL; + + js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); + + if (json_is_object(js_val)) + ret = json_object_update(jwt_valid->req_grants, js_val); + + return ret ? EINVAL : 0; +} + +char *jwt_valid_get_grants_json(jwt_valid_t *jwt_valid, const char *grant) +{ + json_t *js_val = NULL; + + errno = EINVAL; + + if (!jwt_valid) + return NULL; + + if (grant && strlen(grant)) + js_val = json_object_get(jwt_valid->req_grants, grant); + else + js_val = jwt_valid->req_grants; + + if (js_val == NULL) + return NULL; + + errno = 0; + + return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | JSON_ENCODE_ANY); +} + +const char *jwt_valid_get_grant(jwt_valid_t *jwt_valid, const char *grant) +{ + if (!jwt_valid || !grant || !strlen(grant)) { + errno = EINVAL; + return NULL; + } + + errno = 0; + + return get_js_string(jwt_valid->req_grants, grant); +} + +long jwt_valid_get_grant_int(jwt_valid_t *jwt_valid, const char *grant) +{ + if (!jwt_valid || !grant || !strlen(grant)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_int(jwt_valid->req_grants, grant); +} + +int jwt_valid_get_grant_bool(jwt_valid_t *jwt_valid, const char *grant) +{ + if (!jwt_valid || !grant || !strlen(grant)) { + errno = EINVAL; + return 0; + } + + errno = 0; + + return get_js_bool(jwt_valid->req_grants, grant); +} + +int jwt_valid_set_now(jwt_valid_t *jwt_valid, const time_t now) +{ + if (!jwt_valid) + return EINVAL; + + jwt_valid->now = now; + + return 0; +} + +int jwt_valid_set_nbf_leeway(jwt_valid_t *jwt_valid, const time_t nbf_leeway) +{ + if (!jwt_valid) + return EINVAL; + + jwt_valid->nbf_leeway = nbf_leeway; + + return 0; +} + +int jwt_valid_set_exp_leeway(jwt_valid_t *jwt_valid, const time_t exp_leeway) +{ + if (!jwt_valid) + return EINVAL; + + jwt_valid->exp_leeway = exp_leeway; + + return 0; +} + +int jwt_valid_set_headers(jwt_valid_t *jwt_valid, int hdr) +{ + if (!jwt_valid) + return EINVAL; + + jwt_valid->hdr = hdr; + + return 0; +} + +int jwt_valid_del_grants(jwt_valid_t *jwt_valid, const char *grant) +{ + if (!jwt_valid) + return EINVAL; + + if (grant == NULL || !strlen(grant)) + json_object_clear(jwt_valid->req_grants); + else + json_object_del(jwt_valid->req_grants, grant); + + return 0; +} + +#define _SET_AND_RET(__v, __e) do { \ + __v->status |= __e; \ + return __v->status; \ +} while (0) + +jwt_valid_exception_t jwt_validate(jwt_t *jwt, jwt_valid_t *jwt_valid) +{ + const char *jwt_hdr_str, *jwt_body_str, *req_grant; + json_t *js_val_1, *js_val_2; + time_t t; + + if (!jwt_valid) + return JWT_VALIDATION_ERROR; + + if (!jwt) { + jwt_valid->status = JWT_VALIDATION_ERROR; + return jwt_valid->status; + } + + jwt_valid->status = JWT_VALIDATION_SUCCESS; + + /* Validate algorithm */ + if (jwt_valid->alg != jwt_get_alg(jwt)) + jwt_valid->status |= JWT_VALIDATION_ALG_MISMATCH; + + /* Validate expires */ + t = get_js_int(jwt->grants, "exp"); + if (jwt_valid->now && t != -1 && jwt_valid->now - jwt_valid->exp_leeway >= t) + jwt_valid->status |= JWT_VALIDATION_EXPIRED; + + /* Validate not-before */ + t = get_js_int(jwt->grants, "nbf"); + if (jwt_valid->now && t != -1 && jwt_valid->now + jwt_valid->nbf_leeway < t) + jwt_valid->status |= JWT_VALIDATION_TOO_NEW; + + /* Validate replicated issuer */ + jwt_hdr_str = get_js_string(jwt->headers, "iss"); + jwt_body_str = get_js_string(jwt->grants, "iss"); + if (jwt_hdr_str && jwt_body_str && jwt_strcmp(jwt_hdr_str, jwt_body_str)) + jwt_valid->status |= JWT_VALIDATION_ISS_MISMATCH; + + /* Validate replicated subject */ + jwt_hdr_str = get_js_string(jwt->headers, "sub"); + jwt_body_str = get_js_string(jwt->grants, "sub"); + if (jwt_hdr_str && jwt_body_str && jwt_strcmp(jwt_hdr_str, jwt_body_str)) + jwt_valid->status |= JWT_VALIDATION_SUB_MISMATCH; + + /* Validate replicated audience (might be array or string) */ + js_val_1 = json_object_get(jwt->headers, "aud"); + js_val_2 = json_object_get(jwt->grants, "aud"); + if (js_val_1 && js_val_2 && !json_equal(js_val_1, js_val_2)) + jwt_valid->status |= JWT_VALIDATION_AUD_MISMATCH; + + /* Validate required grants */ + json_object_foreach(jwt_valid->req_grants, req_grant, js_val_1) { + json_t *act_js_val = json_object_get(jwt->grants, req_grant); + + if (act_js_val && json_equal(js_val_1, act_js_val)) + continue; + + if (act_js_val) + jwt_valid->status |= JWT_VALIDATION_GRANT_MISMATCH; + else + jwt_valid->status |= JWT_VALIDATION_GRANT_MISSING; + } + + return jwt_valid->status; +} + +typedef struct { + int error; + char *str; +} jwt_exception_dict_t; + +static jwt_exception_dict_t jwt_exceptions[] = { + /* { JWT_VALIDATION_SUCCESS, "SUCCESS" }, */ + { JWT_VALIDATION_ERROR, "general failures" }, + { JWT_VALIDATION_ALG_MISMATCH, "algorithm mismatch" }, + { JWT_VALIDATION_EXPIRED, "token expired" }, + { JWT_VALIDATION_TOO_NEW, "token future dated" }, + { JWT_VALIDATION_ISS_MISMATCH, "issuer mismatch" }, + { JWT_VALIDATION_SUB_MISMATCH, "subject mismatch" }, + { JWT_VALIDATION_AUD_MISMATCH, "audience mismatch" }, + { JWT_VALIDATION_GRANT_MISSING, "grant missing" }, + { JWT_VALIDATION_GRANT_MISMATCH, "grant mismatch" }, +}; + +char *jwt_exception_str(jwt_valid_exception_t exceptions) +{ + int rc, i; + char *str = NULL; + + if (exceptions == JWT_VALIDATION_SUCCESS) { + if ((rc = __append_str(&str, "success"))) + goto fail; + return str; + } + + for (i = 0; i < ARRAY_SIZE(jwt_exceptions); i++) { + if (!(jwt_exceptions[i].error & exceptions)) + continue; + + if (str && (rc = __append_str(&str, ", "))) + goto fail; + + if ((rc = __append_str(&str, jwt_exceptions[i].str))) + goto fail; + } + + /* check if none of the exceptions matched? */ + if (!str && (rc = __append_str(&str, "unknown exceptions"))) + goto fail; + + return str; +fail: + errno = rc; + jwt_freemem(str); + return NULL; +} diff --git a/libjwt/jwt-verify.c b/libjwt/jwt-verify.c new file mode 100644 index 00000000..181fe113 --- /dev/null +++ b/libjwt/jwt-verify.c @@ -0,0 +1,315 @@ +/* Copyright (C) 2015-2024 maClara, LLC + 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 +#include +#include +#include + +#include + +#include "jwt-private.h" + +static json_t *jwt_base64uri_decode_to_json(char *src) +{ + json_t *js; + char *buf; + int len; + + buf = jwt_base64uri_decode(src, &len); + + if (buf == NULL) + return NULL; // LCOV_EXCL_LINE + + buf[len] = '\0'; + + js = json_loads(buf, 0, NULL); + + jwt_freemem(buf); + + return js; +} + +static int jwt_parse_body(jwt_t *jwt, char *body) +{ + if (jwt->grants) + json_decrefp(&(jwt->grants)); + + jwt->grants = jwt_base64uri_decode_to_json(body); + if (!jwt->grants) + return EINVAL; + + return 0; +} + +static int jwt_parse_head(jwt_t *jwt, char *head) +{ + const char *alg; + + if (jwt->headers) + json_decrefp(&(jwt->headers)); + + jwt->headers = jwt_base64uri_decode_to_json(head); + if (!jwt->headers) + return EINVAL; + + alg = get_js_string(jwt->headers, "alg"); + jwt->alg = jwt_str_alg(alg); + if (jwt->alg >= JWT_ALG_INVAL) + return EINVAL; + + return 0; +} + +/** + * @brief Smoke test to save the user from themselves. + */ +static int jwt_verify_alg(jwt_t *jwt, const void *key, const int key_len) +{ + int ret = 0; + + if (jwt->alg == JWT_ALG_NONE) { + /* If the user gave us a key but the JWT has alg = none, + * then we shouldn't even proceed. */ + if (key || key_len) + ret = EINVAL; + } else if (!(key && (key_len > 0))) { + /* If alg != none, then we should have a key to use */ + ret = EINVAL; + } + + /* Releive ourselves of the burden of this secret. */ + if (ret) + jwt_scrub_key(jwt); + + return ret; +} + +static int jwt_parse(jwt_t **jwt, const char *token, unsigned int *len) +{ + char *head = NULL; + char *body, *sig; + int ret = EINVAL; + + head = jwt_strdup(token); + + if (!head) + return ENOMEM; // LCOV_EXCL_LINE + + /* Find the components. */ + for (body = head; body[0] != '.'; body++) { + if (body[0] == '\0') + goto parse_done; + } + + body[0] = '\0'; + body++; + + for (sig = body; sig[0] != '.'; sig++) { + if (sig[0] == '\0') + goto parse_done; + } + + sig[0] = '\0'; + + /* Now that we have everything split up, let's check out the + * header. */ + ret = jwt_new(jwt); + if (ret) + goto parse_done; + + if ((ret = jwt_parse_head((*jwt), head))) + goto parse_done; + + ret = jwt_parse_body((*jwt), body); +parse_done: + if (ret) { + jwt_freep(jwt); + } else { + *len = sig - head; + } + + jwt_freemem(head); + + return ret; +} + +static int jwt_copy_key(jwt_t *jwt, const unsigned char *key, int key_len) +{ + int ret = 0; + + if (!key_len) + return 0; + + /* Always allocate one extra byte. For PEM, it ensures against + * not having a nil at the end (although all crypto backends + * should honor length), and for binary keys, it wont hurt + * because we use key_len for those operations. */ + jwt->key = jwt_malloc(key_len + 1); + if (jwt->key == NULL) + return ENOMEM; // LCOV_EXCL_LINE + + jwt->key[key_len] = '\0'; + + memcpy(jwt->key, key, key_len); + jwt->key_len = key_len; + + return ret; +} + +static int jwt_verify_complete(jwt_t **jwt, const unsigned char *key, + int key_len, const char *token, + unsigned int payload_len) +{ + int ret = EINVAL; + + /* Make sure things make sense when it comes to alg and keys */ + ret = jwt_verify_alg(*jwt, key, key_len); + if (ret) + goto decode_done; + + /* Now we keep it */ + ret = jwt_copy_key(*jwt, key, key_len); + if (ret) + goto decode_done; + + /* Check the signature, if needed. */ + if ((*jwt)->alg != JWT_ALG_NONE) { + const char *sig = token + (payload_len + 1); + ret = jwt_verify_sig(*jwt, token, payload_len, sig); + } + +decode_done: + if (ret) + jwt_freep(jwt); + + return ret; +} + +// LCOV_EXCL_START +int jwt_decode(jwt_t **jwt, const char *token, const unsigned char *key, + int key_len) +{ + unsigned int payload_len; + int ret; + + if (jwt == NULL) + return EINVAL; + + *jwt = NULL; + + if ((ret = jwt_parse(jwt, token, &payload_len))) + return ret; + + return jwt_verify_complete(jwt, key, key_len, token, payload_len); +} + +int jwt_decode_2(jwt_t **jwt, const char *token, jwt_callback_t cb) +{ + JWT_CONFIG_DECLARE(key); + int ret; + unsigned int payload_len; + + if (jwt == NULL) + return EINVAL; + + *jwt = NULL; + + ret = jwt_parse(jwt, token, &payload_len); + if (ret) + return ret; + + if (cb) { + /* The previous code trusted the JWT alg too much. If it was + * NONE, then it wouldn't even bother calling the cb. + * + * We also had some test cases that called this func with no + * cb and exptected it to work. True, this code allowed for + * that. My gut tells me that should never have been the case. + * + * For one, the previous code didn't check for NULL, so if + * you got a key that wasn't alg == none, instant SEGV. + * + * However, since this func is getting deprecated, we'll + * just let that case be like calling jwt_decode() + */ + ret = cb(*jwt, &key); + } + + if (ret) { + jwt_freep(jwt); + return ret; + } + + return jwt_verify_complete(jwt, key.key, key.key_len, token, + payload_len); +} +// LCOV_EXCL_STOP + +/* + * If no callback then we act just like jwt_verify(). + * + * If no config, but there is a callback, then we have to assume + * you do not want us doing much for you. + */ +int jwt_verify_wcb(jwt_t **jwt, const char *token, jwt_config_t *config, + jwt_callback_t cb) +{ + unsigned int payload_len; + int ret; + + if (jwt == NULL) + return EINVAL; + + *jwt = NULL; + + /* Quick smoke test */ + if (cb == NULL && config) { + if (config->alg == JWT_ALG_NONE) { + if (config->key != NULL || config->key_len) + return EINVAL; + } else { + if (config->key == NULL || !config->key_len) + return EINVAL; + } + } + + /* First parsing pass */ + ret = jwt_parse(jwt, token, &payload_len); + if (ret) + return ret; + + /* If the user requested an alg, do checks */ + if (config && config->alg != JWT_ALG_NONE) { + /* Mismatch or no signature */ + if ((config->alg != (*jwt)->alg) || !payload_len) { + jwt_freep(jwt); + return EINVAL; + } + } + + /* Let them handle it now. */ + if (cb) { + ret = cb(*jwt, config); + if (ret) { + jwt_freep(jwt); + return ret; + } + } + + /* Finish it up */ + return jwt_verify_complete(jwt, + (config == NULL) ? NULL : config->key, + (config == NULL) ? 0 : config->key_len, + token, payload_len); +} + +int jwt_verify(jwt_t **jwt, const char *token, jwt_config_t *config) +{ + return jwt_verify_wcb(jwt, token, config, NULL); +} diff --git a/libjwt/jwt.c b/libjwt/jwt.c index 18c0d64c..09d8e5a7 100644 --- a/libjwt/jwt.c +++ b/libjwt/jwt.c @@ -18,173 +18,31 @@ #include "jwt-private.h" -/* Library init functionality */ -static struct jwt_crypto_ops *jwt_ops_available[] = { -#ifdef HAVE_OPENSSL - &jwt_openssl_ops, -#endif -#ifdef HAVE_GNUTLS - &jwt_gnutls_ops, -#endif -#ifdef HAVE_MBEDTLS - &jwt_mbedtls_ops, -#endif - NULL, -}; - -#if defined HAVE_OPENSSL -struct jwt_crypto_ops *jwt_ops = &jwt_openssl_ops; -#elif defined HAVE_GNUTLS -struct jwt_crypto_ops *jwt_ops = &jwt_gnutls_ops; -#elif defined HAVE_MBEDTLS -struct jwt_crypto_ops *jwt_ops = &jwt_mbedtls_ops; -#else -#error No crypto ops providers are enabled -#endif - -const char *jwt_get_crypto_ops(void) +int __append_str(char **buf, const char *str) { - if (jwt_ops == NULL) - return "(unknown)"; // LCOV_EXCL_LINE - - return jwt_ops->name; -} - -jwt_crypto_provider_t jwt_get_crypto_ops_t(void) -{ - if (jwt_ops == NULL) - return JWT_CRYPTO_OPS_NONE; // LCOV_EXCL_LINE - - return jwt_ops->provider; -} - -int jwt_set_crypto_ops_t(jwt_crypto_provider_t opname) -{ - int i; - - /* The user asked for something, let's give it a try */ - for (i = 0; jwt_ops_available[i] != NULL; i++) { - if (jwt_ops_available[i]->provider != opname) - continue; - - jwt_ops = jwt_ops_available[i]; - return 0; - } - - return EINVAL; -} - -int jwt_set_crypto_ops(const char *opname) -{ - int i; - - /* The user asked for something, let's give it a try */ - for (i = 0; jwt_ops_available[i] != NULL; i++) { - if (jwt_strcmp(jwt_ops_available[i]->name, opname)) - continue; + char *new; - jwt_ops = jwt_ops_available[i]; + if (str == NULL || str[0] == '\0') return 0; - } - - return EINVAL; -} - -int jwt_crypto_ops_supports_jwk(void) -{ - return jwt_ops->jwk_implemented ? 1 : 0; -} - -JWT_CONSTRUCTOR -void jwt_init() -{ - const char *opname = getenv("JWT_CRYPTO"); - /* By default, we choose the top spot */ - if (opname == NULL || opname[0] == '\0') { - jwt_ops = jwt_ops_available[0]; - return; + if (*buf == NULL) { + new = jwt_malloc(strlen(str) + 1); + if (new) + new[0] = '\0'; + } else { + new = jwt_realloc(*buf, strlen(*buf) + strlen(str) + 1); } - /* Attempt to set ops */ - if (jwt_set_crypto_ops(opname)) { - jwt_ops = jwt_ops_available[0]; - fprintf(stderr, "LibJWT: No such crypto ops [%s], falling back to [%s]\n", - opname, jwt_ops->name); + if (new == NULL) { + jwt_freemem(*buf); + return 1; } -} - -static jwt_malloc_t pfn_malloc; -static jwt_realloc_t pfn_realloc; -static jwt_free_t pfn_free; - -void *jwt_malloc(size_t size) -{ - if (pfn_malloc) - return pfn_malloc(size); - - return malloc(size); -} - -void *jwt_realloc(void *ptr, size_t size) -{ - if (pfn_realloc) - return pfn_realloc(ptr, size); - - return realloc(ptr, size); -} - -/* Should call the macros instead */ -void __jwt_freemem(void *ptr) -{ - if (pfn_free) - pfn_free(ptr); - else - free(ptr); -} - -static char *jwt_strdup(const char *str) -{ - size_t len; - char *result; - - len = strlen(str); - result = (char *)jwt_malloc(len + 1); - if (!result) - return NULL; // LCOV_EXCL_LINE - - memcpy(result, str, len); - result[len] = '\0'; - return result; -} -/* A time-safe strcmp function */ -int jwt_strcmp(const char *str1, const char *str2) -{ - /* Get the LONGEST length */ - int len1 = strlen(str1); - int len2 = strlen(str2); - int len_max = len1 >= len2 ? len1 : len2; - - int i, ret = 0; - - /* Iterate the entire longest string no matter what. Only testing - * the shortest string would still allow attacks for - * "a" == "aKJSDHkjashaaHJASJ", adding a character each time one - * is found. */ - for (i = 0; i < len_max; i++) { - char c1, c2; - - c1 = (i < len1) ? str1[i] : 0; - c2 = (i < len2) ? str2[i] : 0; - - ret |= c1 ^ c2; - } + strcat(new, str); - /* Don't forget to check length */ - ret |= len1 ^ len2; + *buf = new; - return ret; + return 0; } const char *jwt_alg_str(jwt_alg_t alg) @@ -264,7 +122,7 @@ jwt_alg_t jwt_str_alg(const char *alg) return JWT_ALG_INVAL; } -static void jwt_scrub_key(jwt_t *jwt) +void jwt_scrub_key(jwt_t *jwt) { if (jwt->key) { /* Overwrite it so it's gone from memory. */ @@ -410,7 +268,7 @@ jwt_t *jwt_dup(jwt_t *jwt) return new; } -static const char *get_js_string(json_t *js, const char *key) +const char *get_js_string(json_t *js, const char *key) { const char *val = NULL; json_t *js_val; @@ -428,7 +286,7 @@ static const char *get_js_string(json_t *js, const char *key) return val; } -static long get_js_int(json_t *js, const char *key) +long get_js_int(json_t *js, const char *key) { long val = -1; json_t *js_val; @@ -446,7 +304,7 @@ static long get_js_int(json_t *js, const char *key) return val; } -static int get_js_bool(json_t *js, const char *key) +int get_js_bool(json_t *js, const char *key) { int val = -1; json_t *js_val; @@ -524,27 +382,6 @@ void *jwt_base64uri_decode(const char *src, int *ret_len) return buf; } - -static json_t *jwt_base64uri_decode_to_json(char *src) -{ - json_t *js; - char *buf; - int len; - - buf = jwt_base64uri_decode(src, &len); - - if (buf == NULL) - return NULL; // LCOV_EXCL_LINE - - buf[len] = '\0'; - - js = json_loads(buf, 0, NULL); - - jwt_freemem(buf); - - return js; -} - int jwt_base64uri_encode(char **_dst, const char *plain, int plain_len) { int len, i; @@ -584,8 +421,8 @@ int jwt_base64uri_encode(char **_dst, const char *plain, int plain_len) return i; } -static int jwt_sign(jwt_t *jwt, char **out, unsigned int *len, const char *str, - unsigned int str_len) +int jwt_sign(jwt_t *jwt, char **out, unsigned int *len, const char *str, + unsigned int str_len) { switch (jwt->alg) { /* HMAC */ @@ -620,8 +457,8 @@ static int jwt_sign(jwt_t *jwt, char **out, unsigned int *len, const char *str, } } -static int jwt_verify_sig(jwt_t *jwt, const char *head, - unsigned int head_len, const char *sig) +int jwt_verify_sig(jwt_t *jwt, const char *head, unsigned int head_len, + const char *sig) { switch (jwt->alg) { /* HMAC */ @@ -656,1205 +493,7 @@ static int jwt_verify_sig(jwt_t *jwt, const char *head, } } -static int jwt_parse_body(jwt_t *jwt, char *body) -{ - if (jwt->grants) - json_decrefp(&(jwt->grants)); - - jwt->grants = jwt_base64uri_decode_to_json(body); - if (!jwt->grants) - return EINVAL; - - return 0; -} - -static int jwt_parse_head(jwt_t *jwt, char *head) -{ - const char *alg; - - if (jwt->headers) - json_decrefp(&(jwt->headers)); - - jwt->headers = jwt_base64uri_decode_to_json(head); - if (!jwt->headers) - return EINVAL; - - alg = get_js_string(jwt->headers, "alg"); - jwt->alg = jwt_str_alg(alg); - if (jwt->alg >= JWT_ALG_INVAL) - return EINVAL; - - return 0; -} - -/** - * @brief Smoke test to save the user from themselves. - */ -static int jwt_verify_alg(jwt_t *jwt, const void *key, const int key_len) -{ - int ret = 0; - - if (jwt->alg == JWT_ALG_NONE) { - /* If the user gave us a key but the JWT has alg = none, - * then we shouldn't even proceed. */ - if (key || key_len) - ret = EINVAL; - } else if (!(key && (key_len > 0))) { - /* If alg != none, then we should have a key to use */ - ret = EINVAL; - } - - /* Releive ourselves of the burden of this secret. */ - if (ret) - jwt_scrub_key(jwt); - - return ret; -} - -static int jwt_parse(jwt_t **jwt, const char *token, unsigned int *len) -{ - char *head = NULL; - char *body, *sig; - int ret = EINVAL; - - head = jwt_strdup(token); - - if (!head) - return ENOMEM; // LCOV_EXCL_LINE - - /* Find the components. */ - for (body = head; body[0] != '.'; body++) { - if (body[0] == '\0') - goto parse_done; - } - - body[0] = '\0'; - body++; - - for (sig = body; sig[0] != '.'; sig++) { - if (sig[0] == '\0') - goto parse_done; - } - - sig[0] = '\0'; - - /* Now that we have everything split up, let's check out the - * header. */ - ret = jwt_new(jwt); - if (ret) - goto parse_done; - - if ((ret = jwt_parse_head((*jwt), head))) - goto parse_done; - - ret = jwt_parse_body((*jwt), body); -parse_done: - if (ret) { - jwt_freep(jwt); - } else { - *len = sig - head; - } - - jwt_freemem(head); - - return ret; -} - -static int jwt_copy_key(jwt_t *jwt, const unsigned char *key, int key_len) -{ - int ret = 0; - - if (!key_len) - return 0; - - /* Always allocate one extra byte. For PEM, it ensures against - * not having a nil at the end (although all crypto backends - * should honor length), and for binary keys, it wont hurt - * because we use key_len for those operations. */ - jwt->key = jwt_malloc(key_len + 1); - if (jwt->key == NULL) - return ENOMEM; // LCOV_EXCL_LINE - - jwt->key[key_len] = '\0'; - - memcpy(jwt->key, key, key_len); - jwt->key_len = key_len; - - return ret; -} - -static int jwt_verify_complete(jwt_t **jwt, const unsigned char *key, - int key_len, const char *token, - unsigned int payload_len) -{ - int ret = EINVAL; - - /* Make sure things make sense when it comes to alg and keys */ - ret = jwt_verify_alg(*jwt, key, key_len); - if (ret) - goto decode_done; - - /* Now we keep it */ - ret = jwt_copy_key(*jwt, key, key_len); - if (ret) - goto decode_done; - - /* Check the signature, if needed. */ - if ((*jwt)->alg != JWT_ALG_NONE) { - const char *sig = token + (payload_len + 1); - ret = jwt_verify_sig(*jwt, token, payload_len, sig); - } - -decode_done: - if (ret) - jwt_freep(jwt); - - return ret; -} - -// LCOV_EXCL_START -int jwt_decode(jwt_t **jwt, const char *token, const unsigned char *key, - int key_len) -{ - unsigned int payload_len; - int ret; - - if (jwt == NULL) - return EINVAL; - - *jwt = NULL; - - if ((ret = jwt_parse(jwt, token, &payload_len))) - return ret; - - return jwt_verify_complete(jwt, key, key_len, token, payload_len); -} - -int jwt_decode_2(jwt_t **jwt, const char *token, jwt_callback_t cb) -{ - JWT_CONFIG_DECLARE(key); - int ret; - unsigned int payload_len; - - if (jwt == NULL) - return EINVAL; - - *jwt = NULL; - - ret = jwt_parse(jwt, token, &payload_len); - if (ret) - return ret; - - if (cb) { - /* The previous code trusted the JWT alg too much. If it was - * NONE, then it wouldn't even bother calling the cb. - * - * We also had some test cases that called this func with no - * cb and exptected it to work. True, this code allowed for - * that. My gut tells me that should never have been the case. - * - * For one, the previous code didn't check for NULL, so if - * you got a key that wasn't alg == none, instant SEGV. - * - * However, since this func is getting deprecated, we'll - * just let that case be like calling jwt_decode() - */ - ret = cb(*jwt, &key); - } - - if (ret) { - jwt_freep(jwt); - return ret; - } - - return jwt_verify_complete(jwt, key.key, key.key_len, token, - payload_len); -} -// LCOV_EXCL_STOP - void jwt_config_init(jwt_config_t *config) { memset(config, 0, sizeof(*config)); } - -/* - * If no callback then we act just like jwt_verify(). - * - * If no config, but there is a callback, then we have to assume - * you do not want us doing much for you. - */ -int jwt_verify_wcb(jwt_t **jwt, const char *token, jwt_config_t *config, - jwt_callback_t cb) -{ - unsigned int payload_len; - int ret; - - if (jwt == NULL) - return EINVAL; - - *jwt = NULL; - - /* Quick smoke test */ - if (cb == NULL && config) { - if (config->alg == JWT_ALG_NONE) { - if (config->key != NULL || config->key_len) - return EINVAL; - } else { - if (config->key == NULL || !config->key_len) - return EINVAL; - } - } - - /* First parsing pass */ - ret = jwt_parse(jwt, token, &payload_len); - if (ret) - return ret; - - /* If the user requested an alg, do checks */ - if (config && config->alg != JWT_ALG_NONE) { - /* Mismatch or no signature */ - if ((config->alg != (*jwt)->alg) || !payload_len) { - jwt_freep(jwt); - return EINVAL; - } - } - - /* Let them handle it now. */ - if (cb) { - ret = cb(*jwt, config); - if (ret) { - jwt_freep(jwt); - return ret; - } - } - - /* Finish it up */ - return jwt_verify_complete(jwt, - (config == NULL) ? NULL : config->key, - (config == NULL) ? 0 : config->key_len, - token, payload_len); -} - -int jwt_verify(jwt_t **jwt, const char *token, jwt_config_t *config) -{ - return jwt_verify_wcb(jwt, token, config, NULL); -} - -const char *jwt_get_grant(jwt_t *jwt, const char *grant) -{ - if (!jwt || !grant || !strlen(grant)) { - errno = EINVAL; - return NULL; - } - - errno = 0; - - return get_js_string(jwt->grants, grant); -} - -long jwt_get_grant_int(jwt_t *jwt, const char *grant) -{ - if (!jwt || !grant || !strlen(grant)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_int(jwt->grants, grant); -} - -int jwt_get_grant_bool(jwt_t *jwt, const char *grant) -{ - if (!jwt || !grant || !strlen(grant)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_bool(jwt->grants, grant); -} - -char *jwt_get_grants_json(jwt_t *jwt, const char *grant) -{ - json_t *js_val = NULL; - - if (!jwt) { - errno = EINVAL; - return NULL; - } - - if (grant && strlen(grant)) - js_val = json_object_get(jwt->grants, grant); - else - js_val = jwt->grants; - - if (js_val == NULL) { - errno = ENOENT; - return NULL; - } - - errno = 0; - - return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | JSON_ENCODE_ANY); -} - -int jwt_add_grant(jwt_t *jwt, const char *grant, const char *val) -{ - if (!jwt || !grant || !strlen(grant) || !val) - return EINVAL; - - if (get_js_string(jwt->grants, grant) != NULL) - return EEXIST; - - if (json_object_set_new(jwt->grants, grant, json_string(val))) - return EINVAL; - - return 0; -} - -int jwt_add_grant_int(jwt_t *jwt, const char *grant, long val) -{ - if (!jwt || !grant || !strlen(grant)) - return EINVAL; - - if (get_js_int(jwt->grants, grant) != -1) - return EEXIST; - - if (json_object_set_new(jwt->grants, grant, json_integer((json_int_t)val))) - return EINVAL; - - return 0; -} - -int jwt_add_grant_bool(jwt_t *jwt, const char *grant, int val) -{ - if (!jwt || !grant || !strlen(grant)) - return EINVAL; - - if (get_js_int(jwt->grants, grant) != -1) - return EEXIST; - - if (json_object_set_new(jwt->grants, grant, json_boolean(val))) - return EINVAL; - - return 0; -} - -int jwt_add_grants_json(jwt_t *jwt, const char *json) -{ - json_auto_t *js_val; - int ret = -1; - - if (!jwt) - return EINVAL; - - js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); - - if (json_is_object(js_val)) - ret = json_object_update(jwt->grants, js_val); - - return ret ? EINVAL : 0; -} - -int jwt_del_grants(jwt_t *jwt, const char *grant) -{ - if (!jwt) - return EINVAL; - - if (grant == NULL || !strlen(grant)) - json_object_clear(jwt->grants); - else - json_object_del(jwt->grants, grant); - - return 0; -} - -const char *jwt_get_header(jwt_t *jwt, const char *header) -{ - if (!jwt || !header || !strlen(header)) { - errno = EINVAL; - return NULL; - } - - errno = 0; - - return get_js_string(jwt->headers, header); -} - -long jwt_get_header_int(jwt_t *jwt, const char *header) -{ - if (!jwt || !header || !strlen(header)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_int(jwt->headers, header); -} - -int jwt_get_header_bool(jwt_t *jwt, const char *header) -{ - if (!jwt || !header || !strlen(header)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_bool(jwt->headers, header); -} - -char *jwt_get_headers_json(jwt_t *jwt, const char *header) -{ - json_t *js_val = NULL; - - errno = EINVAL; - - if (!jwt) - return NULL; - - if (header && strlen(header)) - js_val = json_object_get(jwt->headers, header); - else - js_val = jwt->headers; - - if (js_val == NULL) - return NULL; - - errno = 0; - - return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | JSON_ENCODE_ANY); -} - -int jwt_add_header(jwt_t *jwt, const char *header, const char *val) -{ - if (!jwt || !header || !strlen(header) || !val) - return EINVAL; - - if (get_js_string(jwt->headers, header) != NULL) - return EEXIST; - - if (json_object_set_new(jwt->headers, header, json_string(val))) - return EINVAL; - - return 0; -} - -int jwt_add_header_int(jwt_t *jwt, const char *header, long val) -{ - if (!jwt || !header || !strlen(header)) - return EINVAL; - - if (get_js_int(jwt->headers, header) != -1) - return EEXIST; - - if (json_object_set_new(jwt->headers, header, json_integer((json_int_t)val))) - return EINVAL; - - return 0; -} - -int jwt_add_header_bool(jwt_t *jwt, const char *header, int val) -{ - if (!jwt || !header || !strlen(header)) - return EINVAL; - - if (get_js_int(jwt->headers, header) != -1) - return EEXIST; - - if (json_object_set_new(jwt->headers, header, json_boolean(val))) - return EINVAL; - - return 0; -} - -int jwt_add_headers_json(jwt_t *jwt, const char *json) -{ - json_auto_t *js_val; - int ret = -1; - - if (!jwt) - return EINVAL; - - js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); - - if (json_is_object(js_val)) - ret = json_object_update(jwt->headers, js_val); - - return ret ? EINVAL : 0; -} - -int jwt_del_headers(jwt_t *jwt, const char *header) -{ - if (!jwt) - return EINVAL; - - if (header == NULL || !strlen(header)) - json_object_clear(jwt->headers); - else - json_object_del(jwt->headers, header); - - return 0; -} - -static int __append_str(char **buf, const char *str) -{ - char *new; - - if (str == NULL || str[0] == '\0') - return 0; - - if (*buf == NULL) { - new = jwt_malloc(strlen(str) + 1); - if (new) - new[0] = '\0'; - } else { - new = jwt_realloc(*buf, strlen(*buf) + strlen(str) + 1); - } - - if (new == NULL) { - jwt_freemem(*buf); - return 1; - } - - strcat(new, str); - - *buf = new; - - return 0; -} - -#define APPEND_STR(__buf, __str) do { \ - if (__append_str(__buf, __str)) \ - return ENOMEM; \ -} while (0) - -static int write_js(const json_t *js, char **buf, int pretty) -{ - /* Sort keys for repeatability */ - size_t flags = JSON_SORT_KEYS; - char *serial; - - if (pretty) { - APPEND_STR(buf, "\n"); - flags |= JSON_INDENT(4); - } else { - flags |= JSON_COMPACT; - } - - serial = json_dumps(js, flags); - - APPEND_STR(buf, serial); - - jwt_freemem(serial); - - if (pretty) - APPEND_STR(buf, "\n"); - - return 0; -} - -static int jwt_write_head(jwt_t *jwt, char **buf, int pretty) -{ - int ret = 0; - - if (jwt->alg != JWT_ALG_NONE) { - /* Only add default 'typ' header if it has not been defined, - * allowing for any value of it. This allows for signaling - * of application specific extensions to JWT, such as PASSporT, - * RFC 8225. */ - if ((ret = jwt_add_header(jwt, "typ", "JWT"))) { - if (ret != EEXIST) - return ret; - } - } - - if ((ret = jwt_del_headers(jwt, "alg"))) - return ret; - - if ((ret = jwt_add_header(jwt, "alg", jwt_alg_str(jwt->alg)))) - return ret; - - return write_js(jwt->headers, buf, pretty); -} - -static int jwt_write_body(jwt_t *jwt, char **buf, int pretty) -{ - return write_js(jwt->grants, buf, pretty); -} - -static int jwt_dump(jwt_t *jwt, char **buf, int pretty) -{ - int ret; - - ret = jwt_write_head(jwt, buf, pretty); - - if (ret == 0) - ret = __append_str(buf, "."); - - if (ret == 0) - ret = jwt_write_body(jwt, buf, pretty); - - return ret; -} - -char *jwt_dump_grants_str(jwt_t *jwt, int pretty) -{ - char *out = NULL; - int err; - - errno = 0; - - err = jwt_write_body(jwt, &out, pretty); - - if (err) { - errno = err; - if (out) - jwt_freemem(out); - out = NULL; - } - - return out; -} - -int jwt_dump_fp(jwt_t *jwt, FILE *fp, int pretty) -{ - char *out = NULL; - int ret = 0; - - ret = jwt_dump(jwt, &out, pretty); - - if (ret == 0) - fputs(out, fp); - - if (out) - jwt_freemem(out); - - return ret; -} - -char *jwt_dump_str(jwt_t *jwt, int pretty) -{ - char *out = NULL; - - errno = jwt_dump(jwt, &out, pretty); - - if (errno) - jwt_freemem(out); - - return out; -} - -static int jwt_encode(jwt_t *jwt, char **out) -{ - char *buf = NULL, *head = NULL, *body = NULL, *sig = NULL; - int ret, head_len, body_len; - unsigned int sig_len; - - if (out == NULL) - return EINVAL; - *out = NULL; - - /* First the header. */ - ret = jwt_write_head(jwt, &buf, 0); - if (ret) - return ret; - /* Encode it */ - head_len = jwt_base64uri_encode(&head, buf, (int)strlen(buf)); - jwt_freemem(buf); - - if (head_len <= 0) - return -head_len; - - /* Now the body. */ - ret = jwt_write_body(jwt, &buf, 0); - if (ret) { - jwt_freemem(head); - return ret; - } - - body_len = jwt_base64uri_encode(&body, buf, (int)strlen(buf)); - jwt_freemem(buf); - - if (body_len <= 0) { - jwt_freemem(head); - return -body_len; - } - - /* The part we need to sign, but add space for 3 dots and a nil */ - buf = jwt_malloc(head_len + body_len + 4); - if (buf == NULL) { - jwt_freemem(head); - jwt_freemem(body); - return ENOMEM; - } - - strcpy(buf, head); - strcat(buf, "."); - strcat(buf, body); - - if (jwt->alg == JWT_ALG_NONE) { - jwt_freemem(head); - jwt_freemem(body); - - /* Add the trailing dot, and send it back */ - strcat(buf, "."); - *out = buf; - return 0; - } - - /* At this point buf has "head.body" */ - - /* Now the signature. */ - ret = jwt_sign(jwt, &sig, &sig_len, buf, strlen(buf)); - jwt_freemem(buf); - if (ret) { - jwt_freemem(head); - jwt_freemem(body); - return ret; - } - - ret = jwt_base64uri_encode(&buf, sig, sig_len); - jwt_freemem(sig); - /* At this point buf has b64 of sig and ret is size of it */ - - if (ret < 0) { - jwt_freemem(head); - jwt_freemem(body); - jwt_freemem(buf); - return ENOMEM; - } - - /* plus 2 dots and a nil */ - ret = head_len + body_len + ret + 3; - - /* We're good, so let's get it all together */ - *out = jwt_malloc(ret); - if (*out == NULL) { - ret = ENOMEM; - } else { - strcpy(*out, head); - strcat(*out, "."); - strcat(*out, body); - strcat(*out, "."); - strcat(*out, buf); - ret = 0; - } - - jwt_freemem(head); - jwt_freemem(body); - jwt_freemem(buf); - - return ret; -} - -int jwt_encode_fp(jwt_t *jwt, FILE *fp) -{ - char *str = NULL; - int ret; - - ret = jwt_encode(jwt, &str); - if (ret) { - if (str) - jwt_freemem(str); - return ret; - } - - fputs(str, fp); - jwt_freemem(str); - - return 0; -} - -char *jwt_encode_str(jwt_t *jwt) -{ - char *str = NULL; - - errno = jwt_encode(jwt, &str); - if (errno) - jwt_freemem(str); - - return str; -} - -void jwt_free_str(char *str) -{ - jwt_freemem(str); -} - -int jwt_set_alloc(jwt_malloc_t pmalloc, jwt_realloc_t prealloc, jwt_free_t pfree) -{ - /* Set allocator functions for LibJWT. */ - pfn_malloc = pmalloc; - pfn_realloc = prealloc; - pfn_free = pfree; - - /* Set same allocator functions for Jansson. */ - json_set_alloc_funcs(jwt_malloc, __jwt_freemem); - - return 0; -} - -void jwt_get_alloc(jwt_malloc_t *pmalloc, jwt_realloc_t *prealloc, jwt_free_t *pfree) -{ - if (pmalloc) - *pmalloc = pfn_malloc; - - if (prealloc) - *prealloc = pfn_realloc; - - if (pfree) - *pfree = pfn_free; -} - -int jwt_valid_new(jwt_valid_t **jwt_valid, jwt_alg_t alg) -{ - if (!jwt_valid) - return EINVAL; - - *jwt_valid = jwt_malloc(sizeof(jwt_valid_t)); - if (!*jwt_valid) - return ENOMEM; // LCOV_EXCL_LINE - - memset(*jwt_valid, 0, sizeof(jwt_valid_t)); - (*jwt_valid)->alg = alg; - - (*jwt_valid)->status = JWT_VALIDATION_ERROR; - - (*jwt_valid)->nbf_leeway = 0; - (*jwt_valid)->exp_leeway = 0; - - (*jwt_valid)->req_grants = json_object(); - if (!(*jwt_valid)->req_grants) { - jwt_freemem(*jwt_valid); - return ENOMEM; - } - - return 0; -} - -void jwt_valid_free(jwt_valid_t *jwt_valid) -{ - if (!jwt_valid) - return; - - json_decref(jwt_valid->req_grants); - - jwt_freemem(jwt_valid); -} - -jwt_valid_exception_t jwt_valid_get_status(jwt_valid_t *jwt_valid) -{ - if (!jwt_valid) - return JWT_VALIDATION_ERROR; - - return jwt_valid->status; -} - -time_t jwt_valid_get_nbf_leeway(jwt_valid_t *jwt_valid) -{ - if (!jwt_valid) - return EINVAL; - - return jwt_valid->nbf_leeway; -} - -time_t jwt_valid_get_exp_leeway(jwt_valid_t *jwt_valid) -{ - if (!jwt_valid) - return EINVAL; - - return jwt_valid->exp_leeway; -} - -int jwt_valid_add_grant(jwt_valid_t *jwt_valid, const char *grant, const char *val) -{ - if (!jwt_valid || !grant || !strlen(grant) || !val) - return EINVAL; - - if (get_js_string(jwt_valid->req_grants, grant) != NULL) - return EEXIST; - - if (json_object_set_new(jwt_valid->req_grants, grant, json_string(val))) - return EINVAL; - - return 0; -} - -int jwt_valid_add_grant_int(jwt_valid_t *jwt_valid, const char *grant, long val) -{ - if (!jwt_valid || !grant || !strlen(grant)) - return EINVAL; - - if (get_js_int(jwt_valid->req_grants, grant) != -1) - return EEXIST; - - if (json_object_set_new(jwt_valid->req_grants, grant, json_integer((json_int_t)val))) - return EINVAL; - - return 0; -} - -int jwt_valid_add_grant_bool(jwt_valid_t *jwt_valid, const char *grant, int val) -{ - if (!jwt_valid || !grant || !strlen(grant)) - return EINVAL; - - if (get_js_bool(jwt_valid->req_grants, grant) != -1) - return EEXIST; - - if (json_object_set_new(jwt_valid->req_grants, grant, json_boolean(val))) - return EINVAL; - - return 0; -} - -int jwt_valid_add_grants_json(jwt_valid_t *jwt_valid, const char *json) -{ - json_auto_t *js_val; - int ret = -1; - - if (!jwt_valid) - return EINVAL; - - js_val = json_loads(json, JSON_REJECT_DUPLICATES, NULL); - - if (json_is_object(js_val)) - ret = json_object_update(jwt_valid->req_grants, js_val); - - return ret ? EINVAL : 0; -} - -char *jwt_valid_get_grants_json(jwt_valid_t *jwt_valid, const char *grant) -{ - json_t *js_val = NULL; - - errno = EINVAL; - - if (!jwt_valid) - return NULL; - - if (grant && strlen(grant)) - js_val = json_object_get(jwt_valid->req_grants, grant); - else - js_val = jwt_valid->req_grants; - - if (js_val == NULL) - return NULL; - - errno = 0; - - return json_dumps(js_val, JSON_SORT_KEYS | JSON_COMPACT | JSON_ENCODE_ANY); -} - -const char *jwt_valid_get_grant(jwt_valid_t *jwt_valid, const char *grant) -{ - if (!jwt_valid || !grant || !strlen(grant)) { - errno = EINVAL; - return NULL; - } - - errno = 0; - - return get_js_string(jwt_valid->req_grants, grant); -} - -long jwt_valid_get_grant_int(jwt_valid_t *jwt_valid, const char *grant) -{ - if (!jwt_valid || !grant || !strlen(grant)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_int(jwt_valid->req_grants, grant); -} - -int jwt_valid_get_grant_bool(jwt_valid_t *jwt_valid, const char *grant) -{ - if (!jwt_valid || !grant || !strlen(grant)) { - errno = EINVAL; - return 0; - } - - errno = 0; - - return get_js_bool(jwt_valid->req_grants, grant); -} - -int jwt_valid_set_now(jwt_valid_t *jwt_valid, const time_t now) -{ - if (!jwt_valid) - return EINVAL; - - jwt_valid->now = now; - - return 0; -} - -int jwt_valid_set_nbf_leeway(jwt_valid_t *jwt_valid, const time_t nbf_leeway) -{ - if (!jwt_valid) - return EINVAL; - - jwt_valid->nbf_leeway = nbf_leeway; - - return 0; -} - -int jwt_valid_set_exp_leeway(jwt_valid_t *jwt_valid, const time_t exp_leeway) -{ - if (!jwt_valid) - return EINVAL; - - jwt_valid->exp_leeway = exp_leeway; - - return 0; -} - -int jwt_valid_set_headers(jwt_valid_t *jwt_valid, int hdr) -{ - if (!jwt_valid) - return EINVAL; - - jwt_valid->hdr = hdr; - - return 0; -} - -int jwt_valid_del_grants(jwt_valid_t *jwt_valid, const char *grant) -{ - if (!jwt_valid) - return EINVAL; - - if (grant == NULL || !strlen(grant)) - json_object_clear(jwt_valid->req_grants); - else - json_object_del(jwt_valid->req_grants, grant); - - return 0; -} - -#define _SET_AND_RET(__v, __e) do { \ - __v->status |= __e; \ - return __v->status; \ -} while (0) - -jwt_valid_exception_t jwt_validate(jwt_t *jwt, jwt_valid_t *jwt_valid) -{ - const char *jwt_hdr_str, *jwt_body_str, *req_grant; - json_t *js_val_1, *js_val_2; - time_t t; - - if (!jwt_valid) - return JWT_VALIDATION_ERROR; - - if (!jwt) { - jwt_valid->status = JWT_VALIDATION_ERROR; - return jwt_valid->status; - } - - jwt_valid->status = JWT_VALIDATION_SUCCESS; - - /* Validate algorithm */ - if (jwt_valid->alg != jwt_get_alg(jwt)) - jwt_valid->status |= JWT_VALIDATION_ALG_MISMATCH; - - /* Validate expires */ - t = get_js_int(jwt->grants, "exp"); - if (jwt_valid->now && t != -1 && jwt_valid->now - jwt_valid->exp_leeway >= t) - jwt_valid->status |= JWT_VALIDATION_EXPIRED; - - /* Validate not-before */ - t = get_js_int(jwt->grants, "nbf"); - if (jwt_valid->now && t != -1 && jwt_valid->now + jwt_valid->nbf_leeway < t) - jwt_valid->status |= JWT_VALIDATION_TOO_NEW; - - /* Validate replicated issuer */ - jwt_hdr_str = get_js_string(jwt->headers, "iss"); - jwt_body_str = get_js_string(jwt->grants, "iss"); - if (jwt_hdr_str && jwt_body_str && jwt_strcmp(jwt_hdr_str, jwt_body_str)) - jwt_valid->status |= JWT_VALIDATION_ISS_MISMATCH; - - /* Validate replicated subject */ - jwt_hdr_str = get_js_string(jwt->headers, "sub"); - jwt_body_str = get_js_string(jwt->grants, "sub"); - if (jwt_hdr_str && jwt_body_str && jwt_strcmp(jwt_hdr_str, jwt_body_str)) - jwt_valid->status |= JWT_VALIDATION_SUB_MISMATCH; - - /* Validate replicated audience (might be array or string) */ - js_val_1 = json_object_get(jwt->headers, "aud"); - js_val_2 = json_object_get(jwt->grants, "aud"); - if (js_val_1 && js_val_2 && !json_equal(js_val_1, js_val_2)) - jwt_valid->status |= JWT_VALIDATION_AUD_MISMATCH; - - /* Validate required grants */ - json_object_foreach(jwt_valid->req_grants, req_grant, js_val_1) { - json_t *act_js_val = json_object_get(jwt->grants, req_grant); - - if (act_js_val && json_equal(js_val_1, act_js_val)) - continue; - - if (act_js_val) - jwt_valid->status |= JWT_VALIDATION_GRANT_MISMATCH; - else - jwt_valid->status |= JWT_VALIDATION_GRANT_MISSING; - } - - return jwt_valid->status; -} - -typedef struct { - int error; - char *str; -} jwt_exception_dict_t; - -static jwt_exception_dict_t jwt_exceptions[] = { - /* { JWT_VALIDATION_SUCCESS, "SUCCESS" }, */ - { JWT_VALIDATION_ERROR, "general failures" }, - { JWT_VALIDATION_ALG_MISMATCH, "algorithm mismatch" }, - { JWT_VALIDATION_EXPIRED, "token expired" }, - { JWT_VALIDATION_TOO_NEW, "token future dated" }, - { JWT_VALIDATION_ISS_MISMATCH, "issuer mismatch" }, - { JWT_VALIDATION_SUB_MISMATCH, "subject mismatch" }, - { JWT_VALIDATION_AUD_MISMATCH, "audience mismatch" }, - { JWT_VALIDATION_GRANT_MISSING, "grant missing" }, - { JWT_VALIDATION_GRANT_MISMATCH, "grant mismatch" }, -}; - -char *jwt_exception_str(jwt_valid_exception_t exceptions) -{ - int rc, i; - char *str = NULL; - - if (exceptions == JWT_VALIDATION_SUCCESS) { - if ((rc = __append_str(&str, "success"))) - goto fail; - return str; - } - - for (i = 0; i < ARRAY_SIZE(jwt_exceptions); i++) { - if (!(jwt_exceptions[i].error & exceptions)) - continue; - - if (str && (rc = __append_str(&str, ", "))) - goto fail; - - if ((rc = __append_str(&str, jwt_exceptions[i].str))) - goto fail; - } - - /* check if none of the exceptions matched? */ - if (!str && (rc = __append_str(&str, "unknown exceptions"))) - goto fail; - - return str; -fail: - errno = rc; - jwt_freemem(str); - return NULL; -} diff --git a/libjwt/jwks-mbedtls.c b/libjwt/mbedtls/jwk-parse.c similarity index 100% rename from libjwt/jwks-mbedtls.c rename to libjwt/mbedtls/jwk-parse.c diff --git a/libjwt/jwt-mbedtls.c b/libjwt/mbedtls/sign-verify.c similarity index 100% rename from libjwt/jwt-mbedtls.c rename to libjwt/mbedtls/sign-verify.c diff --git a/libjwt/jwks-openssl.c b/libjwt/openssl/jwk-parse.c similarity index 100% rename from libjwt/jwks-openssl.c rename to libjwt/openssl/jwk-parse.c diff --git a/libjwt/jwt-openssl.c b/libjwt/openssl/sign-verify.c similarity index 100% rename from libjwt/jwt-openssl.c rename to libjwt/openssl/sign-verify.c diff --git a/libjwt/jwt-wincrypt.c b/libjwt/wincrypt/sign-verify.c similarity index 100% rename from libjwt/jwt-wincrypt.c rename to libjwt/wincrypt/sign-verify.c