/*
 * 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);
	if (n >= len - 1)
		return 1;
	compact[n++] = '.';
	lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_PYLD],
		     jws->map_b64.len[LJWS_PYLD], len - n);
	n += strlen(compact + n);
	if (n >= len - 1)
		return 1;
	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;
}

int
lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk,
			const char *alg_list, const char *com, size_t len,
			char *temp, int tl, char *out, size_t *out_len)
{
	struct lws_tokenize ts;
	struct lws_jose jose;
	int otl = tl, r = 1;
	struct lws_jws jws;
	size_t n;

	memset(&jws, 0, sizeof(jws));
	lws_jose_init(&jose);

	/*
	 * Decode the b64.b64[.b64] compact serialization
	 * blocks
	 */

	n = lws_jws_compact_decode(com, (int)len, &jws.map, &jws.map_b64,
				   temp, &tl);
	if (n != 3) {
		lwsl_err("%s: concat_map failed: %d\n", __func__, (int)n);
		goto bail;
	}

	temp += otl - tl;
	otl = tl;

	/*
	 * Parse the JOSE header
	 */

	if (lws_jws_parse_jose(&jose, jws.map.buf[LJWS_JOSE],
			       jws.map.len[LJWS_JOSE], temp, &tl) < 0) {
		lwsl_err("%s: JOSE parse failed\n", __func__);
		goto bail;
	}

	/*
	 * Insist to see an alg in there that we list as acceptable
	 */

	lws_tokenize_init(&ts, alg_list, LWS_TOKENIZE_F_COMMA_SEP_LIST |
					 LWS_TOKENIZE_F_RFC7230_DELIMS);
	n = strlen(jose.alg->alg);

	do {
		ts.e = lws_tokenize(&ts);
		if (ts.e == LWS_TOKZE_TOKEN && ts.token_len == n &&
		    !strncmp(jose.alg->alg, ts.token, ts.token_len))
			break;
	} while (ts.e != LWS_TOKZE_ENDED);

	if (ts.e != LWS_TOKZE_TOKEN) {
		lwsl_err("%s: JOSE using alg %s (accepted: %s)\n", __func__,
			 jose.alg->alg, alg_list);
		goto bail;
	}

	/* we liked the alg... now how about the crypto? */

	if (lws_jws_sig_confirm(&jws.map_b64, &jws.map, jwk, ctx) < 0) {
		lwsl_notice("%s: confirm JWT sig failed\n",
			    __func__);
		goto bail;
	}

	/* yeah, it's validated... see about copying it out */

	if (*out_len < jws.map.len[LJWS_PYLD] + 1) {
		/* we don't have enough room */
		r = 2;
		goto bail;
	}

	memcpy(out, jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]);
	*out_len = jws.map.len[LJWS_PYLD];
	out[jws.map.len[LJWS_PYLD]] = '\0';

	r = 0;

bail:
	lws_jws_destroy(&jws);
	lws_jose_destroy(&jose);

	return r;
}

int
lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk,
		     const char *alg, char *out, size_t *out_len, char *temp,
		     int tl, const char *format, ...)
{
	int n, r = 1, otl = tl;
	struct lws_jose jose;
	struct lws_jws jws;
	va_list ap;
	char *q;

	lws_jws_init(&jws, jwk, ctx);
	lws_jose_init(&jose);

	if (lws_gencrypto_jws_alg_to_definition(alg, &jose.alg)) {
		lwsl_err("%s: unknown alg %s\n", __func__, alg);

		goto bail;
	}

	/* create JOSE header, also needed for output */

	if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, temp, &tl,
				  strlen(alg) + 10, 0)) {
		lwsl_err("%s: temp space too small\n", __func__);
		return 1;
	}

	jws.map.len[LJWS_JOSE] = lws_snprintf((char *)jws.map.buf[LJWS_JOSE],
					      tl, "{\"alg\":\"%s\"}", alg);

	temp += otl - tl;
	otl = tl;

	va_start(ap, format);
	n = vsnprintf(NULL, 0, format, ap);
	va_end(ap);
	if (n + 2 >= tl)
		goto bail;

	q = lws_malloc(n + 2, __func__);
	if (!q)
		goto bail;

	va_start(ap, format);
	vsnprintf(q, n + 2, format, ap);
	va_end(ap);

	/* add the plaintext from stdin to the map and a b64 version */

	jws.map.buf[LJWS_PYLD] = q;
	jws.map.len[LJWS_PYLD] = n;

	if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD, temp, &tl,
				       jws.map.buf[LJWS_PYLD],
				       jws.map.len[LJWS_PYLD]))
		goto bail1;

	temp += otl - tl;
	otl = tl;

	/* add the b64 JOSE header to the b64 map */

	if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, temp, &tl,
				       jws.map.buf[LJWS_JOSE],
				       jws.map.len[LJWS_JOSE]))
		goto bail1;

	temp += otl - tl;
	otl = tl;

	/* prepare the space for the b64 signature in the map */

	if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, temp, &tl,
				  lws_base64_size(LWS_JWE_LIMIT_KEY_ELEMENT_BYTES),
				  0))
		goto bail1;

	/* sign the plaintext */

	n = lws_jws_sign_from_b64(&jose, &jws,
				  (char *)jws.map_b64.buf[LJWS_SIG],
				  jws.map_b64.len[LJWS_SIG]);
	if (n < 0)
		goto bail1;

	/* set the actual b64 signature size */
	jws.map_b64.len[LJWS_SIG] = n;

	/* create the compact JWS representation */
	if (lws_jws_write_compact(&jws, out, *out_len))
		goto bail1;

	*out_len = strlen(out);

	r = 0;

