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) {
+49 -10
View File
@@ -1788,6 +1788,9 @@ void mg_free(void *ptr);
// sensitive data (keys, passwords).
void mg_bzero(volatile unsigned char *buf, size_t len);
// Fixed-length constant-time byte equality. Use for MACs, tags, signatures.
bool mg_memeq(const void *a, const void *b, size_t n);
// Fills buf with len cryptographically random bytes. Uses the best available
// hardware or OS source (hardware RNG, /dev/urandom, CryptGenRandom, etc.).
// Falls back to rand() with an error log if no strong source is available.
@@ -2035,6 +2038,9 @@ size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
size_t mg_base64_final(char *buf, size_t len);
size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *buf,
size_t);
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t);
@@ -2668,6 +2674,12 @@ void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *);
#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30
#if MG_TLS == MG_TLS_BUILTIN
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
// KEY input. Accepts PEM or DER. Returns key size, or 0 on error.
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
#endif
// Low-level IO primitives used by TLS layer
enum { MG_IO_ERR = -1, MG_IO_WAIT = -2 };
long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
@@ -2868,28 +2880,28 @@ if 'dest' was filled with random data, or 0 if the random data could not be
generated. The filled-in values should be either truly random, or from a
cryptographically-secure PRNG.
A correctly functioning RNG function must be set (using mg_uecc_set_rng())
before calling mg_uecc_make_key() or mg_uecc_sign().
A correctly functioning RNG is required before calling mg_uecc_make_key() or
mg_uecc_sign().
Setting a correctly functioning RNG function improves the resistance to
side-channel attacks for mg_uecc_shared_secret() and
mg_uecc_sign_deterministic().
A correct RNG function is set by default when building for Windows, Linux, or OS
X. If you are building on another POSIX-compliant system that supports
/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
RNG. For embedded platforms there is no predefined RNG function; you must
provide your own.
*/
typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
// Mongoose: mg_uecc_make_key() and mg_uecc_sign() use mg_random() when no
// explicit uECC RNG is installed. If mg_random() cannot provide a strong source,
// those operations fail. Deterministic signing works without RNG, but only an
// explicit uECC RNG enables its optional side-channel blinding.
/* mg_uecc_set_rng() function.
Set the function that will be used to generate random bytes. The RNG function
should return 1 if the random data was generated, or 0 if the random data could
not be generated.
On platforms where there is no predefined RNG function (eg embedded platforms),
this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
Mongoose: this overrides the default mg_random() fallback used by
mg_uecc_make_key() and mg_uecc_sign(). Do not call mg_uecc_set_rng() unless the
application must provide its own cryptographic RNG.
Inputs:
rng_function - The function that will be used to generate random bytes.
@@ -4199,6 +4211,33 @@ size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
struct mg_jwt_opts {
struct mg_str claims; // JSON payload
struct mg_str header; // Extra header members, no braces; alg/typ are set
struct mg_str kid; // Key ID protected header
struct mg_str secret; // HS256 secret
const uint8_t *private_key; // ES256 private key (MG_TLS_BUILTIN only)
const uint8_t *public_key; // ES256 public key (MG_TLS_BUILTIN only)
};
// Signs JSON claims as a compact JWT. Returns JWT length, or 0 on error.
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
// Verifies JWT and decodes JSON claims into buf. Returns claims length, or 0.
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
// In-flight JSON-RPC request. Populate and pass to mg_rpc_process() to
// dispatch, or receive one pre-filled in a handler registered with mg_rpc_add().
struct mg_rpc_req {
+49 -3
View File
@@ -12,16 +12,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;
@@ -30,6 +30,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) {
@@ -91,3 +99,41 @@ fail:
if (dl > 0) dst[0] = '\0';
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;
}
+3
View File
@@ -4,3 +4,6 @@ size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
size_t mg_base64_final(char *buf, size_t len);
size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *buf,
size_t);
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t);
+164
View File
@@ -0,0 +1,164 @@
#include "jwt.h"
#include "base64.h"
#include "json.h"
#include "log.h"
#include "printf.h"
#include "sha256.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN
#include "tls_uecc.h"
#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
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "arch.h"
#include "str.h"
#include "tls.h"
struct mg_jwt_opts {
struct mg_str claims; // JSON payload
struct mg_str header; // Extra header members, no braces; alg/typ are set
struct mg_str kid; // Key ID protected header
struct mg_str secret; // HS256 secret
const uint8_t *private_key; // ES256 private key (MG_TLS_BUILTIN only)
const uint8_t *public_key; // ES256 public key (MG_TLS_BUILTIN only)
};
// Signs JSON claims as a compact JWT. Returns JWT length, or 0 on error.
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
// Verifies JWT and decodes JSON claims into buf. Returns claims length, or 0.
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
+6
View File
@@ -87,6 +87,12 @@ void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *);
#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30
#if MG_TLS == MG_TLS_BUILTIN
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
// KEY input. Accepts PEM or DER. Returns key size, or 0 on error.
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
#endif
// Low-level IO primitives used by TLS layer
enum { MG_IO_ERR = -1, MG_IO_WAIT = -2 };
long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
+2 -5
View File
@@ -22,6 +22,7 @@
#include "arch.h"
#include "tls.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN
/******************************************************************************/
#define AES_DECRYPTION 1 // whether AES decryption is supported
@@ -1115,8 +1116,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;
@@ -1127,9 +1126,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);
}
+21 -4
View File
@@ -2409,7 +2409,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;
@@ -2442,17 +2442,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;
@@ -2466,7 +2468,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"));
@@ -2571,6 +2573,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;
@@ -2713,7 +2729,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));
+3 -6
View File
@@ -6,6 +6,7 @@
#include "tls_chacha20.h"
#include "tls.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN && MG_ENABLE_CHACHA20
// ******* BEGIN: chacha-portable/chacha-portable.h ********
@@ -1336,8 +1337,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;
}
@@ -1345,10 +1344,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;
+4 -12
View File
@@ -222,11 +222,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;
@@ -2560,15 +2556,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) &&
+10 -10
View File
@@ -113,28 +113,28 @@ if 'dest' was filled with random data, or 0 if the random data could not be
generated. The filled-in values should be either truly random, or from a
cryptographically-secure PRNG.
A correctly functioning RNG function must be set (using mg_uecc_set_rng())
before calling mg_uecc_make_key() or mg_uecc_sign().
A correctly functioning RNG is required before calling mg_uecc_make_key() or
mg_uecc_sign().
Setting a correctly functioning RNG function improves the resistance to
side-channel attacks for mg_uecc_shared_secret() and
mg_uecc_sign_deterministic().
A correct RNG function is set by default when building for Windows, Linux, or OS
X. If you are building on another POSIX-compliant system that supports
/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
RNG. For embedded platforms there is no predefined RNG function; you must
provide your own.
*/
typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
// Mongoose: mg_uecc_make_key() and mg_uecc_sign() use mg_random() when no
// explicit uECC RNG is installed. If mg_random() cannot provide a strong source,
// those operations fail. Deterministic signing works without RNG, but only an
// explicit uECC RNG enables its optional side-channel blinding.
/* mg_uecc_set_rng() function.
Set the function that will be used to generate random bytes. The RNG function
should return 1 if the random data was generated, or 0 if the random data could
not be generated.
On platforms where there is no predefined RNG function (eg embedded platforms),
this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
Mongoose: this overrides the default mg_random() fallback used by
mg_uecc_make_key() and mg_uecc_sign(). Do not call mg_uecc_set_rng() unless the
application must provide its own cryptographic RNG.
Inputs:
rng_function - The function that will be used to generate random bytes.
+8
View File
@@ -25,6 +25,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) {
+3
View File
@@ -22,6 +22,9 @@ void mg_free(void *ptr);
// sensitive data (keys, passwords).
void mg_bzero(volatile unsigned char *buf, size_t len);
// Fixed-length constant-time byte equality. Use for MACs, tags, signatures.
bool mg_memeq(const void *a, const void *b, size_t n);
// Fills buf with len cryptographically random bytes. Uses the best available
// hardware or OS source (hardware RNG, /dev/urandom, CryptGenRandom, etc.).
// Falls back to rand() with an error log if no strong source is available.
+1 -1
View File
@@ -106,7 +106,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
mongoose.h: $(HDRS) Makefile
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
# Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h
+85
View File
@@ -301,6 +301,13 @@ static void test_base64(void) {
ASSERT(mg_base64_decode("AAA=", 4, buf, 2) == 0);
ASSERT(mg_base64_decode("AAA=", 4, buf, 3) == 0);
ASSERT(mg_base64_decode("AAA=", 4, buf, 4) == 2);
ASSERT(mg_base64url_encode((uint8_t *) "???", 3, buf, sizeof(buf)) == 4);
ASSERT(strcmp(buf, "Pz8_") == 0);
ASSERT(mg_base64url_decode("Pz8_", 4, buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "???") == 0);
ASSERT(mg_base64url_decode("Pz8/", 4, buf, sizeof(buf)) == 0);
ASSERT(mg_base64url_decode("A", 1, buf, sizeof(buf)) == 0);
}
static void test_iobuf(void) {
@@ -4388,6 +4395,83 @@ static void test_rsa(void) {
#endif
}
static void test_jwt(void) {
char jwt[512], claims[128], got[128], hdr[128], extra[64], tampered[512];
struct mg_jwt_opts opts, bad;
const char *dot;
char *str;
size_t n, m;
mg_snprintf(claims, sizeof(claims), "{%m:%m,%m:%d}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("iat"), 123);
mg_snprintf(extra, sizeof(extra), "%m:%m", MG_ESC("cty"), MG_ESC("JWT"));
memset(&opts, 0, sizeof(opts));
opts.claims = mg_str(claims);
opts.header = mg_str(extra);
opts.kid = mg_str("hmac-key-1");
opts.secret = mg_str("secret");
n = mg_jwt_sign_hs256(&opts, jwt, sizeof(jwt));
ASSERT(n > 0 && n + 1 < sizeof(jwt));
jwt[n] = '\0';
dot = strchr(jwt, '.');
ASSERT(dot != NULL);
ASSERT(mg_base64url_decode(jwt, (size_t) (dot - jwt), hdr,
sizeof(hdr)) > 0);
str = mg_json_get_str(mg_str(hdr), "$.kid");
ASSERT(str != NULL && strcmp(str, "hmac-key-1") == 0);
mg_free(str);
str = mg_json_get_str(mg_str(hdr), "$.cty");
ASSERT(str != NULL && strcmp(str, "JWT") == 0);
mg_free(str);
m = mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, sizeof(got));
ASSERT(m == strlen(claims));
ASSERT(strcmp(got, claims) == 0);
bad = opts;
bad.secret = mg_str("bad-secret");
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got,
sizeof(got)) == 0);
memcpy(tampered, jwt, n + 1);
tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A';
ASSERT(mg_jwt_verify_hs256(mg_str_n(tampered, n), &opts, got,
sizeof(got)) == 0);
ASSERT(mg_jwt_sign_hs256(&opts, got, 10) == 0);
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, 4) == 0);
#if MG_TLS == MG_TLS_BUILTIN
{
const char *key_pem =
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n";
uint8_t key[32], pub[64];
memset(&opts, 0, sizeof(opts));
opts.claims = mg_str(claims);
opts.kid = mg_str("ec-key-1");
opts.private_key = key;
opts.public_key = pub;
ASSERT(mg_uecc_parse_private_key(mg_str(key_pem), key, sizeof(key)) == 32);
ASSERT(mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) == 1);
n = mg_jwt_sign_es256(&opts, jwt, sizeof(jwt));
ASSERT(n > 0 && n + 1 < sizeof(jwt));
jwt[n] = '\0';
m = mg_jwt_verify_es256(mg_str_n(jwt, n), &opts, got, sizeof(got));
ASSERT(m == strlen(claims));
ASSERT(strcmp(got, claims) == 0);
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got,
sizeof(got)) == 0);
memcpy(tampered, jwt, n + 1);
tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A';
ASSERT(mg_jwt_verify_es256(mg_str_n(tampered, n), &opts, got,
sizeof(got)) == 0);
}
#endif
}
static void test_crypto(void) {
test_md5();
test_sha1();
@@ -4395,6 +4479,7 @@ static void test_crypto(void) {
test_sha384();
test_x25519();
test_rsa();
test_jwt();
}
static void dash_custom(struct mg_connection *c, int ev, void *ev_data) {
+26
View File
@@ -0,0 +1,26 @@
PROG ?= example # Program we are building
DELETE = rm -rf # Command to remove files
OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c # Source code files
CFLAGS = -W -Wall -Wextra -g -I. # Build options
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
PROG ?= example.exe # Use .exe suffix for the binary
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 -lbcrypt # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
OUT ?= -o $(PROG) # Build output
endif
all: $(PROG) # Default target. Build and run program
$(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
+62
View File
@@ -0,0 +1,62 @@
# JWT Bearer Authentication
## Build and run:
```sh
make
```
## HS256:
This path signs and verifies JWTs with the shared secret from `main.c`.
```sh
curl -i http://127.0.0.1:8000/login-hs256
TOKEN=$(curl -s http://127.0.0.1:8000/login-hs256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 47
{"sub":"admin","scope":"dash:read dash:write"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## ES256 (Requires MG_TLS_BUILTIN):
This path parses the EC private key from `main.c`, signs the JWT with it, and
derives the public key for verification to keep the example self-contained.
```sh
curl -i http://127.0.0.1:8000/login-es256
TOKEN=$(curl -s http://127.0.0.1:8000/login-es256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 36
{"sub":"admin","scope":"dash:read"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## Unauthorized access attempt example
```sh
curl -i -H "Authorization: Bearer bad.token.here" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Length: 13
Unauthorized
```
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// HTTP server example. It issues and verifies JWT Bearer tokens.
#include "mongoose.h"
static const char *s_listen_on = "http://0.0.0.0:8000";
static const char *s_secret = "change-me";
enum { TOKEN_LEN = 1024, CLAIMS_LEN = 256, JWT_LEN = 1024 };
static const char *s_ec_private_key =
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n";
#if MG_TLS == MG_TLS_BUILTIN
static size_t verify_es256(struct mg_str token, char *claims, size_t len) {
uint8_t key[32], pub[64];
struct mg_jwt_opts opts = {.public_key = pub};
// This keeps the example self-contained. A real verifier would usually keep
// only the issuer public key, selected by kid, not the signing private key.
if (mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key)) !=
32 ||
mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) != 1) {
return 0;
}
return mg_jwt_verify_es256(token, &opts, claims, len);
}
#endif
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
char user[1], *token, *claims, *jwt;
token = (char *) mg_calloc(1, TOKEN_LEN);
claims = (char *) mg_calloc(1, CLAIMS_LEN);
jwt = (char *) mg_calloc(1, JWT_LEN);
if (token == NULL || claims == NULL || jwt == NULL) {
mg_http_reply(c, 500, "", "OOM\n");
mg_free(token);
mg_free(claims);
mg_free(jwt);
return;
}
mg_http_creds(hm, user, sizeof(user), token, TOKEN_LEN);
if (mg_match(hm->uri, mg_str("/login-hs256"), NULL)) {
struct mg_jwt_opts opts = {.kid = mg_str("hmac-key-1"),
.secret = mg_str(s_secret)};
size_t n;
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"),
MG_ESC("dash:read dash:write"));
opts.claims = mg_str(claims);
n = mg_jwt_sign_hs256(&opts, jwt, JWT_LEN);
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#if MG_TLS == MG_TLS_BUILTIN
} else if (mg_match(hm->uri, mg_str("/login-es256"), NULL)) {
uint8_t key[32];
struct mg_jwt_opts opts = {.kid = mg_str("ec-key-1"),
.private_key = key};
size_t keysz, n;
keysz =
mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key));
// ES256 uses a raw 64-byte r || s signature. mg_jwt_sign_es256() signs it
// deterministically; applications can also hash the signing input and
// call mg_uecc_sign() directly if they need random-nonce ECDSA.
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"), MG_ESC("dash:read"));
opts.claims = mg_str(claims);
n = keysz == 32 ? mg_jwt_sign_es256(&opts, jwt, JWT_LEN) : 0;
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#endif
} else {
struct mg_jwt_opts opts = {.secret = mg_str(s_secret)};
if (mg_jwt_verify_hs256(mg_str(token), &opts, claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#if MG_TLS == MG_TLS_BUILTIN
} else if (verify_es256(mg_str(token), claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#endif
} else {
mg_http_reply(c, 401, "WWW-Authenticate: Bearer\r\n", "Unauthorized\n");
}
}
mg_free(token);
mg_free(claims);
mg_free(jwt);
}
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG);
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr);
return 0;
}
+1
View File
@@ -0,0 +1 @@
../../../mongoose.c
+1
View File
@@ -0,0 +1 @@
../../../mongoose.h