/*
 * JSON Web Token (JWT) processing
 *
 * Copyright 2021 HAProxy Technologies
 * Remi Tricot-Le Breton <rlebreton@haproxy.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#include <sys/stat.h>

#include <import/ebmbtree.h>
#include <import/ebsttree.h>

#include <haproxy/api.h>
#include <haproxy/tools.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/base64.h>
#include <haproxy/jwt.h>
#include <haproxy/buf.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_sock.h>


#ifdef USE_OPENSSL

/* Tree into which the public certificates used to validate JWTs will be stored. */
static struct eb_root jwt_cert_tree = EB_ROOT_UNIQUE;


/*
 * The possible algorithm strings that can be found in a JWS's JOSE header are
 * defined in section 3.1 of RFC7518.
 */
enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len)
{
	enum jwt_alg alg = JWT_ALG_DEFAULT;

	/* Algorithms are all 5 characters long apart from "none". */
	if (alg_len < sizeof("HS256")-1) {
		if (alg_len == sizeof("none")-1 && strcmp("none", alg_str) == 0)
			alg = JWS_ALG_NONE;
		return alg;
	}

	if (alg == JWT_ALG_DEFAULT) {
		switch(*alg_str++) {
		case 'H':
			if (strncmp(alg_str, "S256", alg_len-1) == 0)
				alg = JWS_ALG_HS256;
			else if (strncmp(alg_str, "S384", alg_len-1) == 0)
				alg = JWS_ALG_HS384;
			else if (strncmp(alg_str, "S512", alg_len-1) == 0)
				alg = JWS_ALG_HS512;
			break;
		case 'R':
			if (strncmp(alg_str, "S256", alg_len-1) == 0)
				alg = JWS_ALG_RS256;
			else if (strncmp(alg_str, "S384", alg_len-1) == 0)
				alg = JWS_ALG_RS384;
			else if (strncmp(alg_str, "S512", alg_len-1) == 0)
				alg = JWS_ALG_RS512;
			break;
		case 'E':
			if (strncmp(alg_str, "S256", alg_len-1) == 0)
				alg = JWS_ALG_ES256;
			else if (strncmp(alg_str, "S384", alg_len-1) == 0)
				alg = JWS_ALG_ES384;
			else if (strncmp(alg_str, "S512", alg_len-1) == 0)
				alg = JWS_ALG_ES512;
			break;
		case 'P':
			if (strncmp(alg_str, "S256", alg_len-1) == 0)
				alg = JWS_ALG_PS256;
			else if (strncmp(alg_str, "S384", alg_len-1) == 0)
				alg = JWS_ALG_PS384;
			else if (strncmp(alg_str, "S512", alg_len-1) == 0)
				alg = JWS_ALG_PS512;
			break;
		default:
			break;
		}
	}

	return alg;
}

/*
 * Split a JWT into its separate dot-separated parts.
 * Since only JWS following the Compact Serialization format are managed for
 * now, we don't need to manage more than three subparts in the tokens.
 * See section 3.1 of RFC7515 for more information about JWS Compact
 * Serialization.
 * Returns -1 in case of error, 0 if the token has exactly <item_num> parts, a
 * positive value otherwise.
 */
int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int item_num)
{
	char *ptr = jwt->area;
	char *jwt_end = jwt->area + jwt->data;
	unsigned int index = 0;
	unsigned int length = 0;

	if (item_num == 0)
		return -1;

	items[index].start = ptr;
	items[index].length = 0;

	while (ptr < jwt_end) {
		if (*ptr++ == '.') {
			items[index++].length = length;
			/* We found enough items, no need to keep looking for
			 * separators. */
			if (index == item_num)
				return 1;

			items[index].start = ptr;
			items[index].length = 0;
			length = 0;
		} else
			++length;
	}

	/* We might not have found enough items */
	if (index < item_num - 1)
		return -1;

	items[index].length = length;

	return (ptr != jwt_end);
}

/*
 * Parse a public certificate and insert it into the jwt_cert_tree.
 * Returns 0 in case of success.
 */
