From 3c868190d96e2bdf0ad41350d2ddeee9b94625cc Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sat, 7 Oct 2023 11:19:05 +0200 Subject: [PATCH] WIP: add JWT library Currently does not compile, pending integration into SDK. --- src/CMakeLists.txt | 1 + src/thirdparty/jwt/CMakeLists.txt | 27 + src/thirdparty/jwt/base64.c | 315 ++++++++++ src/thirdparty/jwt/claim.c | 147 +++++ src/thirdparty/jwt/decode.c | 769 +++++++++++++++++++++++++ src/thirdparty/jwt/encode.c | 685 ++++++++++++++++++++++ src/thirdparty/jwt/include/algs.h | 111 ++++ src/thirdparty/jwt/include/base64.h | 77 +++ src/thirdparty/jwt/include/chillbuff.h | 326 +++++++++++ src/thirdparty/jwt/include/claim.h | 146 +++++ src/thirdparty/jwt/include/decode.h | 274 +++++++++ src/thirdparty/jwt/include/encode.h | 206 +++++++ src/thirdparty/jwt/include/retcodes.h | 107 ++++ src/thirdparty/jwt/include/util.h | 58 ++ src/thirdparty/jwt/include/version.h | 87 +++ src/thirdparty/jwt/util.c | 87 +++ src/thirdparty/jwt/version.c | 73 +++ 17 files changed, 3496 insertions(+) create mode 100644 src/thirdparty/jwt/CMakeLists.txt create mode 100644 src/thirdparty/jwt/base64.c create mode 100644 src/thirdparty/jwt/claim.c create mode 100644 src/thirdparty/jwt/decode.c create mode 100644 src/thirdparty/jwt/encode.c create mode 100644 src/thirdparty/jwt/include/algs.h create mode 100644 src/thirdparty/jwt/include/base64.h create mode 100644 src/thirdparty/jwt/include/chillbuff.h create mode 100644 src/thirdparty/jwt/include/claim.h create mode 100644 src/thirdparty/jwt/include/decode.h create mode 100644 src/thirdparty/jwt/include/encode.h create mode 100644 src/thirdparty/jwt/include/retcodes.h create mode 100644 src/thirdparty/jwt/include/util.h create mode 100644 src/thirdparty/jwt/include/version.h create mode 100644 src/thirdparty/jwt/util.c create mode 100644 src/thirdparty/jwt/version.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 99731864..6dd47a16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ add_subdirectory( thirdparty/curl ) add_subdirectory( thirdparty/sdl ) add_subdirectory( thirdparty/imgui ) add_subdirectory( thirdparty/spdlog ) +add_subdirectory( thirdparty/jwt ) add_subdirectory( thirdparty/lzham ) add_subdirectory( thirdparty/fastlz ) diff --git a/src/thirdparty/jwt/CMakeLists.txt b/src/thirdparty/jwt/CMakeLists.txt new file mode 100644 index 00000000..a4bde888 --- /dev/null +++ b/src/thirdparty/jwt/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required( VERSION 3.16 ) +add_module( "lib" "libjwt" "" ${FOLDER_CONTEXT} TRUE TRUE ) + +start_sources() + +add_sources( SOURCE_GROUP "Source" + "base64.c" + "claim.c" + "decode.c" + "encode.c" + "util.c" + "version.c" +) + +add_sources( SOURCE_GROUP "Include" + "include/algs.h" + "include/base64.h" + "include/claim.h" + "include/decode.h" + "include/encode.h" + "include/retcodes.h" + "include/util.h" + "include/version.h" +) + +end_sources() +thirdparty_suppress_warnings() diff --git a/src/thirdparty/jwt/base64.c b/src/thirdparty/jwt/base64.c new file mode 100644 index 00000000..ea27386a --- /dev/null +++ b/src/thirdparty/jwt/base64.c @@ -0,0 +1,315 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +/* + +The above mentioned README can be found under: http://web.mit.edu/freebsd/head/contrib/wpa/ + +Here's a full paste of it (18. January 2020), in case the URL goes numb: + +wpa_supplicant and hostapd +-------------------------- + +Copyright (c) 2002-2012, Jouni Malinen and contributors +All Rights Reserved. + +These programs are licensed under the BSD license (the one with +advertisement clause removed). + +If you are submitting changes to the project, please see CONTRIBUTIONS +file for more instructions. + + +This package may include either wpa_supplicant, hostapd, or both. See +README file respective subdirectories (wpa_supplicant/README or +hostapd/README) for more details. + +Source code files were moved around in v0.6.x releases and compared to +earlier releases, the programs are now built by first going to a +subdirectory (wpa_supplicant or hostapd) and creating build +configuration (.config) and running 'make' there (for Linux/BSD/cygwin +builds). + + +License +------- + +This software may be distributed, used, and modified under the terms of +BSD license: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name(s) of the above-listed copyright holder(s) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* + +http://web.mit.edu/freebsd/head/contrib/wpa/COPYING (at the time of writing, 18. January 2020) + +wpa_supplicant and hostapd +-------------------------- + +Copyright (c) 2002-2012, Jouni Malinen and contributors +All Rights Reserved. + + +See the README file for the current license terms. + +This software was previously distributed under BSD/GPL v2 dual license +terms that allowed either of those license alternatives to be +selected. As of February 11, 2012, the project has chosen to use only +the BSD license option for future distribution. As such, the GPL v2 +license option is no longer used. It should be noted that the BSD +license option (the one with advertisement clause removed) is compatible +with GPL and as such, does not prevent use of this software in projects +that use GPL. + +Some of the files may still include pointers to GPL version 2 license +terms. However, such copyright and license notifications are maintained +only for attribution purposes and any distribution of this software +after February 11, 2012 is no longer under the GPL v2 option. + +*/ + +/* https://github.com/gaspardpetit/base64 */ + +#include +#include +#include "include/base64.h" +#include "include/version.h" +#include "include/retcodes.h" + +static const uint8_t TABLE[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const uint8_t URL_SAFE_TABLE[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +int l8w8jwt_base64_encode(const int url, const uint8_t* data, const size_t data_length, char** out, size_t* out_length) +{ + if (out == NULL || data == NULL || out_length == NULL) + { + return L8W8JWT_NULL_ARG; + } + + if (data_length == 0) + { + return L8W8JWT_INVALID_ARG; + } + + size_t olen = data_length * 4 / 3 + 4; + + olen += olen / 72; + olen++; + + if (olen < data_length) + { + return L8W8JWT_OVERFLOW; + } + + *out = malloc(olen); + if (*out == NULL) + { + return L8W8JWT_OUT_OF_MEM; + } + + uint8_t* pos = (uint8_t*)(*out); + uint8_t* in = (uint8_t*)data; + uint8_t* end = (uint8_t*)data + data_length; + + int line_length = 0; + const uint8_t* table = url ? URL_SAFE_TABLE : TABLE; + + while (end - in >= 3) + { + *pos++ = table[in[0] >> 2]; + *pos++ = table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = table[in[2] & 0x3f]; + + in += 3; + + line_length += 4; + if (line_length >= 72 && !url) + { + *pos++ = '\n'; + line_length = 0; + } + } + + int sub = 0; + if (end - in) + { + *pos++ = table[in[0] >> 2]; + + if (end - in == 1) + { + *pos++ = table[(in[0] & 0x03) << 4]; + *pos++ = url ? '\0' : '='; + if (url) + { + sub++; + } + } + else + { + *pos++ = table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = table[(in[1] & 0x0f) << 2]; + } + + *pos++ = url ? '\0' : '='; + line_length += 4; + if (url) + { + sub++; + } + } + + if (line_length && !url) + { + *pos++ = '\n'; + } + + *pos = '\0'; + *out_length = (pos - (uint8_t*)*out) - sub; + + return L8W8JWT_SUCCESS; +} + +int l8w8jwt_base64_decode(const int url, const char* data, const size_t data_length, uint8_t** out, size_t* out_length) +{ + if (data == NULL || out == NULL || out_length == NULL) + { + return L8W8JWT_NULL_ARG; + } + + size_t in_length = data_length; + + if (in_length == 0) + { + return L8W8JWT_INVALID_ARG; + } + + if (*(data + in_length - 1) == '\0') + { + in_length--; + } + + size_t i; + size_t count = 0; + uint8_t dtable[256]; + const uint8_t* table = url ? URL_SAFE_TABLE : TABLE; + + memset(dtable, 0x80, 256); + + for (i = 0; i < 64; i++) + { + dtable[table[i]] = (uint8_t)i; + } + + dtable['='] = 0; + + for (i = 0; i < in_length; i++) + { + if (dtable[(unsigned char)data[i]] != 0x80) + count++; + } + + int r = (int)(count % 4); + + if (count == 0 || r == 1 || (!url && r > 0)) // Invalid input string (format or padding). + return L8W8JWT_INVALID_ARG; + + if (r == 3) + r = 1; + + *out = calloc(count / 4 * 3 + 16, sizeof(uint8_t)); + if (*out == NULL) + { + return L8W8JWT_OUT_OF_MEM; + } + + count = 0; + int pad = 0; + uint8_t tmp; + uint8_t block[4]; + uint8_t* pos = *out; + + for (i = 0; i < in_length + r; i++) + { + const unsigned char c = i < in_length ? data[i] : '='; + + tmp = dtable[c]; + + if (tmp == 0x80) + continue; + + if (c == '=') + pad++; + + block[count] = tmp; + count++; + + if (count == 4) + { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if (pad) + { + if (pad == 1) + { + pos--; + } + else if (pad == 2) + { + pos -= 2; + } + else + { + l8w8jwt_free(*out); + *out = NULL; + return L8W8JWT_INVALID_ARG; // Invalid padding... + } + break; + } + } + } + + *out_length = pos - *out; + + return L8W8JWT_SUCCESS; +} + +/* + * All credits for this base-64 encoding/decoding implementation go to Jouni Malinen. + * I take no credit for this (not even the modifications I made to it!) whatsoever. + * More information at the top of this file. + */ diff --git a/src/thirdparty/jwt/claim.c b/src/thirdparty/jwt/claim.c new file mode 100644 index 00000000..089baff1 --- /dev/null +++ b/src/thirdparty/jwt/claim.c @@ -0,0 +1,147 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "include/claim.h" +#include "include/version.h" +#include "include/retcodes.h" +#include "include/chillbuff.h" + +#include + +#include // SChannel??? + +void l8w8jwt_free_claims(struct l8w8jwt_claim* claims, const size_t claims_count) +{ + if (claims != NULL && claims_count > 0) + { + for (struct l8w8jwt_claim* claim = claims; claim < claims + claims_count; ++claim) + { + if (claim == NULL) + continue; + + l8w8jwt_zero(claim->key, claim->key_length); + l8w8jwt_zero(claim->value, claim->value_length); + + l8w8jwt_free(claim->key); + l8w8jwt_free(claim->value); + } + + l8w8jwt_zero(claims, claims_count * sizeof(struct l8w8jwt_claim)); + l8w8jwt_free(claims); + } +} + +static inline void l8w8jwt_escape_claim_string(struct chillbuff* stringbuilder, const char* string, const size_t string_length) +{ + for (size_t i = 0; i < string_length; ++i) + { + const char c = string[i]; + + switch (c) + { + case '\\': + chillbuff_push_back(stringbuilder, "\\\\", 2); + break; + case '\"': + chillbuff_push_back(stringbuilder, "\\\"", 2); + break; + default: + chillbuff_push_back(stringbuilder, &c, 1); + break; + } + } +} + +int l8w8jwt_write_claims(struct chillbuff* stringbuilder, struct l8w8jwt_claim* claims, const size_t claims_count) +{ + if (stringbuilder == NULL || claims == NULL) + { + return L8W8JWT_NULL_ARG; + } + + if (claims_count == 0) + { + return L8W8JWT_INVALID_ARG; + } + + struct chillbuff escape_buffer; + if (chillbuff_init(&escape_buffer, 256, sizeof(char), CHILLBUFF_GROW_LINEAR) != 0) + { + return L8W8JWT_OUT_OF_MEM; + } + + int first = 1; + for (struct l8w8jwt_claim* claim = claims; claim < claims + claims_count; ++claim) + { + if (claim->key == NULL) + { + continue; + } + + if (!first) + { + chillbuff_push_back(stringbuilder, ",", 1); + } + + const size_t key_length = claim->key_length ? claim->key_length : strlen(claim->key); + const size_t value_length = claim->value_length ? claim->value_length : strlen(claim->value); + + chillbuff_clear(&escape_buffer); + l8w8jwt_escape_claim_string(&escape_buffer, claim->key, key_length); + + chillbuff_push_back(stringbuilder, "\"", 1); + chillbuff_push_back(stringbuilder, escape_buffer.array, escape_buffer.length); + chillbuff_push_back(stringbuilder, "\":", 2); + + if (claim->type == L8W8JWT_CLAIM_TYPE_STRING) + chillbuff_push_back(stringbuilder, "\"", 1); + + chillbuff_clear(&escape_buffer); + l8w8jwt_escape_claim_string(&escape_buffer, claim->value, value_length); + + chillbuff_push_back(stringbuilder, escape_buffer.array,escape_buffer.length); + + if (claim->type == L8W8JWT_CLAIM_TYPE_STRING) + chillbuff_push_back(stringbuilder, "\"", 1); + + first = 0; + } + + chillbuff_free(&escape_buffer); + return L8W8JWT_SUCCESS; +} + +struct l8w8jwt_claim* l8w8jwt_get_claim(struct l8w8jwt_claim* claims, const size_t claims_count, const char* key, const size_t key_length) +{ + if (claims == NULL || key == NULL || claims_count == 0 || key_length == 0) + return NULL; + + for (struct l8w8jwt_claim* claim = claims; claim < claims + claims_count; ++claim) + { + if (strncmp(claim->key, key, key_length) == 0) + return claim; + } + + return NULL; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/thirdparty/jwt/decode.c b/src/thirdparty/jwt/decode.c new file mode 100644 index 00000000..65ecfa25 --- /dev/null +++ b/src/thirdparty/jwt/decode.c @@ -0,0 +1,769 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#define JSMN_STATIC + +#include "include/util.h" +#include "include/decode.h" +#include "include/base64.h" +#include "include/chillbuff.h" + +#include // RapidJSON? +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if L8W8JWT_ENABLE_EDDSA +#include +#endif + +static inline void md_info_from_alg(const int alg, mbedtls_md_info_t** md_info, mbedtls_md_type_t* md_type, size_t* md_length) +{ + switch (alg) + { + case L8W8JWT_ALG_HS256: + case L8W8JWT_ALG_RS256: + case L8W8JWT_ALG_PS256: + case L8W8JWT_ALG_ES256: + case L8W8JWT_ALG_ES256K: + *md_length = 32; + *md_type = MBEDTLS_MD_SHA256; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + break; + + case L8W8JWT_ALG_HS384: + case L8W8JWT_ALG_RS384: + case L8W8JWT_ALG_PS384: + case L8W8JWT_ALG_ES384: + *md_length = 48; + *md_type = MBEDTLS_MD_SHA384; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + break; + + case L8W8JWT_ALG_HS512: + case L8W8JWT_ALG_RS512: + case L8W8JWT_ALG_PS512: + case L8W8JWT_ALG_ES512: + case L8W8JWT_ALG_ED25519: + *md_length = 64; + *md_type = MBEDTLS_MD_SHA512; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + break; + + default: + break; + } +} + +static int l8w8jwt_unescape_claim(struct l8w8jwt_claim* claim, const char* key, const size_t key_length, const char* value, const size_t value_length) +{ + claim->key_length = 0; + claim->key = calloc(sizeof(char), key_length + 1); + + claim->value_length = 0; + claim->value = calloc(sizeof(char), value_length + 1); + + if (claim->key == NULL || claim->value == NULL) + { + free(claim->key); + free(claim->value); + return L8W8JWT_OUT_OF_MEM; + } + + char* out_key = claim->key; + char* out_value = claim->value; + + for (size_t i = 0; i < key_length; ++i) + { + const char c = key[i]; + *out_key = c; + + if (c == '\\' && i != key_length - 1) + { + const char nc = key[i + 1]; + if (nc == '\"') + { + *out_key = '\"'; + } + ++i; + } + + ++out_key; + } + + for (size_t i = 0; i < value_length; ++i) + { + const char c = value[i]; + *out_value = c; + + if (c == '\\' && i != value_length - 1) + { + const char nc = value[i + 1]; + + switch (nc) + { + case '\"': + *out_value = '\"'; + break; + case '/': + *out_value = '/'; + break; + default: + break; + } + + ++i; + } + + ++out_value; + } + + claim->key_length = (size_t)(out_key - claim->key); + claim->value_length = (size_t)(out_value - claim->value); + + return L8W8JWT_SUCCESS; +} + +static int l8w8jwt_parse_claims(chillbuff* buffer, char* json, const size_t json_length) +{ + jsmn_parser parser; + jsmn_init(&parser); + + int r = jsmn_parse(&parser, json, json_length, NULL, 0); + + if (r == 0) + { + return L8W8JWT_SUCCESS; + } + else if (r < 0) + { + return L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + } + + jsmntok_t _tokens[64]; + jsmntok_t* tokens = r <= (sizeof(_tokens) / sizeof(_tokens[0])) ? _tokens : malloc(r * sizeof(jsmntok_t)); + + if (tokens == NULL) + { + return L8W8JWT_OUT_OF_MEM; + } + + jsmn_init(&parser); + r = jsmn_parse(&parser, json, json_length, tokens, r); + + if (r < 0) + { + return L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + } + + if (tokens->type != JSMN_OBJECT) + { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + + for (size_t i = 1; i < r; ++i) + { + struct l8w8jwt_claim claim; + + const jsmntok_t key = tokens[i]; + const jsmntok_t value = tokens[++i]; + + if (i >= r || key.type != JSMN_STRING) + { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + + switch (value.type) + { + case JSMN_UNDEFINED: { + claim.type = L8W8JWT_CLAIM_TYPE_OTHER; + break; + } + case JSMN_OBJECT: { + claim.type = L8W8JWT_CLAIM_TYPE_OBJECT; + break; + } + case JSMN_ARRAY: { + claim.type = L8W8JWT_CLAIM_TYPE_ARRAY; + break; + } + case JSMN_STRING: { + claim.type = L8W8JWT_CLAIM_TYPE_STRING; + break; + } + case JSMN_PRIMITIVE: { + const int value_length = value.end - value.start; + + if (value_length <= 5 && (strncmp(json + value.start, "true", 4) == 0 || strncmp(json + value.start, "false", 5) == 0)) + { + claim.type = L8W8JWT_CLAIM_TYPE_BOOLEAN; + break; + } + + if (value_length == 4 && strncmp(json + value.start, "null", 4) == 0) + { + claim.type = L8W8JWT_CLAIM_TYPE_NULL; + break; + } + + switch (checknum(json + value.start, value_length)) + { + case 1: { + claim.type = L8W8JWT_CLAIM_TYPE_INTEGER; + break; + } + case 2: { + claim.type = L8W8JWT_CLAIM_TYPE_NUMBER; + break; + } + default: { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + } + + break; + } + default: { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + } + + int ur = l8w8jwt_unescape_claim(&claim, json + key.start, (size_t)key.end - key.start, json + value.start, (size_t)value.end - value.start); + if (ur != L8W8JWT_SUCCESS) + { + r = ur; + goto exit; + } + + chillbuff_push_back(buffer, &claim, 1); + } + + r = L8W8JWT_SUCCESS; +exit: + if (tokens != _tokens) + { + l8w8jwt_free(tokens); + } + return r; +} + +void l8w8jwt_decoding_params_init(struct l8w8jwt_decoding_params* params) +{ + if (params == NULL) + { + return; + } + memset(params, 0x00, sizeof(struct l8w8jwt_decoding_params)); + params->alg = -2; +} + +int l8w8jwt_validate_decoding_params(struct l8w8jwt_decoding_params* params) +{ + if (params == NULL || params->jwt == NULL || params->verification_key == NULL) + { + return L8W8JWT_NULL_ARG; + } + + if (params->jwt_length == 0 || params->verification_key_length == 0 || params->verification_key_length > L8W8JWT_MAX_KEY_SIZE) + { + return L8W8JWT_INVALID_ARG; + } + +#if !L8W8JWT_ENABLE_EDDSA + if (params->alg == L8W8JWT_ALG_ED25519) + { + return L8W8JWT_UNSUPPORTED_ALG; + } +#endif + + return L8W8JWT_SUCCESS; +} + +int l8w8jwt_decode(struct l8w8jwt_decoding_params* params, enum l8w8jwt_validation_result* out_validation_result, struct l8w8jwt_claim** out_claims, size_t* out_claims_length) +{ + if (params == NULL || (out_claims != NULL && out_claims_length == NULL)) + { + return L8W8JWT_NULL_ARG; + } + + const int alg = params->alg; + enum l8w8jwt_validation_result validation_res = L8W8JWT_VALID; + + int r = l8w8jwt_validate_decoding_params(params); + if (r != L8W8JWT_SUCCESS) + { + return r; + } + + if (out_validation_result == NULL) + { + return L8W8JWT_NULL_ARG; + } + + *out_validation_result = ~L8W8JWT_VALID; + + char* header = NULL; + size_t header_length = 0; + + char* payload = NULL; + size_t payload_length = 0; + + uint8_t* signature = NULL; + size_t signature_length = 0; + + char* current = params->jwt; + char* next = strchr(params->jwt, '.'); + + if (next == NULL) /* No payload. */ + { + return L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + } + + int is_cert = 0; // If the validation PEM is a X.509 certificate, this will be set to 1. + + mbedtls_pk_context pk; + mbedtls_pk_init(&pk); + + mbedtls_x509_crt crt; + mbedtls_x509_crt_init(&crt); + + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + +#if L8W8JWT_SMALL_STACK + unsigned char* key = calloc(sizeof(unsigned char), L8W8JWT_MAX_KEY_SIZE); + if (key == NULL) + { + r = L8W8JWT_OUT_OF_MEM; + goto exit; + } +#else + unsigned char key[L8W8JWT_MAX_KEY_SIZE] = { 0x00 }; +#endif + + size_t key_length = params->verification_key_length; + memcpy(key, params->verification_key, key_length); + + key_length += key[key_length - 1] != '\0'; + + chillbuff claims; + r = chillbuff_init(&claims, 16, sizeof(struct l8w8jwt_claim), CHILLBUFF_GROW_DUPLICATIVE); + if (r != CHILLBUFF_SUCCESS) + { + r = L8W8JWT_OUT_OF_MEM; + goto exit; + } + + size_t current_length = next - current; + + r = l8w8jwt_base64_decode(true, current, current_length, (uint8_t**)&header, &header_length); + if (r != L8W8JWT_SUCCESS) + { + if (r != L8W8JWT_OUT_OF_MEM) + r = L8W8JWT_BASE64_FAILURE; + goto exit; + } + + current = next + 1; + next = strchr(current, '.'); + + if (next == NULL && alg != -1) /* No signature. */ + { + r = L8W8JWT_DECODE_FAILED_MISSING_SIGNATURE; + goto exit; + } + + current_length = (next != NULL ? next : params->jwt + params->jwt_length) - current; + + r = l8w8jwt_base64_decode(true, current, current_length, (uint8_t**)&payload, &payload_length); + if (r != L8W8JWT_SUCCESS) + { + if (r != L8W8JWT_OUT_OF_MEM) + r = L8W8JWT_BASE64_FAILURE; + goto exit; + } + + if (next != NULL) + { + current = next + 1; + current_length = (params->jwt + params->jwt_length) - current; + + r = l8w8jwt_base64_decode(true, current, current_length, &signature, &signature_length); + if (r != L8W8JWT_SUCCESS) + { + if (r != L8W8JWT_OUT_OF_MEM) + r = L8W8JWT_BASE64_FAILURE; + goto exit; + } + } + + /* Signature verification. */ + if (signature != NULL && signature_length > 0 && alg != -1) + { + is_cert = strstr((const char*)key, "-----BEGIN CERTIFICATE-----") != NULL; + if (is_cert) + { + r = mbedtls_x509_crt_parse(&crt, key, key_length); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + + pk = crt.pk; + } + + size_t md_length; + mbedtls_md_type_t md_type; + mbedtls_md_info_t* md_info; + + md_info_from_alg(alg, &md_info, &md_type, &md_length); + + unsigned char hash[64] = { 0x00 }; + + switch (alg) + { + case L8W8JWT_ALG_ES256: + case L8W8JWT_ALG_ES384: + case L8W8JWT_ALG_ES512: + case L8W8JWT_ALG_RS256: + case L8W8JWT_ALG_RS384: + case L8W8JWT_ALG_RS512: + case L8W8JWT_ALG_PS256: + case L8W8JWT_ALG_PS384: + case L8W8JWT_ALG_PS512: + case L8W8JWT_ALG_ES256K: { + + r = mbedtls_md(md_info, (const unsigned char*)params->jwt, (current - 1) - params->jwt, hash); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_SHA2_FAILURE; + goto exit; + } + break; + } + default: + break; + } + + switch (alg) + { + case L8W8JWT_ALG_HS256: + case L8W8JWT_ALG_HS384: + case L8W8JWT_ALG_HS512: { + + unsigned char signature_cmp[64]; + memset(signature_cmp, '\0', sizeof(signature_cmp)); + + r = mbedtls_md_hmac(md_info, key, key_length - 1, (const unsigned char*)params->jwt, (current - 1) - params->jwt, signature_cmp); + if (r != 0) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + break; + } + + r = memcmp(signature, signature_cmp, 32 + (16 * alg)); + if (r != 0) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + break; + } + + break; + } + case L8W8JWT_ALG_RS256: + case L8W8JWT_ALG_RS384: + case L8W8JWT_ALG_RS512: { + + if (!is_cert) + { + r = mbedtls_pk_parse_public_key(&pk, key, key_length); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + } + + r = mbedtls_pk_verify(&pk, md_type, hash, md_length, (const unsigned char*)signature, signature_length); + if (r != 0) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + break; + } + + break; + } + case L8W8JWT_ALG_PS256: + case L8W8JWT_ALG_PS384: + case L8W8JWT_ALG_PS512: { + + if (!is_cert) + { + r = mbedtls_pk_parse_public_key(&pk, key, key_length); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + } + + mbedtls_rsa_context* rsa = mbedtls_pk_rsa(pk); + mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, md_type); + + r = mbedtls_rsa_rsassa_pss_verify(rsa, md_type, md_length, hash, signature); + if (r != 0) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + break; + } + + break; + } + case L8W8JWT_ALG_ES256: + case L8W8JWT_ALG_ES256K: + case L8W8JWT_ALG_ES384: + case L8W8JWT_ALG_ES512: { + + if (!is_cert) + { + r = mbedtls_pk_parse_public_key(&pk, key, key_length); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + } + + const size_t half_signature_length = signature_length / 2; + + mbedtls_ecdsa_context ecdsa; + mbedtls_ecdsa_init(&ecdsa); + + mbedtls_mpi sig_r, sig_s; + mbedtls_mpi_init(&sig_r); + mbedtls_mpi_init(&sig_s); + + r = mbedtls_ecdsa_from_keypair(&ecdsa, mbedtls_pk_ec(pk)); + + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + mbedtls_ecdsa_free(&ecdsa); + mbedtls_mpi_free(&sig_r); + mbedtls_mpi_free(&sig_s); + goto exit; + } + + mbedtls_mpi_read_binary(&sig_r, signature, half_signature_length); + mbedtls_mpi_read_binary(&sig_s, signature + half_signature_length, half_signature_length); + + r = mbedtls_ecdsa_verify(&ecdsa.MBEDTLS_PRIVATE(grp), hash, md_length, &ecdsa.MBEDTLS_PRIVATE(Q), &sig_r, &sig_s); + if (r != 0) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + } + + mbedtls_ecdsa_free(&ecdsa); + mbedtls_mpi_free(&sig_r); + mbedtls_mpi_free(&sig_s); + + break; + } + case L8W8JWT_ALG_ED25519: { + +#if L8W8JWT_ENABLE_EDDSA + if (key_length != 64 && !(key_length == 65 && key[64] == 0x00)) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + unsigned char public_key[32 + 1] = { 0x00 }; + + if (l8w8jwt_hexstr2bin((const char*)key, key_length, public_key, sizeof(public_key), NULL) != 0) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + if (!ed25519_verify(signature, (const unsigned char*)params->jwt, (current - 1) - params->jwt, public_key)) + { + validation_res |= (unsigned)L8W8JWT_SIGNATURE_VERIFICATION_FAILURE; + break; + } + + break; +#else + r = L8W8JWT_UNSUPPORTED_ALG; + goto exit; +#endif + } + default: + break; + } + } + + r = l8w8jwt_parse_claims(&claims, header, header_length); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + + r = l8w8jwt_parse_claims(&claims, payload, payload_length); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT; + goto exit; + } + + if (params->validate_sub != NULL) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "sub", 3); + if (c == NULL || strncmp(c->value, params->validate_sub, params->validate_sub_length ? params->validate_sub_length : strlen(params->validate_sub)) != 0) + { + validation_res |= (unsigned)L8W8JWT_SUB_FAILURE; + } + } + + if (params->validate_aud != NULL) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "aud", 3); + if (c == NULL || strncmp(c->value, params->validate_aud, params->validate_aud_length ? params->validate_aud_length : strlen(params->validate_aud)) != 0) + { + validation_res |= (unsigned)L8W8JWT_AUD_FAILURE; + } + } + + if (params->validate_iss != NULL) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "iss", 3); + if (c == NULL || strncmp(c->value, params->validate_iss, params->validate_iss_length ? params->validate_iss_length : strlen(params->validate_iss)) != 0) + { + validation_res |= (unsigned)L8W8JWT_ISS_FAILURE; + } + } + + if (params->validate_jti != NULL) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "jti", 3); + if (c == NULL || strncmp(c->value, params->validate_jti, params->validate_jti_length ? params->validate_jti_length : strlen(params->validate_jti)) != 0) + { + validation_res |= (unsigned)L8W8JWT_JTI_FAILURE; + } + } + + const time_t ct = time(NULL); + + if (params->validate_exp) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "exp", 3); + if (c == NULL || ct - params->exp_tolerance_seconds > strtoll(c->value, NULL, 10)) + { + validation_res |= (unsigned)L8W8JWT_EXP_FAILURE; + } + } + + if (params->validate_nbf) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "nbf", 3); + if (c == NULL || ct + params->nbf_tolerance_seconds < strtoll(c->value, NULL, 10)) + { + validation_res |= (unsigned)L8W8JWT_NBF_FAILURE; + } + } + + if (params->validate_iat) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "iat", 3); + if (c == NULL || ct + params->iat_tolerance_seconds < strtoll(c->value, NULL, 10)) + { + validation_res |= (unsigned)L8W8JWT_IAT_FAILURE; + } + } + + if (params->validate_typ) + { + struct l8w8jwt_claim* c = l8w8jwt_get_claim(claims.array, claims.length, "typ", 3); + if (c == NULL || l8w8jwt_strncmpic(c->value, params->validate_typ, params->validate_typ_length) != 0) + { + validation_res |= (unsigned)L8W8JWT_TYP_FAILURE; + } + } + + r = L8W8JWT_SUCCESS; + *out_validation_result = validation_res; + + if (out_claims != NULL && out_claims_length != NULL) + { + *out_claims_length = claims.length; + *out_claims = (struct l8w8jwt_claim*)claims.array; + } + +exit: + l8w8jwt_free(header); + l8w8jwt_free(payload); + l8w8jwt_free(signature); + + if (out_claims == NULL || r != L8W8JWT_SUCCESS) + { + l8w8jwt_free_claims((struct l8w8jwt_claim*)claims.array, claims.length); + } + + mbedtls_platform_zeroize(key, L8W8JWT_MAX_KEY_SIZE); +#if L8W8JWT_SMALL_STACK + l8w8jwt_free(key); +#endif + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + if (is_cert) + { + mbedtls_x509_crt_free(&crt); + } + else + { + mbedtls_pk_free(&pk); + } + + return r; +} + +#undef JSMN_STATIC + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/thirdparty/jwt/encode.c b/src/thirdparty/jwt/encode.c new file mode 100644 index 00000000..081901e2 --- /dev/null +++ b/src/thirdparty/jwt/encode.c @@ -0,0 +1,685 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "include/util.h" +#include "include/encode.h" +#include "include/base64.h" +#include "include/chillbuff.h" + +#include +#include +#include +#include +#include +#include + +#if L8W8JWT_ENABLE_EDDSA +#include +#endif + +static inline void md_info_from_alg(const int alg, mbedtls_md_info_t** md_info, mbedtls_md_type_t* md_type, size_t* md_length) +{ + switch (alg) + { + case L8W8JWT_ALG_HS256: + case L8W8JWT_ALG_RS256: + case L8W8JWT_ALG_PS256: + case L8W8JWT_ALG_ES256: + case L8W8JWT_ALG_ES256K: + *md_length = 32; + *md_type = MBEDTLS_MD_SHA256; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + break; + + case L8W8JWT_ALG_HS384: + case L8W8JWT_ALG_RS384: + case L8W8JWT_ALG_PS384: + case L8W8JWT_ALG_ES384: + *md_length = 48; + *md_type = MBEDTLS_MD_SHA384; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + break; + + case L8W8JWT_ALG_HS512: + case L8W8JWT_ALG_RS512: + case L8W8JWT_ALG_PS512: + case L8W8JWT_ALG_ES512: + *md_length = 64; + *md_type = MBEDTLS_MD_SHA512; + *md_info = (mbedtls_md_info_t*)mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + break; + + default: + break; + } +} + +/* Step 1: prepare the token by encoding header + payload claims into a stringbuilder, ready to be signed! */ +static int write_header_and_payload(chillbuff* stringbuilder, struct l8w8jwt_encoding_params* params) +{ + int r; + chillbuff buff; + + r = chillbuff_init(&buff, 256, sizeof(char), CHILLBUFF_GROW_DUPLICATIVE); + if (r != CHILLBUFF_SUCCESS) + { + return L8W8JWT_OUT_OF_MEM; + } + + switch (params->alg) + { + case L8W8JWT_ALG_HS256: + chillbuff_push_back(&buff, "{\"alg\":\"HS256\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_HS384: + chillbuff_push_back(&buff, "{\"alg\":\"HS384\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_HS512: + chillbuff_push_back(&buff, "{\"alg\":\"HS512\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_RS256: + chillbuff_push_back(&buff, "{\"alg\":\"RS256\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_RS384: + chillbuff_push_back(&buff, "{\"alg\":\"RS384\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_RS512: + chillbuff_push_back(&buff, "{\"alg\":\"RS512\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_PS256: + chillbuff_push_back(&buff, "{\"alg\":\"PS256\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_PS384: + chillbuff_push_back(&buff, "{\"alg\":\"PS384\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_PS512: + chillbuff_push_back(&buff, "{\"alg\":\"PS512\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_ES256: + chillbuff_push_back(&buff, "{\"alg\":\"ES256\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_ES384: + chillbuff_push_back(&buff, "{\"alg\":\"ES384\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_ES512: + chillbuff_push_back(&buff, "{\"alg\":\"ES512\",\"typ\":\"JWT\"", 26); + break; + case L8W8JWT_ALG_ES256K: + chillbuff_push_back(&buff, "{\"alg\":\"ES256K\",\"typ\":\"JWT\",\"kty\":\"EC\",\"crv\":\"secp256k1\"", 56); + break; + case L8W8JWT_ALG_ED25519: + chillbuff_push_back(&buff, "{\"alg\":\"EdDSA\",\"typ\":\"JWT\",\"kty\":\"EC\",\"crv\":\"Ed25519\"", 53); + break; + default: + chillbuff_free(&buff); + return L8W8JWT_INVALID_ARG; + } + + if (params->additional_header_claims_count > 0) + { + chillbuff_push_back(&buff, ",", 1); + l8w8jwt_write_claims(&buff, params->additional_header_claims, params->additional_header_claims_count); + } + + chillbuff_push_back(&buff, "}", 1); + + char* segment; + size_t segment_length; + + r = l8w8jwt_base64_encode(1, buff.array, buff.length, &segment, &segment_length); + if (r != L8W8JWT_SUCCESS) + { + chillbuff_free(&buff); + return r; + } + + chillbuff_push_back(stringbuilder, segment, segment_length); + + chillbuff_clear(&buff); + + l8w8jwt_free(segment); + segment = NULL; + + char iatnbfexp[64] = { 0x00 }; + + if (params->iat) + { + snprintf(iatnbfexp + 00, 21, "%" PRIu64 "", (uint64_t)params->iat); + } + + if (params->nbf) + { + snprintf(iatnbfexp + 21, 21, "%" PRIu64 "", (uint64_t)params->nbf); + } + + if (params->exp) + { + snprintf(iatnbfexp + 42, 21, "%" PRIu64 "", (uint64_t)params->exp); + } + + struct l8w8jwt_claim claims[] = { + // Setting l8w8jwt_claim::value_length to 0 makes the encoder use strlen, which in this case is fine. + { .key = *(iatnbfexp + 00) ? "iat" : NULL, .key_length = 3, .value = iatnbfexp + 00, .value_length = 0, .type = L8W8JWT_CLAIM_TYPE_INTEGER }, + { .key = *(iatnbfexp + 21) ? "nbf" : NULL, .key_length = 3, .value = iatnbfexp + 21, .value_length = 0, .type = L8W8JWT_CLAIM_TYPE_INTEGER }, + { .key = *(iatnbfexp + 42) ? "exp" : NULL, .key_length = 3, .value = iatnbfexp + 42, .value_length = 0, .type = L8W8JWT_CLAIM_TYPE_INTEGER }, + { .key = params->sub ? "sub" : NULL, .key_length = 3, .value = params->sub, .value_length = params->sub_length, .type = L8W8JWT_CLAIM_TYPE_STRING }, + { .key = params->iss ? "iss" : NULL, .key_length = 3, .value = params->iss, .value_length = params->iss_length, .type = L8W8JWT_CLAIM_TYPE_STRING }, + { .key = params->aud ? "aud" : NULL, .key_length = 3, .value = params->aud, .value_length = params->aud_length, .type = L8W8JWT_CLAIM_TYPE_STRING }, + { .key = params->jti ? "jti" : NULL, .key_length = 3, .value = params->jti, .value_length = params->jti_length, .type = L8W8JWT_CLAIM_TYPE_STRING }, + }; + + chillbuff_push_back(&buff, "{", 1); + + l8w8jwt_write_claims(&buff, claims, sizeof(claims) / sizeof(struct l8w8jwt_claim)); + + if (params->additional_payload_claims_count > 0) + { + if (params->iat || params->exp || params->nbf || params->iss_length || params->sub_length || params->jti_length || params->aud_length) + chillbuff_push_back(&buff, ",", 1); + + l8w8jwt_write_claims(&buff, params->additional_payload_claims, params->additional_payload_claims_count); + } + + chillbuff_push_back(&buff, "}", 1); + + r = l8w8jwt_base64_encode(1, buff.array, buff.length, &segment, &segment_length); + if (r != L8W8JWT_SUCCESS) + { + chillbuff_free(&buff); + return r; + } + + chillbuff_push_back(stringbuilder, ".", 1); + chillbuff_push_back(stringbuilder, segment, segment_length); + + l8w8jwt_free(segment); + chillbuff_free(&buff); + + return L8W8JWT_SUCCESS; +} + +/* Step 2: call write_header_and_payload before you call this! */ +static int write_signature(chillbuff* stringbuilder, struct l8w8jwt_encoding_params* params) +{ + int r; + const int alg = params->alg; + + char* signature = NULL; + size_t signature_length = 0, signature_bytes_length = 0, key_length = 0; + + mbedtls_pk_context pk; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_pk_init(&pk); + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + +#if L8W8JWT_SMALL_STACK + unsigned char* signature_bytes = calloc(sizeof(unsigned char), 4096); + unsigned char* key = calloc(sizeof(unsigned char), L8W8JWT_MAX_KEY_SIZE); + + if (signature_bytes == NULL || key == NULL) + { + r = L8W8JWT_OUT_OF_MEM; + goto exit; + } +#else + unsigned char signature_bytes[4096] = { 0x00 }; + unsigned char key[L8W8JWT_MAX_KEY_SIZE] = { 0x00 }; +#endif + + key_length = params->secret_key_length; + memcpy(key, params->secret_key, key_length); + + /* + * MbedTLS requires the NUL-terminator to be included + * in the PEM-formatted key string passed to the key parse function. + * HMAC-key variants should subtract 1 from key_length again to compensate. + */ + key_length += key[key_length - 1] != '\0'; + + r = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char*)"l8w8jwt_mbedtls_pers.!#@", 24); + if (r != 0) + { + r = L8W8JWT_MBEDTLS_CTR_DRBG_SEED_FAILURE; + goto exit; + } + + size_t md_length; + mbedtls_md_type_t md_type; + mbedtls_md_info_t* md_info; + + md_info_from_alg(alg, &md_info, &md_type, &md_length); + + unsigned char hash[64] = { 0x00 }; + + switch (alg) + { + case L8W8JWT_ALG_HS256: + case L8W8JWT_ALG_HS384: + case L8W8JWT_ALG_HS512: { + + /* + * "key_length - 1" because the MbedTLS implementation of HMAC + * does not require its key string to include the NUL-terminator, + * unlike the RSA/ECC PEM key parse function "mbedtls_pk_parse_key", + * which MUST include the '\0' in the PEM-formatted key string. + */ + r = mbedtls_md_hmac(md_info, key, key_length - 1, (const unsigned char*)stringbuilder->array, stringbuilder->length, signature_bytes); + if (r != 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto exit; + } + + signature_bytes_length = 32 + (16 * params->alg); + break; + } + case L8W8JWT_ALG_RS256: + case L8W8JWT_ALG_RS384: + case L8W8JWT_ALG_RS512: { + + /* Parse & load the key string into the mbedtls pk instance. */ + + r = mbedtls_pk_parse_key(&pk, key, key_length, params->secret_key_pw, params->secret_key_pw_length, mbedtls_ctr_drbg_random, &ctr_drbg); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + + /* Ensure RSA functionality. */ + if (!mbedtls_pk_can_do(&pk, MBEDTLS_PK_RSA) && !mbedtls_pk_can_do(&pk, MBEDTLS_PK_RSA_ALT)) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + /* Weak RSA keys are forbidden! */ + if (mbedtls_pk_get_bitlen(&pk) < 2048) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + /* Hash the JWT header + payload. */ + r = mbedtls_md(md_info, (const unsigned char*)stringbuilder->array, stringbuilder->length, hash); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_SHA2_FAILURE; + goto exit; + } + + /* Sign the hash using the provided private key. */ + r = mbedtls_pk_sign(&pk, md_type, hash, md_length, signature_bytes, 4096, &signature_bytes_length, mbedtls_ctr_drbg_random, &ctr_drbg); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto exit; + } + + break; + } + case L8W8JWT_ALG_PS256: + case L8W8JWT_ALG_PS384: + case L8W8JWT_ALG_PS512: { + + r = mbedtls_pk_parse_key(&pk, key, key_length, params->secret_key_pw, params->secret_key_pw_length, mbedtls_ctr_drbg_random, &ctr_drbg); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto exit; + } + + if (!mbedtls_pk_can_do(&pk, MBEDTLS_PK_RSASSA_PSS)) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + if (mbedtls_pk_get_bitlen(&pk) < 2048) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + r = mbedtls_md(md_info, (const unsigned char*)stringbuilder->array, stringbuilder->length, hash); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_SHA2_FAILURE; + goto exit; + } + + mbedtls_rsa_context* rsa = mbedtls_pk_rsa(pk); + mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, md_type); + + r = mbedtls_rsa_rsassa_pss_sign(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, md_type, (unsigned int)md_length, hash, signature_bytes); + if (r != 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto exit; + } + + signature_bytes_length = mbedtls_pk_get_bitlen(&pk) / 8; + break; + } + case L8W8JWT_ALG_ES256: + case L8W8JWT_ALG_ES384: + case L8W8JWT_ALG_ES512: + case L8W8JWT_ALG_ES256K: { + + mbedtls_ecdsa_context ecdsa; + mbedtls_ecdsa_init(&ecdsa); + + mbedtls_mpi sig_r, sig_s; + mbedtls_mpi_init(&sig_r); + mbedtls_mpi_init(&sig_s); + + r = mbedtls_pk_parse_key(&pk, key, key_length, params->secret_key_pw, params->secret_key_pw_length, mbedtls_ctr_drbg_random, &ctr_drbg); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto ecdsa_exit; + } + + if (!mbedtls_pk_can_do(&pk, MBEDTLS_PK_ECDSA)) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + r = mbedtls_ecdsa_from_keypair(&ecdsa, mbedtls_pk_ec(pk)); + if (r != 0) + { + r = L8W8JWT_KEY_PARSE_FAILURE; + goto ecdsa_exit; + } + + r = mbedtls_md(md_info, (const unsigned char*)stringbuilder->array, stringbuilder->length, hash); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_SHA2_FAILURE; + goto ecdsa_exit; + } + + r = 0; + switch (alg) + { + case L8W8JWT_ALG_ES256: { + + if (ecdsa.MBEDTLS_PRIVATE(grp).id != MBEDTLS_ECP_DP_SECP256R1) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + signature_bytes_length = 64; + r = mbedtls_pk_get_bitlen(&pk) == 256; + break; + } + case L8W8JWT_ALG_ES256K: { + + if (ecdsa.MBEDTLS_PRIVATE(grp).id != MBEDTLS_ECP_DP_SECP256K1) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + signature_bytes_length = 64; + r = mbedtls_pk_get_bitlen(&pk) == 256; + break; + } + case L8W8JWT_ALG_ES384: { + + if (ecdsa.MBEDTLS_PRIVATE(grp).id != MBEDTLS_ECP_DP_SECP384R1) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + signature_bytes_length = 96; + r = mbedtls_pk_get_bitlen(&pk) == 384; + break; + } + case L8W8JWT_ALG_ES512: { + + if (ecdsa.MBEDTLS_PRIVATE(grp).id != MBEDTLS_ECP_DP_SECP521R1) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + signature_bytes_length = 132; + r = mbedtls_pk_get_bitlen(&pk) == 521; + break; + } + default: + break; + } + + /* + * Ensure that the passed elliptic-curve cryptography key + * has a size that is valid and compatible with the selected JWT alg. + */ + if (r == 0) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto ecdsa_exit; + } + + r = mbedtls_ecdsa_sign(&ecdsa.MBEDTLS_PRIVATE(grp), &sig_r, &sig_s, &ecdsa.MBEDTLS_PRIVATE(d), hash, md_length, mbedtls_ctr_drbg_random, &ctr_drbg); + if (r != 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto ecdsa_exit; + } + + const size_t half_signature_bytes_length = signature_bytes_length / 2; + + r = mbedtls_mpi_write_binary(&sig_r, signature_bytes, half_signature_bytes_length); + if (r != 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto ecdsa_exit; + } + + r = mbedtls_mpi_write_binary(&sig_s, signature_bytes + half_signature_bytes_length, half_signature_bytes_length); + if (r != 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto ecdsa_exit; + } + + r = L8W8JWT_SUCCESS; + + ecdsa_exit: + mbedtls_mpi_free(&sig_r); + mbedtls_mpi_free(&sig_s); + mbedtls_ecdsa_free(&ecdsa); + if (r != L8W8JWT_SUCCESS) + { + goto exit; + } + break; + } + case L8W8JWT_ALG_ED25519: { + +#if L8W8JWT_ENABLE_EDDSA + if (params->secret_key_length != 128 && !(params->secret_key_length == 129 && params->secret_key[128] == 0x00)) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + unsigned char private_key_ref10[64 + 1] = { 0x00 }; + + if (l8w8jwt_hexstr2bin((const char*)params->secret_key, params->secret_key_length, private_key_ref10, sizeof(private_key_ref10), NULL) != 0) + { + r = L8W8JWT_WRONG_KEY_TYPE; + goto exit; + } + + ed25519_sign_ref10(signature_bytes, (const unsigned char*)stringbuilder->array, stringbuilder->length, private_key_ref10); + signature_bytes_length = 64; + + l8w8jwt_zero(private_key_ref10, sizeof(private_key_ref10)); + break; +#else + r = L8W8JWT_UNSUPPORTED_ALG; + goto exit; +#endif + } + default: { + r = L8W8JWT_INVALID_ARG; + goto exit; + } + } + + if (signature_bytes_length == 0) + { + r = L8W8JWT_SIGNATURE_CREATION_FAILURE; + goto exit; + } + + /* + * If this succeeds, it mallocs "signature" and assigns the resulting string length to "signature_length". + */ + r = l8w8jwt_base64_encode(1, (uint8_t*)signature_bytes, signature_bytes_length, &signature, &signature_length); + if (r != L8W8JWT_SUCCESS) + { + r = L8W8JWT_BASE64_FAILURE; + goto exit; + } + + chillbuff_push_back(stringbuilder, ".", 1); + chillbuff_push_back(stringbuilder, signature, signature_length); + +exit: + l8w8jwt_zero(key, L8W8JWT_MAX_KEY_SIZE); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + mbedtls_pk_free(&pk); + l8w8jwt_free(signature); +#if L8W8JWT_SMALL_STACK + l8w8jwt_free(key); + l8w8jwt_free(signature_bytes); +#endif + + return r; +} + +/* Step 3: finalize the token by writing it into the "out" string defined in the l8w8jwt_encoding_params argument. */ +static int write_token(chillbuff* stringbuilder, struct l8w8jwt_encoding_params* params) +{ + *(params->out) = malloc(stringbuilder->length + 1); + if (*(params->out) == NULL) + { + return L8W8JWT_OUT_OF_MEM; + } + + *(params->out_length) = stringbuilder->length; + (*(params->out))[stringbuilder->length] = '\0'; + memcpy(*(params->out), stringbuilder->array, stringbuilder->length); + + return L8W8JWT_SUCCESS; +} + +void l8w8jwt_encoding_params_init(struct l8w8jwt_encoding_params* params) +{ + if (params == NULL) + { + return; + } + memset(params, 0x00, sizeof(struct l8w8jwt_encoding_params)); + params->alg = -2; +} + +int l8w8jwt_validate_encoding_params(struct l8w8jwt_encoding_params* params) +{ + if (params == NULL || params->secret_key == NULL || params->out == NULL || params->out_length == NULL) + { + return L8W8JWT_NULL_ARG; + } + + if (params->secret_key_length == 0 || params->secret_key_length > L8W8JWT_MAX_KEY_SIZE) + { + return L8W8JWT_INVALID_ARG; + } + + if ((params->additional_payload_claims != NULL && params->additional_payload_claims_count == 0)) + { + return L8W8JWT_INVALID_ARG; + } + + if ((params->additional_header_claims != NULL && params->additional_header_claims_count == 0)) + { + return L8W8JWT_INVALID_ARG; + } + + return L8W8JWT_SUCCESS; +} + +int l8w8jwt_encode(struct l8w8jwt_encoding_params* params) +{ + int r; + chillbuff stringbuilder; + + r = l8w8jwt_validate_encoding_params(params); + if (r != L8W8JWT_SUCCESS) + { + return r; + } + + r = chillbuff_init(&stringbuilder, 1024, sizeof(char), CHILLBUFF_GROW_DUPLICATIVE); + if (r != CHILLBUFF_SUCCESS) + { + return L8W8JWT_OUT_OF_MEM; + } + + r = write_header_and_payload(&stringbuilder, params); + if (r != L8W8JWT_SUCCESS) + { + goto exit; + } + + if (params->alg != -1) + { + r = write_signature(&stringbuilder, params); + if (r != L8W8JWT_SUCCESS) + { + goto exit; + } + } + + r = write_token(&stringbuilder, params); + if (r != L8W8JWT_SUCCESS) + { + goto exit; + } + +exit: + chillbuff_free(&stringbuilder); + return r; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/thirdparty/jwt/include/algs.h b/src/thirdparty/jwt/include/algs.h new file mode 100644 index 00000000..63aabbcb --- /dev/null +++ b/src/thirdparty/jwt/include/algs.h @@ -0,0 +1,111 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file algs.h + * @author Raphael Beck + * @brief JWT algorithms as defined in https://tools.ietf.org/html/rfc7518#section-3.1 + */ + +#ifndef L8W8JWT_ALGS_H +#define L8W8JWT_ALGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * HMAC-SHA256 signing algorithm. + */ +#define L8W8JWT_ALG_HS256 0 + +/** + * HMAC-SHA384 signing algorithm. + */ +#define L8W8JWT_ALG_HS384 1 + +/** + * HMAC-SHA512 signing algorithm. + */ +#define L8W8JWT_ALG_HS512 2 + +/** + * RSASSA-PKCS1-v1_5-SHA256 signing algorithm. + */ +#define L8W8JWT_ALG_RS256 3 + +/** + * RSASSA-PKCS1-v1_5-SHA384 signing algorithm. + */ +#define L8W8JWT_ALG_RS384 4 + +/** + * RSASSA-PKCS1-v1_5-SHA512 signing algorithm. + */ +#define L8W8JWT_ALG_RS512 5 + +/** + * RSASSA-PSS MGF1 SHA-256 signing algorithm. + */ +#define L8W8JWT_ALG_PS256 6 + +/** + * RSASSA-PSS MGF1 SHA-384 signing algorithm. + */ +#define L8W8JWT_ALG_PS384 7 + +/** + * RSASSA-PSS MGF1 SHA-512 signing algorithm. + */ +#define L8W8JWT_ALG_PS512 8 + +/** + * ECDSA + P-256 + SHA256 signing algorithm. + */ +#define L8W8JWT_ALG_ES256 9 + +/** + * ECDSA + P-384 + SHA384 signing algorithm. + */ +#define L8W8JWT_ALG_ES384 10 + +/** + * ECDSA + P-521 + SHA512 signing algorithm. + */ +#define L8W8JWT_ALG_ES512 11 + +/** + * ECDSA over secp256k1 + SHA256 signing algorithm. + */ +#define L8W8JWT_ALG_ES256K 12 + +/** + * EdDSA over ed25519 + SHA512 signing algorithm. + */ +#define L8W8JWT_ALG_ED25519 13 + +#ifndef L8W8JWT_ENABLE_EDDSA +/** + * Set this to \c 1 if you want to enable the EdDSA signing algorithm + */ +#define L8W8JWT_ENABLE_EDDSA 0 +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_ALGS_H diff --git a/src/thirdparty/jwt/include/base64.h b/src/thirdparty/jwt/include/base64.h new file mode 100644 index 00000000..f3124fc8 --- /dev/null +++ b/src/thirdparty/jwt/include/base64.h @@ -0,0 +1,77 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file base64.h + * @author Raphael Beck + * @brief Base-64 encode and decode strings/bytes.

