/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2021 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.
 *
 * cose_sign handling
 *
 * Validation:
 *
 *  - we put all our pieces and results in an lwsac in the parse state object
 *
 *  - we collect pieces needed for sig validation into lwsac elements
 *
 *  - we go through each signature making discrete results in the lwsac for
 *    the user code to assess
 */

#include "private-lib-core.h"
#include "private-lib-cose.h"

const uint8_t *sig_mctx[] = { (uint8_t *)"",
				    (uint8_t *)"\x85\x69""Signature",
				    (uint8_t *)"\x84\x6a""Signature1",
				    (uint8_t *)"\x85\x6f""CounterSignature",
				    (uint8_t *)"\x84\x63""MAC",
				    (uint8_t *)"\x84\x64""MAC0",
};
uint8_t sig_mctx_len[] = { 0, 11, 12, 17, 5, 6 };

struct alg_names {
	const char	*name;
	cose_param_t	alg;
} alg_names[] = {
	{ "ES256",	LWSCOSE_WKAECDSA_ALG_ES256 },
	{ "ES384", 	LWSCOSE_WKAECDSA_ALG_ES384 },
	{ "ES512",	LWSCOSE_WKAECDSA_ALG_ES512 },
	{ "HS256_64", 	LWSCOSE_WKAHMAC_256_64 },
	{ "HS256", 	LWSCOSE_WKAHMAC_256_256 },
	{ "HS384", 	LWSCOSE_WKAHMAC_384_384 },
	{ "HS512", 	LWSCOSE_WKAHMAC_512_512 },
	{ "RS256", 	LWSCOSE_WKARSA_ALG_RS256 },
	{ "RS384", 	LWSCOSE_WKARSA_ALG_RS384 },
	{ "RS512", 	LWSCOSE_WKARSA_ALG_RS512 },
};

/*
 * The Sig_structure plaintext is new temp CBOR made up from pieces from the
 * cose_sign, cose_signature, and payload in a specific order
 *
 *  tstr     context string
 *  bstr     0-len or protected body headers
 *  bstr     (Missing for sign1) 0-len or protected signer headers
 *  bstr     0-len or protected application part
 *  bstr     the payload
 *
 * We are getting CBOR with an optional outer tag and then an array of exactly
 * 4 items in a fixed order
 *
 * [
 *   protected headers: bstr containing a map (captured as CBOR in cps->ph[])
 *   unprotected: map: for sign1, eg, the alg (!?), the kid
 *   payload: bstr
 *   if sign: signatures: [ cose_signature struct array,
 *   			    each is a 3-element array
 *     [
 *       protected: bstr containing a map: (eg, the alg) (captured as CBOR)
 *       unprotected: map: (eg, the kid)
 *       signature:  bstr
 *     ]
 *   if sign1: bstr containing signature
 * ]
 *
 * The last signatures field may be an array of signatures, or a single
 * cose_signature object for cose_sign1.
 *
 * For cose_sign1, we know the signature alg before the payload and can do it
 * in a single pass.  But for sign, we do not know the signature algs until
 * after the payload, which is an unfortunate oversight in cose_sign, meaning we
 * cannot hash the payload one or more ways in a single pass.
 */

#if defined(VERBOSE)
const char *cose_sections[] = {
	"ST_UNKNOWN",

	"ST_OUTER_PROTECTED",
	"ST_OUTER_UNPROTECTED",
	"ST_OUTER_PAYLOAD",
	"ST_OUTER_SIGN1_SIGNATURE",

	"ST_OUTER_SIGN_SIGARRAY",

	"ST_OUTER_MACTAG",

	"ST_INNER_PROTECTED",
	"ST_INNER_UNPROTECTED",
	"ST_INNER_SIGNATURE",

	"ST_INNER_EXCESS",
};
#endif

const char *
lws_cose_alg_to_name(cose_param_t alg)
{
	size_t n;

	for (n = 0; n < LWS_ARRAY_SIZE(alg_names); n++)
		if (alg_names[n].alg == alg)
			return alg_names[n].name;

	return "unknown_alg";
}