int jwt_tree_load_cert(char *path, int pathlen, int tryload_cert, const char *file, int line, char **err)
{
	int retval = -1;
	struct jwt_cert_tree_entry *entry = NULL;
	EVP_PKEY *pubkey = NULL;
	BIO *bio = NULL;
	struct stat buf;
	struct ebmb_node *eb = NULL;
	struct ckch_store *store = NULL;

	eb = ebst_lookup(&jwt_cert_tree, path);

	if (eb)
		return 0; /* Entry already in the tree, nothing to do. */

	if (stat(path, &buf) == 0) {
		bio = BIO_new(BIO_s_file());
		if (!bio) {
			memprintf(err, "%sunable to allocate memory (BIO).\n", err && *err ? *err : "");
			goto end;
		}

		if (BIO_read_filename(bio, path) == 1) {
			pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);

			/* The file might exist but not contain a public key if
			 * we were given an actual certificate path (or a
			 * named crt-store).
			 */
			if (pubkey) {
				entry = calloc(1, sizeof(*entry) + pathlen + 1);
				if (!entry) {
					memprintf(err, "%sunable to allocate memory (jwt_cert_tree_entry).\n", err && *err ? *err : "");
					goto end;
				}
				memcpy(entry->path, path, pathlen + 1);

				entry->pubkey = pubkey;

				/* The entry insertion can't fail, we already
				 * checked if the entry existed. */
				ebst_insert(&jwt_cert_tree, &entry->node);

				retval = 0;
				goto end;
			}
		}
	}

	if (!tryload_cert)
		goto end;

	/* Look for an actual certificate or crt-store with the given name.
	 * If the path corresponds to an actual certificate that was not loaded
	 * yet we will create the corresponding ckch_store. */
	if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
		goto end;

	store = ckchs_lookup(path);
	if (!store) {
		struct ckch_conf conf = {};
		int err_code = 0;

		/* Create a new store with the given path */
		store = ckch_store_new(path);
		if (!store) {
			HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
			goto end;
		}

		conf.crt = path;

		err_code = ckch_store_load_files(&conf, store,  0, file, line, err);
		if (err_code & ERR_FATAL) {
			ckch_store_free(store);

			/* If we are in this case we are in the conf
			 * parsing phase and this case might happen if
			 * we were provided an HMAC secret or a variable
			 * name.
			 */
			retval = 0;
			ha_free(err);
			HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
			goto end;
		}

		if (ebst_insert(&ckchs_tree, &store->node) != &store->node) {
			ckch_store_free(store);
			HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
			goto end;
		}
	}

	store->conf.jwt = 1;

	retval = 0;

	HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);

end:
	if (retval) {
		/* Some error happened during pubkey parsing, remove the already
		 * inserted node from the tree and free it.
		 */
		EVP_PKEY_free(pubkey);
		free(entry);
	}
	BIO_free(bio);

	return retval;
}


/*
 * Calculate the HMAC signature of a specific JWT and check that it matches the
 * one included in the token.
 * Returns 1 in case of success.
 */
static enum jwt_vrfy_status
jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signature)
{
	const EVP_MD *evp = NULL;
	unsigned char signature[EVP_MAX_MD_SIZE];
	unsigned int signature_length = 0;
	unsigned char *hmac_res = NULL;
	enum jwt_vrfy_status retval = JWT_VRFY_KO;

	if (ctx->is_x509) {
		return JWT_VRFY_UNMANAGED_ALG;
	}

	switch(ctx->alg) {
	case JWS_ALG_HS256:
		evp = EVP_sha256();
		break;
	case JWS_ALG_HS384:
		evp = EVP_sha384();
		break;
	case JWS_ALG_HS512:
		evp = EVP_sha512();
		break;
	default: break;
	}

	hmac_res = HMAC(evp, ctx->key, ctx->key_length, (const unsigned char*)ctx->jose.start,
			ctx->jose.length + ctx->claims.length + 1, signature, &signature_length);

	if (hmac_res && signature_length == decoded_signature->data &&
		  (CRYPTO_memcmp(decoded_signature->area, signature, signature_length) == 0))
		retval = JWT_VRFY_OK;

	return retval;
}

/*
 * Convert a JWT ECDSA signature (R and S parameters concatenated, see section
 * 3.4 of RFC7518) into an ECDSA_SIG that can be fed back into OpenSSL's digest
 * verification functions.
 * Returns 0 in case of success.
 */
