/*
 * 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;
}

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] = in_len;
	map->buf[idx] = temp;

	*temp_len -= 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] = 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 -= 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] = len;
	map->buf[idx] = temp;
	*temp_len -= 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, 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;
}

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 = (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;
}

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 = 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 */

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, 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 */

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>"
 *   }
 */

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;
}