cose_param_t
lws_cose_name_to_alg(const char *name)
{
	size_t n;

	for (n = 0; n < LWS_ARRAY_SIZE(alg_names); n++)
		if (!strcmp(alg_names[n].name, name))
			return alg_names[n].alg;

	return 0;
}

static size_t
bstr_len(uint8_t *t, size_t buflen, uint8_t opcode, uint64_t len)
{
	uint8_t *ot = t;

	if (buflen < 9)
		return 0;

	if (len < 24) {
		*t = (uint8_t)(opcode | len);

		return 1;
	}
	if (len < 256) {
		*t++ = opcode | LWS_CBOR_1;
		goto b;
	}
	if (len < 65536) {
		*t++ = opcode | LWS_CBOR_2;
		goto b1;
	}
	if (len < 0xffffffffu) {
		*t++ = opcode | LWS_CBOR_4;
		goto b2;
	}

	*t++ = opcode | LWS_CBOR_8;

	*t++ = (uint8_t)(len >> 56);
	*t++ = (uint8_t)(len >> 48);
	*t++ = (uint8_t)(len >> 40);
	*t++ = (uint8_t)(len >> 32);

b2:
	*t++ = (uint8_t)(len >> 24);
	*t++ = (uint8_t)(len >> 16);
b1:
	*t++ = (uint8_t)(len >> 8);
b:
	*t++ = (uint8_t)len;

	return lws_ptr_diff_size_t(t, ot);
}

static int
apply_external(struct lws_cose_validate_context *cps)
{
	lws_cose_sig_alg_t *alg;
	uint8_t t[9];

	alg = lws_container_of(cps->algs.head, lws_cose_sig_alg_t, list);
	if (!alg)
		/* expected if no key */
		return 0;

	/* get the external payload first, if any indicated */

	if (cps->info.ext_len) {
		lws_cose_sig_ext_pay_t ex;
		size_t s;

		s = bstr_len(t, sizeof(t), LWS_CBOR_MAJTYP_BSTR,
			     cps->info.ext_len);
		if (lws_cose_val_alg_hash(alg, t, s))
			return 1;

		memset(&ex, 0, sizeof(ex));
		ex.cps = cps;

		do {
			int n;

			ex.xl = 0;
			n = cps->info.ext_cb(&ex);

			if (ex.xl &&
			    lws_cose_val_alg_hash(alg, ex.ext, ex.xl))
				return 1;

			if (n == LCOSESIGEXTCB_RET_ERROR)
				return 1;

			if (n == LCOSESIGEXTCB_RET_FINISHED)
				break;
		} while (1);
	}

	return 0;
}

