mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-16 10:39:49 +00:00
Merge pull request #3642 from cesanta/jwt
Add JWT Bearer Authentication support and tutorial
This commit is contained in:
21 files changed
+888
-81
No files matched your search
+255
-30
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
../../../mongoose.c
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../mongoose.h
|
||||
Reference in new issue
Block a user