 /*
 * 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.
 *
 * JOSE is actually specified as part of JWS RFC7515.  JWE references RFC7515
 * to specify its JOSE JSON object.  So it lives in ./lib/jose/jws/jose.c.
 */

#include "private-lib-core.h"
#include "jose/private-lib-jose.h"

#include <stdint.h>

static const char * const jws_jose[] = {
	"alg", /* REQUIRED */
	"jku",
	"jwk",
	"kid",
	"x5u",
	"x5c",
	"x5t",
	"x5t#S256",
	"typ",
	"cty",
	"crit",

	/* valid for JWE only below here */

	"recipients[].header",
	"recipients[].header.alg",
	"recipients[].header.kid",
	"recipients[].encrypted_key",

	"enc",
	"zip", /* ("DEF" = deflate) */

	"epk", /* valid for JWE ECDH only */
	"apu", /* valid for JWE ECDH only */
	"apv", /* valid for JWE ECDH only */
	"iv",  /* valid for JWE AES only */
	"tag", /* valid for JWE AES only */
	"p2s", /* valid for JWE PBES2 only */
	"p2c"  /* valid for JWE PBES2 only */
};

struct jose_cb_args {
	struct lws_jose *jose;

	struct lejp_ctx jwk_jctx; /* fake lejp context used to parse epk */
	struct lws_jwk_parse_state jps; /* fake jwk parse state */

	char *temp;
	int *temp_len;

	unsigned int is_jwe;
	unsigned int recipients_array;

	int recip;
};

/*
 * JWE A.4.7 Complete JWE JSON Serialization example
 *
 * LEJPCB_CONSTRUCTED
 *  LEJPCB_START
 *   LEJPCB_OBJECT_START
 *
 *    protected LEJPCB_PAIR_NAME
 *    protected LEJPCB_VAL_STR_START
 *    protected LEJPCB_VAL_STR_END
 *
 *    unprotected LEJPCB_PAIR_NAME
 *    unprotected LEJPCB_OBJECT_START
 *     unprotected.jku LEJPCB_PAIR_NAME
 *     unprotected.jku LEJPCB_VAL_STR_START
 *     unprotected.jku LEJPCB_VAL_STR_END
 *    unprotected.jku LEJPCB_OBJECT_END
 *
 *    recipients LEJPCB_PAIR_NAME
 *    recipients[] LEJPCB_ARRAY_START
 *
 *     recipients[] LEJPCB_OBJECT_START
 *      recipients[].header LEJPCB_PAIR_NAME
 *      recipients[].header LEJPCB_OBJECT_START
 *       recipients[].header.alg LEJPCB_PAIR_NAME
 *       recipients[].header.alg LEJPCB_VAL_STR_START
 *       recipients[].header.alg LEJPCB_VAL_STR_END
 *       recipients[].header.kid LEJPCB_PAIR_NAME
 *       recipients[].header.kid LEJPCB_VAL_STR_START
 *       recipients[].header.kid LEJPCB_VAL_STR_END
 *      recipients[] LEJPCB_OBJECT_END
 *      recipients[].encrypted_key LEJPCB_PAIR_NAME
 *      recipients[].encrypted_key LEJPCB_VAL_STR_START
 *      recipients[].encrypted_key LEJPCB_VAL_STR_CHUNK
 *      recipients[].encrypted_key LEJPCB_VAL_STR_END
 *     recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
 *
 *     recipients[] LEJPCB_OBJECT_START
 *      recipients[].header LEJPCB_PAIR_NAME
 *      recipients[].header LEJPCB_OBJECT_START
 *       recipients[].header.alg LEJPCB_PAIR_NAME
 *       recipients[].header.alg LEJPCB_VAL_STR_START
 *       recipients[].header.alg LEJPCB_VAL_STR_END
 *       recipients[].header.kid LEJPCB_PAIR_NAME
 *       recipients[].header.kid LEJPCB_VAL_STR_START
 *       recipients[].header.kid LEJPCB_VAL_STR_END
 *      recipients[] LEJPCB_OBJECT_END
 *      recipients[].encrypted_key LEJPCB_PAIR_NAME
 *      recipients[].encrypted_key LEJPCB_VAL_STR_START
 *      recipients[].encrypted_key LEJPCB_VAL_STR_END
 *     recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
 *
 *    recipients[] LEJPCB_ARRAY_END
 *
 *    iv LEJPCB_PAIR_NAME
 *    iv LEJPCB_VAL_STR_START
 *    iv LEJPCB_VAL_STR_END
 *    ciphertext LEJPCB_PAIR_NAME
 *    ciphertext LEJPCB_VAL_STR_START
 *    ciphertext LEJPCB_VAL_STR_END
 *    tag LEJPCB_PAIR_NAME
 *    tag LEJPCB_VAL_STR_START
 *    tag LEJPCB_VAL_STR_END
 *
 *   tag LEJPCB_OBJECT_END
 *  tag LEJPCB_COMPLETE
 * tag LEJPCB_DESTRUCTED
 *
 */