static int
create_alg(struct lecp_ctx *ctx, struct lws_cose_validate_context *cps)
{
	lws_cose_validate_param_stack_t *sl = &cps->st[cps->sp], *sl0 = &cps->st[0];
	lws_cose_validate_res_t *res;
	lws_cose_sig_alg_t *alg;
	lws_cose_key_t *ck;
	uint8_t *p;
	size_t s;

	/* with sign1, we can hash the payload in a
	 * single pass */

	ck = lws_cose_key_from_set(cps->info.keyset, sl->kid.buf, sl->kid.len);
	if (!ck) {
		lwsl_notice("%s: no key\n", __func__);
		lwsl_hexdump_notice(sl->kid.buf, sl->kid.len);
		goto no_key_or_alg;
	}

	// lwsl_notice("%s: cps->alg %d\n", __func__, (int)cps->alg);

	alg = lws_cose_val_alg_create(cps->info.cx, ck, cps->st[0].alg,
				      LWSCOSE_WKKO_VERIFY);
	if (!alg) {
		lwsl_info("%s: no alg\n", __func__);

no_key_or_alg:
		/*
		 * We can't create the alg then, so we can't normally
		 * create a result object.  Create one especially for this
		 * case and continue on
		 */

		res = lws_zalloc(sizeof(*res), __func__);
		if (res) {
			res->result = -1001;

			lws_dll2_add_tail(&res->list, &cps->results);
		}

		return 0;
	}

	lws_dll2_add_tail(&alg->list, &cps->algs);

	/*
	 * Hash step 1: The first hash content depends on
	 *              sign/sign1/csign/mac/mac0 constant bstr
	 */

	if (lws_cose_val_alg_hash(alg, sig_mctx[cps->info.sigtype],
			       sig_mctx_len[cps->info.sigtype]))
		goto bail;

	/*
	 * Hash step 2: A zero-length bstr, or a copy of the
	 *              OUTER protected headers
	 *
	 *              A zero-entry map alone becomes a zero-
	 *              length bstr
	 */

	if (sl0->ph_pos[0] < 2) {
		/* nothing to speak of */
		sl0->ph[0][0] = LWS_CBOR_MAJTYP_BSTR;
		p = &sl0->ph[0][0];
		s = 1;
	} else {
		if (sl0->ph_pos[0] < 24) {
			sl0->ph[0][2] = (uint8_t)
			   (LWS_CBOR_MAJTYP_BSTR | sl0->ph_pos[0]);
			p = &sl0->ph[0][2];
			s = (size_t)sl0->ph_pos[0] + 1;
		} else {
			sl0->ph[0][1] = LWS_CBOR_MAJTYP_BSTR |
					LWS_CBOR_1;
			sl0->ph[0][2] = (uint8_t)sl0->ph_pos[0];
			p = &sl0->ph[0][1];
			s = (size_t)sl0->ph_pos[0] + 2;
		}
	}

	if (lws_cose_val_alg_hash(alg, p, s))
		goto bail;

	/*
	 * Hash step 3: Protected signer headers (Elided for sign1)
	 */

	if (cps->info.sigtype == SIGTYPE_MULTI) {
		if (sl->ph_pos[2] < 2) {
			/* nothing to speak of */
			sl->ph[2][0] = LWS_CBOR_MAJTYP_BSTR;
			p = &sl->ph[2][0];
			s = 1;
		} else {
			if (sl->ph_pos[2] < 24) {
				sl->ph[2][2] = (uint8_t)
				   (LWS_CBOR_MAJTYP_BSTR | sl->ph_pos[2]);
				p = &sl->ph[2][2];
				s = (size_t)sl->ph_pos[2] + 1;
			} else {
				sl->ph[2][1] = LWS_CBOR_MAJTYP_BSTR |
						LWS_CBOR_1;
				sl->ph[2][2] = (uint8_t)sl->ph_pos[2];
				p = &sl->ph[2][1];
				s = (size_t)sl->ph_pos[2] + 2;
			}
		}

		if (lws_cose_val_alg_hash(alg, p, s))
			goto bail;
	}

	/* Hash step 4: bstr for applictation protected pieces
	 *              empty for now
	 */

	if (!cps->info.ext_len) { /* ie, if no app data */
		uint8_t u = LWS_CBOR_MAJTYP_BSTR;
		if (lws_cose_val_alg_hash(alg, &u, 1))
			goto bail;
	}

	/*
	 * The final part is the payload in its own bstr, as
	 * we get it if sign1, else replayed from a cache in heap
	 */

	if (cps->info.sigtype == SIGTYPE_SINGLE)
		return 0;

	if (!cps->payload_stash) {
		lwsl_notice("%s: no payload stash\n", __func__);
		goto bail;
	}

	apply_external(cps);

	if (lws_cose_val_alg_hash(alg, cps->payload_stash, cps->payload_pos))
		goto bail;
lwsl_notice("a %d\n", (int)cps->sig_agg_pos);

	lws_cose_val_alg_destroy(cps, &alg, (const uint8_t *)cps->sig_agg,
				 cps->sig_agg_pos);

	return 0;

bail:
	return 1;
}

