diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-09-22 17:32:44 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2026-02-13 09:57:39 +0100 |
| commit | 78bf2fe4a5bf37514f6dfd203ef969da0bf40c2e (patch) | |
| tree | 33f9440b8ee0fa7a3b3ad033616d722d2101bb4d /jwt.c | |
| parent | 107a2e43d54f9a42fb85b00b83cb0d9abb194680 (diff) | |
Diffstat (limited to 'jwt.c')
| -rw-r--r-- | jwt.c | 229 |
1 files changed, 229 insertions, 0 deletions
@@ -0,0 +1,229 @@ +/* + * nanobbs, a tiny forums software. + * Copyright (C) 2025-2026 Xavier Del Campo Romero + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "jwt.h" +#include <dynstr.h> +#include <sodium.h> +#include <errno.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#define VARIANT sodium_base64_VARIANT_URLSAFE + +static char *b64(const void *const p, const size_t len) +{ + char *ret = NULL; + const size_t n = sodium_base64_encoded_len(len, VARIANT); + + if (!(ret = malloc(n))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + return NULL; + } + else if (!sodium_bin2base64(ret, n, p, len, VARIANT)) + { + fprintf(stderr, "%s: sodium_bin2base64 failed\n", __func__); + free(ret); + return NULL; + } + + return ret; +} + +static int b64d(const char *const b64, const size_t len, void **const outbuf, + size_t *const outsz) +{ + int ret = 1; + size_t sz = 0, binlen; + const char *end; + void *buf = NULL, *p; + +again: + + if (!(p = realloc(buf, ++sz))) + { + fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); + goto failure; + } + else if (sodium_base642bin(buf = p, sz, b64, len, NULL, &binlen, &end, + VARIANT)) + { + if (errno == ERANGE) + goto again; + + fprintf(stderr, "%s: sodium_base642bin failed\n", __func__); + goto failure; + } + + *outbuf = buf; + *outsz = sz; + return 0; + +failure: + free(buf); + return ret; +} + +static char *get_hmac(const void *const buf, const size_t n, + const void *const key, const size_t keyn) +{ + unsigned char hmac[crypto_auth_hmacsha256_KEYBYTES]; + char *ret; + + if (keyn != crypto_auth_hmacsha256_KEYBYTES) + { + fprintf(stderr, "%s: invalid key size (%zu), expected %u\n", __func__, + n, crypto_auth_hmacsha256_KEYBYTES); + return NULL; + } + else if (crypto_auth_hmacsha256(hmac, buf, n, key)) + { + fprintf(stderr, "%s: HMAC failed\n", __func__); + return NULL; + } + else if (!(ret = b64(hmac, sizeof hmac))) + fprintf(stderr, "%s: b64 failed\n", __func__); + + return ret; +} + +char *jwt_encode(const cJSON *const j, const void *const key, const size_t n) +{ + static const char jwt_header[] = "{\"alg\": \"HS256\", \"typ\": \"JWT\"}"; + struct dynstr d; + char *ret = NULL, *p = NULL, *header = NULL, *hmac = NULL, *encp = NULL; + + dynstr_init(&d); + + if (!(p = cJSON_PrintUnformatted(j))) + { + fprintf(stderr, "%s: cJSON_PrintUnformatted failed\n", __func__); + goto end; + } + else if (!(encp = b64(p, strlen(p)))) + { + fprintf(stderr, "%s: b64 %s failed\n", __func__, p); + goto end; + } + else if (!(header = b64(jwt_header, strlen(jwt_header)))) + { + fprintf(stderr, "%s: b64 %s failed\n", __func__, jwt_header); + } + else if (dynstr_append(&d, "%s.%s", header, encp)) + { + fprintf(stderr, "%s: dynstr_append header+payload failed", __func__); + goto end; + } + else if (!(hmac = get_hmac(d.str, d.len, key, n))) + { + fprintf(stderr, "%s: get_hmac failed\n", __func__); + goto end; + } + else if (dynstr_append(&d, ".%s", hmac)) + { + fprintf(stderr, "%s: dynstr_append hmac failed\n", __func__); + goto end; + } + + ret = d.str; + +end: + free(header); + free(hmac); + free(encp); + cJSON_free(p); + + if (!ret) + dynstr_free(&d); + + return ret; +} + +static size_t cnt(const char *s, const char c) +{ + size_t ret = 0; + + while (*s) + if (*s++ == c) + ret++; + + return ret; +} + +int jwt_decode(const char *const jwt, const void *const key, const size_t n, + cJSON **const payload) +{ + int ret = 1; + cJSON *j = NULL; + void *p = NULL; + const char *first = strchr(jwt, '.'), *const last = strrchr(jwt, '.'); + const unsigned char *const in = (const unsigned char *)jwt; + unsigned char hmac[crypto_auth_hmacsha256_KEYBYTES]; + size_t sz; + + if (n != crypto_auth_hmacsha256_KEYBYTES) + { + fprintf(stderr, "%s: invalid key size (%zu), expected %u\n", __func__, + n, crypto_auth_hmacsha256_KEYBYTES); + ret = -1; + goto end; + } + else if (cnt(jwt, '.') != 2 || ++first == last) + goto end; + else if (sodium_base642bin(hmac, sizeof hmac, last + 1, strlen(last + 1), + NULL, NULL, NULL, VARIANT)) + { + fprintf(stderr, "%s: sodium_base642bin failed\n", __func__); + goto end; + } + else if (crypto_auth_hmacsha256_verify(hmac, in, last - jwt, key)) + { + fprintf(stderr, "%s: crypto_auth_hmacsha256_verify failed\n", __func__); + goto end; + } + else if ((ret = b64d(first, last - first, &p, &sz))) + { + if (ret < 0) + fprintf(stderr, "%s: b64d failed\n", __func__); + + goto end; + } + else if (!(j = cJSON_ParseWithLength(p, sz))) + { + fprintf(stderr, "%s: cJSON_ParseWithLength failed\n", __func__); + goto end; + } + else if (!cJSON_IsObject(j)) + { + fprintf(stderr, "%s: cJSON_IsObject failed\n", __func__); + goto end; + } + + *payload = j; + ret = 0; + +end: + + if (ret) + cJSON_Delete(j); + + free(p); + return ret; +} |
