mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-22 00:57:52 +00:00
24fdd1f225
Improve rejection of invalid chars
1315 lines
31 KiB
C
1315 lines
31 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "private-lib-core.h"
|
|
#include "private-lib-jose-jws.h"
|
|
|
|
/*
|
|
* Currently only support flattened or compact (implicitly single signature)
|
|
*/
|
|
|
|
static const char * const jws_json[] = {
|
|
"protected", /* base64u */
|
|
"header", /* JSON */
|
|
"payload", /* base64u payload */
|
|
"signature", /* base64u signature */
|
|
|
|
//"signatures[].protected",
|
|
//"signatures[].header",
|
|
//"signatures[].signature"
|
|
};
|
|
|
|
enum lws_jws_json_tok {
|
|
LJWSJT_PROTECTED,
|
|
LJWSJT_HEADER,
|
|
LJWSJT_PAYLOAD,
|
|
LJWSJT_SIGNATURE,
|
|
|
|
// LJWSJT_SIGNATURES_PROTECTED,
|
|
// LJWSJT_SIGNATURES_HEADER,
|
|
// LJWSJT_SIGNATURES_SIGNATURE,
|
|
};
|
|
|
|
/* parse a JWS complete or flattened JSON object */
|
|
|
|
struct jws_cb_args {
|
|
struct lws_jws *jws;
|
|
|
|
char *temp;
|
|
int *temp_len;
|
|
};
|
|
|
|
static signed char
|
|
lws_jws_json_cb(struct lejp_ctx *ctx, char reason)
|
|
{
|
|
struct jws_cb_args *args = (struct jws_cb_args *)ctx->user;
|
|
int n, m;
|
|
|
|
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
|
return 0;
|
|
|
|
switch (ctx->path_match - 1) {
|
|
|
|
/* strings */
|
|
|
|
case LJWSJT_PROTECTED: /* base64u: JOSE: must contain 'alg' */
|
|
m = LJWS_JOSE;
|
|
goto append_string;
|
|
case LJWSJT_PAYLOAD: /* base64u */
|
|
m = LJWS_PYLD;
|
|
goto append_string;
|
|
case LJWSJT_SIGNATURE: /* base64u */
|
|
m = LJWS_SIG;
|
|
goto append_string;
|
|
|
|
case LJWSJT_HEADER: /* unprotected freeform JSON */
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
append_string:
|
|
|
|
if (*args->temp_len < ctx->npos) {
|
|
lwsl_err("%s: out of parsing space\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* We keep both b64u and decoded in temp mapped using map / map_b64,
|
|
* the jws signature is actually over the b64 content not the plaintext,
|
|
* and we can't do it until we see the protected alg.
|
|
*/
|
|
|
|
if (!args->jws->map_b64.buf[m]) {
|
|
args->jws->map_b64.buf[m] = args->temp;
|
|
args->jws->map_b64.len[m] = 0;
|
|
}
|
|
|
|
memcpy(args->temp, ctx->buf, ctx->npos);
|
|
args->temp += ctx->npos;
|
|
*args->temp_len -= ctx->npos;
|
|
args->jws->map_b64.len[m] += ctx->npos;
|
|
|
|
if (reason == LEJPCB_VAL_STR_END) {
|
|
args->jws->map.buf[m] = args->temp;
|
|
|
|
n = lws_b64_decode_string_len(
|
|
(const char *)args->jws->map_b64.buf[m],
|
|
(int)args->jws->map_b64.len[m],
|
|
(char *)args->temp, *args->temp_len);
|
|
if (n < 0) {
|
|
lwsl_err("%s: b64 decode failed: in len %d, m %d\n", __func__, (int)args->jws->map_b64.len[m], m);
|
|
return -1;
|
|
}
|
|
|
|
args->temp += n;
|
|
*args->temp_len -= n;
|
|
args->jws->map.len[m] = (unsigned int)n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_jws_json_parse(struct lws_jws *jws, const uint8_t *buf, int len,
|
|
char *temp, int *temp_len)
|
|
{
|
|
struct jws_cb_args args;
|
|
struct lejp_ctx jctx;
|
|
int m = 0;
|
|
|
|
args.jws = jws;
|
|
args.temp = temp;
|
|
args.temp_len = temp_len;
|
|
|
|
lejp_construct(&jctx, lws_jws_json_cb, &args, jws_json,
|
|
LWS_ARRAY_SIZE(jws_json));
|
|
|
|
m = lejp_parse(&jctx, (uint8_t *)buf, len);
|
|
lejp_destruct(&jctx);
|
|
if (m < 0) {
|
|
lwsl_notice("%s: parse returned %d\n", __func__, m);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_jws_init(struct lws_jws *jws, struct lws_jwk *jwk,
|
|
struct lws_context *context)
|
|
{
|
|
memset(jws, 0, sizeof(*jws));
|
|
jws->context = context;
|
|
jws->jwk = jwk;
|
|
}
|
|
|
|
static void
|
|
lws_jws_map_bzero(struct lws_jws_map *map)
|
|
{
|
|
int n;
|
|
|
|
/* no need to scrub first jose header element (it can be canned then) */
|
|
|
|
for (n = 1; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++)
|
|
if (map->buf[n])
|
|
lws_explicit_bzero((void *)map->buf[n], map->len[n]);
|
|
}
|
|
|
|
void
|
|
lws_jws_destroy(struct lws_jws *jws)
|
|
{
|
|
lws_jws_map_bzero(&jws->map);
|
|
jws->jwk = NULL;
|
|
}
|
|
|
|
int
|
|
lws_jws_dup_element(struct lws_jws_map *map, int idx, char *temp, int *temp_len,
|
|
const void *in, size_t in_len, size_t actual_alloc)
|
|
{
|
|
if (!actual_alloc)
|
|
actual_alloc = in_len;
|
|
|
|
if ((size_t)*temp_len < actual_alloc)
|
|
return -1;
|
|
|
|
memcpy(temp, in, in_len);
|
|
|
|
map->len[idx] = (uint32_t)in_len;
|
|
map->buf[idx] = temp;
|
|
|
|
*temp_len -= (int)actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_encode_b64_element(struct lws_jws_map *map, int idx,
|
|
char *temp, int *temp_len, const void *in,
|
|
size_t in_len)
|
|
{
|
|
int n;
|
|
|
|
if (*temp_len < lws_base64_size((int)in_len))
|
|
return -1;
|
|
|
|
n = lws_jws_base64_enc(in, in_len, temp, (size_t)*temp_len);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
map->len[idx] = (unsigned int)n;
|
|
map->buf[idx] = temp;
|
|
|
|
*temp_len -= n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_randomize_element(struct lws_context *context, struct lws_jws_map *map,
|
|
int idx, char *temp, int *temp_len, size_t random_len,
|
|
size_t actual_alloc)
|
|
{
|
|
if (!actual_alloc)
|
|
actual_alloc = random_len;
|
|
|
|
if ((size_t)*temp_len < actual_alloc)
|
|
return -1;
|
|
|
|
map->len[idx] = (uint32_t)random_len;
|
|
map->buf[idx] = temp;
|
|
|
|
if (lws_get_random(context, temp, random_len) != random_len) {
|
|
lwsl_err("Problem getting random\n");
|
|
return -1;
|
|
}
|
|
|
|
*temp_len -= (int)actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_alloc_element(struct lws_jws_map *map, int idx, char *temp,
|
|
int *temp_len, size_t len, size_t actual_alloc)
|
|
{
|
|
if (!actual_alloc)
|
|
actual_alloc = len;
|
|
|
|
if ((size_t)*temp_len < actual_alloc)
|
|
return -1;
|
|
|
|
map->len[idx] = (uint32_t)len;
|
|
map->buf[idx] = temp;
|
|
*temp_len -= (int)actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max)
|
|
{
|
|
int n;
|
|
|
|
n = lws_b64_encode_string_url(in, (int)in_len, out, (int)out_max - 1);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: in len %d too large for %d out buf\n",
|
|
__func__, (int)in_len, (int)out_max);
|
|
return n; /* too large for output buffer */
|
|
}
|
|
|
|
/* trim the terminal = */
|
|
while (n && out[n - 1] == '=')
|
|
n--;
|
|
|
|
out[n] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
int
|
|
lws_jws_b64_compact_map(const char *in, int len, struct lws_jws_map *map)
|
|
{
|
|
int me = 0;
|
|
|
|
memset(map, 0, sizeof(*map));
|
|
|
|
map->buf[me] = (char *)in;
|
|
map->len[me] = 0;
|
|
|
|
while (len--) {
|
|
if (*in++ == '.') {
|
|
if (++me == LWS_JWS_MAX_COMPACT_BLOCKS)
|
|
return -1;
|
|
map->buf[me] = (char *)in;
|
|
map->len[me] = 0;
|
|
continue;
|
|
}
|
|
map->len[me]++;
|
|
}
|
|
|
|
return me + 1;
|
|
}
|
|
|
|
/* b64 in, map contains decoded elements, if non-NULL,
|
|
* map_b64 set to b64 elements
|
|
*/
|
|
|
|
int
|
|
lws_jws_compact_decode(const char *in, int len, struct lws_jws_map *map,
|
|
struct lws_jws_map *map_b64, char *out,
|
|
int *out_len)
|
|
{
|
|
int blocks, n, m = 0;
|
|
|
|
if (!map_b64)
|
|
map_b64 = map;
|
|
|
|
memset(map_b64, 0, sizeof(*map_b64));
|
|
memset(map, 0, sizeof(*map));
|
|
|
|
blocks = lws_jws_b64_compact_map(in, len, map_b64);
|
|
|
|
if (blocks > LWS_JWS_MAX_COMPACT_BLOCKS)
|
|
return -1;
|
|
|
|
while (m < blocks) {
|
|
if ((int)map_b64->len[m]) {
|
|
n = lws_b64_decode_string_len(map_b64->buf[m], (int)map_b64->len[m],
|
|
out, *out_len);
|
|
if (n < 0) {
|
|
lwsl_err("%s: b64 decode failed len %d\n",
|
|
__func__, (int)map_b64->len[m]);
|
|
return -1;
|
|
}
|
|
/* replace the map entry with the decoded content */
|
|
if (n)
|
|
map->buf[m] = out;
|
|
else
|
|
map->buf[m] = NULL;
|
|
map->len[m++] = (unsigned int)n;
|
|
out += n;
|
|
*out_len -= n;
|
|
} else
|
|
m++;
|
|
|
|
if (*out_len < 1)
|
|
return -1;
|
|
}
|
|
|
|
return blocks;
|
|
}
|
|
|
|
static int
|
|
lws_jws_compact_decode_map(struct lws_jws_map *map_b64, struct lws_jws_map *map,
|
|
char *out, int *out_len)
|
|
{
|
|
int n, m = 0;
|
|
|
|
for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
|
|
if ((int)map_b64->len[m]) {
|
|
n = lws_b64_decode_string_len(map_b64->buf[m], (int)map_b64->len[m],
|
|
out, *out_len);
|
|
if (n < 0) {
|
|
lwsl_err("%s: b64 decode failed len %d\n",
|
|
__func__, (int)map_b64->len[m]);
|
|
|
|
return -1;
|
|
}
|
|
/* replace the map entry with the decoded content */
|
|
map->buf[m] = out;
|
|
map->len[m++] = (unsigned int)n;
|
|
} else
|
|
m++;
|
|
out += n;
|
|
*out_len -= n;
|
|
|
|
if (*out_len < 1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_encode_section(const char *in, size_t in_len, int first, char **p,
|
|
char *end)
|
|
{
|
|
int n, len = lws_ptr_diff(end, (*p)) - 1;
|
|
char *p_entry = *p;
|
|
|
|
if (len < 3)
|
|
return -1;
|
|
|
|
if (!first)
|
|
*(*p)++ = '.';
|
|
|
|
n = lws_jws_base64_enc(in, in_len, *p, (unsigned int)len - 1);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
*p += n;
|
|
|
|
return lws_ptr_diff((*p), p_entry);
|
|
}
|
|
|
|
int
|
|
lws_jws_compact_encode(struct lws_jws_map *map_b64, /* b64-encoded */
|
|
const struct lws_jws_map *map, /* non-b64 */
|
|
char *buf, int *len)
|
|
{
|
|
int n, m;
|
|
|
|
for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
|
|
if (!map->buf[n]) {
|
|
map_b64->buf[n] = NULL;
|
|
map_b64->len[n] = 0;
|
|
continue;
|
|
}
|
|
m = lws_jws_base64_enc(map->buf[n], map->len[n], buf, (size_t)*len);
|
|
if (m < 0)
|
|
return -1;
|
|
buf += m;
|
|
*len -= m;
|
|
if (*len < 1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This takes both a base64 -encoded map and a plaintext map.
|
|
*
|
|
* JWS demands base-64 encoded elements for hash computation and at least for
|
|
* the JOSE header and signature, decoded versions too.
|
|
*/
|
|
|
|
int
|
|
lws_jws_sig_confirm(struct lws_jws_map *map_b64, struct lws_jws_map *map,
|
|
struct lws_jwk *jwk, struct lws_context *context)
|
|
{
|
|
enum enum_genrsa_mode padding = LGRSAM_PKCS1_1_5;
|
|
char temp[256];
|
|
int n, h_len, b = 3, temp_len = sizeof(temp);
|
|
uint8_t digest[LWS_GENHASH_LARGEST];
|
|
struct lws_genhash_ctx hash_ctx;
|
|
struct lws_genec_ctx ecdsactx;
|
|
struct lws_genrsa_ctx rsactx;
|
|
struct lws_genhmac_ctx ctx;
|
|
struct lws_jose jose;
|
|
|
|
lws_jose_init(&jose);
|
|
|
|
/* only valid if no signature or key */
|
|
if (!map_b64->buf[LJWS_SIG] && !map->buf[LJWS_UHDR])
|
|
b = 2;
|
|
|
|
if (lws_jws_parse_jose(&jose, map->buf[LJWS_JOSE], (int)map->len[LJWS_JOSE],
|
|
temp, &temp_len) < 0 || !jose.alg) {
|
|
lwsl_notice("%s: parse failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (!strcmp(jose.alg->alg, "none")) {
|
|
/* "none" compact serialization has 2 blocks: jose.payload */
|
|
if (b != 2 || jwk)
|
|
return -1;
|
|
|
|
/* the lack of a key matches the lack of a signature */
|
|
return 0;
|
|
}
|
|
|
|
/* all other have 3 blocks: jose.payload.sig */
|
|
if (b != 3 || !jwk) {
|
|
lwsl_notice("%s: %d blocks\n", __func__, b);
|
|
return -1;
|
|
}
|
|
|
|
switch (jose.alg->algtype_signing) {
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
|
|
padding = LGRSAM_PKCS1_OAEP_PSS;
|
|
/* fallthru */
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
|
|
|
|
/* RSASSA-PKCS1-v1_5 or OAEP using SHA-256/384/512 */
|
|
|
|
if (jwk->kty != LWS_GENCRYPTO_KTY_RSA)
|
|
return -1;
|
|
|
|
/* 6(RSA): compute the hash of the payload into "digest" */
|
|
|
|
if (lws_genhash_init(&hash_ctx, jose.alg->hash_type))
|
|
return -1;
|
|
|
|
/*
|
|
* JWS Signing Input value:
|
|
*
|
|
* BASE64URL(UTF8(JWS Protected Header)) || '.' ||
|
|
* BASE64URL(JWS Payload)
|
|
*/
|
|
|
|
if (lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
|
|
map_b64->len[LJWS_JOSE]) ||
|
|
lws_genhash_update(&hash_ctx, ".", 1) ||
|
|
lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
|
|
map_b64->len[LJWS_PYLD]) ||
|
|
lws_genhash_destroy(&hash_ctx, digest)) {
|
|
lws_genhash_destroy(&hash_ctx, NULL);
|
|
|
|
return -1;
|
|
}
|
|
// h_len = lws_genhash_size(jose.alg->hash_type);
|
|
|
|
if (lws_genrsa_create(&rsactx, jwk->e, context, padding,
|
|
LWS_GENHASH_TYPE_UNKNOWN)) {
|
|
lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
n = lws_genrsa_hash_sig_verify(&rsactx, digest,
|
|
jose.alg->hash_type,
|
|
(uint8_t *)map->buf[LJWS_SIG],
|
|
map->len[LJWS_SIG]);
|
|
|
|
lws_genrsa_destroy(&rsactx);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: decrypt fail\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case LWS_JOSE_ENCTYPE_NONE: /* HSxxx */
|
|
|
|
/* SHA256/384/512 HMAC */
|
|
|
|
h_len = (int)lws_genhmac_size(jose.alg->hmac_type);
|
|
|
|
/* 6) compute HMAC over payload */
|
|
|
|
if (lws_genhmac_init(&ctx, jose.alg->hmac_type,
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len))
|
|
return -1;
|
|
|
|
/*
|
|
* JWS Signing Input value:
|
|
*
|
|
* BASE64URL(UTF8(JWS Protected Header)) || '.' ||
|
|
* BASE64URL(JWS Payload)
|
|
*/
|
|
|
|
if (lws_genhmac_update(&ctx, map_b64->buf[LJWS_JOSE],
|
|
map_b64->len[LJWS_JOSE]) ||
|
|
lws_genhmac_update(&ctx, ".", 1) ||
|
|
lws_genhmac_update(&ctx, map_b64->buf[LJWS_PYLD],
|
|
map_b64->len[LJWS_PYLD]) ||
|
|
lws_genhmac_destroy(&ctx, digest)) {
|
|
lws_genhmac_destroy(&ctx, NULL);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* 7) Compare the computed and decoded hashes */
|
|
|
|
if (lws_timingsafe_bcmp(digest, map->buf[2], (uint32_t)h_len)) {
|
|
lwsl_notice("digest mismatch\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case LWS_JOSE_ENCTYPE_ECDSA:
|
|
|
|
/* ECDSA using SHA-256/384/512 */
|
|
|
|
/* Confirm the key coming in with this makes sense */
|
|
|
|
/* has to be an EC key :-) */
|
|
if (jwk->kty != LWS_GENCRYPTO_KTY_EC)
|
|
return -1;
|
|
|
|
/* key must state its curve */
|
|
if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
|
|
return -1;
|
|
|
|
/* key must match the selected alg curve */
|
|
if (strcmp((const char *)jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
|
|
jose.alg->curve_name))
|
|
return -1;
|
|
|
|
/*
|
|
* JWS Signing Input value:
|
|
*
|
|
* BASE64URL(UTF8(JWS Protected Header)) || '.' ||
|
|
* BASE64URL(JWS Payload)
|
|
*
|
|
* Validating the JWS Signature is a bit different from the
|
|
* previous examples. We need to split the 64 member octet
|
|
* sequence of the JWS Signature (which is base64url decoded
|
|
* from the value encoded in the JWS representation) into two
|
|
* 32 octet sequences, the first representing R and the second
|
|
* S. We then pass the public key (x, y), the signature (R, S),
|
|
* and the JWS Signing Input (which is the initial substring of
|
|
* the JWS Compact Serialization representation up until but not
|
|
* including the second period character) to an ECDSA signature
|
|
* verifier that has been configured to use the P-256 curve with
|
|
* the SHA-256 hash function.
|
|
*/
|
|
|
|
if (lws_genhash_init(&hash_ctx, jose.alg->hash_type) ||
|
|
lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
|
|
map_b64->len[LJWS_JOSE]) ||
|
|
lws_genhash_update(&hash_ctx, ".", 1) ||
|
|
lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
|
|
map_b64->len[LJWS_PYLD]) ||
|
|
lws_genhash_destroy(&hash_ctx, digest)) {
|
|
lws_genhash_destroy(&hash_ctx, NULL);
|
|
|
|
return -1;
|
|
}
|
|
|
|
h_len = (int)lws_genhash_size(jose.alg->hash_type);
|
|
|
|
if (lws_genecdsa_create(&ecdsactx, context, NULL)) {
|
|
lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
if (lws_genecdsa_set_key(&ecdsactx, jwk->e)) {
|
|
lws_genec_destroy(&ecdsactx);
|
|
lwsl_notice("%s: ec key import fail\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
n = lws_genecdsa_hash_sig_verify_jws(&ecdsactx, digest,
|
|
jose.alg->hash_type,
|
|
jose.alg->keybits_fixed,
|
|
(uint8_t *)map->buf[LJWS_SIG],
|
|
map->len[LJWS_SIG]);
|
|
lws_genec_destroy(&ecdsactx);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: verify fail\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
lwsl_err("%s: unknown alg from jose\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* it's already a b64 map, we will make a temp plain version */
|
|
|
|
int
|
|
lws_jws_sig_confirm_compact_b64_map(struct lws_jws_map *map_b64,
|
|
struct lws_jwk *jwk,
|
|
struct lws_context *context,
|
|
char *temp, int *temp_len)
|
|
{
|
|
struct lws_jws_map map;
|
|
int n;
|
|
|
|
n = lws_jws_compact_decode_map(map_b64, &map, temp, temp_len);
|
|
if (n > 3 || n < 0)
|
|
return -1;
|
|
|
|
return lws_jws_sig_confirm(map_b64, &map, jwk, context);
|
|
}
|
|
|
|
/*
|
|
* it's already a compact / concatenated b64 string, we will make a temp
|
|
* plain version
|
|
*/
|
|
|
|
int
|
|
lws_jws_sig_confirm_compact_b64(const char *in, size_t len,
|
|
struct lws_jws_map *map, struct lws_jwk *jwk,
|
|
struct lws_context *context,
|
|
char *temp, int *temp_len)
|
|
{
|
|
struct lws_jws_map map_b64;
|
|
int n;
|
|
|
|
if (lws_jws_b64_compact_map(in, (int)len, &map_b64) < 0)
|
|
return -1;
|
|
|
|
n = lws_jws_compact_decode(in, (int)len, map, &map_b64, temp, temp_len);
|
|
if (n > 3 || n < 0)
|
|
return -1;
|
|
|
|
return lws_jws_sig_confirm(&map_b64, map, jwk, context);
|
|
}
|
|
|
|
/* it's already plain, we will make a temp b64 version */
|
|
|
|
int
|
|
lws_jws_sig_confirm_compact(struct lws_jws_map *map, struct lws_jwk *jwk,
|
|
struct lws_context *context, char *temp,
|
|
int *temp_len)
|
|
{
|
|
struct lws_jws_map map_b64;
|
|
|
|
if (lws_jws_compact_encode(&map_b64, map, temp, temp_len) < 0)
|
|
return -1;
|
|
|
|
return lws_jws_sig_confirm(&map_b64, map, jwk, context);
|
|
}
|
|
|
|
int
|
|
lws_jws_sig_confirm_json(const char *in, size_t len,
|
|
struct lws_jws *jws, struct lws_jwk *jwk,
|
|
struct lws_context *context,
|
|
char *temp, int *temp_len)
|
|
{
|
|
if (lws_jws_json_parse(jws, (const uint8_t *)in,
|
|
(int)len, temp, temp_len)) {
|
|
lwsl_err("%s: lws_jws_json_parse failed\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
return lws_jws_sig_confirm(&jws->map_b64, &jws->map, jwk, context);
|
|
}
|
|
|
|
|
|
int
|
|
lws_jws_sign_from_b64(struct lws_jose *jose, struct lws_jws *jws,
|
|
char *b64_sig, size_t sig_len)
|
|
{
|
|
enum enum_genrsa_mode pad = LGRSAM_PKCS1_1_5;
|
|
uint8_t digest[LWS_GENHASH_LARGEST];
|
|
struct lws_genhash_ctx hash_ctx;
|
|
struct lws_genec_ctx ecdsactx;
|
|
struct lws_genrsa_ctx rsactx;
|
|
uint8_t *buf;
|
|
int n, m;
|
|
|
|
if (jose->alg->hash_type == LWS_GENHASH_TYPE_UNKNOWN &&
|
|
jose->alg->hmac_type == LWS_GENHMAC_TYPE_UNKNOWN &&
|
|
!strcmp(jose->alg->alg, "none"))
|
|
return 0;
|
|
|
|
if (lws_genhash_init(&hash_ctx, jose->alg->hash_type) ||
|
|
lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_JOSE],
|
|
jws->map_b64.len[LJWS_JOSE]) ||
|
|
lws_genhash_update(&hash_ctx, ".", 1) ||
|
|
lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_PYLD],
|
|
jws->map_b64.len[LJWS_PYLD]) ||
|
|
lws_genhash_destroy(&hash_ctx, digest)) {
|
|
lws_genhash_destroy(&hash_ctx, NULL);
|
|
|
|
return -1;
|
|
}
|
|
|
|
switch (jose->alg->algtype_signing) {
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
|
|
pad = LGRSAM_PKCS1_OAEP_PSS;
|
|
/* fallthru */
|
|
case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
|
|
|
|
if (jws->jwk->kty != LWS_GENCRYPTO_KTY_RSA)
|
|
return -1;
|
|
|
|
if (lws_genrsa_create(&rsactx, jws->jwk->e, jws->context,
|
|
pad, LWS_GENHASH_TYPE_UNKNOWN)) {
|
|
lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
n = (int)jws->jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len;
|
|
buf = lws_malloc((unsigned int)lws_base64_size(n), "jws sign");
|
|
if (!buf)
|
|
return -1;
|
|
|
|
n = lws_genrsa_hash_sign(&rsactx, digest, jose->alg->hash_type,
|
|
buf, (unsigned int)n);
|
|
lws_genrsa_destroy(&rsactx);
|
|
if (n < 0) {
|
|
lwsl_err("%s: lws_genrsa_hash_sign failed\n", __func__);
|
|
lws_free(buf);
|
|
|
|
return -1;
|
|
}
|
|
|
|
n = lws_jws_base64_enc((char *)buf, (unsigned int)n, b64_sig, sig_len);
|
|
lws_free(buf);
|
|
if (n < 0) {
|
|
lwsl_err("%s: lws_jws_base64_enc failed\n", __func__);
|
|
}
|
|
|
|
return n;
|
|
|
|
case LWS_JOSE_ENCTYPE_NONE:
|
|
return lws_jws_base64_enc((char *)digest,
|
|
lws_genhash_size(jose->alg->hash_type),
|
|
b64_sig, sig_len);
|
|
case LWS_JOSE_ENCTYPE_ECDSA:
|
|
/* ECDSA using SHA-256/384/512 */
|
|
|
|
/* the key coming in with this makes sense, right? */
|
|
|
|
/* has to be an EC key :-) */
|
|
if (jws->jwk->kty != LWS_GENCRYPTO_KTY_EC)
|
|
return -1;
|
|
|
|
/* key must state its curve */
|
|
if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
|
|
return -1;
|
|
|
|
/* must have all his pieces for a private key */
|
|
if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf ||
|
|
!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf ||
|
|
!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
|
|
return -1;
|
|
|
|
/* key must match the selected alg curve */
|
|
if (strcmp((const char *)
|
|
jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
|
|
jose->alg->curve_name))
|
|
return -1;
|
|
|
|
if (lws_genecdsa_create(&ecdsactx, jws->context, NULL)) {
|
|
lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
if (lws_genecdsa_set_key(&ecdsactx, jws->jwk->e)) {
|
|
lws_genec_destroy(&ecdsactx);
|
|
lwsl_notice("%s: ec key import fail\n", __func__);
|
|
return -1;
|
|
}
|
|
m = lws_gencrypto_bits_to_bytes(jose->alg->keybits_fixed) * 2;
|
|
buf = lws_malloc((unsigned int)m, "jws sign");
|
|
if (!buf)
|
|
return -1;
|
|
|
|
n = lws_genecdsa_hash_sign_jws(&ecdsactx, digest,
|
|
jose->alg->hash_type,
|
|
jose->alg->keybits_fixed,
|
|
(uint8_t *)buf, (unsigned int)m);
|
|
lws_genec_destroy(&ecdsactx);
|
|
if (n < 0) {
|
|
lws_free(buf);
|
|
lwsl_notice("%s: lws_genecdsa_hash_sign_jws fail\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
n = lws_jws_base64_enc((char *)buf, (unsigned int)m, b64_sig, sig_len);
|
|
lws_free(buf);
|
|
|
|
return n;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* unknown key type */
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Flattened JWS JSON:
|
|
*
|
|
* {
|
|
* "payload": "<payload contents>",
|
|
* "protected": "<integrity-protected header contents>",
|
|
* "header": <non-integrity-protected header contents>,
|
|
* "signature": "<signature contents>"
|
|
* }
|
|
*/
|
|
|
|
int
|
|
lws_jws_write_flattened_json(struct lws_jws *jws, char *flattened, size_t len)
|
|
{
|
|
size_t n = 0;
|
|
|
|
if (len < 1)
|
|
return 1;
|
|
|
|
n += (unsigned int)lws_snprintf(flattened + n, len - n , "{\"payload\": \"");
|
|
lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_PYLD],
|
|
jws->map_b64.len[LJWS_PYLD], len - n);
|
|
n = n + strlen(flattened + n);
|
|
|
|
n += (unsigned int)lws_snprintf(flattened + n, len - n , "\",\n \"protected\": \"");
|
|
lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_JOSE],
|
|
jws->map_b64.len[LJWS_JOSE], len - n);
|
|
n = n + strlen(flattened + n);
|
|
|
|
if (jws->map_b64.buf[LJWS_UHDR]) {
|
|
n += (unsigned int)lws_snprintf(flattened + n, len - n , "\",\n \"header\": ");
|
|
lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_UHDR],
|
|
jws->map_b64.len[LJWS_UHDR], len - n);
|
|
n = n + strlen(flattened + n);
|
|
}
|
|
|
|
n += (unsigned int)lws_snprintf(flattened + n, len - n , "\",\n \"signature\": \"");
|
|
lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_SIG],
|
|
jws->map_b64.len[LJWS_SIG], len - n);
|
|
n = n + strlen(flattened + n);
|
|
|
|
n += (unsigned int)lws_snprintf(flattened + n, len - n , "\"}\n");
|
|
|
|
return (n >= len - 1);
|
|
}
|
|
|
|
int
|
|
lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len)
|
|
{
|
|
size_t n = 0;
|
|
|
|
if (len < 1)
|
|
return 1;
|
|
|
|
lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_JOSE],
|
|
jws->map_b64.len[LJWS_JOSE], len - n);
|
|
n += strlen(compact + n);
|
|
if (n >= len - 1)
|
|
return 1;
|
|
compact[n++] = '.';
|
|
lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_PYLD],
|
|
jws->map_b64.len[LJWS_PYLD], len - n);
|
|
n += strlen(compact + n);
|
|
if (n >= len - 1)
|
|
return 1;
|
|
compact[n++] = '.';
|
|
lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_SIG],
|
|
jws->map_b64.len[LJWS_SIG], len - n);
|
|
n += strlen(compact + n);
|
|
|
|
return n >= len - 1;
|
|
}
|
|
|
|
int
|
|
lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk,
|
|
const char *alg_list, const char *com, size_t len,
|
|
char *temp, int tl, char *out, size_t *out_len)
|
|
{
|
|
struct lws_tokenize ts;
|
|
struct lws_jose jose;
|
|
int otl = tl, r = 1;
|
|
struct lws_jws jws;
|
|
size_t n;
|
|
|
|
memset(&jws, 0, sizeof(jws));
|
|
lws_jose_init(&jose);
|
|
|
|
/*
|
|
* Decode the b64.b64[.b64] compact serialization
|
|
* blocks
|
|
*/
|
|
|
|
n = (size_t)lws_jws_compact_decode(com, (int)len, &jws.map, &jws.map_b64,
|
|
temp, &tl);
|
|
if (n != 3) {
|
|
lwsl_err("%s: concat_map failed: %d\n", __func__, (int)n);
|
|
goto bail;
|
|
}
|
|
|
|
temp += otl - tl;
|
|
otl = tl;
|
|
|
|
/*
|
|
* Parse the JOSE header
|
|
*/
|
|
|
|
if (lws_jws_parse_jose(&jose, jws.map.buf[LJWS_JOSE],
|
|
(int)jws.map.len[LJWS_JOSE], temp, &tl) < 0) {
|
|
lwsl_err("%s: JOSE parse failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Insist to see an alg in there that we list as acceptable
|
|
*/
|
|
|
|
lws_tokenize_init(&ts, alg_list, LWS_TOKENIZE_F_COMMA_SEP_LIST |
|
|
LWS_TOKENIZE_F_RFC7230_DELIMS);
|
|
n = strlen(jose.alg->alg);
|
|
|
|
do {
|
|
ts.e = (int8_t)lws_tokenize(&ts);
|
|
if (ts.e == LWS_TOKZE_TOKEN && ts.token_len == n &&
|
|
!strncmp(jose.alg->alg, ts.token, ts.token_len))
|
|
break;
|
|
} while (ts.e != LWS_TOKZE_ENDED);
|
|
|
|
if (ts.e != LWS_TOKZE_TOKEN) {
|
|
lwsl_err("%s: JOSE using alg %s (accepted: %s)\n", __func__,
|
|
jose.alg->alg, alg_list);
|
|
goto bail;
|
|
}
|
|
|
|
/* we liked the alg... now how about the crypto? */
|
|
|
|
if (lws_jws_sig_confirm(&jws.map_b64, &jws.map, jwk, ctx) < 0) {
|
|
lwsl_notice("%s: confirm JWT sig failed\n",
|
|
__func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* yeah, it's validated... see about copying it out */
|
|
|
|
if (*out_len < jws.map.len[LJWS_PYLD] + 1) {
|
|
/* we don't have enough room */
|
|
r = 2;
|
|
goto bail;
|
|
}
|
|
|
|
memcpy(out, jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]);
|
|
*out_len = jws.map.len[LJWS_PYLD];
|
|
out[jws.map.len[LJWS_PYLD]] = '\0';
|
|
|
|
r = 0;
|
|
|
|
bail:
|
|
lws_jws_destroy(&jws);
|
|
lws_jose_destroy(&jose);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int lws_jwt_vsign_via_info(struct lws_context *ctx, struct lws_jwk *jwk,
|
|
const struct lws_jwt_sign_info *info, const char *format, va_list ap)
|
|
{
|
|
size_t actual_hdr_len;
|
|
struct lws_jose jose;
|
|
struct lws_jws jws;
|
|
va_list ap_cpy;
|
|
int n, r = 1;
|
|
int otl, tlr;
|
|
char *p, *q;
|
|
|
|
lws_jws_init(&jws, jwk, ctx);
|
|
lws_jose_init(&jose);
|
|
|
|
otl = tlr = info->tl;
|
|
p = info->temp;
|
|
|
|
/*
|
|
* We either just use the provided info->jose_hdr, or build a
|
|
* minimal header from info->alg
|
|
*/
|
|
actual_hdr_len = info->jose_hdr ? info->jose_hdr_len :
|
|
10 + strlen(info->alg);
|
|
|
|
if (actual_hdr_len > INT_MAX) {
|
|
goto bail;
|
|
}
|
|
|
|
if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, info->temp, &tlr,
|
|
actual_hdr_len, 0)) {
|
|
lwsl_err("%s: temp space too small\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
if (!info->jose_hdr) {
|
|
|
|
/* get algorithm from 'alg' string and write minimal JOSE header */
|
|
if (lws_gencrypto_jws_alg_to_definition(info->alg, &jose.alg)) {
|
|
lwsl_err("%s: unknown alg %s\n", __func__, info->alg);
|
|
|
|
goto bail;
|
|
}
|
|
jws.map.len[LJWS_JOSE] = (uint32_t)lws_snprintf(
|
|
(char *)jws.map.buf[LJWS_JOSE], (size_t)otl,
|
|
"{\"alg\":\"%s\"}", info->alg);
|
|
} else {
|
|
|
|
/*
|
|
* Get algorithm by parsing the given JOSE header and copy it,
|
|
* if it's ok
|
|
*/
|
|
if (lws_jws_parse_jose(&jose, info->jose_hdr,
|
|
(int)actual_hdr_len, info->temp, &tlr)) {
|
|
lwsl_err("%s: invalid jose header\n", __func__);
|
|
goto bail;
|
|
}
|
|
tlr = otl;
|
|
memcpy((char *)jws.map.buf[LJWS_JOSE], info->jose_hdr,
|
|
actual_hdr_len);
|
|
jws.map.len[LJWS_JOSE] = (uint32_t)actual_hdr_len;
|
|
tlr -= (int)actual_hdr_len;
|
|
}
|
|
|
|
p += otl - tlr;
|
|
otl = tlr;
|
|
|
|
va_copy(ap_cpy, ap);
|
|
n = vsnprintf(NULL, 0, format, ap_cpy);
|
|
va_end(ap_cpy);
|
|
if (n + 2 >= tlr)
|
|
goto bail;
|
|
|
|
q = lws_malloc((unsigned int)n + 2, __func__);
|
|
if (!q)
|
|
goto bail;
|
|
|
|
vsnprintf(q, (unsigned int)n + 2, format, ap);
|
|
|
|
/* add the plaintext from stdin to the map and a b64 version */
|
|
|
|
jws.map.buf[LJWS_PYLD] = q;
|
|
jws.map.len[LJWS_PYLD] = (uint32_t)n;
|
|
|
|
if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD, p, &tlr,
|
|
jws.map.buf[LJWS_PYLD],
|
|
jws.map.len[LJWS_PYLD]))
|
|
goto bail1;
|
|
|
|
p += otl - tlr;
|
|
otl = tlr;
|
|
|
|
/* add the b64 JOSE header to the b64 map */
|
|
|
|
if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, p, &tlr,
|
|
jws.map.buf[LJWS_JOSE],
|
|
jws.map.len[LJWS_JOSE]))
|
|
goto bail1;
|
|
|
|
p += otl - tlr;
|
|
otl = tlr;
|
|
|
|
/* prepare the space for the b64 signature in the map */
|
|
|
|
if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, p, &tlr,
|
|
(size_t)lws_base64_size(LWS_JWE_LIMIT_KEY_ELEMENT_BYTES),
|
|
0))
|
|
goto bail1;
|
|
|
|
/* sign the plaintext */
|
|
|
|
n = lws_jws_sign_from_b64(&jose, &jws,
|
|
(char *)jws.map_b64.buf[LJWS_SIG],
|
|
jws.map_b64.len[LJWS_SIG]);
|
|
if (n < 0)
|
|
goto bail1;
|
|
|
|
/* set the actual b64 signature size */
|
|
jws.map_b64.len[LJWS_SIG] = (uint32_t)n;
|
|
|
|
/* create the compact JWS representation */
|
|
if (lws_jws_write_compact(&jws, info->out, *info->out_len))
|
|
goto bail1;
|
|
|
|
*info->out_len = strlen(info->out);
|
|
|
|
r = 0;
|
|
|
|
bail1:
|
|
lws_free(q);
|
|
|
|
bail:
|
|
jws.map.buf[LJWS_PYLD] = NULL;
|
|
jws.map.len[LJWS_PYLD] = 0;
|
|
lws_jws_destroy(&jws);
|
|
lws_jose_destroy(&jose);
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
lws_jwt_sign_via_info(struct lws_context *ctx, struct lws_jwk *jwk,
|
|
const struct lws_jwt_sign_info *info, const char *format,
|
|
...)
|
|
{
|
|
int ret;
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
ret = lws_jwt_vsign_via_info(ctx, jwk, info, format, ap);
|
|
va_end(ap);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk,
|
|
const char *alg, char *out, size_t *out_len, char *temp,
|
|
int tl, const char *format, ...)
|
|
{
|
|
struct lws_jwt_sign_info info = {
|
|
.alg = alg,
|
|
.jose_hdr = NULL,
|
|
.out = out,
|
|
.out_len = out_len,
|
|
.temp = temp,
|
|
.tl = tl
|
|
};
|
|
int r = 1;
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
r = lws_jwt_vsign_via_info(ctx, jwk, &info, format, ap);
|
|
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
lws_jwt_token_sanity(const char *in, size_t in_len,
|
|
const char *iss, const char *aud,
|
|
const char *csrf_in,
|
|
char *sub, size_t sub_len, unsigned long *expiry_unix_time)
|
|
{
|
|
unsigned long now = lws_now_secs(), exp;
|
|
const char *cp;
|
|
size_t len;
|
|
|
|
/*
|
|
* It has our issuer?
|
|
*/
|
|
|
|
if (lws_json_simple_strcmp(in, in_len, "\"iss\":", iss)) {
|
|
lwsl_notice("%s: iss mismatch\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* ... it is indended for us to consume? (this is set
|
|
* to the public base url for this sai instance)
|
|
*/
|
|
if (lws_json_simple_strcmp(in, in_len, "\"aud\":", aud)) {
|
|
lwsl_notice("%s: aud mismatch\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* ...it's not too early for it?
|
|
*/
|
|
cp = lws_json_simple_find(in, in_len, "\"nbf\":", &len);
|
|
if (!cp || (unsigned long)atol(cp) > now) {
|
|
lwsl_notice("%s: nbf fail\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* ... and not too late for it?
|
|
*/
|
|
cp = lws_json_simple_find(in, in_len, "\"exp\":", &len);
|
|
exp = (unsigned long)atol(cp);
|
|
if (!cp || (unsigned long)atol(cp) < now) {
|
|
lwsl_notice("%s: exp fail %lu vs %lu\n", __func__,
|
|
cp ? (unsigned long)atol(cp) : 0, now);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Caller cares about subject? Then we must have it, and it can't be
|
|
* empty.
|
|
*/
|
|
|
|
if (sub) {
|
|
cp = lws_json_simple_find(in, in_len, "\"sub\":", &len);
|
|
if (!cp || !len) {
|
|
lwsl_notice("%s: missing subject\n", __func__);
|
|
return 1;
|
|
}
|
|
lws_strnncpy(sub, cp, len, sub_len);
|
|
}
|
|
|
|
/*
|
|
* If caller has been told a Cross Site Request Forgery (CSRF) nonce,
|
|
* require this JWT to express the same CSRF... this makes generated
|
|
* links for dangerous privileged auth'd actions expire with the JWT
|
|
* that was accessing the site when the links were generated. And it
|
|
* leaves an attacker not knowing what links to synthesize unless he
|
|
* can read the token or pages generated with it.
|
|
*
|
|
* Using this is very good for security, but it implies you must refresh
|
|
* generated pages still when the auth token is expiring (and the user
|
|
* must log in again).
|
|
*/
|
|
|
|
if (csrf_in &&
|
|
lws_json_simple_strcmp(in, in_len, "\"csrf\":", csrf_in)) {
|
|
lwsl_notice("%s: csrf mismatch\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
if (expiry_unix_time)
|
|
*expiry_unix_time = exp;
|
|
|
|
return 0;
|
|
}
|