blob: fca8346ebf4638323957859f8965c79962eeef62 [file] [log] [blame]
/*
* libwebsockets - JSON Web Key support
*
* Copyright (C) 2017 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include "core/private.h"
#include <fcntl.h>
#include <unistd.h>
static const char * const jwk_tok[] = {
"e", "n", "d", "p", "q", "dp", "dq", "qi", "kty", "k",
};
static int
_lws_jwk_set_element(struct lws_genrsa_element *e, char *in, int len)
{
int dec_size = ((len * 3) / 4) + 4, n;
e->buf = lws_malloc(dec_size, "jwk");
if (!e->buf)
return -1;
n = lws_b64_decode_string_len(in, len, (char *)e->buf, dec_size - 1);
if (n < 0)
return -1;
e->len = n;
return 0;
}
struct cb_lws_jwk {
struct lws_jwk *s;
char *b64;
int b64max;
int pos;
};
static signed char
cb_jwk(struct lejp_ctx *ctx, char reason)
{
struct cb_lws_jwk *cbs = (struct cb_lws_jwk *)ctx->user;
struct lws_jwk *s = cbs->s;
int idx;
if (reason == LEJPCB_VAL_STR_START)
cbs->pos = 0;
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
return 0;
switch (ctx->path_match - 1) {
case JWK_KTY:
lws_strncpy(s->keytype, ctx->buf, sizeof(s->keytype));
if (!strcmp(ctx->buf, "oct")) {
break;
}
if (!strcmp(ctx->buf, "RSA")) {
break;
}
return -1;
case JWK_KEY:
// if (strcmp(s->keytype, "oct"))
// return -1;
idx = JWK_KEY_E;
goto read_element1;
case JWK_KEY_N:
case JWK_KEY_E:
case JWK_KEY_D:
case JWK_KEY_P:
case JWK_KEY_Q:
case JWK_KEY_DP:
case JWK_KEY_DQ:
case JWK_KEY_QI:
idx = ctx->path_match - 1;
goto read_element;
}
return 0;
read_element:
/* kty is no longer first in lex order */
// if (strcmp(s->keytype, "RSA"))
// return -1;
read_element1:
if (cbs->pos + ctx->npos >= cbs->b64max)
return -1;
memcpy(cbs->b64 + cbs->pos, ctx->buf, ctx->npos);
cbs->pos += ctx->npos;
if (reason == LEJPCB_VAL_STR_CHUNK)
return 0;
if (_lws_jwk_set_element(&s->el.e[idx], cbs->b64, cbs->pos) < 0) {
lws_jwk_destroy_genrsa_elements(&s->el);
return -1;
}
return 0;
}
LWS_VISIBLE int
lws_jwk_import(struct lws_jwk *s, const char *in, size_t len)
{
struct lejp_ctx jctx;
struct cb_lws_jwk cbs;
const int b64max = (((8192 / 8) * 4) / 3) + 1; /* enough for 8K key */
char b64[b64max];
int m;
memset(s, 0, sizeof(*s));
cbs.s = s;
cbs.b64 = b64;
cbs.b64max = b64max;
cbs.pos = 0;
lejp_construct(&jctx, cb_jwk, &cbs, jwk_tok, LWS_ARRAY_SIZE(jwk_tok));
m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)in, len);
lejp_destruct(&jctx);
if (m < 0) {
lwsl_notice("%s: parse got %d\n", __func__, m);
return -1;
}
return 0;
}
LWS_VISIBLE void
lws_jwk_destroy(struct lws_jwk *s)
{
lws_jwk_destroy_genrsa_elements(&s->el);
}
LWS_VISIBLE int
lws_jwk_export(struct lws_jwk *s, int private, char *p, size_t len)
{
char *start = p, *end = &p[len - 1];
int n, limit = LWS_COUNT_RSA_ELEMENTS;
/* RFC7638 lexicographic order requires
* RSA: e -> kty -> n
* oct: k -> kty
*/
p += lws_snprintf(p, end - p, "{");
if (!strcmp(s->keytype, "oct")) {
if (!s->el.e[JWK_KEY_E].buf)
return -1;
p += lws_snprintf(p, end - p, "\"k\":\"");
n = lws_jws_base64_enc((const char *)s->el.e[JWK_KEY_E].buf,
s->el.e[JWK_KEY_E].len, p,
end - p - 4);
if (n < 0) {
lwsl_notice("%s: enc failed\n", __func__);
return -1;
}
p += n;
p += lws_snprintf(p, end - p, "\",\"kty\":\"%s\"}", s->keytype);
return p - start;
}
if (!strcmp(s->keytype, "RSA")) {
if (!s->el.e[JWK_KEY_E].buf ||
!s->el.e[JWK_KEY_N].buf ||
(private && (!s->el.e[JWK_KEY_D].buf ||
!s->el.e[JWK_KEY_P].buf ||
!s->el.e[JWK_KEY_Q].buf))
) {
lwsl_notice("%s: not enough elements filled\n",
__func__);
return -1;
}
if (!private)
limit = JWK_KEY_N + 1;
for (n = 0; n < limit; n++) {
int m;
if (!s->el.e[n].buf)
continue;
lwsl_info("%d: len %d\n", n, s->el.e[n].len);
if (n)
p += lws_snprintf(p, end - p, ",");
p += lws_snprintf(p, end - p, "\"%s\":\"", jwk_tok[n]);
m = lws_jws_base64_enc((const char *)s->el.e[n].buf,
s->el.e[n].len, p,
end - p - 4);
if (m < 0) {
lwsl_notice("%s: enc fail inlen %d outlen %d\n",
__func__, (int)s->el.e[n].len,
lws_ptr_diff(end, p) - 4);
return -1;
}
p += m;
*p++ = '\"';
if (!n) /* RFC7638 lexicographic order */
p += lws_snprintf(p, end - p, ",\"kty\":\"%s\"",
s->keytype);
}
p += lws_snprintf(p, end - p, "}");
return p - start;
}
lwsl_err("%s: unknown key type %s\n", __func__, s->keytype);
return -1;
}
LWS_VISIBLE int
lws_jwk_rfc7638_fingerprint(struct lws_jwk *s, char *digest32)
{
struct lws_genhash_ctx hash_ctx;
int tmpsize = 2536, n;
char *tmp;
tmp = lws_malloc(tmpsize, "rfc7638 tmp");
n = lws_jwk_export(s, 0, tmp, tmpsize);
if (n < 0)
goto bail;
if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
goto bail;
if (lws_genhash_update(&hash_ctx, tmp, 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;
}
LWS_VISIBLE int
lws_jwk_load(struct lws_jwk *s, const char *filename)
{
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(s, buf, n);
lws_free(buf);
return n;
bail:
lws_free(buf);
return -1;
}
LWS_VISIBLE int
lws_jwk_save(struct lws_jwk *s, const char *filename)
{
int buflen = 4096;
char *buf = lws_malloc(buflen, "jwk-save");
int n, m;
if (!buf)
return -1;
n = lws_jwk_export(s, 1, buf, buflen);
if (n < 0)
goto bail;
m = lws_plat_write_file(filename, buf, n);
lws_free(buf);
if (m)
return -1;
return 0;
bail:
lws_free(buf);
return -1;
}