/*
 * Copyright (c) 2005-2026 Roumen Petrov.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "ssh-xkalg.h"
#include <string.h>
#include <openssl/sha.h>
#include <openssl/evp.h>

#include "sshxkey.h"
#include "sshbuf.h"
#include "log.h"
#include "match.h"
#include "myproposal.h"
#include "xmalloc.h"


extern const struct sshkey_impl sshkey_ed25519_impl;
extern const struct sshkey_impl sshkey_ecdsa_nistp256_impl;
extern const struct sshkey_impl sshkey_ecdsa_nistp384_impl;
extern const struct sshkey_impl sshkey_ecdsa_nistp521_impl;
extern const struct sshkey_impl sshkey_rsa_impl;
extern const struct sshkey_impl sshkey_dss_impl;
extern const struct sshkey_impl sshkey_mldsa44_impl;
extern const struct sshkey_impl sshkey_mldsa65_impl;
extern const struct sshkey_impl sshkey_mldsa87_impl;

/* SSH X509 public key algorithms*/
static int x509keyalgs_initialized = 0;
static SSHX509KeyAlgs x509keyalgs[20];


static void
initialize_xkalg(void) {
	SSHX509KeyAlgs *p = x509keyalgs;
	int k;

	if (x509keyalgs_initialized) return;

#ifdef TRACE_XKALG
logit("TRACE_XKALG initialize_xkalg:");
#endif
	k = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);
	for (; k > 0; k--, p++) {
		p->name = NULL;
		p->dgst = NULL;
		p->signame = NULL;
		p->chain = 0;
		p->base_id = NULL;
	}
	x509keyalgs_initialized = 1;
}


void
ssh_xkalg_cleanup(void) {
	SSHX509KeyAlgs *p = x509keyalgs;
	int k;

	if (!x509keyalgs_initialized) return;

	k = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);
	for (; k > 0; k--, p++) {
		free((void*)p->name);
		p->name = NULL;
	}
}


static inline int
ssh_md5_allowed(void) {
	if (ssh_is_fips_enabled())
		return 0;

	return 1;
}

#define ADD_X509_ALG(enabled, data)	if (enabled) { \
	if (ssh_add_x509key_alg(data) < 0) fatal_f("oops"); }

static void
add_default_xkalg(void) {
#ifdef TRACE_XKALG
logit("TRACE_XKALG add_default_xkalg:");
#endif

	/* MLDSA public key algorithm: */
	ADD_X509_ALG(sshkey_mldsa65_impl.enabled(),
	    "x509v3-mldsa-65,none,mldsa-65")
	ADD_X509_ALG(sshkey_mldsa44_impl.enabled(),
	    "x509v3-mldsa-44,none,mldsa-44")
	ADD_X509_ALG(sshkey_mldsa87_impl.enabled(),
	    "x509v3-mldsa-87,none,mldsa-87")

	/* EC public key algorithm: */
	/* - RFC6187: ssh opaque signature */
# ifdef OPENSSL_HAS_NISTP256
	ADD_X509_ALG(1,
	    "x509v3-ecdsa-sha2-nistp256,ssh-sha256,ecdsa-sha2-nistp256")
# endif
# ifdef OPENSSL_HAS_NISTP384
	ADD_X509_ALG(1,
	    "x509v3-ecdsa-sha2-nistp384,ssh-sha384,ecdsa-sha2-nistp384")
# endif
# ifdef OPENSSL_HAS_NISTP521
	ADD_X509_ALG(1,
	    "x509v3-ecdsa-sha2-nistp521,ssh-sha512,ecdsa-sha2-nistp521")
# endif

	/* RSA public key algorithm: */
	/* - RFC6187 */
#ifdef HAVE_EVP_SHA256
	ADD_X509_ALG(1,
	    "x509v3-rsa2048-sha256,rsa2048-sha256,rsa2048-sha256")
#endif
	ADD_X509_ALG(1,
	    "x509v3-ssh-rsa,rsa-sha1,ssh-rsa")
	/* - draft-ietf-secsh-transport-NN.txt where NN <= 12
	 * does not define explicitly signature format.
	 * - starting from version 7.1 first is rsa-sha1
	 */
	ADD_X509_ALG(1,
	    "x509v3-sign-rsa,rsa-sha1")
	ADD_X509_ALG(ssh_md5_allowed(),
	    "x509v3-sign-rsa,rsa-md5")

#ifdef WITH_DSA
	/* DSA public key algorithm: */
	/* - RFC6187 */
	ADD_X509_ALG(sshkey_dss_impl.enabled(),
	    "x509v3-ssh-dss,dss-raw,ssh-dss")
	/* - compatible with draft-ietf-secsh-transport-NN.txt
	 * where NN <= 12
	 */
	ADD_X509_ALG(sshkey_dss_impl.enabled(),
	    "x509v3-sign-dss,dss-asn1")
	/* - some secsh implementations incompatible with
	 * draft-ietf-secsh-transport-NN.txt where NN <= 12
	 */
	ADD_X509_ALG(sshkey_dss_impl.enabled(),
	    "x509v3-sign-dss,dss-raw")
#endif /*WITH_DSA*/

#ifdef USE_PKEY_ED25519
	/* NOTE: USE_PKEY_ED25519 implies HAVE_EVP_DIGESTSIGN */
	ADD_X509_ALG(ssh_key_allowed("ED25519", EVP_PKEY_ED25519),
	    "x509v3-ssh-ed25519,none,ssh-ed25519")
#endif
}


