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

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.
920 lines
23 KiB
C
920 lines
23 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.h"
|
|
|
|
#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
static const char * const kty_names[] = {
|
|
"unknown", /* LWS_GENCRYPTO_KTY_UNKNOWN */
|
|
"oct", /* LWS_GENCRYPTO_KTY_OCT */
|
|
"RSA", /* LWS_GENCRYPTO_KTY_RSA */
|
|
"EC" /* LWS_GENCRYPTO_KTY_EC */
|
|
};
|
|
|
|
/*
|
|
* These are the entire legal token set for names in jwk.
|
|
*
|
|
* The first version is used to parse a detached single jwk that don't have any
|
|
* parent JSON context. The second version is used to parse full jwk objects
|
|
* that has a "keys": [ ] array containing the keys.
|
|
*/
|
|
|
|
static const char * const jwk_tok[] = {
|
|
"keys[]", /* dummy */
|
|
"e", "n", "d", "p", "q", "dp", "dq", "qi", /* RSA */
|
|
"kty", /* generic */
|
|
"k", /* symmetric key data */
|
|
"crv", "x", "y", /* EC (also "D") */
|
|
"kid", /* generic */
|
|
"use" /* mutually exclusive with "key_ops" */,
|
|
"key_ops" /* mutually exclusive with "use" */,
|
|
"x5c", /* generic */
|
|
"alg" /* generic */
|
|
}, * const jwk_outer_tok[] = {
|
|
"keys[]",
|
|
"keys[].e", "keys[].n", "keys[].d", "keys[].p", "keys[].q", "keys[].dp",
|
|
"keys[].dq", "keys[].qi",
|
|
|
|
"keys[].kty", "keys[].k", /* generic */
|
|
"keys[].crv", "keys[].x", "keys[].y", /* EC (also "D") */
|
|
"keys[].kid", "keys[].use" /* mutually exclusive with "key_ops" */,
|
|
"keys[].key_ops", /* mutually exclusive with "use" */
|
|
"keys[].x5c", "keys[].alg"
|
|
};
|
|
|
|
/* information about each token declared above */
|
|
|
|
#define F_M (1 << 9) /* Mandatory for key type */
|
|
#define F_B64 (1 << 10) /* Base64 coded octets */
|
|
#define F_B64U (1 << 11) /* Base64 Url coded octets */
|
|
#define F_META (1 << 12) /* JWK key metainformation */
|
|
#define F_RSA (1 << 13) /* RSA key */
|
|
#define F_EC (1 << 14) /* Elliptic curve key */
|
|
#define F_OCT (1 << 15) /* octet key */
|
|
|
|
static unsigned short tok_map[] = {
|
|
F_RSA | F_EC | F_OCT | F_META | 0xff,
|
|
F_RSA | F_B64U | F_M | LWS_GENCRYPTO_RSA_KEYEL_E,
|
|
F_RSA | F_B64U | F_M | LWS_GENCRYPTO_RSA_KEYEL_N,
|
|
F_RSA | F_EC | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_D,
|
|
F_RSA | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_P,
|
|
F_RSA | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_Q,
|
|
F_RSA | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_DP,
|
|
F_RSA | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_DQ,
|
|
F_RSA | F_B64U | LWS_GENCRYPTO_RSA_KEYEL_QI,
|
|
|
|
F_RSA | F_EC | F_OCT | F_META | F_M | JWK_META_KTY,
|
|
F_OCT | F_B64U | F_M | LWS_GENCRYPTO_OCT_KEYEL_K,
|
|
|
|
F_EC | F_M | LWS_GENCRYPTO_EC_KEYEL_CRV,
|
|
F_EC | F_B64U | F_M | LWS_GENCRYPTO_EC_KEYEL_X,
|
|
F_EC | F_B64U | F_M | LWS_GENCRYPTO_EC_KEYEL_Y,
|
|
|
|
F_RSA | F_EC | F_OCT | F_META | JWK_META_KID,
|
|
F_RSA | F_EC | F_OCT | F_META | JWK_META_USE,
|
|
|
|
F_RSA | F_EC | F_OCT | F_META | JWK_META_KEY_OPS,
|
|
F_RSA | F_EC | F_OCT | F_META | F_B64 | JWK_META_X5C,
|
|
F_RSA | F_EC | F_OCT | F_META | JWK_META_ALG,
|
|
};
|
|
|
|
static const char *meta_names[] = {
|
|
"kty", "kid", "use", "key_ops", "x5c", "alg"
|
|
};
|
|
|
|
struct lexico {
|
|
const char *name;
|
|
int idx;
|
|
char meta;
|
|
} lexico_ec[] = {
|
|
{ "alg", JWK_META_ALG, 1 },
|
|
{ "crv", LWS_GENCRYPTO_EC_KEYEL_CRV, 0 },
|
|
{ "d", LWS_GENCRYPTO_EC_KEYEL_D, 2 | 0 },
|
|
{ "key_ops", JWK_META_KEY_OPS, 1 },
|
|
{ "kid", JWK_META_KID, 1 },
|
|
{ "kty", JWK_META_KTY, 1 },
|
|
{ "use", JWK_META_USE, 1 },
|
|
{ "x", LWS_GENCRYPTO_EC_KEYEL_X, 0 },
|
|
{ "x5c", JWK_META_X5C, 1 },
|
|
{ "y", LWS_GENCRYPTO_EC_KEYEL_Y, 0 }
|
|
}, lexico_oct[] = {
|
|
{ "alg", JWK_META_ALG, 1 },
|
|
{ "k", LWS_GENCRYPTO_OCT_KEYEL_K, 0 },
|
|
{ "key_ops", JWK_META_KEY_OPS, 1 },
|
|
{ "kid", JWK_META_KID, 1 },
|
|
{ "kty", JWK_META_KTY, 1 },
|
|
{ "use", JWK_META_USE, 1 },
|
|
{ "x5c", JWK_META_X5C, 1 }
|
|
}, lexico_rsa[] = {
|
|
{ "alg", JWK_META_ALG, 1 },
|
|
{ "d", LWS_GENCRYPTO_RSA_KEYEL_D, 2 | 0 },
|
|
{ "dp", LWS_GENCRYPTO_RSA_KEYEL_DP, 2 | 0 },
|
|
{ "dq", LWS_GENCRYPTO_RSA_KEYEL_DQ, 2 | 0 },
|
|
{ "e", LWS_GENCRYPTO_RSA_KEYEL_E, 0 },
|
|
{ "key_ops", JWK_META_KEY_OPS, 1 },
|
|
{ "kid", JWK_META_KID, 1 },
|
|
{ "kty", JWK_META_KTY, 1 },
|
|
{ "n", LWS_GENCRYPTO_RSA_KEYEL_N, 0 },
|
|
{ "p", LWS_GENCRYPTO_RSA_KEYEL_P, 2 | 0 },
|
|
{ "q", LWS_GENCRYPTO_RSA_KEYEL_Q, 2 | 0 },
|
|
{ "qi", LWS_GENCRYPTO_RSA_KEYEL_QI, 2 | 0 },
|
|
{ "use", JWK_META_USE, 1 },
|
|
{ "x5c", JWK_META_X5C, 1 }
|
|
};
|
|
|
|
static const char meta_b64[] = { 0, 0, 0, 0, 1, 0 };
|
|
|
|
static const char *oct_names[] = {
|
|
"k"
|
|
};
|
|
static const char oct_b64[] = { 1 };
|
|
|
|
static const char *rsa_names[] = {
|
|
"e", "n", "d", "p", "q", "dp", "dq", "qi"
|
|
};
|
|
static const char rsa_b64[] = { 1, 1, 1, 1, 1, 1, 1, 1 };
|
|
|
|
static const char *ec_names[] = {
|
|
"crv", "x", "d", "y",
|
|
};
|
|
static const char ec_b64[] = { 0, 1, 1, 1 };
|
|
|
|
int
|
|
lws_jwk_dump(struct lws_jwk *jwk)
|
|
{
|
|
const char **enames, *b64;
|
|
int elems;
|
|
int n;
|
|
|
|
(void)enames;
|
|
(void)meta_names;
|
|
|
|
switch (jwk->kty) {
|
|
default:
|
|
case LWS_GENCRYPTO_KTY_UNKNOWN:
|
|
lwsl_err("%s: jwk %p: unknown type\n", __func__, jwk);
|
|
|
|
return 1;
|
|
case LWS_GENCRYPTO_KTY_OCT:
|
|
elems = LWS_GENCRYPTO_OCT_KEYEL_COUNT;
|
|
enames = oct_names;
|
|
b64 = oct_b64;
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_RSA:
|
|
elems = LWS_GENCRYPTO_RSA_KEYEL_COUNT;
|
|
enames = rsa_names;
|
|
b64 = rsa_b64;
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_EC:
|
|
elems = LWS_GENCRYPTO_EC_KEYEL_COUNT;
|
|
enames = ec_names;
|
|
b64 = ec_b64;
|
|
break;
|
|
}
|
|
|
|
lwsl_info("%s: jwk %p\n", __func__, jwk);
|
|
|
|
for (n = 0; n < LWS_COUNT_JWK_ELEMENTS; n++) {
|
|
if (jwk->meta[n].buf && meta_b64[n]) {
|
|
lwsl_info(" meta: %s\n", meta_names[n]);
|
|
lwsl_hexdump_info(jwk->meta[n].buf, jwk->meta[n].len);
|
|
}
|
|
if (jwk->meta[n].buf && !meta_b64[n])
|
|
lwsl_info(" meta: %s: '%s'\n", meta_names[n],
|
|
jwk->meta[n].buf);
|
|
}
|
|
|
|
for (n = 0; n < elems; n++) {
|
|
if (jwk->e[n].buf && b64[n]) {
|
|
lwsl_info(" e: %s\n", enames[n]);
|
|
lwsl_hexdump_info(jwk->e[n].buf, jwk->e[n].len);
|
|
}
|
|
if (jwk->e[n].buf && !b64[n])
|
|
lwsl_info(" e: %s: '%s'\n", enames[n], jwk->e[n].buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_lws_jwk_set_el_jwk(struct lws_gencrypto_keyelem *e, char *in, size_t len)
|
|
{
|
|
e->buf = lws_malloc(len + 1, "jwk");
|
|
if (!e->buf)
|
|
return -1;
|
|
|
|
memcpy(e->buf, in, len);
|
|
e->buf[len] = '\0';
|
|
e->len = (uint32_t)len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_lws_jwk_set_el_jwk_b64(struct lws_gencrypto_keyelem *e, char *in, int len)
|
|
{
|
|
size_t dec_size = (unsigned int)lws_base64_size(len);
|
|
int n;
|
|
|
|
e->buf = lws_malloc(dec_size, "jwk");
|
|
if (!e->buf)
|
|
return -1;
|
|
|
|
/* same decoder accepts both url or original styles */
|
|
|
|
n = lws_b64_decode_string_len(in, len, (char *)e->buf, (int)dec_size - 1);
|
|
if (n < 0)
|
|
return -1;
|
|
e->len = (uint32_t)n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_lws_jwk_set_el_jwk_b64u(struct lws_gencrypto_keyelem *e, char *in, int len)
|
|
{
|
|
size_t dec_size = (size_t)lws_base64_size(len);
|
|
int n;
|
|
|
|
e->buf = lws_malloc(dec_size, "jwk");
|
|
if (!e->buf)
|
|
return -1;
|
|
|
|
/* same decoder accepts both url or original styles */
|
|
|
|
n = lws_b64_decode_string_len(in, len, (char *)e->buf, (int)dec_size - 1);
|
|
if (n < 0)
|
|
return -1;
|
|
e->len = (uint32_t)n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_jwk_destroy_elements(struct lws_gencrypto_keyelem *el, int m)
|
|
{
|
|
int n;
|
|
|
|
for (n = 0; n < m; n++)
|
|
if (el[n].buf) {
|
|
/* wipe all key material when it goes out of scope */
|
|
lws_explicit_bzero(el[n].buf, el[n].len);
|
|
lws_free_set_NULL(el[n].buf);
|
|
el[n].len = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
lws_jwk_destroy(struct lws_jwk *jwk)
|
|
{
|
|
lws_jwk_destroy_elements(jwk->e, LWS_ARRAY_SIZE(jwk->e));
|
|
lws_jwk_destroy_elements(jwk->meta, LWS_ARRAY_SIZE(jwk->meta));
|
|
}
|
|
|
|
static signed char
|
|
cb_jwk(struct lejp_ctx *ctx, char reason)
|
|
{
|
|
struct lws_jwk_parse_state *jps = (struct lws_jwk_parse_state *)ctx->user;
|
|
struct lws_jwk *jwk = jps->jwk;
|
|
unsigned int idx, n;
|
|
unsigned short poss;
|
|
char dotstar[64];
|
|
|
|
if (reason == LEJPCB_VAL_STR_START)
|
|
jps->pos = 0;
|
|
|
|
if (reason == LEJPCB_OBJECT_START && ctx->path_match == 0 + 1)
|
|
/*
|
|
* new keys[] member is starting
|
|
*
|
|
* Until we see some JSON names, it could be anything...
|
|
* there is no requirement for kty to be given first and eg,
|
|
* ACME specifies the keys must be ordered in lexographic
|
|
* order - where kty is not first.
|
|
*/
|
|
jps->possible = F_RSA | F_EC | F_OCT;
|
|
|
|
if (reason == LEJPCB_OBJECT_END && ctx->path_match == 0 + 1) {
|
|
/* we completed parsing a key */
|
|
if (jps->per_key_cb && jps->possible) {
|
|
if (jps->per_key_cb(jps->jwk, jps->user)) {
|
|
|
|
lwsl_notice("%s: user cb halts import\n",
|
|
__func__);
|
|
|
|
return -2;
|
|
}
|
|
|
|
/* clear it down */
|
|
lws_jwk_destroy(jps->jwk);
|
|
jps->possible = 0;
|
|
}
|
|
}
|
|
|
|
if (reason == LEJPCB_COMPLETE) {
|
|
|
|
/*
|
|
* Now we saw the whole jwk and know the key type, let'jwk insist
|
|
* that as a whole, it must be consistent and complete.
|
|
*
|
|
* The tracking of ->possible bits from even before we know the
|
|
* kty already makes certain we cannot have key element members
|
|
* defined that are inconsistent with the key type.
|
|
*/
|
|
|
|
for (n = 0; n < LWS_ARRAY_SIZE(tok_map); n++)
|
|
/*
|
|
* All mandataory elements for the key type
|
|
* must be present
|
|
*/
|
|
if ((tok_map[n] & jps->possible) && (
|
|
((tok_map[n] & (F_M | F_META)) == (F_M | F_META) &&
|
|
!jwk->meta[tok_map[n] & 0xff].buf) ||
|
|
((tok_map[n] & (F_M | F_META)) == F_M &&
|
|
!jwk->e[tok_map[n] & 0xff].buf))) {
|
|
lwsl_notice("%s: missing %s\n", __func__,
|
|
jwk_tok[n]);
|
|
return -3;
|
|
}
|
|
|
|
/*
|
|
* When the key may be public or public + private, ensure the
|
|
* intra-key members related to that are consistent.
|
|
*
|
|
* Only RSA keys need extra care, since EC keys are already
|
|
* confirmed by making CRV, X and Y mandatory and only D
|
|
* (the singular private part) optional. For RSA, N and E are
|
|
* also already known to be present using mandatory checking.
|
|
*/
|
|
|
|
/*
|
|
* If a private key, it must have all D, P and Q. Public key
|
|
* must have none of them.
|
|
*/
|
|
if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
|
|
!(((!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf) &&
|
|
(!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) &&
|
|
(!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf)) ||
|
|
(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf))
|
|
) {
|
|
lwsl_notice("%s: RSA requires D, P and Q for private\n",
|
|
__func__);
|
|
return -3;
|
|
}
|
|
|
|
/*
|
|
* If the precomputed private key terms appear, they must all
|
|
* appear together.
|
|
*/
|
|
if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
|
|
!(((!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf) &&
|
|
(!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf) &&
|
|
(!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf)) ||
|
|
(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf))
|
|
) {
|
|
lwsl_notice("%s: RSA DP, DQ, QI must all appear "
|
|
"or none\n", __func__);
|
|
return -3;
|
|
}
|
|
|
|
/*
|
|
* The precomputed private key terms must not appear without
|
|
* the private key itself also appearing.
|
|
*/
|
|
if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
|
|
!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf) {
|
|
lwsl_notice("%s: RSA DP, DQ, QI can appear only with "
|
|
"private key\n", __func__);
|
|
return -3;
|
|
}
|
|
|
|
if ((jwk->kty == LWS_GENCRYPTO_KTY_RSA ||
|
|
jwk->kty == LWS_GENCRYPTO_KTY_EC) &&
|
|
jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf)
|
|
jwk->private_key = 1;
|
|
}
|
|
|
|
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
|
return 0;
|
|
|
|
if (ctx->path_match == 0 + 1)
|
|
return 0;
|
|
|
|
idx = tok_map[ctx->path_match - 1];
|
|
if ((idx & 0xff) == 0xff)
|
|
return 0;
|
|
|
|
switch (idx) {
|
|
/* note: kty is not necessarily first... we have to keep track of
|
|
* what could match given which element names have already been
|
|
* seen. Once kty comes, we confirm it'jwk still possible (ie, it'jwk
|
|
* not trying to tell us that it'jwk RSA now when we saw a "crv"
|
|
* earlier) and then reduce the possibilities to just the one that
|
|
* kty told. */
|
|
case F_RSA | F_EC | F_OCT | F_META | F_M | JWK_META_KTY:
|
|
|
|
if (ctx->npos == 3 && !strncmp(ctx->buf, "oct", 3)) {
|
|
if (!(jps->possible & F_OCT))
|
|
goto elements_mismatch;
|
|
jwk->kty = LWS_GENCRYPTO_KTY_OCT;
|
|
jps->possible = F_OCT;
|
|
goto cont;
|
|
}
|
|
if (ctx->npos == 3 && !strncmp(ctx->buf, "RSA", 3)) {
|
|
if (!(jps->possible & F_RSA))
|
|
goto elements_mismatch;
|
|
jwk->kty = LWS_GENCRYPTO_KTY_RSA;
|
|
jps->possible = F_RSA;
|
|
goto cont;
|
|
}
|
|
if (ctx->npos == 2 && !strncmp(ctx->buf, "EC", 2)) {
|
|
if (!(jps->possible & F_EC))
|
|
goto elements_mismatch;
|
|
jwk->kty = LWS_GENCRYPTO_KTY_EC;
|
|
jps->possible = F_EC;
|
|
goto cont;
|
|
}
|
|
lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar));
|
|
lwsl_err("%s: Unknown KTY '%s'\n", __func__, dotstar);
|
|
return -1;
|
|
|
|
default:
|
|
cont:
|
|
if (jps->pos + ctx->npos >= (int)sizeof(jps->b64))
|
|
goto bail;
|
|
|
|
memcpy(jps->b64 + jps->pos, ctx->buf, ctx->npos);
|
|
jps->pos += ctx->npos;
|
|
|
|
if (reason == LEJPCB_VAL_STR_CHUNK)
|
|
return 0;
|
|
|
|
/* chunking has been collated */
|
|
|
|
poss = idx & (F_RSA | F_EC | F_OCT);
|
|
jps->possible &= poss;
|
|
if (!jps->possible)
|
|
goto elements_mismatch;
|
|
|
|
if (idx & F_META) {
|
|
if (_lws_jwk_set_el_jwk(&jwk->meta[idx & 0x7f],
|
|
jps->b64, (unsigned int)jps->pos) < 0)
|
|
goto bail;
|
|
|
|
break;
|
|
}
|
|
|
|
if (idx & F_B64U) {
|
|
/* key data... do the base64 decode as needed */
|
|
if (_lws_jwk_set_el_jwk_b64u(&jwk->e[idx & 0x7f],
|
|
jps->b64, jps->pos) < 0)
|
|
goto bail;
|
|
|
|
if (jwk->e[idx & 0x7f].len >
|
|
LWS_JWE_LIMIT_KEY_ELEMENT_BYTES) {
|
|
lwsl_notice("%s: oversize keydata\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (idx & F_B64) {
|
|
|
|
/* cert data... do non-urlcoded base64 decode */
|
|
if (_lws_jwk_set_el_jwk_b64(&jwk->e[idx & 0x7f],
|
|
jps->b64, jps->pos) < 0)
|
|
goto bail;
|
|
return 0;
|
|
}
|
|
|
|
if (_lws_jwk_set_el_jwk(&jwk->e[idx & 0x7f],
|
|
jps->b64, (unsigned int)jps->pos) < 0)
|
|
goto bail;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
elements_mismatch:
|
|
lwsl_err("%s: jwk elements mismatch\n", __func__);
|
|
|
|
bail:
|
|
lwsl_err("%s: element failed\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
lws_jwk_init_jps(struct lejp_ctx *jctx, struct lws_jwk_parse_state *jps,
|
|
struct lws_jwk *jwk, lws_jwk_key_import_callback cb,
|
|
void *user)
|
|
{
|
|
if (jwk)
|
|
memset(jwk, 0, sizeof(*jwk));
|
|
|
|
jps->jwk = jwk;
|
|
jps->possible = F_RSA | F_EC | F_OCT;
|
|
jps->per_key_cb = cb;
|
|
jps->user = user;
|
|
jps->pos = 0;
|
|
|
|
lejp_construct(jctx, cb_jwk, jps, cb ? jwk_outer_tok: jwk_tok,
|
|
LWS_ARRAY_SIZE(jwk_tok));
|
|
}
|
|
|
|
int
|
|
lws_jwk_dup_oct(struct lws_jwk *jwk, const void *key, int len)
|
|
{
|
|
unsigned int ulen = (unsigned int)len;
|
|
|
|
jwk->e[LWS_GENCRYPTO_KTY_OCT].buf = lws_malloc(ulen, __func__);
|
|
if (!jwk->e[LWS_GENCRYPTO_KTY_OCT].buf)
|
|
return -1;
|
|
|
|
jwk->kty = LWS_GENCRYPTO_KTY_OCT;
|
|
jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].len = ulen;
|
|
|
|
memcpy(jwk->e[LWS_GENCRYPTO_KTY_OCT].buf, key, ulen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jwk_generate(struct lws_context *context, struct lws_jwk *jwk,
|
|
enum lws_gencrypto_kty kty, int bits, const char *curve)
|
|
{
|
|
size_t sn;
|
|
int n;
|
|
|
|
memset(jwk, 0, sizeof(*jwk));
|
|
|
|
jwk->kty = (int)kty;
|
|
jwk->private_key = 1;
|
|
|
|
switch (kty) {
|
|
case LWS_GENCRYPTO_KTY_RSA:
|
|
{
|
|
struct lws_genrsa_ctx ctx;
|
|
|
|
lwsl_notice("%s: generating %d bit RSA key\n", __func__, bits);
|
|
n = lws_genrsa_new_keypair(context, &ctx, LGRSAM_PKCS1_1_5,
|
|
jwk->e, bits);
|
|
lws_genrsa_destroy(&ctx);
|
|
if (n) {
|
|
lwsl_err("%s: problem generating RSA key\n", __func__);
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_OCT:
|
|
sn = (unsigned int)lws_gencrypto_bits_to_bytes(bits);
|
|
jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].buf = lws_malloc(sn, "oct");
|
|
jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].len = (uint32_t)sn;
|
|
if (lws_get_random(context,
|
|
jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].buf, sn) != sn) {
|
|
lwsl_err("%s: problem getting random\n", __func__);
|
|
return 1;
|
|
}
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_EC:
|
|
{
|
|
struct lws_genec_ctx ctx;
|
|
|
|
if (!curve) {
|
|
lwsl_err("%s: must have a named curve\n", __func__);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (lws_genecdsa_create(&ctx, context, NULL))
|
|
return 1;
|
|
|
|
lwsl_notice("%s: generating ECDSA key on curve %s\n", __func__,
|
|
curve);
|
|
|
|
n = lws_genecdsa_new_keypair(&ctx, curve, jwk->e);
|
|
lws_genec_destroy(&ctx);
|
|
if (n) {
|
|
lwsl_err("%s: problem generating ECDSA key\n", __func__);
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LWS_GENCRYPTO_KTY_UNKNOWN:
|
|
default:
|
|
lwsl_err("%s: unknown kty\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jwk_import(struct lws_jwk *jwk, lws_jwk_key_import_callback cb, void *user,
|
|
const char *in, size_t len)
|
|
{
|
|
struct lejp_ctx jctx;
|
|
struct lws_jwk_parse_state jps;
|
|
int m;
|
|
|
|
lws_jwk_init_jps(&jctx, &jps, jwk, cb, user);
|
|
|
|
m = lejp_parse(&jctx, (uint8_t *)in, (int)len);
|
|
lejp_destruct(&jctx);
|
|
|
|
if (m < 0) {
|
|
lwsl_notice("%s: parse got %d\n", __func__, m);
|
|
lws_jwk_destroy(jwk);
|
|
return -1;
|
|
}
|
|
|
|
switch (jwk->kty) {
|
|
case LWS_GENCRYPTO_KTY_UNKNOWN:
|
|
lwsl_notice("%s: missing or unknown kyt\n", __func__);
|
|
lws_jwk_destroy(jwk);
|
|
return -1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
lws_jwk_export(struct lws_jwk *jwk, int flags, char *p, int *len)
|
|
{
|
|
char *start = p, *end = &p[*len - 1];
|
|
int n, m, limit, first = 1, asym = 0;
|
|
struct lexico *l;
|
|
|
|
/* RFC7638 lexicographic order requires
|
|
* RSA: e -> kty -> n
|
|
* oct: k -> kty
|
|
*
|
|
* ie, meta and key data elements appear interleaved in name alpha order
|
|
*/
|
|
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{");
|
|
|
|
switch (jwk->kty) {
|
|
case LWS_GENCRYPTO_KTY_OCT:
|
|
l = lexico_oct;
|
|
limit = LWS_ARRAY_SIZE(lexico_oct);
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_RSA:
|
|
l = lexico_rsa;
|
|
limit = LWS_ARRAY_SIZE(lexico_rsa);
|
|
asym = 1;
|
|
break;
|
|
case LWS_GENCRYPTO_KTY_EC:
|
|
l = lexico_ec;
|
|
limit = LWS_ARRAY_SIZE(lexico_ec);
|
|
asym = 1;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < limit; n++) {
|
|
const char *q, *q_end;
|
|
char tok[12];
|
|
int pos = 0, f = 1;
|
|
|
|
if ((l->meta & 1) && (jwk->meta[l->idx].buf ||
|
|
l->idx == (int)JWK_META_KTY)) {
|
|
|
|
switch (l->idx) {
|
|
case JWK_META_KTY:
|
|
if (!first)
|
|
*p++ = ',';
|
|
first = 0;
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"%s\":\"%s\"",
|
|
l->name, kty_names[jwk->kty]);
|
|
break;
|
|
case JWK_META_KEY_OPS:
|
|
if (!first)
|
|
*p++ = ',';
|
|
first = 0;
|
|
q = (const char *)jwk->meta[l->idx].buf;
|
|
q_end = q + jwk->meta[l->idx].len;
|
|
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
|
|
"\"%s\":[", l->name);
|
|
/*
|
|
* For the public version, usages that
|
|
* require the private part must be
|
|
* snipped
|
|
*/
|
|
|
|
while (q < q_end) {
|
|
if (*q != ' ' && pos < (int)sizeof(tok) - 1) {
|
|
tok[pos++] = *q++;
|
|
if (q != q_end)
|
|
continue;
|
|
}
|
|
tok[pos] = '\0';
|
|
pos = 0;
|
|
if ((flags & LWSJWKF_EXPORT_PRIVATE) ||
|
|
!asym || (strcmp(tok, "sign") &&
|
|
strcmp(tok, "encrypt"))) {
|
|
if (!f)
|
|
*p++ = ',';
|
|
f = 0;
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
|
|
"\"%s\"", tok);
|
|
}
|
|
q++;
|
|
}
|
|
|
|
*p++ = ']';
|
|
|
|
break;
|
|
|
|
default:
|
|
/* both sig and enc require asym private key */
|
|
if (!(flags & LWSJWKF_EXPORT_PRIVATE) &&
|
|
asym && l->idx == (int)JWK_META_USE)
|
|
break;
|
|
if (!first)
|
|
*p++ = ',';
|
|
first = 0;
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"%s\":\"",
|
|
l->name);
|
|
lws_strnncpy(p, (const char *)jwk->meta[l->idx].buf,
|
|
jwk->meta[l->idx].len, end - p);
|
|
p += strlen(p);
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((!(l->meta & 1)) && jwk->e[l->idx].buf &&
|
|
((flags & LWSJWKF_EXPORT_PRIVATE) || !(l->meta & 2))) {
|
|
if (!first)
|
|
*p++ = ',';
|
|
first = 0;
|
|
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"%s\":\"", l->name);
|
|
|
|
if (jwk->kty == LWS_GENCRYPTO_KTY_EC &&
|
|
l->idx == (int)LWS_GENCRYPTO_EC_KEYEL_CRV) {
|
|
lws_strnncpy(p,
|
|
(const char *)jwk->e[l->idx].buf,
|
|
jwk->e[l->idx].len, end - p);
|
|
m = (int)strlen(p);
|
|
} else
|
|
m = lws_jws_base64_enc(
|
|
(const char *)jwk->e[l->idx].buf,
|
|
jwk->e[l->idx].len, p, lws_ptr_diff_size_t(end, p) - 4);
|
|
if (m < 0) {
|
|
lwsl_notice("%s: enc failed\n", __func__);
|
|
return -1;
|
|
}
|
|
p += m;
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"");
|
|
}
|
|
|
|
l++;
|
|
}
|
|
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
|
|
(flags & LWSJWKF_EXPORT_NOCRLF) ? "}" : "}\n");
|
|
|
|
*len -= lws_ptr_diff(p, start);
|
|
|
|
return lws_ptr_diff(p, start);
|
|
}
|
|
|
|
int
|
|
lws_jwk_rfc7638_fingerprint(struct lws_jwk *jwk, char *digest32)
|
|
{
|
|
struct lws_genhash_ctx hash_ctx;
|
|
size_t tmpsize = 2536;
|
|
char *tmp;
|
|
int n, m = (int)tmpsize;
|
|
|
|
tmp = lws_malloc(tmpsize, "rfc7638 tmp");
|
|
|
|
n = lws_jwk_export(jwk, LWSJWKF_EXPORT_NOCRLF, tmp, &m);
|
|
if (n < 0)
|
|
goto bail;
|
|
|
|
if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
|
|
goto bail;
|
|
|
|
if (lws_genhash_update(&hash_ctx, tmp, (unsigned int)n)) {
|
|
lws_genhash_destroy(&hash_ctx, NULL);
|
|
|
|
goto bail;
|
|
}
|
|
lws_free(tmp);
|
|
|
|
if (lws_genhash_destroy(&hash_ctx, digest32))
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
lws_free(tmp);
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
lws_jwk_strdup_meta(struct lws_jwk *jwk, enum enum_jwk_meta_tok idx,
|
|
const char *in, int len)
|
|
{
|
|
jwk->meta[idx].buf = lws_malloc((unsigned int)len, __func__);
|
|
if (!jwk->meta[idx].buf)
|
|
return 1;
|
|
jwk->meta[idx].len = (uint32_t)(unsigned int)len;
|
|
memcpy(jwk->meta[idx].buf, in, (unsigned int)len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jwk_load(struct lws_jwk *jwk, const char *filename,
|
|
lws_jwk_key_import_callback cb, void *user)
|
|
{
|
|
unsigned int buflen = 4096;
|
|
char *buf = lws_malloc(buflen, "jwk-load");
|
|
int n;
|
|
|
|
if (!buf)
|
|
return -1;
|
|
|
|
n = lws_plat_read_file(filename, buf, buflen);
|
|
if (n < 0)
|
|
goto bail;
|
|
|
|
n = lws_jwk_import(jwk, cb, user, buf, (unsigned int)n);
|
|
lws_free(buf);
|
|
|
|
return n;
|
|
bail:
|
|
lws_free(buf);
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
lws_jwk_save(struct lws_jwk *jwk, const char *filename)
|
|
{
|
|
int buflen = 4096;
|
|
char *buf = lws_malloc((unsigned int)buflen, "jwk-save");
|
|
int n, m;
|
|
|
|
if (!buf)
|
|
return -1;
|
|
|
|
n = lws_jwk_export(jwk, LWSJWKF_EXPORT_PRIVATE, buf, &buflen);
|
|
if (n < 0)
|
|
goto bail;
|
|
|
|
m = lws_plat_write_file(filename, buf, (size_t)n);
|
|
|
|
lws_free(buf);
|
|
if (m)
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
lws_free(buf);
|
|
|
|
return -1;
|
|
}
|