bail1:
	lws_free(q);

bail:
	jws.map.buf[LJWS_PYLD] = NULL;
	jws.map.len[LJWS_PYLD] = 0;
	lws_jws_destroy(&jws);
	lws_jose_destroy(&jose);

	return r;
}

int
lws_jwt_token_sanity(const char *in, size_t in_len,
		     const char *iss, const char *aud,
		     const char *csrf_in,
		     char *sub, size_t sub_len, unsigned long *expiry_unix_time)
{
	unsigned long now = lws_now_secs(), exp;
	const char *cp;
	size_t len;

	/*
	 * It has our issuer?
	 */

	if (lws_json_simple_strcmp(in, in_len, "\"iss\":", iss)) {
		lwsl_notice("%s: iss mismatch\n", __func__);
		return 1;
	}

	/*
	 * ... it is indended for us to consume? (this is set
	 * to the public base url for this sai instance)
	 */
	if (lws_json_simple_strcmp(in, in_len, "\"aud\":", aud)) {
		lwsl_notice("%s: aud mismatch\n", __func__);
		return 1;
	}

	/*
	 * ...it's not too early for it?
	 */
	cp = lws_json_simple_find(in, in_len, "\"nbf\":", &len);
	if (!cp || (unsigned long)atol(cp) > now) {
		lwsl_notice("%s: nbf fail\n", __func__);
		return 1;
	}

	/*
	 * ... and not too late for it?
	 */
	cp = lws_json_simple_find(in, in_len, "\"exp\":", &len);
	exp = (unsigned long)atol(cp);
	if (!cp || (unsigned long)atol(cp) < now) {
		lwsl_notice("%s: exp fail %lu vs %lu\n", __func__,
				cp ? (unsigned long)atol(cp) : 0, now);
		return 1;
	}

	/*
	 * Caller cares about subject?  Then we must have it, and it can't be
	 * empty.
	 */

	if (sub) {
		cp = lws_json_simple_find(in, in_len, "\"sub\":", &len);
		if (!cp || !len) {
			lwsl_notice("%s: missing subject\n", __func__);
			return 1;
		}
		lws_strnncpy(sub, cp, len, sub_len);
	}

	/*
	 * If caller has been told a Cross Site Request Forgery (CSRF) nonce,
	 * require this JWT to express the same CSRF... this makes generated
	 * links for dangerous privileged auth'd actions expire with the JWT
	 * that was accessing the site when the links were generated.  And it
	 * leaves an attacker not knowing what links to synthesize unless he
	 * can read the token or pages generated with it.
	 *
	 * Using this is very good for security, but it implies you must refresh
	 * generated pages still when the auth token is expiring (and the user
	 * must log in again).
	 */

	if (csrf_in &&
	    lws_json_simple_strcmp(in, in_len, "\"csrf\":", csrf_in)) {
		lwsl_notice("%s: csrf mismatch\n", __func__);
		return 1;
	}

	if (expiry_unix_time)
		*expiry_unix_time = exp;

	return 0;
}