void
fill_default_xkalg(void) {
	SSHX509KeyAlgs *p = x509keyalgs;

#ifdef TRACE_XKALG
logit("TRACE_XKALG fill_default_xkalg:");
#endif
	initialize_xkalg();
	if (p[0].name == NULL) add_default_xkalg();
}


static int
ssh_x509key_alg_digest(SSHX509KeyAlgs* p, const char *dgstname) {
	int k;
	struct {
		char *name;
		int id;
	} digest_map[] = {
		{ "rsa-sha1", SSH_MD_RSA_SHA1 },
		{ "rsa-md5", SSH_MD_RSA_MD5 },
#ifdef HAVE_EVP_SHA256
		{ "rsa2048-sha256", SSH_MD_RSA_SHA256 },
#endif
#ifdef OPENSSL_HAS_NISTP256
		{ "ssh-sha256", SSH_MD_EC_SHA256_SSH },
		{ "sha256", SSH_MD_EC_SHA256 },
#endif
#ifdef OPENSSL_HAS_NISTP384
		{ "ssh-sha384", SSH_MD_EC_SHA384_SSH },
		{ "sha384", SSH_MD_EC_SHA384 },
#endif
#ifdef OPENSSL_HAS_NISTP521
		{ "ssh-sha512", SSH_MD_EC_SHA512_SSH },
		{ "sha512", SSH_MD_EC_SHA512 },
#endif
		{ "dss-asn1", SSH_MD_DSA_SHA1 },
		{ "dss-raw" , SSH_MD_DSA_RAW },
#ifdef HAVE_EVP_DIGESTSIGN
		{ "none", SSH_MD_NONE },
#endif
		{ NULL, 0 }
	};
	int id = -1;

	if (dgstname == NULL) {
		fatal_f("dgstname is NULL");
		return -1; /*unreachable code*/
	}

	for (k = 0; digest_map[k].name != NULL; k++) {
		if (strcasecmp(digest_map[k].name, dgstname) == 0) {
			id = digest_map[k].id;
			break;
		}
	}

	if (id < 0) return -1;

	p->dgst = ssh_evp_md_find(id);

#ifdef HAVE_FIPS_MODULE
	/* FIXME: Default configuration exclude X.509 ED25519 algorithm
	 * when is not alloved by system. This prevents crash below.
	 * Update ed25519 "enabled" method if key type should be excluded
	 * in FIPS mode.
	 */
	if (ssh_is_fips_enabled()) {
	/* The FIPS flag has no effect for providers */
	#ifdef ENABLE_KEY_PROVIDERS
		/* Digests for DSA are excluded indirectly - by key type */
		if (id == SSH_MD_RSA_MD5) {
			/* Not allowed under any FIPS validation */
			p->dgst = NULL;
		}
	#else
		/* NOTE some vendor OpenSSL 1.1.1 releases crash in EVP_MD_flags()
		 * when FIPS mode is activated and digest is none.
		 */
		if ((EVP_MD_flags(p->dgst->md()) & EVP_MD_FLAG_FIPS) == 0)
			p->dgst = NULL;
	#endif
		if (p->dgst == NULL)
			error_f("%s in not enabled in FIPS mode ", dgstname);
	}
#endif

	return 0;
}