/*
 * RFC7516 7.2.2
 *
 * Note that when using the flattened syntax, just as when using the
 * general syntax, any unprotected Header Parameter values can reside in
 * either the "unprotected" member or the "header" member, or in both.
 */

static signed char
lws_jws_jose_cb(struct lejp_ctx *ctx, char reason)
{
	struct jose_cb_args *args = (struct jose_cb_args *)ctx->user;
	int n; //, dest;

	/*
	 * In JOSE JSON, the element "epk" contains a fully-formed JWK.
	 *
	 * For JOSE paths beginning "epk." or "jwk.", we pass them through to a JWK
	 * LEJP subcontext to parse using the JWK parser directly.
	 */

	if ((args->is_jwe && !strncmp(ctx->path, "epk.", 4)) ||
	    !strncmp(ctx->path, "jwk.", 4)) {
		memcpy(args->jwk_jctx.path, ctx->path + 4,
		       sizeof(ctx->path) - 4);
		memcpy(args->jwk_jctx.buf, ctx->buf, ctx->npos);
		args->jwk_jctx.npos = ctx->npos;

		if (!ctx->path_match)
			args->jwk_jctx.path_match = 0;
		lejp_check_path_match(&args->jwk_jctx);

		if (args->jwk_jctx.path_match)
			args->jwk_jctx.pst[args->jwk_jctx.pst_sp].
				callback(&args->jwk_jctx, reason);

		return 0;
	}

	// lwsl_notice("%s: %s %d (%d)\n", __func__, ctx->path, reason, ctx->sp);

	/* at the end of each recipients[] entry, bump recipients count */

	if (args->is_jwe && reason == LEJPCB_OBJECT_END && ctx->sp == 1 &&
	    !strcmp(ctx->path, "recipients[]"))
		args->jose->recipients++;

	if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
		return 0;

	//dest = ctx->path_match - 1;

	switch (ctx->path_match - 1) {

	/* strings */

	case LJJHI_ALG: /* REQUIRED */

		/*
		 * look up whether we support this alg and point the caller at
		 * its definition if so
		 */

		if (!args->is_jwe &&
		    lws_gencrypto_jws_alg_to_definition(ctx->buf,
						        &args->jose->alg)) {
			lwsl_notice("%s: unknown alg '%s'\n", __func__,
				    ctx->buf);

			return -1;
		}

		if (args->is_jwe &&
		    lws_gencrypto_jwe_alg_to_definition(ctx->buf,
						        &args->jose->alg)) {
			lwsl_notice("%s: unknown JWE alg '%s'\n", __func__,
				    ctx->buf);

			return -1;
		}

		return 0;

	case LJJHI_TYP: /* Optional: string: media type */
		lws_strnncpy(args->jose->typ, ctx->buf, ctx->npos,
			     sizeof(args->jose->typ));
		break;

	case LJJHI_JKU:	/* Optional: string */
	case LJJHI_KID:	/* Optional: string */
	case LJJHI_X5U:	/* Optional: string: url of public key cert / chain */
	case LJJHI_CTY:	/* Optional: string: content media type */

	/* base64 */

	case LJJHI_X5C:	/* Optional: base64 (NOT -url): actual cert */

	/* base64-url */

	case LJJHI_X5T:	/* Optional: base64url: SHA-1 of actual cert */
	case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of actual cert */

	/* array of strings */

	case LJJHI_CRIT: /* Optional for send, REQUIRED: array of strings:
			  * mustn't contain standardized strings or null set */
		break;

	/* jwk child */

	case LJJHI_JWK:	/* Optional: jwk JSON object: public key: */

	/* past here, JWE only */

	case LJJHI_RECIPS_HDR:
		if (!args->is_jwe) {
			lwsl_info("%s: recipients in jws\n", __func__);
			return -1;
		}
		args->recipients_array = 1;
		break;

	case LJJHI_RECIPS_HDR_ALG:
	case LJJHI_RECIPS_HDR_KID:
		break;

	case LJJHI_RECIPS_EKEY:
		if (!args->is_jwe) {
			lwsl_info("%s: recipients in jws\n", __func__);
			return -1;
		}
		args->recipients_array = 1;
		//dest = ;
		goto append_string;

	case LJJHI_ENC:	/* JWE only: Mandatory: string */
		if (!args->is_jwe) {
			lwsl_info("%s: enc in jws\n", __func__);
			return -1;
		}
		if (lws_gencrypto_jwe_enc_to_definition(ctx->buf,
							&args->jose->enc_alg)) {
			lwsl_notice("%s: unknown enc '%s'\n", __func__,
				    ctx->buf);

			return -1;
		}
		break;

	case LJJHI_ZIP:	/* JWE only: Optional: string ("DEF" = deflate) */
		if (!args->is_jwe)
			return -1;
		goto append_string;

	case LJJHI_EPK:	/* Additional arg for JWE ECDH */
		if (!args->is_jwe)
			return -1;
		/* Ephemeral key... this JSON subsection is actually a JWK */
		break;

	case LJJHI_APU:	/* Additional arg for JWE ECDH */
		if (!args->is_jwe)
			return -1;
		/* Agreement Party U */
		goto append_string;

	case LJJHI_APV:	/* Additional arg for JWE ECDH */
		if (!args->is_jwe)
			return -1;
		/* Agreement Party V */
		goto append_string;

	case LJJHI_IV:  /* Additional arg for JWE AES */
		if (!args->is_jwe)
			return -1;
		goto append_string;

	case LJJHI_TAG:	/* Additional arg for JWE AES */
		if (!args->is_jwe)
			return -1;
		goto append_string;

	case LJJHI_P2S:	/* Additional arg for JWE PBES2 */
		if (!args->is_jwe)
			return -1;
		goto append_string;
	case LJJHI_P2C:	/* Additional arg for JWE PBES2 */
		if (!args->is_jwe)
			return -1;
		goto append_string;

	/* ignore what we don't understand */

	default:
		return 0;
	}

	return 0;

append_string:

	if (*args->temp_len < ctx->npos) {
		lwsl_err("%s: out of parsing space\n", __func__);
		return -1;
	}

	if (!args->jose->e[ctx->path_match - 1].buf) {
		args->jose->e[ctx->path_match - 1].buf = (uint8_t *)args->temp;
		args->jose->e[ctx->path_match - 1].len = 0;
	}

	memcpy(args->temp, ctx->buf, ctx->npos);
	args->temp += ctx->npos;
	*args->temp_len -= ctx->npos;
	args->jose->e[ctx->path_match - 1].len += ctx->npos;

	if (reason == LEJPCB_VAL_STR_END &&
	    (int)args->jose->e[ctx->path_match - 1].len &&
	    !args->jose->edone[ctx->path_match - 1]) {
		n = lws_b64_decode_string_len(
			(const char *)args->jose->e[ctx->path_match - 1].buf,
			(int)args->jose->e[ctx->path_match - 1].len,
			(char *)args->jose->e[ctx->path_match - 1].buf,
			(int)args->jose->e[ctx->path_match - 1].len + 1);
		if (n < 0) {
			lwsl_err("%s: b64 decode failed len %d\n", __func__,
					(int)args->jose->e[ctx->path_match - 1].len);

			return -1;
		}

		args->jose->edone[ctx->path_match - 1] = 1;
		args->temp -= (int)args->jose->e[ctx->path_match - 1].len - n - 1;
		*args->temp_len +=
			(int)args->jose->e[ctx->path_match - 1].len - n - 1;

		args->jose->e[ctx->path_match - 1].len = (uint32_t)n;
	}

	return 0;
}