+ * @warning The caller is responsible for freeing the returned buffers!

+ * Pass true as first parameter if you want to use base64url encoding instead of base64. + * @see https://en.wikipedia.org/wiki/Base64#URL_applications + */ + +#ifndef L8W8JWT_BASE64_H +#define L8W8JWT_BASE64_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "version.h" +#include +#include + +/** + * Encodes a byte array to a base-64 string.

+ * If you're encoding a string, don't include the NUL terminator + * (pass strlen(data) instead of the array's size to the data_length parameter).

+ * + * @note The output buffer is NUL-terminated to make it easier to use as a C string. + * @note The NUL terminator is NOT included in the out_length. + * @note DO NOT FORGET to free the output buffer once you're done using it! + * + * @param url base64url encode instead of base64? Set to \c 0 for \c false; anything else for \c true. + * @param data The data (array of bytes) to base-64 encode. + * @param data_length The length of the input data array (in case of a C string: array size - 1 in order to omit the NUL terminator). + * @param out Output where the base-64 encoded string should be written into (will be malloc'ed, so make sure to free() this as soon as you're done using it!). + * @param out_length Pointer to a size_t variable containing the length of the output buffer minus the NUL terminator. + * + * @return Return code as defined in retcodes.h + */ +L8W8JWT_API int l8w8jwt_base64_encode(int url, const uint8_t* data, size_t data_length, char** out, size_t* out_length); + +/** + * Decodes a base-64 encoded string to an array of bytes.

+ * + * @note The returned bytes buffer is NUL-terminated to allow usage as a C string. + * @note The NUL terminator is NOT included in the out_length. + * @note DO NOT FORGET to free the output buffer once you're done using it! + * + * @param url Decode using base64url instead of base64? Set to \c 0 for \c false; anything else for \c true. + * @param data The base-64 encoded string to decode (obtained via {@link #l8w8jwt_base64_encode}). + * @param data_length The length of the string to decode. + * @param out Output where the decoded bytes should be written into (will be malloc'ed, so make sure to free() this as soon as you're done using it!). + * @param out_length Pointer to a size_t variable into which to write the output buffer's length. + * + * @return Return code as defined in retcodes.h + */ +L8W8JWT_API int l8w8jwt_base64_decode(int url, const char* data, size_t data_length, uint8_t** out, size_t* out_length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_BASE64_H diff --git a/src/thirdparty/jwt/include/chillbuff.h b/src/thirdparty/jwt/include/chillbuff.h new file mode 100644 index 00000000..49deca76 --- /dev/null +++ b/src/thirdparty/jwt/include/chillbuff.h @@ -0,0 +1,326 @@ +/* + Copyright 2019 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file chillbuff.h + * @author Raphael Beck + * @date 27. December 2019 + * @brief Array. Dynamic size. Push back 'n' chill. Buffer stuff. Dynamic stuff that's buff. Dynamically reallocating buff.. Yeah! + * @see https://github.com/GlitchedPolygons/chillbuff + */ + +#ifndef CHILLBUFF_H +#define CHILLBUFF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/* The following are the chillbuff exit codes returned from the various chillbuff functions. */ + +/** + * Returned from a chillbuff function when everything went smooth 'n' chill. Time to get Schwifty! + */ +#define CHILLBUFF_SUCCESS 0 + +/** + * Chill time is over, you're out of memory... Time to reconsider memory usage. + */ +#define CHILLBUFF_OUT_OF_MEM 100 + +/** + * Error code returned by a chillbuff function if you passed a NULL argument that shouldn't have been NULL. + */ +#define CHILLBUFF_NULL_ARG 200 + +/** + * This error code is returned by a chillbuff function if you passed an invalid parameter into it. + */ +#define CHILLBUFF_INVALID_ARG 300 + +/** + * Not good... + */ +#define CHILLBUFF_OVERFLOW 400 + +/** @private */ +static void (*_chillbuff_error_callback)(const char*) = NULL; + +/** + * How should the chillbuff's underlying array grow in size + * once its maximum capacity is reached during a push_back? + */ +typedef enum chillbuff_growth_method +{ + /** + * Double the capacity. + */ + CHILLBUFF_GROW_DUPLICATIVE = 0, + + /** + * Triple the capacity. + */ + CHILLBUFF_GROW_TRIPLICATIVE = 1, + + /** + * Grow by the same capacity every time the buffer is full. + */ + CHILLBUFF_GROW_LINEAR = 2, + + /** + * Multiplies the capacity by itself. Not the greatest idea... Use carefully! + */ + CHILLBUFF_GROW_EXPONENTIAL = 3 +} chillbuff_growth_method; + +/** + * Self-reallocating dynamic size array of no strictly defined type. + * Easy 'n' "chill" (hope you like segmentation fault errors). + */ +typedef struct chillbuff +{ + /** + * The buffer's underlying array that stores the data. + */ + void* array; + + /** + * The current amount of elements stored in the chillbuff. DO NOT touch this yourself, only read! + */ + size_t length; + + /** + * The current buffer capacity. This grows dynamically according to the specified {@link #chillbuff_growth_method}. + */ + size_t capacity; + + /** + * The size of each stored element. DO NOT CHANGE THIS! Only read (if necessary)... + */ + size_t element_size; + + /** + * The way the buffer's capacity is increased when it's full. + */ + chillbuff_growth_method growth_method; +} chillbuff; + +/** @private */ +static inline void _chillbuff_printerr(const char* error, const char* origin) +{ + const size_t error_length = 64 + strlen(error) + strlen(origin); + char* error_msg = (char*)malloc(error_length * sizeof(char)); // cast malloc because of compat with C++ D: + if (error_msg != NULL) + { + snprintf(error_msg, error_length, "\nCHILLBUFF ERROR: (%s) %s\n", origin, error); + if (_chillbuff_error_callback != NULL) + { + _chillbuff_error_callback(error_msg); + } + free(error_msg); + } +} + +/** + * Sets the chillbuff error callback.

+ * If errors occur, they'll be passed as a string into the provided callback function. + * @param error_callback The function to call when errors occur. + * @return Whether the callback was set up correctly or not (chillbuff exit code, see top of chillbuff.h file for more details). + */ +static inline int chillbuff_set_error_callback(void (*error_callback)(const char*)) +{ + if (error_callback == NULL) + { + _chillbuff_printerr("The passed error callback is empty; Operation cancelled!", __func__); + return CHILLBUFF_NULL_ARG; + } + + _chillbuff_error_callback = error_callback; + return CHILLBUFF_SUCCESS; +} + +/** + * Clears the chillbuff error callback (errors won't be printed anymore). + */ +static inline void chillbuff_unset_error_callback() +{ + _chillbuff_error_callback = NULL; +} + +/** + * Initializes a chillbuff instance and makes it ready to accept data. + * @param buff The chillbuff instance to init (or rather, a pointer to it). + * @param initial_capacity The initial capacity of the underlying array. If you pass 0 here, 16 is used by default. + * @param element_size How big should every array element be? E.g. if you're storing int you should pass sizeof(int). + * @param growth_method How should the buffer grow once its maximum capacity is reached? @see chillbuff_growth_method + * @return Chillbuff exit code as defined at the top of the chillbuff.h header file. 0 means success. + */ +static inline int chillbuff_init(chillbuff* buff, const size_t initial_capacity, const size_t element_size, const chillbuff_growth_method growth_method) +{ + if (buff == NULL) + { + _chillbuff_printerr("Tried to init a NULL chillbuff instance; wouldn't end well. Cancelled...", __func__); + return CHILLBUFF_NULL_ARG; + } + + if (element_size == 0) + { + _chillbuff_printerr("Storing elements of size \"0\" makes no sense...", __func__); + return CHILLBUFF_INVALID_ARG; + } + + if (growth_method < 0 || growth_method > 3) + { + _chillbuff_printerr("Invalid grow method! Please use the appropriate chillbuff_growth_method enum!", __func__); + return CHILLBUFF_INVALID_ARG; + } + + buff->length = 0; + buff->element_size = element_size; + buff->growth_method = growth_method; + buff->capacity = initial_capacity == 0 ? 16 : initial_capacity; + buff->array = calloc(buff->capacity, buff->element_size); + + if (buff->array == NULL) + { + _chillbuff_printerr("OUT OF MEMORY!", __func__); + return CHILLBUFF_OUT_OF_MEM; + } + + return CHILLBUFF_SUCCESS; +} + +/** + * Frees a chillbuff instance. + * @param buff The chillbuff to deallocate. If this is NULL, nothing happens at all. + */ +static inline void chillbuff_free(chillbuff* buff) +{ + if (buff == NULL) + { + return; + } + + memset(buff->array, '\0', buff->length); + free(buff->array); + buff->array = NULL; + buff->length = buff->capacity = buff->element_size = 0; +} + +/** + * Clears a chillbuff's data.

+ * Deletes all of the underlying array's elements and resets the length to 0.

+ * Leaves the array allocated at the current capacity. + * @param buff The chillbuff to clear. If this is NULL, nothing happens at all. + */ +static inline void chillbuff_clear(chillbuff* buff) +{ + if (buff == NULL) + { + return; + } + + memset(buff->array, '\0', buff->capacity); + buff->length = 0; +} + +/** + * Appends one or more elements to the buffer. + * If the buffer is full, it will be expanded automatically. + * @param buff The buffer into which to insert the elements. + * @param elements The array of elements to insert (pointer to the first element). + * @param elements_count Amount of elements to add (for example: if your buffer stores the type uint32_t, you'd pass sizeof(elements_to_add) / sizeof(uint32_t) here). If you're only adding a single element, pass 1. + * @return Chillbuff exit code that describes the insertion's outcome. + */ +static int chillbuff_push_back(chillbuff* buff, const void* elements, const size_t elements_count) +{ + if (buff == NULL) + { + _chillbuff_printerr("Tried to append to a NULL chillbuff instance!", __func__); + return CHILLBUFF_NULL_ARG; + } + + if (elements == NULL) + { + _chillbuff_printerr("Tried to append NULL element(s) to a chillbuff instance!", __func__); + return CHILLBUFF_NULL_ARG; + } + + if (elements_count == 0) + { + _chillbuff_printerr("The passed \"elements_count\" argument is zero; nothing to append!", __func__); + return CHILLBUFF_INVALID_ARG; + } + + for (size_t i = 0; i < elements_count; i++) + { + if (buff->length == buff->capacity) + { + size_t new_capacity; + + switch (buff->growth_method) + { + default: + _chillbuff_printerr("Invalid grow method! Please use the appropriate chillbuff_growth_method enum!", __func__); + return CHILLBUFF_INVALID_ARG; + case CHILLBUFF_GROW_DUPLICATIVE: + new_capacity = (buff->capacity * 2); + break; + case CHILLBUFF_GROW_TRIPLICATIVE: + new_capacity = (buff->capacity * 3); + break; + case CHILLBUFF_GROW_LINEAR: + new_capacity = (buff->capacity + buff->element_size); + break; + case CHILLBUFF_GROW_EXPONENTIAL: + new_capacity = (buff->capacity * buff->capacity); + break; + } + + if (new_capacity <= buff->capacity || new_capacity >= UINT64_MAX / buff->element_size) + { + _chillbuff_printerr("Couldn't push back due to buffer OVERFLOW!", __func__); + return CHILLBUFF_OVERFLOW; + } + + void* new_array = realloc(buff->array, new_capacity * buff->element_size); + if (new_array == NULL) + { + _chillbuff_printerr("Couldn't resize chillbuff underlying array; OUT OF MEMORY!", __func__); + return CHILLBUFF_OUT_OF_MEM; + } + + memset((char*)new_array + (buff->element_size * buff->length), '\0', (new_capacity - buff->length) * buff->element_size); + buff->array = new_array; + buff->capacity = new_capacity; + } + + memcpy((char*)buff->array + (buff->element_size * buff->length++), (char*)elements + (i * buff->element_size), buff->element_size); + } + + return CHILLBUFF_SUCCESS; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // CHILLBUFF_H diff --git a/src/thirdparty/jwt/include/claim.h b/src/thirdparty/jwt/include/claim.h new file mode 100644 index 00000000..0b55acad --- /dev/null +++ b/src/thirdparty/jwt/include/claim.h @@ -0,0 +1,146 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file claim.h + * @author Raphael Beck + * @brief JWT claims as described in https://auth0.com/docs/tokens/concepts/jwt-claims + */ + +#ifndef L8W8JWT_CLAIM_H +#define L8W8JWT_CLAIM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "version.h" +#include + +// Forward declare chillbuff +/** @private */ +struct chillbuff; + +/** + * JWT claim value is a string (e.g. "iss": "glitchedpolygons.com"). + */ +#define L8W8JWT_CLAIM_TYPE_STRING 0 + +/** + * JWT claim value is an integer (e.g. "exp": 1579610629) + */ +#define L8W8JWT_CLAIM_TYPE_INTEGER 1 + +/** + * JWT claim value type number (e.g. "size": 1.85). + */ +#define L8W8JWT_CLAIM_TYPE_NUMBER 2 + +/** + * JWT claim value is a boolean (e.g. "done": true). + */ +#define L8W8JWT_CLAIM_TYPE_BOOLEAN 3 + +/** + * JWT claim value is null (e.g. "ref": null). + */ +#define L8W8JWT_CLAIM_TYPE_NULL 4 + +/** + * JWT claim value type JSON array (e.g. "ids": [2, 4, 8, 16]). + */ +#define L8W8JWT_CLAIM_TYPE_ARRAY 5 + +/** + * JWT claim value type is a JSON object (e.g. "objs": { "name": "GMan", "id": 420 }). + */ +#define L8W8JWT_CLAIM_TYPE_OBJECT 6 + +/** + * JWT claim value is some other type. + */ +#define L8W8JWT_CLAIM_TYPE_OTHER 7 + +/** + * Struct containing a jwt claim key-value pair.

+ * If allocated on the heap by the decode function, + * remember to call l8w8jwt_claims_free() on it once you're done using it. + */ +struct l8w8jwt_claim +{ + /** + * The token claim key (e.g. "iss", "iat", "sub", etc...).

+ * NUL-terminated C-string! + */ + char* key; + + /** + * key string length.

+ * Set this to 0 if you want to make the encoder use strlen(key) instead. + */ + size_t key_length; + + /** + * The claim's value as a NUL-terminated C-string. + */ + char* value; + + /** + * value string length.

+ * Set this to 0 if you want to make the encoder use strlen(value) instead. + */ + size_t value_length; + + /** + * The type of the claim's value.

+ * 0 = string, 1 = integer, 2 = number, 3 = boolean, 4 = null, 5 = array, 6 = object, 7 = other. + * @see https://www.w3schools.com/js/js_json_datatypes.asp + */ + int type; +}; + +/** + * Frees a heap-allocated l8w8jwt_claim array. + * @param claims The claims to free. + * @param claims_count The size of the passed claims array. + */ +L8W8JWT_API void l8w8jwt_free_claims(struct l8w8jwt_claim* claims, size_t claims_count); + +/** + * Writes a bunch of JWT claims into a chillbuff stringbuilder.

+ * Curly braces and trailing commas won't be written; only the "key":"value" pairs! + * @param stringbuilder The buffer into which to write the claims. + * @param claims The l8w8jwt_claim array of claims to write. + * @param claims_count The claims array size. + * @return Return code as specified inside retcodes.h + */ +L8W8JWT_API int l8w8jwt_write_claims(struct chillbuff* stringbuilder, struct l8w8jwt_claim* claims, size_t claims_count); + +/** + * Gets a claim by key from a l8w8jwt_claim array. + * @param claims The array to look in. + * @param claims_count The claims array size. + * @param key The claim key (e.g. "sub") to look for. + * @param key_length The claim key's string length. + * @return The found claim; NULL if no such claim was found in the array. + */ +L8W8JWT_API struct l8w8jwt_claim* l8w8jwt_get_claim(struct l8w8jwt_claim* claims, size_t claims_count, const char* key, size_t key_length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_CLAIM_H diff --git a/src/thirdparty/jwt/include/decode.h b/src/thirdparty/jwt/include/decode.h new file mode 100644 index 00000000..a8a31cb4 --- /dev/null +++ b/src/thirdparty/jwt/include/decode.h @@ -0,0 +1,274 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file decode.h + * @author Raphael Beck + * @brief Core DECODE function for l8w8jwt. Use this to decode and validate a JWT! + */ + +#ifndef L8W8JWT_DECODE_H +#define L8W8JWT_DECODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "algs.h" +#include "claim.h" +#include "version.h" +#include "retcodes.h" +#include +#include +#include + +#ifndef L8W8JWT_MAX_KEY_SIZE +#define L8W8JWT_MAX_KEY_SIZE 8192 +#endif + +/** + * Enum containing the validation result flags. + */ +enum l8w8jwt_validation_result { + /** + * The JWT is valid (according to the passed validation parameters). + */ + L8W8JWT_VALID = (unsigned)0, + + /** + * The issuer claim is invalid. + */ + L8W8JWT_ISS_FAILURE = (unsigned)1 << (unsigned)0, + + /** + * The subject claim is invalid. + */ + L8W8JWT_SUB_FAILURE = (unsigned)1 << (unsigned)1, + + /** + * The audience claim is invalid. + */ + L8W8JWT_AUD_FAILURE = (unsigned)1 << (unsigned)2, + + /** + * The JWT ID claim is invalid. + */ + L8W8JWT_JTI_FAILURE = (unsigned)1 << (unsigned)3, + + /** + * The token is expired. + */ + L8W8JWT_EXP_FAILURE = (unsigned)1 << (unsigned)4, + + /** + * The token is not yet valid. + */ + L8W8JWT_NBF_FAILURE = (unsigned)1 << (unsigned)5, + + /** + * The token was not issued yet, are you from the future? + */ + L8W8JWT_IAT_FAILURE = (unsigned)1 << (unsigned)6, + + /** + * The token was potentially tampered with: its signature couldn't be verified. + */ + L8W8JWT_SIGNATURE_VERIFICATION_FAILURE = (unsigned)1 << (unsigned)7, + + /** + * The token's "typ" claim validation failed. + */ + L8W8JWT_TYP_FAILURE = (unsigned)1 << (unsigned)8 +}; + +/** + * Struct containing the parameters to use for decoding and validating a JWT. + */ +struct l8w8jwt_decoding_params +{ + /** + * The token to decode and validate. + */ + char* jwt; + + /** + * The jwt string length. + */ + size_t jwt_length; + + /** + * The signature algorithm ID.

+ * [0;2] = HS256/384/512 | [3;5] = RS256/384/512 | [6;8] = PS256/384/512 | [9;11] = ES256/384/512

+ * This affects what should be the value of {@link #verification_key} + */ + int alg; + + /** + * [OPTIONAL] The issuer claim (who issued the JWT?).

+ * Set to NULL if you don't want to validate the issuer.

+ * The JWT will only pass verification if its iss claim matches this string. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.1 + */ + char* validate_iss; + + /** + * validate_iss string length. + */ + size_t validate_iss_length; + + /** + * [OPTIONAL] The subject claim (who is the JWT about?).

+ * Set to NULL if you don't want to validate the subject claim.

+ * The JWT will only pass verification if its sub matches this string. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.2 + */ + char* validate_sub; + + /** + * validate_sub string length. + */ + size_t validate_sub_length; + + /** + * [OPTIONAL] The audience claim (who is the JWT intended for? Who is the intended JWT's recipient?).

+ * Set to NULL if you don't want to validate the audience.

+ * The JWT will only pass verification if its aud matches this string. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.3 + */ + char* validate_aud; + + /** + * validate_aud string length. + */ + size_t validate_aud_length; + + /** + * [OPTIONAL] The JWT ID. Provides a unique identifier for the token.

+ * Set to NULL if you don't want to validate the jti claim.

+ * The JWT will only pass verification if its jti matches this string. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.7 + */ + char* validate_jti; + + /** + * validate_jti claim length. + */ + size_t validate_jti_length; + + /** + * Should the expiration claim be verified? + * If this is set to 1, the exp claim will be compared to the current date and time + {@link #exp_tolerance_seconds} + */ + int validate_exp; + + /** + * Should the "not before" claim be verified? + * If this is set to 1, the nbf claim will be compared to the current date and time + {@link #nbf_tolerance_seconds} + */ + int validate_nbf; + + /** + * Should the "issued at" claim be verified? + * If this is set to 1, the iat claim will be compared to the current date and time + {@link #iat_tolerance_seconds} + */ + int validate_iat; + + /** + * Small inconsistencies in time can happen, or also latency between clients and servers. + * That's just life. You can forgive a few seconds of expiration, but don't exaggerate this!

+ * Only taken into consideration if {@link #validate_exp} is set to 1. + */ + uint8_t exp_tolerance_seconds; + + /** + * The amount of seconds to subtract from the current time when comparing the "not before" claim, to allow for a small tolerance time frame. + * Only taken into consideration if {@link #validate_nbf} is set to 1. + */ + uint8_t nbf_tolerance_seconds; + + /** + * The amount of seconds to subtract from the current time when comparing the "issued at" claim, to allow for a small tolerance time frame. + * Only taken into consideration if {@link #validate_iat} is set to 1. + */ + uint8_t iat_tolerance_seconds; + + /** + * The key to use for verifying the token's signature + * (e.g. if you chose HS256 as algorithm, this will be the HMAC secret; for RS512 this will be the PEM-formatted public RSA key string, etc...). + */ + unsigned char* verification_key; + + /** + * Length of the {@link #verification_key} + */ + size_t verification_key_length; + + /** + * [OPTIONAL] The typ claim (what type is the token?).

+ * Set to NULL if you don't want to validate the "typ" claim.

+ */ + char* validate_typ; + + /** + * validate_typ string length. + */ + size_t validate_typ_length; +}; + +/** + * Initializes a {@link #l8w8jwt_decoding_params} instance by setting its fields to default values. + * @param params The l8w8jwt_decoding_params to initialize (set to default values). + */ +L8W8JWT_API void l8w8jwt_decoding_params_init(struct l8w8jwt_decoding_params* params); + +/** + * Validates a set of l8w8jwt_decoding_params. + * @param params The l8w8jwt_decoding_params to validate. + * @return Return code as defined in retcodes.h + */ +L8W8JWT_API int l8w8jwt_validate_decoding_params(struct l8w8jwt_decoding_params* params); + +/** + * Decode (and validate) a JWT using specific parameters.

+ * The resulting {@link #l8w8jwt_validation_result} written into the passed "out_validation_result" pointer + * contains validation failure flags (see the {@link #l8w8jwt_validation_result} enum docs for more details).

+ * This only happens if decoding also succeeded: if the token is malformed, nothing will be written into "out_validation_result".

+ * If validation succeeds, the {@link #l8w8jwt_validation_result} receives the value 0 (enum value L8W8JWT_VALID).

+ * The same applies to the "out_claims" argument: it is only allocated and written to if it (obviously) isn't NULL and if the decoding was also successful! + * + * @param params The parameters to use for decoding and validating the token. + * + * @param out_validation_result Where to write the validation result flags into (0 means success). In case of a decoding failure this is set to -1 (or ~L8W8JWT_VALID)! + * + * @param out_claims + * [OPTIONAL] Where the decoded claims (header + payload claims together) should be written into. + * This pointer will be dereferenced + allocated, so make sure to pass a fresh pointer! + * If you don't need the claims, set this to NULL (they will only be validated, e.g. signature, exp, etc...). + * Check the note down below for more infos! + * + * @param out_claims_length Where to write the decoded claims count into. This will receive the value of how many claims were written into "out_claims" (0 if you decided to set "out_claims" to NULL). + * + * @note If you decide to keep the claims stored in the out_claims parameter, REMEMBER to call {@link #l8w8jwt_free_claims()} on it once you're done using them! + * + * @return Return code as defined in retcodes.h (this is NOT the validation result that's written into the out_validation_result argument; the returned int describes whether the actual parsing/decoding part failed). + */ +L8W8JWT_API int l8w8jwt_decode(struct l8w8jwt_decoding_params* params, enum l8w8jwt_validation_result* out_validation_result, struct l8w8jwt_claim** out_claims, size_t* out_claims_length); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_DECODE_H diff --git a/src/thirdparty/jwt/include/encode.h b/src/thirdparty/jwt/include/encode.h new file mode 100644 index 00000000..a1b8e1c9 --- /dev/null +++ b/src/thirdparty/jwt/include/encode.h @@ -0,0 +1,206 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file encode.h + * @author Raphael Beck + * @brief Core ENCODE function for l8w8jwt. Use this to encode a JWT header + payload WITHOUT signing. + */ + +#ifndef L8W8JWT_ENCODE_H +#define L8W8JWT_ENCODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "algs.h" +#include "claim.h" +#include "version.h" +#include "retcodes.h" +#include +#include + +#ifndef L8W8JWT_MAX_KEY_SIZE +#define L8W8JWT_MAX_KEY_SIZE 8192 +#endif + +/** + * Struct containing the parameters to use for creating a JWT with l8w8jwt. + */ +L8W8JWT_API struct l8w8jwt_encoding_params +{ + /** + * The signature algorithm ID.

+ * [0;2] = HS256/384/512 | [3;5] = RS256/384/512 | [6;8] = PS256/384/512 | [9;11] = ES256/384/512 + */ + int alg; + + /** + * [OPTIONAL] The issuer claim (who issued the JWT?). Can be omitted by setting this to NULL. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.1 + */ + char* iss; + + /** + * iss claim string length. + */ + size_t iss_length; + + /** + * [OPTIONAL] The subject claim (who is the JWT about?). Set to NULL if you don't want it in your token. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.2 + */ + char* sub; + + /** + * sub claim string length. + */ + size_t sub_length; + + /** + * [OPTIONAL] The audience claim (who is the JWT intended for? Who is the intended JWT's recipient?). + * Set this to NULL if you don't wish to add this claim to the token. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.3 + */ + char* aud; + + /** + * aud claim string length. + */ + size_t aud_length; + + /** + * [OPTIONAL] The JWT ID. Provides a unique identifier for the token. Can be omitted by setting this to NULL. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.7 + */ + char* jti; + + /** + * jti claim string length. + */ + size_t jti_length; + + /** + * Expiration time claim; specifies when this token should stop being valid (in seconds since Unix epoch).

+ * If you want to omit this, set this to 0, but do NOT FORGET to set it to something, + * otherwise it will be set to whatever random value was in the memory where this variable resides. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.4 + */ + time_t exp; + + /** + * "Not before" time claim; specifies when this token should start being valid (in seconds since Unix epoch).

+ * If you want to omit this, set this to 0, but do NOT FORGET to set it to something, + * otherwise it will be set to whatever random value was in the memory where this variable resides. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.5 + */ + time_t nbf; + + /** + * "Issued at" timestamp claim; specifies when this token was emitted (in seconds since Unix epoch).

+ * If you want to omit this, set this to 0, but do NOT FORGET to set it to something, + * otherwise it will be set to whatever random value was in the memory where this variable resides. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.6 + */ + time_t iat; + + /** + * [OPTIONAL] Array of additional claims to include in the JWT's header like for example "kid" or "cty"; pass NULL if you don't wish to add any!

+ * Avoid header claims such as typ and alg, since those are written by the encoding function itself. + * @see https://tools.ietf.org/html/rfc7519#section-4.1.7 + */ + struct l8w8jwt_claim* additional_header_claims; + + /** + * [OPTIONAL] The additional_header_claims array size; pass 0 if you don't wish to include any custom claims! + */ + size_t additional_header_claims_count; + + /** + * [OPTIONAL] Array of additional claims to include in the JWT's payload; pass NULL if you don't wish to add any!

+ * Registered claim names such as "iss", "exp", etc... have their own dedicated field within this struct: do not include those in this array to prevent uncomfortable duplicates! + * @see https://tools.ietf.org/html/rfc7519#section-4 + */ + struct l8w8jwt_claim* additional_payload_claims; + + /** + * [OPTIONAL] The additional_payload_claims array size; pass 0 if you don't wish to include any custom claims! + */ + size_t additional_payload_claims_count; + + /** + * The secret key to use for signing the token + * (e.g. if you chose HS256 as algorithm, this will be the HMAC secret; for RS512 this will be the private PEM-formatted RSA key string, and so on...). + */ + unsigned char* secret_key; + + /** + * Length of the secret_key + */ + size_t secret_key_length; + + /** + * If the secret key requires a password for usage, please assign it to this field.

+ * You can only omit this when using JWT algorithms "HS256", "HS384" or "HS512" (it's ignored in that case actually).

+ * Every other algorithm requires you to at least set this to NULL if the {@link #secret_key} isn't password-protected. + */ + unsigned char* secret_key_pw; + + /** + * The secret key's password length (if there is any). If there's none, set this to zero! + */ + size_t secret_key_pw_length; + + /** + * Where the encoded token should be written into + * (will be malloc'ed, so make sure to l8w8jwt_free() this as soon as you're done using it!). + */ + char** out; + + /** + * Where the output token string length should be written into. + */ + size_t* out_length; +}; + +/** + * Initializes a {@link #l8w8jwt_encoding_params} instance by setting its fields to default values. + * @param params The l8w8jwt_encoding_params to initialize (set to default values). + */ +L8W8JWT_API void l8w8jwt_encoding_params_init(struct l8w8jwt_encoding_params* params); + +/** + * Validates a set of l8w8jwt_encoding_params. + * @param params The l8w8jwt_encoding_params to validate. + * @return Return code as defined in retcodes.h + */ +L8W8JWT_API int l8w8jwt_validate_encoding_params(struct l8w8jwt_encoding_params* params); + +/** + * Creates, signs and encodes a Json-Web-Token.

+ * An example output could be: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbWUta2V5LWlkLWhlcmUtMDEyMzQ1NiJ9.eyJpYXQiOjE1Nzk2NDUzNTUsImV4cCI6MTU3OTY0NTk1NSwic3ViIjoiR29yZG9uIEZyZWVtYW4iLCJpc3MiOiJCbGFjayBNZXNhIiwiYXVkIjoiQWRtaW5pc3RyYXRvciJ9.uk4EEoq0ql_SguLto5EWzklakpzO-6GE2U26crB8vUY

+ * @param params The token encoding parameters (e.g. "alg", "iss", "exp", etc...). + * @return Return code as defined in retcodes.h + * @see l8w8jwt_encoding_params + */ +L8W8JWT_API int l8w8jwt_encode(struct l8w8jwt_encoding_params* params); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_ENCODE_H diff --git a/src/thirdparty/jwt/include/retcodes.h b/src/thirdparty/jwt/include/retcodes.h new file mode 100644 index 00000000..fe3bef71 --- /dev/null +++ b/src/thirdparty/jwt/include/retcodes.h @@ -0,0 +1,107 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file retcodes.h + * @author Raphael Beck + * @brief Macros for possible integer codes returned by the various l8w8jwt functions. + */ + +#ifndef L8W8JWT_RETCODES_H +#define L8W8JWT_RETCODES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returned from a l8w8jwt function when everything went smooth 'n' chill. Time to get Schwifty, Morteyy! + */ +#define L8W8JWT_SUCCESS 0 + +/** + * Error code returned by a l8w8jwt function if you passed a NULL argument that shouldn't have been NULL. + */ +#define L8W8JWT_NULL_ARG 100 + +/** + * This error code is returned by a l8w8jwt function if you passed an invalid parameter into it. + */ +#define L8W8JWT_INVALID_ARG 200 + +/** + * This is returned if some allocation inside a l8w8jwt function failed: you're out of memory at this point. + */ +#define L8W8JWT_OUT_OF_MEM 300 + +/** + * Not good... + */ +#define L8W8JWT_OVERFLOW 310 + +/** + * Returned if signing a JWT failed. + */ +#define L8W8JWT_SIGNATURE_CREATION_FAILURE 400 + +/** + * If one of the SHA-2 functions fails (e.g. SHA-256). + */ +#define L8W8JWT_SHA2_FAILURE 410 + +/** + * Returned if some PEM-formatted key string couldn't be parsed. + */ +#define L8W8JWT_KEY_PARSE_FAILURE 420 + +/** + * Base64(URL) encoding or decoding error. + */ +#define L8W8JWT_BASE64_FAILURE 425 + +/** + * Returned if you passed the wrong private or public key type (e.g. trying to use an RSA key for ECDSA tokens, etc...).

+ * Especially for the ECDSA algorithms like ES256, ES384 and ES512 double-check that you passed keys of the correct curve!

+ * Only use the P-256 curve for ES256, P-384 (a.k.a. secp384r1) for ES384 and P-521 (a.k.a. secp521r1) for ES512. + */ +#define L8W8JWT_WRONG_KEY_TYPE 450 + +/** + * When the mbedtls_ctr_drbg_seed() function fails... + */ +#define L8W8JWT_MBEDTLS_CTR_DRBG_SEED_FAILURE 500 + +/** + * Returned if the token is invalid (format-wise). + */ +#define L8W8JWT_DECODE_FAILED_INVALID_TOKEN_FORMAT 600 + +/** + * Returned if the token is invalid because it's missing the signature (despite having specified an alg that isn't "none"). + */ +#define L8W8JWT_DECODE_FAILED_MISSING_SIGNATURE 700 + +/** + * Returned if the JWT signing alg parameter that was passed is not supported (e.g. the used l8w8jwt library was built without support for that algo, e.g. Ed25519). + * See the README.md for more details! + */ +#define L8W8JWT_UNSUPPORTED_ALG 800 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_RETCODES_H diff --git a/src/thirdparty/jwt/include/util.h b/src/thirdparty/jwt/include/util.h new file mode 100644 index 00000000..7bd4d479 --- /dev/null +++ b/src/thirdparty/jwt/include/util.h @@ -0,0 +1,58 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file util.h + * @author Raphael Beck + * @brief Useful utility functions. + */ + +#ifndef L8W8JWT_UTIL_H +#define L8W8JWT_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "version.h" + +/** + * Converts a hex-encoded string to a binary array.

+ * A NUL-terminator is appended at the end of the output buffer, so make sure to allocate at least (hexstr_length / 2) + 1 bytes! + * @param hexstr The hex string to convert. + * @param hexstr_length Length of the \p hexstr + * @param output Where to write the converted binary data into. + * @param output_size Size of the output buffer (make sure to allocate at least (hexstr_length / 2) + 1 bytes!). + * @param output_length [OPTIONAL] Where to write the output array length into. This is always gonna be hexstr_length / 2, but you can still choose to write it out just to be sure. If you want to omit this: no problem.. just pass NULL! + * @return 0 if conversion succeeded. 1 if one or more required arguments were NULL or invalid. 2 if the hexadecimal string is in an invalid format (e.g. not divisible by 2). 3 if output buffer size was insufficient (needs to be at least (hexstr_length / 2) + 1 bytes). + */ +L8W8JWT_API int l8w8jwt_hexstr2bin(const char* hexstr, size_t hexstr_length, unsigned char* output, size_t output_size, size_t* output_length); + +/** + * Compares two strings ignoring UPPER vs. lowercase. + * @param str1 String to compare. + * @param str2 String to compare to. + * @param n How many characters of the string should be compared (starting from index 0)? + * @return If the strings are equal, 0 is returned. Otherwise, something else. + */ +L8W8JWT_API int l8w8jwt_strncmpic(const char* str1, const char* str2, size_t n); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_UTIL_H \ No newline at end of file diff --git a/src/thirdparty/jwt/include/version.h b/src/thirdparty/jwt/include/version.h new file mode 100644 index 00000000..9fce28a0 --- /dev/null +++ b/src/thirdparty/jwt/include/version.h @@ -0,0 +1,87 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file version.h + * @author Raphael Beck + * @brief l8w8jwt version checking. + */ + +#ifndef L8W8JWT_VERSION_H +#define L8W8JWT_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Current l8w8jwt version number. + */ +#define L8W8JWT_VERSION 220 + +/** + * Current l8w8jwt version number (as a human-readable string). + */ +#define L8W8JWT_VERSION_STR "2.2.0" + +#if defined(_WIN32) && defined(L8W8JWT_DLL) +#ifdef L8W8JWT_BUILD_DLL +#define L8W8JWT_API __declspec(dllexport) +#else +#define L8W8JWT_API __declspec(dllimport) +#endif +#else +#define L8W8JWT_API +#endif + +#ifndef L8W8JWT_SMALL_STACK +/** + * Set this pre-processor definition to \c 1 if you're using this + * on a low-memory device with increased risk of stack overflow. + */ +#define L8W8JWT_SMALL_STACK 0 +#endif + +/** + * Free memory that was allocated by L8W8JWT. + * @param mem The memory to free. + */ +L8W8JWT_API void l8w8jwt_free(void* mem); + +/** + * Zero memory securely. + * @param mem The memory to zero. + * @param len The length to zero. + */ +L8W8JWT_API void l8w8jwt_zero(void* buf, size_t len); + +/** + * Gets the l8w8jwt version number as an integer. + * @return Version number (e.g. "2.1.4" => 214) + */ +L8W8JWT_API int l8w8jwt_get_version_number(void); + +/** + * Gets the l8w8jwt version number as a nicely formatted string. + * @param out A writable \c char buffer of at least 32B where to write the version number string into. The string will be NUL-terminated, no worries! Passing \c NULL here is a very bad idea. Undefined, unpleasant, and just... just don't! + */ +L8W8JWT_API void l8w8jwt_get_version_string(char out[32]); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // L8W8JWT_VERSION_H diff --git a/src/thirdparty/jwt/util.c b/src/thirdparty/jwt/util.c new file mode 100644 index 00000000..686659e9 --- /dev/null +++ b/src/thirdparty/jwt/util.c @@ -0,0 +1,87 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "include/util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int l8w8jwt_hexstr2bin(const char* hexstr, const size_t hexstr_length, unsigned char* output, const size_t output_size, size_t* output_length) +{ + if (hexstr == NULL || output == NULL || hexstr_length == 0) + { + return 1; + } + + const size_t hl = hexstr[hexstr_length - 1] ? hexstr_length : hexstr_length - 1; + + if (hl % 2 != 0) + { + return 2; + } + + const size_t final_length = hl / 2; + + if (output_size < final_length + 1) + { + return 3; + } + + for (size_t i = 0, ii = 0; ii < final_length; i += 2, ii++) + { + output[ii] = (hexstr[i] % 32 + 9) % 25 * 16 + (hexstr[i + 1] % 32 + 9) % 25; + } + + output[final_length] = '\0'; + + if (output_length != NULL) + { + *output_length = final_length; + } + + return 0; +} + +int l8w8jwt_strncmpic(const char* str1, const char* str2, size_t n) +{ + size_t cmp = 0; + int ret = -1; + + if (str1 == NULL || str2 == NULL) + { + return ret; + } + + while ((*str1 || *str2) && cmp < n) + { + if ((ret = tolower((int)(*str1)) - tolower((int)(*str2))) != 0) + { + break; + } + + cmp++; + str1++; + str2++; + } + + return ret; +} + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/src/thirdparty/jwt/version.c b/src/thirdparty/jwt/version.c new file mode 100644 index 00000000..2484b772 --- /dev/null +++ b/src/thirdparty/jwt/version.c @@ -0,0 +1,73 @@ +/* + Copyright 2020 Raphael Beck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "include/version.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void l8w8jwt_free(void* mem) +{ + free(mem); +} + +void l8w8jwt_zero(void* buf, size_t len) +{ + MBEDTLS_INTERNAL_VALIDATE(len == 0 || buf != NULL); + + if (len > 0) + { +#if defined(MBEDTLS_PLATFORM_HAS_EXPLICIT_BZERO) + explicit_bzero(buf, len); +#if defined(HAVE_MEMORY_SANITIZER) + /* You'd think that Msan would recognize explicit_bzero() as + * equivalent to bzero(), but it actually doesn't on several + * platforms, including Linux (Ubuntu 20.04). + * https://github.com/google/sanitizers/issues/1507 + * https://github.com/openssh/openssh-portable/commit/74433a19bb6f4cef607680fa4d1d7d81ca3826aa + */ + __msan_unpoison(buf, len); +#endif +#elif defined(__STDC_LIB_EXT1__) + memset_s(buf, len, 0, len); +#elif defined(_WIN32) + RtlSecureZeroMemory(buf, len); +#else + memset_func(buf, 0, len); +#endif + } +} + +int l8w8jwt_get_version_number() +{ + return (int)L8W8JWT_VERSION; +} + +void l8w8jwt_get_version_string(char out[32]) +{ + const char version_string[] = L8W8JWT_VERSION_STR; + const size_t version_string_length = sizeof(version_string) - 1; + + memcpy(out, version_string, version_string_length); + out[version_string_length] = '\0'; +} + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file