static int convert_ecdsa_sig(const struct jwt_ctx *ctx, struct buffer *signature)
{
	int retval = 0;
	ECDSA_SIG *ecdsa_sig = NULL;
	BIGNUM *ec_R = NULL, *ec_S = NULL;
	unsigned int bignum_len;
	unsigned char *p;

	ecdsa_sig = ECDSA_SIG_new();
	if (!ecdsa_sig) {
		retval = JWT_VRFY_OUT_OF_MEMORY;
		goto end;
	}

	if (b_data(signature) % 2) {
		retval = JWT_VRFY_INVALID_TOKEN;
		goto end;
	}

	bignum_len = b_data(signature) / 2;

	ec_R = BN_bin2bn((unsigned char*)b_orig(signature), bignum_len, NULL);
	ec_S = BN_bin2bn((unsigned char *)(b_orig(signature) + bignum_len), bignum_len, NULL);

	if (!ec_R || !ec_S) {
		retval = JWT_VRFY_INVALID_TOKEN;
		goto end;
	}

	/* Build ecdsa out of R and S values. */
	ECDSA_SIG_set0(ecdsa_sig, ec_R, ec_S);

	/* i2d_ECDSA_SIG writes with no output bound. The DER encoding adds
	 * a SEQUENCE header (~4 bytes), two INTEGER headers (~4 bytes each),
	 * and up to two sign-padding bytes on top of the raw R||S bytes.
	 * Compute the length first to avoid overflowing signature->area.
	 */
	retval = i2d_ECDSA_SIG(ecdsa_sig, NULL);
	if (retval <= 0 || (size_t)retval > signature->size) {
		retval = JWT_VRFY_INVALID_TOKEN;
		goto end;
	}

	p = (unsigned char*)signature->area;
	signature->data = i2d_ECDSA_SIG(ecdsa_sig, &p);
	retval = 0;

end:
	ECDSA_SIG_free(ecdsa_sig);
	return retval;
}

/*
 * Check that the signature included in a JWT signed via RSA or ECDSA is valid
 * and can be verified thanks to a given public certificate.
 * Returns 1 in case of success.
 */
static enum jwt_vrfy_status
jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signature)
{
	const EVP_MD *evp = NULL;
	EVP_MD_CTX *evp_md_ctx;
	EVP_PKEY_CTX *pkey_ctx = NULL;
	enum jwt_vrfy_status retval = JWT_VRFY_KO;
	struct ebmb_node *eb = NULL;
	struct jwt_cert_tree_entry *entry = NULL;
	int is_ecdsa = 0;
	int padding = RSA_PKCS1_PADDING;
	EVP_PKEY *pubkey = NULL;

	switch(ctx->alg) {
	case JWS_ALG_RS256:
		evp = EVP_sha256();
		break;
	case JWS_ALG_RS384:
		evp = EVP_sha384();
		break;
	case JWS_ALG_RS512:
		evp = EVP_sha512();
		break;

	case JWS_ALG_ES256:
		evp = EVP_sha256();
		is_ecdsa = 1;
		break;
	case JWS_ALG_ES384:
		evp = EVP_sha384();
		is_ecdsa = 1;
		break;
	case JWS_ALG_ES512:
		evp = EVP_sha512();
		is_ecdsa = 1;
		break;

	case JWS_ALG_PS256:
		evp = EVP_sha256();
		padding = RSA_PKCS1_PSS_PADDING;
		break;
	case JWS_ALG_PS384:
		evp = EVP_sha384();
		padding = RSA_PKCS1_PSS_PADDING;
		break;
	case JWS_ALG_PS512:
		evp = EVP_sha512();
		padding = RSA_PKCS1_PSS_PADDING;
		break;
	default: break;
	}

	evp_md_ctx = EVP_MD_CTX_new();
	if (!evp_md_ctx)
		return JWT_VRFY_OUT_OF_MEMORY;

	if (ctx->is_x509) {
		struct ckch_store *store = NULL;
		if (!HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {

			store = ckchs_lookup(ctx->key);
			if (store) {
				if (store->conf.jwt) {
					pubkey = X509_get_pubkey(store->data->cert);
					if (pubkey)
						EVP_PKEY_up_ref(pubkey);
				} else
					retval = JWT_VRFY_UNAVAIL_CERT;
			}
			HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
		}
	} else {
		/* Look for a public key in the JWT tree */
		eb = ebst_lookup(&jwt_cert_tree, ctx->key);

		if (eb) {
			entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);

			pubkey = entry->pubkey;
			if (pubkey)
				EVP_PKEY_up_ref(pubkey);
		}
	}

	if (!pubkey) {
		if (!retval)
			retval = JWT_VRFY_UNKNOWN_CERT;
		goto end;
	}

	/*
	 * ECXXX signatures are a direct concatenation of the (R, S) pair and
	 * need to be converted back to asn.1 in order for verify operations to
	 * work with OpenSSL.
	 */
	if (is_ecdsa) {
		int conv_retval = convert_ecdsa_sig(ctx, decoded_signature);
		if (conv_retval != 0) {
			retval = conv_retval;
			goto end;
		}
	}

	if (EVP_DigestVerifyInit(evp_md_ctx, &pkey_ctx, evp, NULL, pubkey) == 1) {
		if (is_ecdsa || EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding) > 0) {
			if (EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
						   ctx->jose.length + ctx->claims.length + 1) == 1 &&
			    EVP_DigestVerifyFinal(evp_md_ctx, (const unsigned char*)decoded_signature->area, decoded_signature->data) == 1) {
				retval = JWT_VRFY_OK;
			}
		}
	}

