1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/lib/jose/jws/jws.c
Andy Green c9731c5f17 type comparisons: fixes
This is a huge patch that should be a global NOP.

For unix type platforms it enables -Wconversion to issue warnings (-> error)
for all automatic casts that seem less than ideal but are normally concealed
by the toolchain.

This is things like passing an int to a size_t argument.  Once enabled, I
went through all args on my default build (which build most things) and
tried to make the removed default cast explicit.

With that approach it neither change nor bloat the code, since it compiles
to whatever it was doing before, just with the casts made explicit... in a
few cases I changed some length args from int to size_t but largely left
the causes alone.

From now on, new code that is relying on less than ideal casting
will complain and nudge me to improve it by warnings.
2021-01-05 10:56:38 +00:00

1236 lines
29 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) {
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\n", __func__);
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;
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++) {
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\n", __func__);
return -1;
}
/* replace the map entry with the decoded content */
map->buf[m] = out;
map->len[m++] = (unsigned int)n;
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;
}
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, ...)
{
int n, r = 1, otl = tl;
struct lws_jose jose;
struct lws_jws jws;
va_list ap;
char *q;
lws_jws_init(&jws, jwk, ctx);
lws_jose_init(&jose);
if (lws_gencrypto_jws_alg_to_definition(alg, &jose.alg)) {
lwsl_err("%s: unknown alg %s\n", __func__, alg);
goto bail;
}
/* create JOSE header, also needed for output */
if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, temp, &tl,
strlen(alg) + 10, 0)) {
lwsl_err("%s: temp space too small\n", __func__);
return 1;
}
jws.map.len[LJWS_JOSE] = (uint32_t)lws_snprintf((char *)jws.map.buf[LJWS_JOSE],
(size_t)tl, "{\"alg\":\"%s\"}", alg);
temp += otl - tl;
otl = tl;
va_start(ap, format);
n = vsnprintf(NULL, 0, format, ap);
va_end(ap);
if (n + 2 >= tl)
goto bail;
q = lws_malloc((unsigned int)n + 2, __func__);
if (!q)
goto bail;
va_start(ap, format);
vsnprintf(q, (unsigned int)n + 2, format, ap);
va_end(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, temp, &tl,
jws.map.buf[LJWS_PYLD],
jws.map.len[LJWS_PYLD]))
goto bail1;
temp += otl - tl;
otl = tl;
/* add the b64 JOSE header to the b64 map */
if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, temp, &tl,
jws.map.buf[LJWS_JOSE],
jws.map.len[LJWS_JOSE]))
goto bail1;
temp += otl - tl;
otl = tl;
/* prepare the space for the b64 signature in the map */
if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, temp, &tl,
(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, out, *out_len))
goto bail1;
*out_len = strlen(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_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;
}