static const struct sshkey_impl*
find_sshkey_impl(const char *base_alg,
    const struct sshkey_impl *const*base_impls
) {
	const struct sshkey_impl *const* p;
	/* do not call sshkey_impl_from...() */
	for (p = base_impls; *p != NULL; p++) {
		if (strcmp(base_alg, (*p)->name) == 0)
			break;
	}
	return *p;
}


int
ssh_add_x509key_alg(const char *data) {
	char *name, *mdname, *signame;
	SSHX509KeyAlgs *p;
	const struct sshkey_impl *impl;

	if (data == NULL) {
		error_f("data is NULL");
		return(-1);
	}

	name = xstrdup(data); /*fatal on error*/

	mdname = strchr(name, ',');
	if (mdname == NULL) {
		error_f("cannot parse digest");
		goto err;
	}
	*mdname++ = '\0';

	signame = strchr(mdname, ',');
	if (signame != NULL) *signame++ = '\0';

	initialize_xkalg();
	p = x509keyalgs;
	{
		int k = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);

		for (; k > 0; k--, p++) {
			if (p->name == NULL) break;
		}
		if (k <= 0) {
			error_f("insufficient slots");
			goto err;
		}
	}

#ifdef OPENSSL_HAS_ECC
	if (strncmp(name, "x509v3-ecdsa-sha2-", 18) == 0) {
		const struct sshkey_impl * const ecdsa_impls[] = {
			&sshkey_ecdsa_nistp256_impl,
			&sshkey_ecdsa_nistp384_impl,
			&sshkey_ecdsa_nistp521_impl,
			NULL
		};
		impl = find_sshkey_impl(name + 7, ecdsa_impls);
		if (impl == NULL)
			fatal_f("unsupported curve %s", name + 18);
		p->chain = 1;
	} else
#endif
#ifdef HAVE_EVP_SHA256
	if (strcmp(name, "x509v3-rsa2048-sha256") == 0) {
		impl = &sshkey_rsa_impl;
		p->chain = 1;
	} else
#endif
	if (strncmp(name, "x509v3-ssh-rsa", 14) == 0) {
		impl = &sshkey_rsa_impl;
		p->chain = 1;
	} else
	if (strncmp(name, "x509v3-sign-rsa", 15) == 0) {
		impl = &sshkey_rsa_impl;
		p->chain = 0;
	} else
#ifdef WITH_DSA
	if (strncmp(name, "x509v3-ssh-dss", 14) == 0) {
		impl = &sshkey_dss_impl;
		p->chain = 1;
	} else
	if (strncmp(name, "x509v3-sign-dss", 15) == 0) {
		impl = &sshkey_dss_impl;
		p->chain = 0;
	} else
#endif
#ifdef USE_PKEY_ED25519
	if (strncmp(name, "x509v3-ssh-ed25519", 18) == 0) {
		impl = &sshkey_ed25519_impl;
		p->chain = 1;
	} else
#endif
	if (strncmp(name, "x509v3-mldsa-", 13) == 0) {
		const struct sshkey_impl * const base_impls[] = {
			&sshkey_mldsa44_impl,
			&sshkey_mldsa65_impl,
			&sshkey_mldsa87_impl,
			NULL
		};
		impl = find_sshkey_impl(name + 7, base_impls);
		if (impl == NULL)
			fatal_f("unsupported parameter set %s", name + 13);
		p->chain = 1;
	} else
	{
		error_f("unsupported public key algorithm '%s'", name);
		goto err;
	}

	if (!impl->enabled()) {
		error_f("unsupported key %s", impl->id.type_name[0]);
		goto err;
	}
	p->base_id = &impl->id;

	if (ssh_x509key_alg_digest(p, mdname) < 0) {
		error_f("unsupported digest %.50s", mdname);
		goto err;
	}
	if (p->dgst == NULL) {
		/* digest is not allowed */
		goto err;
	}

	p->name = name;
	p->signame = signame;

	return 1;

err:
	free((void*)name);
	return -1;
}


int/*bool*/
ssh_is_x509signame(const char *signame) {
	SSHX509KeyAlgs *xkalg;
	int k;

	if (signame == NULL) {
		fatal_f("signame is NULL");
		return(0); /*unreachable code*/
	}

	initialize_xkalg();
	xkalg = x509keyalgs;
	k = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);

	for (; k > 0; k--, xkalg++) {
		if (xkalg->name == NULL) return(0);
		if (strcmp(signame, X509PUBALG_SIGNAME(xkalg)) == 0) return(1);
	}
	return(0);
}