#if defined(VERBOSE)
static const char * const reason_names[] = {
	"LECPCB_CONSTRUCTED",
	"LECPCB_DESTRUCTED",
	"LECPCB_START",
	"LECPCB_COMPLETE",
	"LECPCB_FAILED",
	"LECPCB_PAIR_NAME",
	"LECPCB_VAL_TRUE",
	"LECPCB_VAL_FALSE",
	"LECPCB_VAL_NULL",
	"LECPCB_VAL_NUM_INT",
	"LECPCB_VAL_RESERVED", /* float in lejp */
	"LECPCB_VAL_STR_START",
	"LECPCB_VAL_STR_CHUNK",
	"LECPCB_VAL_STR_END",
	"LECPCB_ARRAY_START",
	"LECPCB_ARRAY_END",
	"LECPCB_OBJECT_START",
	"LECPCB_OBJECT_END",
	"LECPCB_TAG_START",
	"LECPCB_TAG_END",
	"LECPCB_VAL_NUM_UINT",
	"LECPCB_VAL_UNDEFINED",
	"LECPCB_VAL_FLOAT16",
	"LECPCB_VAL_FLOAT32",
	"LECPCB_VAL_FLOAT64",
	"LECPCB_VAL_SIMPLE",
	"LECPCB_VAL_BLOB_START",
	"LECPCB_VAL_BLOB_CHUNK",
	"LECPCB_VAL_BLOB_END",
	"LECPCB_ARRAY_ITEM_START",
	"LECPCB_ARRAY_ITEM_END",
	"LECPCB_LITERAL_CBOR"
};
#endif

static int
ph_index(struct lws_cose_validate_context *cps)
{
	switch (cps->tli) {
	case ST_OUTER_PROTECTED:
		return 0;
	case ST_OUTER_UNPROTECTED:
		return 1;
	case ST_INNER_PROTECTED:
		return 2;
	case ST_INNER_UNPROTECTED:
		return 3;
	}

	assert(0);
	return 0;
}