void
lws_jose_init(struct lws_jose *jose)
{
	memset(jose, 0, sizeof(*jose));
}

static void
lws_jose_recip_destroy(struct lws_jws_recpient *r)
{
	lws_jwk_destroy(&r->jwk_ephemeral);
	lws_jwk_destroy(&r->jwk);
}

void
lws_jose_destroy(struct lws_jose *jose)
{
	int n;

	for (n = 0; n < (int)LWS_ARRAY_SIZE(jose->recipient); n++)
		lws_jose_recip_destroy(&jose->recipient[n]);
}

static int
lws_jose_parse(struct lws_jose *jose, const uint8_t *buf, int n,
	       char *temp, int *temp_len, int is_jwe)
{
	struct lejp_ctx jctx;
	struct jose_cb_args args;
	int m;

	if (is_jwe) {
		/* prepare a context for JOSE epk ephemeral jwk parsing */
		lws_jwk_init_jps(&args.jps,
				 &jose->recipient[jose->recipients].jwk_ephemeral,
				 NULL, NULL);
	} else {
		/* prepare a context for JOSE jwk embedded public key parsing */
		lws_jwk_init_jps(&args.jps,
				 &jose->recipient[jose->recipients].jwk,
				 NULL, NULL);
	}
	lejp_construct(&args.jwk_jctx, cb_jwk, &args.jps,
			jwk_tok, LWS_ARRAY_SIZE(jwk_tok));

	args.is_jwe		= (unsigned int)is_jwe;
	args.temp		= temp;
	args.temp_len		= temp_len;
	args.jose		= jose;
	args.recip		= 0;
	args.recipients_array	= 0;
	jose->recipients	= 0;

	lejp_construct(&jctx, lws_jws_jose_cb, &args, jws_jose,
		       LWS_ARRAY_SIZE(jws_jose));

	m = lejp_parse(&jctx, (uint8_t *)buf, n);
	lejp_destruct(&jctx);
	if (m < 0) {
		lwsl_notice("%s: parse returned %d\n", __func__, m);
		return -1;
	}

	if (!args.recipients_array && jose->recipient[0].unprot[LJJHI_ALG].buf)
		/* if no explicit recipients[], we got one */
		jose->recipients++;

	return 0;
}