int
ssh_xkalg_nameind(const char *name, const SSHX509KeyAlgs **q, int loc) {
	int k, n;
	const SSHX509KeyAlgs *p;

	if (name == NULL) return (-1);

	initialize_xkalg();
	k = (loc < 0) ? 0 : (loc + 1);
	n = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);
	if (k < n) p = &x509keyalgs[k];

	for (; k < n; k++, p++) {
		if (p->name == NULL) return(-1);
		if (strcmp(p->name, name) == 0) {
			if (q) *q = p;
			return(k);
		}
	}
	return(-1);
}


static int
ssh_xkalg_typeind(int type, int subtype, const SSHX509KeyAlgs **q, int loc) {
	int k, n;
	const SSHX509KeyAlgs *p;

	initialize_xkalg();
	k = (loc < 0) ? 0 : (loc + 1);
	n = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);
	if (k < n) p = &x509keyalgs[k];

	type = sshkey_type_plain(type);
	for (; k < n; k++, p++) {
		if (p->name == NULL) break;
	{	const sshkey_id *base_id = p->base_id;
		if (type != base_id->type) continue;
		/* NOTE if subtype is negative list all that match type */
		if ((subtype > 0) && (subtype != base_id->subtype))
			continue;
	}
		if (q) *q = p;
		return k;
	}
	return -1;
}


int
ssh_xkalg_keyind(const struct sshkey *key, const SSHX509KeyAlgs **q, int loc) {
	return ssh_xkalg_typeind(key->type, key->ecdsa_nid, q, loc);
}


int
ssh_xkalg_keyfrmind(const struct sshkey *key, int frm, const SSHX509KeyAlgs **q, int loc) {

	while ((loc = ssh_xkalg_typeind(key->type, key->ecdsa_nid, q, loc)) >= 0) {
		int found = 0;

		switch (frm) {
		case X509FORMAT_FIRSTMATCH: found = 1;
			break;
		case X509FORMAT_LEGACY    : found = !(*q)->chain;
			break;
		case X509FORMAT_RFC6187   : found = (*q)->chain;
			break;
		}
		if (found) break;
	}
	return(loc);
}


int
ssh_xkalg_ind(const SSHX509KeyAlgs **q, int loc) {
	int k, n;

	initialize_xkalg();
	k = (loc < 0) ? 0 : (loc + 1);
	n = sizeof(x509keyalgs) / sizeof(x509keyalgs[0]);

	if (k < n) {
		const SSHX509KeyAlgs *p;

		p = &x509keyalgs[k];
		if (p->name != NULL) {
			if (q) *q = p;
			return(k);
		}
	}
	return(-1);
}


static void
ssh_xkalg_list(int type, struct sshbuf *b, const char *sep) {
	const SSHX509KeyAlgs *xkalg;
	int loc;
	int seplen;

	if (b == NULL) {
		error_f("buffer is NULL");
		return;
	}

/*
IMPORTANT NOTE:
  For every unique "key name" we MUST define unique "key type"
otherwise cannot distinguish them !
As example structure Kex contain integer attribute "kex_type"
and kex use method "load_host_key" to find hostkey. When client
request hostkey algorithms (comma separated list with names)
server should be able to find first hostkey that match one of them.
Note to "load_host_key" is assigned method "get_hostkey_by_type"
defined in "sshd.c".
*/

	if (sep == NULL) sep = ",";
	seplen = strlen(sep);

	for (
	    loc = ssh_xkalg_typeind(type, -1, &xkalg, -1);
	    loc >= 0;
	    loc = ssh_xkalg_typeind(type, -1, &xkalg, loc)
	) {
		const char *p;
		int dupl, k, r;

		/* exclude duplicate names */
		p = xkalg->name;
		dupl = 0;
		for (
		    k = ssh_xkalg_typeind(type, -1, &xkalg, -1);
		    (k >= 0) && (k < loc);
		    k = ssh_xkalg_typeind(type, -1, &xkalg, k)
		) {
			if (strcmp(p, xkalg->name) == 0) {
				dupl = 1;
				break;
			}
		}
		if (dupl) continue;

		if (sshbuf_len(b) > 0) {
			if ((r = sshbuf_put(b, sep, seplen)) != 0)
				fatal_fr(r, "buffer error");
		}
		if ((r = sshbuf_put(b, p, strlen(p))) != 0)
			fatal_fr(r, "buffer error");
	}
}


