Merge pull request #3642 from cesanta/jwt

Add JWT Bearer Authentication support and tutorial
This commit is contained in:
Sergio R. Caprile authored and GitHub committed 2026-07-24 11:49:46 -03:00
commit f78cb1b02f
21 files changed
+888 -81

No files matched your search

+255 -30
View File
@@ -36,16 +36,16 @@ static int mg_base64_encode_single(int c) {
}
}
static int mg_base64_decode_single(int c) {
static int base64_decode_single(int c, char plus, char slash) {
if (c >= 'A' && c <= 'Z') {
return c - 'A';
} else if (c >= 'a' && c <= 'z') {
return c + 26 - 'a';
} else if (c >= '0' && c <= '9') {
return c + 52 - '0';
} else if (c == '+') {
} else if (c == plus) {
return 62;
} else if (c == '/') {
} else if (c == slash) {
return 63;
} else if (c == '=') {
return 64;
@@ -54,6 +54,14 @@ static int mg_base64_decode_single(int c) {
}
}
static int mg_base64_decode_single(int c) {
return base64_decode_single(c, '+', '/');
}
static int mg_base64url_decode_single(int c) {
return base64_decode_single(c, '-', '_');
}
size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
unsigned long rem = (n & 3) % 3;
if (rem == 0) {
@@ -116,6 +124,44 @@ fail:
return 0;
}
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *to,
size_t dl) {
size_t i, len = mg_base64_encode(p, n, to, dl);
if (len == 0) return 0;
for (i = 0; i < len; i++) {
if (to[i] == '+') {
to[i] = '-';
} else if (to[i] == '/') {
to[i] = '_';
}
}
while (len > 0 && to[len - 1] == '=') to[--len] = '\0';
return len;
}
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t dl) {
size_t i, len = 0;
unsigned int bits = 0, v = 0;
if (dl == 0 || (n & 3) == 1) goto fail;
for (i = 0; src != NULL && i < n; i++) {
int c = mg_base64url_decode_single(src[i]);
if (c == 64) break;
if (c < 0) goto fail;
v = (v << 6) | (unsigned int) c;
bits += 6;
if (bits >= 8) {
bits -= 8;
if (len + 1 >= dl) goto fail;
dst[len++] = (char) ((v >> bits) & 255U);
}
}
dst[len] = '\0';
return len;
fail:
if (dl > 0) dst[0] = '\0';
return 0;
}
#ifdef MG_ENABLE_LINES
#line 1 "src/bsd.c"
#endif
@@ -5076,6 +5122,174 @@ long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
return result;
}
#ifdef MG_ENABLE_LINES
#line 1 "src/jwt.c"
#endif
#if MG_TLS == MG_TLS_BUILTIN
#endif
static size_t jwt_split(struct mg_str jwt, struct mg_str *h, struct mg_str *p,
struct mg_str *s) {
size_t i, a = jwt.len, b = jwt.len;
for (i = 0; i < jwt.len; i++) {
if (jwt.buf[i] == '.') {
if (a == jwt.len) a = i;
if (a != i) b = i;
if (b != jwt.len) break;
}
}
if (a == jwt.len || b == jwt.len || b + 1 >= jwt.len) return 0;
*h = mg_str_n(jwt.buf, a);
*p = mg_str_n(jwt.buf + a + 1, b - a - 1);
*s = mg_str_n(jwt.buf + b + 1, jwt.len - b - 1);
return b;
}
static bool jwt_alg(struct mg_str h, const char *alg) {
char got[16];
size_t len = h.len * 3 / 4 + 2;
char *buf = (char *) mg_calloc(1, len);
bool ok;
if (buf == NULL) return false;
ok = mg_base64url_decode(h.buf, h.len, buf, len) > 0 &&
mg_json_unescape(mg_str(buf), "$.alg", got, sizeof(got)) > 0 &&
strcmp(got, alg) == 0;
mg_free(buf);
return ok;
}
static size_t jwt_header_payload(const char *alg, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
char *header, *kid = NULL;
size_t n, m;
if (opts->kid.len > 0) {
kid = mg_mprintf(",%m:%m", MG_ESC("kid"), mg_print_esc,
(int) opts->kid.len, opts->kid.buf);
if (kid == NULL) return 0;
}
header = mg_mprintf("{%m:%m,%m:%m%.*s%.*s%.*s}", MG_ESC("alg"),
MG_ESC(alg), MG_ESC("typ"), MG_ESC("JWT"),
kid == NULL ? 0 : (int) strlen(kid),
kid == NULL ? "" : kid,
opts->header.len > 0 ? 1 : 0, ",",
(int) opts->header.len, opts->header.buf);
mg_free(kid);
if (header == NULL) return 0;
n = mg_base64url_encode((uint8_t *) header, strlen(header), buf, len);
mg_free(header);
if (n == 0 || n + 1 >= len) return 0;
buf[n++] = '.';
m = mg_base64url_encode((uint8_t *) opts->claims.buf, opts->claims.len,
buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
uint8_t sig[32];
size_t n = jwt_header_payload("HS256", opts, buf, len);
size_t m;
if (n == 0 || n + 1 >= len) return 0;
mg_hmac_sha256(sig, (uint8_t *) opts->secret.buf, opts->secret.len,
(uint8_t *) buf, n);
buf[n++] = '.';
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
struct mg_str h, p, s;
uint8_t sig[33], digest[32];
size_t n = jwt_split(jwt, &h, &p, &s);
if (n == 0 || !jwt_alg(h, "HS256")) return 0;
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) !=
sizeof(digest))
return 0;
mg_hmac_sha256(digest, (uint8_t *) opts->secret.buf, opts->secret.len,
(uint8_t *) jwt.buf, n);
if (!mg_memeq(sig, digest, sizeof(digest))) return 0;
return mg_base64url_decode(p.buf, p.len, buf, len);
}
#if MG_TLS == MG_TLS_BUILTIN
typedef struct {
MG_UECC_HashContext uECC;
mg_sha256_ctx sha256;
} jwt_hash_ctx;
static void jwt_hash_init(const MG_UECC_HashContext *ctx) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_init(&c->sha256);
}
static void jwt_hash_update(const MG_UECC_HashContext *ctx, const uint8_t *msg,
unsigned len) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_update(&c->sha256, msg, len);
}
static void jwt_hash_finish(const MG_UECC_HashContext *ctx, uint8_t *hash) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_final(hash, &c->sha256);
}
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
uint8_t hash[32], sig[64], tmp[2 * 32 + 64];
jwt_hash_ctx ctx = {
{jwt_hash_init, jwt_hash_update, jwt_hash_finish, 64, 32, tmp},
{{0}, 0, 0, {0}}};
size_t n = jwt_header_payload("ES256", opts, buf, len);
size_t m;
if (n == 0 || n + 1 >= len) return 0;
mg_sha256(hash, (uint8_t *) buf, n);
if (mg_uecc_sign_deterministic(opts->private_key, hash, sizeof(hash),
&ctx.uECC, sig, mg_uecc_secp256r1()) != 1)
return 0;
buf[n++] = '.';
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
struct mg_str h, p, s;
uint8_t sig[65], hash[32];
size_t n = jwt_split(jwt, &h, &p, &s);
if (n == 0 || !jwt_alg(h, "ES256")) return 0;
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) != 64)
return 0;
mg_sha256(hash, (uint8_t *) jwt.buf, n);
if (mg_uecc_verify(opts->public_key, hash, sizeof(hash), sig,
mg_uecc_secp256r1()) != 1)
return 0;
return mg_base64url_decode(p.buf, p.len, buf, len);
}
#else
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
(void) opts, (void) buf, (void) len;
MG_ERROR(("JWT ES256 requires built-in TLS"));
return 0;
}
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
(void) jwt, (void) opts, (void) buf, (void) len;
return mg_jwt_sign_es256(NULL, NULL, 0);
}
#endif
#ifdef MG_ENABLE_LINES
#line 1 "src/l2.c"
#endif
@@ -14901,6 +15115,7 @@ void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
#if MG_TLS == MG_TLS_BUILTIN
/******************************************************************************/
#define AES_DECRYPTION 1 // whether AES decryption is supported
@@ -15994,8 +16209,6 @@ int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
int ret = 0; // our return value
gcm_context ctx; // includes the AES context structure
unsigned char computed_tag[16];
size_t i;
int diff = 0;
if (tag_len > sizeof(computed_tag)) return -1;
@@ -16006,9 +16219,7 @@ int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
gcm_zero_ctx(&ctx);
// compare tags
for (i = 0; i < tag_len; i++) diff |= computed_tag[i] ^ tag[i];
if (diff != 0) ret = -1;
if (!mg_memeq(computed_tag, tag, tag_len)) ret = -1;
return (ret);
}
@@ -18429,7 +18640,7 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
// parameters ANY OPTIONAL
// }
static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
struct tls_data *tls) {
struct mg_rsa_key *rsa, uint8_t *ec_key) {
struct mg_der_tlv root, version, alg_id, private_key_octets;
struct mg_der_tlv alg_oid, alg_params;
@@ -18462,17 +18673,19 @@ static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
if (alg_oid.len == sizeof(mg_rsa_oid) &&
memcmp(alg_oid.value, mg_rsa_oid, sizeof(mg_rsa_oid)) == 0) {
struct mg_rsa_key rsa_key;
if (rsa == NULL) return -1;
if (mg_rsa_parse_key(private_key_octets.value, private_key_octets.len,
&rsa_key) < 0) {
MG_ERROR(("PKCS#8: failed to parse inner RSA key"));
return -1;
}
tls->rsa = rsa_key;
*rsa = rsa_key;
return 0;
} else if (alg_oid.len == sizeof(mg_ec_public_key_oid) &&
memcmp(alg_oid.value, mg_ec_public_key_oid,
sizeof(mg_ec_public_key_oid)) == 0) {
if (ec_key == NULL) return -1;
if (mg_der_next(&alg_id, &alg_params) < 0 || alg_params.type != 0x06) {
MG_ERROR(("PKCS#8: invalid EC parameters OID"));
return -1;
@@ -18486,7 +18699,7 @@ static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
}
return mg_parse_ec_private_key(private_key_octets.value,
private_key_octets.len, tls->ec_key);
private_key_octets.len, ec_key);
} else {
MG_ERROR(("PKCS#8: unsupported algorithm"));
@@ -18591,6 +18804,20 @@ static int mg_parse_pem_certs(const struct mg_str pem, struct mg_str **ders) {
return count;
}
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len) {
struct mg_str der = mg_str_n(NULL, 0);
size_t n = 0;
if (buf == NULL || len < 32) return 0;
// current mg_parse_ec_private_key() only handles 32-byte keys
if (mg_parse_pem(key, mg_str_s("EC PRIVATE KEY"), &der) == 0) {
if (mg_parse_ec_private_key((uint8_t *) der.buf, der.len, buf) == 0) n = 32;
} else if (mg_parse_pem(key, mg_str_s("PRIVATE KEY"), &der) == 0) {
if (mg_parse_pkcs8_key((uint8_t *) der.buf, der.len, NULL, buf) == 0) n = 32;
}
mg_free((void *) der.buf);
return n;
}
static void sync_time_cancel(struct mg_connection *c) {
c->fn = NULL;
c->is_closing = 1;
@@ -18733,7 +18960,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
// Copy parsed RSA key components to tls->rsa for signing operations
tls->rsa = rsa_key;
} else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) {
if (mg_parse_pkcs8_key((const uint8_t *) key.buf, key.len, tls) == 0) {
if (mg_parse_pkcs8_key((const uint8_t *) key.buf, key.len, &tls->rsa,
tls->ec_key) == 0) {
if (tls->rsa.n.len > 0) {
tls->rsa_key_der = key;
MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
@@ -18877,6 +19105,7 @@ void mg_tls_ctx_free(struct mg_mgr *mgr) {
#if MG_TLS == MG_TLS_BUILTIN && MG_ENABLE_CHACHA20
// ******* BEGIN: chacha-portable/chacha-portable.h ********
@@ -20206,8 +20435,6 @@ PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt(
// first we calculate the mac and see if it lines up, only then do we decrypt
size_t actual_size = cipher_text_size - RFC_8439_TAG_SIZE;
uint8_t computed_mac[RFC_8439_TAG_SIZE];
int diff = 0;
size_t i;
if (MG_OVERLAPPING(plain_text, actual_size, cipher_text, cipher_text_size)) {
return (size_t) -1;
}
@@ -20215,10 +20442,8 @@ PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt(
poly1305_calculate_mac(computed_mac, cipher_text, actual_size, key, nonce, ad,
ad_size);
// compare tags
for (i = 0; i < RFC_8439_TAG_SIZE; i++)
diff |= computed_mac[i] ^ cipher_text[actual_size + i];
if (diff != 0) return (size_t) -1;
if (!mg_memeq(computed_mac, cipher_text + actual_size, RFC_8439_TAG_SIZE))
return (size_t) -1;
chacha20_xor_stream(plain_text, cipher_text, actual_size, key, nonce, 1);
return actual_size;
@@ -22145,11 +22370,7 @@ static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
#define asm_mmod_fast_secp256r1 0
#endif
#if defined(default_RNG_defined) && default_RNG_defined
static MG_UECC_RNG_Function g_rng_function = &default_RNG;
#else
static MG_UECC_RNG_Function g_rng_function = 0;
#endif
static MG_UECC_RNG_Function g_rng_function = NULL;
void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
g_rng_function = rng_function;
@@ -24483,15 +24704,11 @@ MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random,
mg_uecc_word_t tries;
bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
if (!g_rng_function) {
return 0;
}
for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
if (!g_rng_function((uint8_t *) random,
(unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
unsigned int len = (unsigned int) (num_words * MG_UECC_WORD_SIZE);
if (!(g_rng_function == NULL ? mg_random((uint8_t *) random, len)
: g_rng_function((uint8_t *) random, len)))
return 0;
}
random[num_words - 1] &=
mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits));
if (!mg_uecc_vli_isZero(random, num_words) &&
@@ -25619,6 +25836,14 @@ uint64_t mg_timegm(unsigned int year, unsigned int month, unsigned int day,
day_secs * ((ly + 299) / 400);
}
bool mg_memeq(const void *a, const void *b, size_t n) {
const uint8_t *p = (const uint8_t *) a, *q = (const uint8_t *) b;
uint8_t r = 0;
size_t i;
for (i = 0; i < n; i++) r = (uint8_t) (r | (uint8_t) (p[i] ^ q[i]));
return r == 0;
}
#if MG_ENABLE_CUSTOM_RANDOM
#else
bool mg_random(void *buf, size_t len) {