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

lejp_parse() return type is an int... but in the function, the temp for it is a char. This leads to badness that is currently worked around by casting the return through a signed char type. But that leads to more badness since if there's >127 bytes of buffer left after the end of the JSON object, we misreport it. Bite the bullet and fix the temp type, and fix up all the guys who were working around it at the caller return casting to use the resulting straight int. If you are using this api, remove any casting you may have cut- and-pasted like this n = (int)(signed char)lejp_parse(...); ... to just be like this... n = lejp_parse(...);
945 lines
22 KiB
C
945 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 = 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, *temp_len);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
map->len[idx] = 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], 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;
|
|
}
|
|
|
|
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, 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, *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], 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], 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 = 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>"
|
|
* }
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|