void
ssh_xkalg_listall(struct sshbuf *b, const char *sep) {
	ssh_xkalg_list(KEY_ECDSA, b, sep);
	ssh_xkalg_list(KEY_RSA, b, sep);
#ifdef WITH_DSA
	ssh_xkalg_list(KEY_DSA, b, sep);
#endif
#ifdef USE_PKEY_ED25519
	ssh_xkalg_list(KEY_ED25519, b, sep);
#endif
	ssh_xkalg_list(KEY_MLDSA, b, sep);
}


char*
default_hostkey_algorithms(void) {
	struct sshbuf *b;
	char *p;
	int r;

	b = sshbuf_new();
	if (b == NULL)
		fatal_f("sshbuf_new failed");

	ssh_xkalg_listall(b, ",");


{	char *allalgs = sshkey_alg_list(0, 0, 1, ',');
	/* NOTE
	 * - Define KEX_DEFAULT_PK_ALG list only key-based
	 *   algorithms (in order of precedence).
	 * - Since PKIX-SSH 12.4 KEX_DEFAULT_PK_ALG list even
	 *   unsupported algorithms!
	 * - Since PKIX-SSH 8.5 ssh-dss is not listed by default.
	 */
	/* filter unsupported by build */
	p = match_filter_allowlist(KEX_DEFAULT_PK_ALG",ssh-dss", allalgs);
	free(allalgs);
}
	if (p == NULL)
		fatal_f("oops");

	if ((r = sshbuf_put(b, ",", 1)) != 0 ||
	    (r = sshbuf_put(b, p, strlen(p))) != 0)
		fatal_fr(r, "buffer error");

	free(p);
	p = xstrdup(sshbuf_ptr(b));

	sshbuf_free(b);

	return p;
}


char*
ssh_get_allnames(char sep, int sigflag, const char* pattern) {
	const char **list = NULL;;
	int loc;
	size_t k, n = 0, len = 0;

	if (pattern == NULL) pattern= "*";

{	const SSHX509KeyAlgs *xkalg;
	for (
	    loc = ssh_xkalg_ind(&xkalg, -1);
	    loc >= 0;
	    loc = ssh_xkalg_ind(&xkalg, loc)
	) {
		const char *p;

		if (match_pattern_list(xkalg->name, pattern, 0) != 1)
			continue;

		p = sigflag ? X509PUBALG_SIGNAME(xkalg) : xkalg->name;
		for (k = 0; k < n; k++) {
			if (strcmp(p, list[k]) == 0) {
				p = NULL;
				break;
			}
		}
		if (p == NULL) continue;

		k = n++;
		list = realloc(list, n * sizeof(char*));
		if (list == NULL)
			fatal_f("realloc fail for xkalg");
		list[k] = p;
		len += 1 + strlen(p);
	}
}
{	const char *name;
	/*
	 * According RFC8308 protocol extension "server-sig-algs",
	 * despite of it name, list public key algorithms that the
	 * server is able to process.
	 * OpenBSD version is still broken and does not list algorithms
	 * that use custom certificates. Broken behaviour will be keep
	 * until is fixed in OpenBSD version. Note that brokenness is
	 * keep as well in check for allowed keys - see sshconnect2.c
	 * function try_identity().
	 */
	for (
	    loc = sshkey_algind(&name, SSHKEY_ALG_PLAINKEY, -1);
	    loc >= 0;
	    loc = sshkey_algind(&name, SSHKEY_ALG_PLAINKEY, loc)
	) {
		const char *p;

		if (match_pattern_list(name, pattern, 0) != 1)
			continue;

		p = name;
		for (k = 0; k < n; k++) {
			if (strcmp(p, list[k]) == 0) {
				p = NULL;
				break;
			}
		}
		if (p == NULL) continue;

		k = n++;
		list = realloc(list, n * sizeof(char*));
		if (list == NULL)
			fatal_f("realloc fail for alg");
		list[k] = p;
		len += 1 + strlen(p);
	}
}
{	char *s = NULL, *p;

	if (n > 0) {
		s = p = malloc(len);
		if (s == NULL)
			fatal_f("out of memory");
	}
	for (k = 0; k < n; k++) {
		size_t l = strlen(list[k]);
		memmove(p, list[k], l);
		p += l;
		*p++ = sep;
	}
	if (n > 0)
		*(--p) = '\0';

	free(list);
	return s;
}
}