static signed char
cb_cose_sig(struct lecp_ctx *ctx, char reason)
{
	struct lws_cose_validate_context *cps =
			(struct lws_cose_validate_context *)ctx->user;
	lws_cose_validate_param_stack_t *sl;
	struct lws_gencrypto_keyelem *ke;
	lws_cose_sig_alg_t *alg;
	uint8_t t[9];
	size_t s;
	int hi;

#if defined(VERBOSE)
	lwsl_notice("%s: %s, tli %s, sub %d, ppos %d, sp %d\n", __func__,
			reason_names[reason & 0x1f], cose_sections[cps->tli],
			cps->sub, ctx->pst[ctx->pst_sp].ppos, cps->sp);
#endif

	switch (reason) {
	case LECPCB_CONSTRUCTED:
		break;

	case LECPCB_TAG_START:

		lwsl_notice("%s: tag sigtype %d\n", __func__, cps->info.sigtype);

		switch (cps->info.sigtype) {
		default:
			assert(0);
			break;
		case SIGTYPE_UNKNOWN:
			/* it means use the tag value to set the type */
			switch (ctx->item.u.u64) {
			case LWSCOAP_CONTENTFORMAT_COSE_SIGN:
				cps->info.sigtype = SIGTYPE_MULTI;
				break;
			case LWSCOAP_CONTENTFORMAT_COSE_SIGN1:
				cps->info.sigtype = SIGTYPE_SINGLE;
				break;
//			case LWSCOAP_CONTENTFORMAT_COSE_SIGN__:
//				cps->info.sigtype = SIGTYPE_COUNTERSIGNED;
//				break;
			case LWSCOAP_CONTENTFORMAT_COSE_MAC0:
				cps->info.sigtype = SIGTYPE_MAC0;
				break;
			case LWSCOAP_CONTENTFORMAT_COSE_MAC:
				cps->info.sigtype = SIGTYPE_MAC;
				break;
			default:
				goto unexpected_tag_l;
			}
			break;
		case SIGTYPE_MULTI:
			if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_SIGN)
				goto unexpected_tag_l;
			break;
		case SIGTYPE_SINGLE:
			if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_SIGN1)
				goto unexpected_tag_l;
			break;
		case SIGTYPE_COUNTERSIGNED:
			if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_SIGN)
				goto unexpected_tag_l;
			break;
		case SIGTYPE_MAC0:
			if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_MAC0)
				goto unexpected_tag_l;
			break;
		case SIGTYPE_MAC:
			if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_MAC)
				goto unexpected_tag_l;
			break;
		}

		cps->depth++;
		break;

	case LECPCB_ARRAY_ITEM_START:

		if (cps->sub)
			break;

		if (ctx->pst[ctx->pst_sp].ppos == 4 ||
		    ctx->pst[ctx->pst_sp].ppos == 6) {
			switch (cps->tli) {
			case ST_INNER_UNPROTECTED:
			case ST_INNER_PROTECTED:
				hi = ph_index(cps);
				sl = &cps->st[cps->sp];
				sl->ph_pos[hi] = 0;
				lecp_parse_report_raw(ctx, 1);
				break;
			default:
				break;
			}
			break;
		}

		if (ctx->pst[ctx->pst_sp].ppos != 2)
			break;

		switch (cps->tli) {
		case ST_OUTER_UNPROTECTED:
		case ST_OUTER_PROTECTED:
			/*
			 * Holy type confusion, Batman... this is a CBOR bstr
			 * containing valid CBOR that must also be parsed as
			 * part of the containing array... we need to collect
			 * it anyway since it is part of the signing plaintext
			 * in bstr form, let's get it and then parse it at the
			 * END of the bstr.
			 */
			lecp_parse_report_raw(ctx, 1);
			break;

		case ST_OUTER_PAYLOAD:
			if (cps->info.sigtype != SIGTYPE_SINGLE)
				break;

			if (create_alg(ctx, cps))
				goto bail;

			break;

		case ST_OUTER_SIGN_SIGARRAY:
			cps->tli = ST_INNER_PROTECTED;
			break;
		}
		break;

	case LECPCB_ARRAY_ITEM_END:

		if (cps->sub)
			break;

		if (ctx->pst[ctx->pst_sp].ppos == 2) {
			sl = &cps->st[cps->sp];
			switch (cps->tli) {
			case ST_OUTER_UNPROTECTED:
				break;
				/* fallthru */
			case ST_OUTER_PROTECTED:
				lecp_parse_report_raw(ctx, 0);

				hi = ph_index(cps);

				if (!sl->ph_pos[hi] || cps->sub)
					break;

				cps->sub = 1;
				s = (size_t)sl->ph_pos[hi];

				if (lecp_parse_subtree(&cps->ctx,
						       sl->ph[hi] + 3, s) !=
							      LECP_CONTINUE)
					goto bail;
				cps->sub = 0;
				break;

			case ST_OUTER_PAYLOAD:
				switch (cps->info.sigtype) {
				case SIGTYPE_MULTI:
					cps->tli = ST_OUTER_SIGN_SIGARRAY - 1;
					break;
				case SIGTYPE_MAC:
				case SIGTYPE_MAC0:
					cps->tli = ST_OUTER_MACTAG - 1;
					break;
				case SIGTYPE_COUNTERSIGNED:
					break;
				default:
					break;
				}
				break;

			case ST_OUTER_SIGN1_SIGNATURE:
			case ST_OUTER_MACTAG:
				cps->sp++;
				cps->tli = ST_INNER_PROTECTED - 1;
				break;

			case ST_INNER_UNPROTECTED:
				lwsl_notice("ST_INNER_UNPROTECTED end\n");
				break;
			case ST_INNER_PROTECTED:
				lwsl_notice("ST_INNER_PROTECTED end\n");
				break;

			case ST_INNER_EXCESS:
			case ST_OUTER_SIGN_SIGARRAY:
				cps->tli--; /* so no change */
				break;
			}
			if (!cps->sub)
				cps->tli++;
		}

		if (ctx->pst[ctx->pst_sp].ppos >= 4) {
			uint8_t *p;
			uint8_t u;
			size_t s1;

			switch (cps->tli) {
			case ST_INNER_UNPROTECTED:
			case ST_INNER_PROTECTED:

				hi = ph_index(cps);
				sl = &cps->st[cps->sp];
				p = sl->ph[hi] + 3;
				lecp_parse_report_raw(ctx, 0);

				if (!sl->ph_pos[hi] || cps->sub) {
					if (!cps->sub)
						cps->tli++;
					break;
				}

				cps->sub = 1;
				s = (size_t)sl->ph_pos[hi];

				/*
				 * somehow the raw captures the
				 * initial BSTR container length,
				 * let's strip it
				 */

				u = (*p) & LWS_CBOR_SUBMASK;
				if (((*p) & LWS_CBOR_MAJTYP_MASK) ==
							LWS_CBOR_MAJTYP_BSTR) {
					s1 = 1;
					if (u == LWS_CBOR_1)
						s1 = 2;
					else if (u == LWS_CBOR_2)
						s1 = 3;
					else if (u == LWS_CBOR_4)
						s1 = 5;
					else if (u == LWS_CBOR_8)
						s1 = 9;

					if (s1 > s)
						goto bail;

					sl->ph_pos[hi] = (int)
						(sl->ph_pos[hi] - (ssize_t)s1);
					s = s - s1;
					memmove(p, p + s1, s);
				}

				if (lecp_parse_subtree(&cps->ctx, p, s) !=
								LECP_CONTINUE)
					goto bail;

				cps->sub = 0;

				if (!cps->sub)
					cps->tli++;
				break;

			case ST_INNER_SIGNATURE:
				if (cps->info.sigtype == SIGTYPE_MAC) {
					// lwsl_err("Y: alg %d\n", (int)cps->alg);
					if (create_alg(ctx, cps))
						goto bail;
				}
				cps->tli++;
				break;
			default:
				break;
			}
		}

		break;

	case LECPCB_VAL_NUM_INT:
	case LECPCB_VAL_NUM_UINT:
		switch (cps->tli) {
		case ST_INNER_PROTECTED:
		case ST_INNER_UNPROTECTED:
		case ST_INNER_SIGNATURE:
		case ST_OUTER_PROTECTED:
		case ST_OUTER_UNPROTECTED:
			if (lecp_parse_map_is_key(ctx)) {
				cps->map_key = ctx->item.u.i64;
				// lwsl_notice("%s: key %d\n", __func__, (int)cps->map_key);
				break;
			}

			// lwsl_notice("%s: key %d val %d\n", __func__, (int)cps->map_key, (int)ctx->item.u.i64);

			if (cps->map_key == LWSCOSE_WKL_ALG) {
				sl = &cps->st[cps->sp];
				cps->map_key = 0;
				if (cps->tli == ST_INNER_PROTECTED ||
				     cps->tli == ST_INNER_UNPROTECTED ||
				     cps->tli == ST_INNER_SIGNATURE) {
					sl->alg = ctx->item.u.i64;
					if (!cps->st[0].alg)
						cps->st[0].alg = sl->alg;
				} else
					sl->alg = ctx->item.u.i64;
				break;
			}
			break;
		}
		break;

	case LECPCB_VAL_STR_END:
		switch (cps->tli) {
		case ST_OUTER_UNPROTECTED:
			break;
		}
		break;

	case LECPCB_VAL_BLOB_START:

		lwsl_notice("%s: blob size %d\n", __func__, (int)ctx->item.u.u64);

		if (cps->tli == ST_OUTER_SIGN1_SIGNATURE ||
		    cps->tli == ST_INNER_SIGNATURE) {
			if (ctx->item.u.u64 > sizeof(cps->sig_agg))
				goto bail;
			cps->sig_agg_pos = 0;
			break;
		}

		if (cps->tli != ST_OUTER_PAYLOAD)
			break;

		if (apply_external(cps)) {
			lwsl_notice("%s: ext\n", __func__);
			goto bail;
		}

		s = bstr_len(t, sizeof(t), LWS_CBOR_MAJTYP_BSTR,
			     ctx->item.u.u64);

		if (cps->info.sigtype == SIGTYPE_SINGLE) {
			alg = lws_container_of(cps->algs.head,
					       lws_cose_sig_alg_t, list);
			if (!alg)
				/* expected if no key */
				break;
			if (lws_cose_val_alg_hash(alg, t, s)) {
				lwsl_notice("%s: hash failed\n", __func__);
				goto bail;
			}

			break;
		}

		cps->payload_stash_size = (size_t)(ctx->item.u.u64 + s);
		cps->payload_stash = lws_malloc(cps->payload_stash_size,
							__func__);
		if (!cps->payload_stash) {
			lwsl_notice("%s: oom\n", __func__);
			goto bail;
		}

		memcpy(cps->payload_stash, t, s);
		cps->payload_pos = s;

		break;

	case LECPCB_VAL_BLOB_CHUNK:
		switch (cps->tli) {
		case ST_OUTER_PAYLOAD:

			if (cps->info.pay_cb && ctx->npos)
				cps->info.pay_cb(cps, cps->info.pay_opaque,
						 (uint8_t *)ctx->buf, ctx->npos);

			if (cps->payload_stash) {
				if (cps->payload_pos + ctx->npos >
					cps->payload_stash_size)
					goto bail;
				memcpy(cps->payload_stash + cps->payload_pos,
						ctx->buf, ctx->npos);
				cps->payload_pos += ctx->npos;
				break;
			}
			alg = lws_container_of(cps->algs.head,
					       lws_cose_sig_alg_t, list);
			if (!alg)
				/* expected if no key */
				break;
			if (ctx->npos &&
			    lws_cose_val_alg_hash(alg, (uint8_t *)ctx->buf,
					      ctx->npos)) {
				lwsl_notice("%s: chunk fail\n", __func__);
				goto bail;
			}
			break;
		case ST_INNER_SIGNATURE:
		case ST_OUTER_SIGN1_SIGNATURE:
			/* the sig is big compared to ctx->buf... we need to
			 * stash it then */
			memcpy(cps->sig_agg + cps->sig_agg_pos, ctx->buf,
				ctx->npos);
			cps->sig_agg_pos = cps->sig_agg_pos + ctx->npos;
			break;
		}
		break;

	case LECPCB_VAL_BLOB_END:
		switch (cps->tli) {

		case ST_INNER_SIGNATURE:
			if (cps->info.sigtype == SIGTYPE_MULTI) {
				memcpy(cps->sig_agg + cps->sig_agg_pos, ctx->buf,
					ctx->npos);
				cps->sig_agg_pos = cps->sig_agg_pos + ctx->npos;
				// lwsl_err("Y: alg %d\n", (int)cps->alg);
				if (create_alg(ctx, cps))
					goto bail;
				break;
			}
			if (cps->info.sigtype != SIGTYPE_MAC)
				break;
			/* fallthru */
		case ST_OUTER_PROTECTED:
		case ST_OUTER_UNPROTECTED:
		case ST_INNER_PROTECTED:
		case ST_INNER_UNPROTECTED:
			if (cps->map_key == LWSCOSE_WKL_KID) {
				sl = &cps->st[cps->sp];
				ke = &sl->kid;
				if (ke->buf)
					lws_free(ke->buf);
				ke->buf = lws_malloc(ctx->npos, __func__);
				if (!ke->buf)
					goto bail;
				ke->len = ctx->npos;
				memcpy(ke->buf, ctx->buf, ctx->npos);
				cps->map_key = 0;
			}
			break;

		case ST_OUTER_PAYLOAD:
			if (cps->info.pay_cb && ctx->npos)
				cps->info.pay_cb(cps, cps->info.pay_opaque,
						 (uint8_t *)ctx->buf, ctx->npos);
			if (cps->payload_stash) {
				if (cps->payload_pos + ctx->npos >
					cps->payload_stash_size)
					goto bail;
				memcpy(cps->payload_stash + cps->payload_pos,
						ctx->buf, ctx->npos);
				cps->payload_pos += ctx->npos;
				break;
			}
			alg = lws_container_of(cps->algs.head,
					       lws_cose_sig_alg_t, list);
			if (!alg)
				/* expected if no key */
				break;

			if (ctx->npos &&
			    lws_cose_val_alg_hash(alg, (uint8_t *)ctx->buf,
					      ctx->npos))
				goto bail;
			break;

		case ST_OUTER_SIGN1_SIGNATURE:
			if (cps->info.sigtype == SIGTYPE_MULTI)
				break;

			memcpy(cps->sig_agg + cps->sig_agg_pos, ctx->buf,
				ctx->npos);
			cps->sig_agg_pos += ctx->npos;

			alg = lws_container_of(cps->algs.head,
					lws_cose_sig_alg_t, list);
			lwsl_notice("b\n");
			if (alg)
				lws_cose_val_alg_destroy(cps, &alg,
							 cps->sig_agg,
							 cps->sig_agg_pos);
			break;

		case ST_OUTER_MACTAG:
			if (cps->mac_pos + ctx->npos > sizeof(cps->mac))
				goto bail;
			memcpy(cps->mac + cps->mac_pos, ctx->buf, ctx->npos);
			cps->mac_pos += ctx->npos;

			if (cps->info.sigtype == SIGTYPE_MAC0) {
				if (create_alg(ctx, cps))
					goto bail;
			}

			break;
		}
		break;

	case LECPCB_LITERAL_CBOR:
		/* only used for protected headers */
		switch (cps->tli) {
		case ST_INNER_PROTECTED:
		case ST_OUTER_PROTECTED:
		case ST_INNER_UNPROTECTED:
		case ST_OUTER_UNPROTECTED:
			sl = &cps->st[cps->sp];
			hi = ph_index(cps);
			if (sl->ph_pos[hi] + 3 + ctx->cbor_len >
					(int)sizeof(sl->ph[hi]) - 3)
				/* more protected cbor than we can handle */
				goto bail;
			memcpy(sl->ph[hi] + 3 + sl->ph_pos[hi], ctx->cbor,
			       ctx->cbor_len);
			sl->ph_pos[hi] += ctx->cbor_len;
			break;
		}
	}

	return 0;