end:
	EVP_MD_CTX_free(evp_md_ctx);
	if (retval != JWT_VRFY_OK) {
		/* Don't forget to remove SSL errors to be sure they cannot be
		 * caught elsewhere. The error queue is cleared because it seems
		 * at least 2 errors are produced.
		 */
		ERR_clear_error();
	}
	EVP_PKEY_free(pubkey);
	return retval;
}

/*
 * Check that the <token> that was signed via algorithm <alg> using the <key>
 * (either an HMAC secret or the path to a public certificate) has a valid
 * signature.
 * <key> is either a HMAC secret or a public key path if <is_509_path> is 0,
 * otherwise <key> is an X509 certificate path.
 * Returns 1 in case of success.
 */
enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
				const struct buffer *key, int is_x509_path)
{
	struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
	unsigned int item_num = JWT_ELT_MAX;
	struct buffer *decoded_sig = NULL;
	struct jwt_ctx ctx = {};
	enum jwt_vrfy_status retval = JWT_VRFY_KO;
	int ret;

	ctx.alg = jwt_parse_alg(alg->area, alg->data);

	if (ctx.alg == JWT_ALG_DEFAULT)
		return JWT_VRFY_UNKNOWN_ALG;

	if (jwt_tokenize(token, items, item_num))
		return JWT_VRFY_INVALID_TOKEN;

	ctx.jose = items[JWT_ELT_JOSE];
	ctx.claims = items[JWT_ELT_CLAIMS];
	ctx.signature = items[JWT_ELT_SIG];

	/* "alg" is "none", the signature must be empty for the JWS to be valid. */
	if (ctx.alg == JWS_ALG_NONE) {
		return (ctx.signature.length == 0) ? JWT_VRFY_OK : JWT_VRFY_KO;
	}

	if (ctx.signature.length == 0)
		return JWT_VRFY_INVALID_TOKEN;

	decoded_sig = alloc_trash_chunk();
	if (!decoded_sig)
		return JWT_VRFY_OUT_OF_MEMORY;

	ret = base64urldec(ctx.signature.start, ctx.signature.length,
	                   decoded_sig->area, decoded_sig->size);
	if (ret == -1) {
		retval = JWT_VRFY_INVALID_TOKEN;
		goto end;
	}

	decoded_sig->data = ret;
	ctx.key = key->area;
	ctx.key_length = key->data;
	ctx.is_x509 = is_x509_path;

	/* We have all three sections, signature calculation can begin. */

	switch(ctx.alg) {

	case JWS_ALG_HS256:
	case JWS_ALG_HS384:
	case JWS_ALG_HS512:
		/* HMAC + SHA-XXX */
		retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
		break;
	case JWS_ALG_RS256:
	case JWS_ALG_RS384:
	case JWS_ALG_RS512:
	case JWS_ALG_ES256:
	case JWS_ALG_ES384:
	case JWS_ALG_ES512:
	case JWS_ALG_PS256:
	case JWS_ALG_PS384:
	case JWS_ALG_PS512:
		/* RSASSA-PKCS1-v1_5 + SHA-XXX */
		/* ECDSA using P-XXX and SHA-XXX */
		/* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
		retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
		break;
	default:
		/* Not managed yet */
		retval = JWT_VRFY_UNMANAGED_ALG;
		break;
	}

end:
	free_trash_chunk(decoded_sig);

	return retval;
}


static void jwt_deinit(void)
{
	struct ebmb_node *node = NULL;
	struct jwt_cert_tree_entry *entry = NULL;

	node = ebmb_first(&jwt_cert_tree);
	while (node) {
		entry = ebmb_entry(node, struct jwt_cert_tree_entry, node);
		ebmb_delete(node);
		EVP_PKEY_free(entry->pubkey);
		ha_free(&entry);
		node = ebmb_first(&jwt_cert_tree);
	}
}
REGISTER_POST_DEINIT(jwt_deinit);


#endif /* USE_OPENSSL */
