mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-23 00:00:06 +01:00

The %.*s is very handy to print strings where you have a length, but there is no NUL termination. It's quite widely supported but at least one vendor RTOS toolchain doesn't have it. Since there aren't that many uses of it yet, audit all uses and convert to a new helper lws_strnncpy() which uses the smaller of two lengths.
944 lines
22 KiB
C
944 lines
22 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],
|
|
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] = 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 = (int)(signed char)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;
|
|
}
|
|
|
|
LWS_VISIBLE 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]);
|
|
}
|
|
|
|
LWS_VISIBLE void
|
|
lws_jws_destroy(struct lws_jws *jws)
|
|
{
|
|
lws_jws_map_bzero(&jws->map);
|
|
jws->jwk = NULL;
|
|
}
|
|
|
|
LWS_VISIBLE 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] = in_len;
|
|
map->buf[idx] = temp;
|
|
|
|
*temp_len -= actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE 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, *temp_len);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
map->len[idx] = n;
|
|
map->buf[idx] = temp;
|
|
|
|
*temp_len -= n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE 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] = random_len;
|
|
map->buf[idx] = temp;
|
|
|
|
if (lws_get_random(context, temp, random_len) != (int)random_len) {
|
|
lwsl_err("Problem getting random\n");
|
|
return -1;
|
|
}
|
|
|
|
*temp_len -= actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE 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] = len;
|
|
map->buf[idx] = temp;
|
|
*temp_len -= actual_alloc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE 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, in_len, out, 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;
|
|
}
|
|
|
|
LWS_VISIBLE 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
|
|
*/
|
|
|
|
LWS_VISIBLE 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], 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++] = 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], 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++] = n;
|
|
out += n;
|
|
*out_len -= n;
|
|
|
|
if (*out_len < 1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE int
|
|
lws_jws_encode_section(const char *in, size_t in_len, int first, char **p,
|
|
char *end)
|
|
{
|
|
int n, len = (end - *p) - 1;
|
|
char *p_entry = *p;
|
|
|
|
if (len < 3)
|
|
return -1;
|
|
|
|
if (!first)
|
|
*(*p)++ = '.';
|
|
|
|
n = lws_jws_base64_enc(in, in_len, *p, len - 1);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
*p += n;
|
|
|
|
return (*p) - p_entry;
|
|
}
|
|
|
|
LWS_VISIBLE 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, *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.
|
|
*/
|
|
|
|
LWS_VISIBLE 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], 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 = 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], 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 = 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 */
|
|
|
|
LWS_VISIBLE 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
|
|
*/
|
|
|
|
LWS_VISIBLE 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, len, &map_b64) < 0)
|
|
return -1;
|
|
|
|
n = lws_jws_compact_decode(in, 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 */
|
|
|
|
LWS_VISIBLE 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, 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 = jws->jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len;
|
|
buf = lws_malloc(lws_base64_size(n), "jws sign");
|
|
if (!buf)
|
|
return -1;
|
|
|
|
n = lws_genrsa_hash_sign(&rsactx, digest, jose->alg->hash_type,
|
|
buf, 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, 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(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, 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, 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>"
|
|
* }
|
|
*/
|
|
|
|
LWS_VISIBLE 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 += 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 += strlen(flattened + n);
|
|
|
|
n += 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 += strlen(flattened + n);
|
|
|
|
if (jws->map_b64.buf[LJWS_UHDR]) {
|
|
n += 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 += strlen(flattened + n);
|
|
}
|
|
|
|
n += 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 += strlen(flattened + n);
|
|
|
|
n += lws_snprintf(flattened + n, len - n , "\"}\n");
|
|
|
|
return (n >= len - 1);
|
|
}
|
|
|
|
LWS_VISIBLE 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);
|
|
lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_PYLD],
|
|
jws->map_b64.len[LJWS_PYLD], len - n);
|
|
n += strlen(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;
|
|
}
|