unexpected_tag_l:
	lwsl_warn("%s: unexpected tag %d\n", __func__,
			(int)ctx->item.u.u64);
	goto bail;

bail:

	return -1;
}

struct lws_cose_validate_context *
lws_cose_validate_create(const lws_cose_validate_create_info_t *info)
{
	struct lws_cose_validate_context *cps;

	/* you have to provide at least one key in a cose_keyset */
	assert(info->keyset);
	/* you have to provide an lws_context (for crypto random) */
	assert(info->cx);

	cps = lws_zalloc(sizeof(*cps), __func__);
	if (!cps)
		return NULL;

	cps->info			= *info;
	cps->tli			= ST_OUTER_PROTECTED;

	lecp_construct(&cps->ctx, cb_cose_sig, cps, NULL, 0);

	return cps;
}

int
lws_cose_validate_chunk(struct lws_cose_validate_context *cps,
			const uint8_t *in, size_t in_len, size_t *used_in)
{
	int n;

	n = lecp_parse(&cps->ctx, in, in_len);
	if (used_in)
		*used_in = cps->ctx.used_in;

	if (n == LECP_CONTINUE)
		return LECP_CONTINUE;

	lecp_destruct(&cps->ctx);

	return n;
}

lws_dll2_owner_t *
lws_cose_validate_results(struct lws_cose_validate_context *cps)
{
	return &cps->results;
}

void
lws_cose_validate_destroy(struct lws_cose_validate_context **_cps)
{
	struct lws_cose_validate_context *cps = *_cps;

	if (!cps)
		return;

	lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
				   lws_dll2_get_head(&cps->algs)) {
		lws_cose_sig_alg_t *alg = lws_container_of(p,
						lws_cose_sig_alg_t, list);

		lws_dll2_remove(p);
		lws_cose_val_alg_destroy(cps, &alg, NULL, 0);
	} lws_end_foreach_dll_safe(p, tp);

	lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
				   lws_dll2_get_head(&cps->results)) {
		lws_cose_validate_res_t *res = lws_container_of(p,
					lws_cose_validate_res_t, list);

		lws_dll2_remove(p);
		lws_free(res);
	} lws_end_foreach_dll_safe(p, tp);

	lws_free_set_NULL(cps->payload_stash);

	lwsac_free(&cps->ac);

	while (cps->sp >= 0) {
		if (cps->st[cps->sp].kid.buf)
			lws_free(cps->st[cps->sp].kid.buf);
		cps->sp--;
	}

	lws_free_set_NULL(*_cps);
}