int
lws_jws_parse_jose(struct lws_jose *jose,
		   const char *buf, int len, char *temp, int *temp_len)
{
	return lws_jose_parse(jose, (const uint8_t *)buf, len,
			temp, temp_len, 0);
}

int
lws_jwe_parse_jose(struct lws_jose *jose,
		   const char *buf, int len, char *temp, int *temp_len)
{
	return lws_jose_parse(jose,
			      (const uint8_t *)buf, len, temp, temp_len, 1);
}

int
lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk,
		char *out, size_t out_len)
{
	struct lws_jwk *jwk;
	char *end = out + out_len - 1;
	int n, m, f, sub = 0, vl;

	/* JOSE requires an alg */
	if (!jose->alg || !jose->alg->alg)
		goto bail;

	*out++ = '{';

	for (n = 0; n < LWS_COUNT_JOSE_HDR_ELEMENTS; n++) {
		switch (n) {

		/* strings */

		case LJJHI_ALG:	/* REQUIRED */
		case LJJHI_JKU:	/* Optional: string */
		case LJJHI_KID:	/* Optional: string */
		case LJJHI_TYP:	/* Optional: string: media type */
		case LJJHI_CTY:	/* Optional: string: content media type */
		case LJJHI_X5U:	/* Optional: string: pubkey cert / chain URL */
		case LJJHI_ENC:	/* JWE only: Optional: string */
		case LJJHI_ZIP:	/* JWE only: Optional: string ("DEF"=deflate) */
			if (jose->e[n].buf) {
				out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
					"%s\"%s\":\"%s\"", sub ? ",\n" : "",
					jws_jose[n], jose->e[n].buf);
				sub = 1;
			}
			break;

		case LJJHI_X5T:	/* Optional: base64url: SHA-1 of actual cert */
		case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of cert */
		case LJJHI_APU:	/* Additional arg for JWE ECDH:  b64url */
		case LJJHI_APV:	/* Additional arg for JWE ECDH:  b64url */
		case LJJHI_IV:	/* Additional arg for JWE AES:   b64url */
		case LJJHI_TAG:	/* Additional arg for JWE AES:   b64url */
		case LJJHI_P2S:	/* Additional arg for JWE PBES2: b64url: salt */
			if (jose->e[n].buf) {
				out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
					"%s\"%s\":\"", sub ? ",\n" : "",
						jws_jose[n]);
				sub = 1;
				m = lws_b64_encode_string_url((const char *)
						jose->e[n].buf, (int)jose->e[n].len,
						out, lws_ptr_diff(end, out));
				if (m < 0)
					return -1;
				out += m;
				out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "\"");
			}
			break;

		case LJJHI_P2C: /* Additional arg for JWE PBES2: int: count */
			break; /* don't support atm */

		case LJJHI_X5C:	/* Optional: base64 (NOT -url): actual cert */
			if (jose->e[n].buf) {
				out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
					"%s\"%s\":\"", sub ? ",\n" : "",
							jws_jose[n]);
				sub = 1;
				m = lws_b64_encode_string((const char *)
						jose->e[n].buf, (int)jose->e[n].len,
						out, lws_ptr_diff(end, out));
				if (m < 0)
					return -1;
				out += m;
				out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "\"");
			}
			break;

		case LJJHI_EPK:	/* Additional arg for JWE ECDH:  eph pubkey */
		case LJJHI_JWK:	/* Optional: jwk JSON object: public key: */

			jwk = n == LJJHI_EPK ? &jose->recipient[0].jwk_ephemeral : aux_jwk;
			if (!jwk || !jwk->kty)
				break;

			out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "%s\"%s\":",
					    sub ? ",\n" : "", jws_jose[n]);
			sub = 1;
			vl = lws_ptr_diff(end, out);
			m = lws_jwk_export(jwk, 0, out, &vl);
			if (m < 0) {
				lwsl_notice("%s: failed to export key\n",
						__func__);

				return -1;
			}
			out += m;
			break;

		case LJJHI_CRIT:/* Optional for send, REQUIRED: array of strings:
				 * mustn't contain standardized strings or null set */
			if (!jose->e[n].buf)
				break;

			out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
				"%s\"%s\":[", sub ? ",\n" : "", jws_jose[n]);
			sub = 1;

			m = 0;
			f = 1;
			while ((unsigned int)m < jose->e[n].len && (end - out) > 1) {
				if (jose->e[n].buf[m] == ' ') {
					if (!f)
						*out++ = '\"';

					m++;
					f = 1;
					continue;
				}

				if (f) {
					if (m)
						*out++ = ',';
					*out++ = '\"';
					f = 0;
				}

				*out++ = (char)jose->e[n].buf[m];
				m++;
			}

			break;
		}
	}

	*out++ = '}';

	if (out > end - 2)
		return -1;

	return lws_ptr_diff(out_len, (end - out)) - 1;

bail:
	return -1;
}
