xref: /openbsd-src/usr.bin/ssh/kex-names.c (revision 4e5f03cb47c24aef03761f29b01d695b98a8148e)
1*4e5f03cbSdjm /* $OpenBSD: kex-names.c,v 1.4 2024/09/09 02:39:57 djm Exp $ */
23235f7a9Sdjm /*
33235f7a9Sdjm  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
43235f7a9Sdjm  *
53235f7a9Sdjm  * Redistribution and use in source and binary forms, with or without
63235f7a9Sdjm  * modification, are permitted provided that the following conditions
73235f7a9Sdjm  * are met:
83235f7a9Sdjm  * 1. Redistributions of source code must retain the above copyright
93235f7a9Sdjm  *    notice, this list of conditions and the following disclaimer.
103235f7a9Sdjm  * 2. Redistributions in binary form must reproduce the above copyright
113235f7a9Sdjm  *    notice, this list of conditions and the following disclaimer in the
123235f7a9Sdjm  *    documentation and/or other materials provided with the distribution.
133235f7a9Sdjm  *
143235f7a9Sdjm  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
153235f7a9Sdjm  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
163235f7a9Sdjm  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
173235f7a9Sdjm  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
183235f7a9Sdjm  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
193235f7a9Sdjm  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
203235f7a9Sdjm  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
213235f7a9Sdjm  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
223235f7a9Sdjm  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
233235f7a9Sdjm  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
243235f7a9Sdjm  */
253235f7a9Sdjm 
263235f7a9Sdjm 
273235f7a9Sdjm #include <stdio.h>
283235f7a9Sdjm #include <stdlib.h>
293235f7a9Sdjm #include <string.h>
303235f7a9Sdjm #include <unistd.h>
313235f7a9Sdjm #include <signal.h>
323235f7a9Sdjm 
333235f7a9Sdjm #ifdef WITH_OPENSSL
343235f7a9Sdjm #include <openssl/crypto.h>
353235f7a9Sdjm #include <openssl/evp.h>
363235f7a9Sdjm #endif
373235f7a9Sdjm 
383235f7a9Sdjm #include "kex.h"
393235f7a9Sdjm #include "log.h"
403235f7a9Sdjm #include "match.h"
413235f7a9Sdjm #include "digest.h"
423235f7a9Sdjm #include "misc.h"
433235f7a9Sdjm 
443235f7a9Sdjm #include "ssherr.h"
453235f7a9Sdjm #include "xmalloc.h"
463235f7a9Sdjm 
473235f7a9Sdjm struct kexalg {
483235f7a9Sdjm 	char *name;
493235f7a9Sdjm 	u_int type;
503235f7a9Sdjm 	int ec_nid;
513235f7a9Sdjm 	int hash_alg;
523235f7a9Sdjm };
533235f7a9Sdjm static const struct kexalg kexalgs[] = {
543235f7a9Sdjm #ifdef WITH_OPENSSL
553235f7a9Sdjm 	{ KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 },
563235f7a9Sdjm 	{ KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 },
573235f7a9Sdjm 	{ KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 },
583235f7a9Sdjm 	{ KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 },
593235f7a9Sdjm 	{ KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 },
603235f7a9Sdjm 	{ KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 },
613235f7a9Sdjm 	{ KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 },
623235f7a9Sdjm 	{ KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2,
633235f7a9Sdjm 	    NID_X9_62_prime256v1, SSH_DIGEST_SHA256 },
643235f7a9Sdjm 	{ KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1,
653235f7a9Sdjm 	    SSH_DIGEST_SHA384 },
663235f7a9Sdjm 	{ KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
673235f7a9Sdjm 	    SSH_DIGEST_SHA512 },
683235f7a9Sdjm #endif
693235f7a9Sdjm 	{ KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
703235f7a9Sdjm 	{ KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
713235f7a9Sdjm 	{ KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0,
723235f7a9Sdjm 	    SSH_DIGEST_SHA512 },
73258a53eeSdjm 	{ KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0,
74258a53eeSdjm 	    SSH_DIGEST_SHA512 },
759dc26a4eSdjm 	{ KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
769dc26a4eSdjm 	    SSH_DIGEST_SHA256 },
773235f7a9Sdjm 	{ NULL, 0, -1, -1},
783235f7a9Sdjm };
793235f7a9Sdjm 
803235f7a9Sdjm char *
813235f7a9Sdjm kex_alg_list(char sep)
823235f7a9Sdjm {
833235f7a9Sdjm 	char *ret = NULL, *tmp;
843235f7a9Sdjm 	size_t nlen, rlen = 0;
853235f7a9Sdjm 	const struct kexalg *k;
863235f7a9Sdjm 
873235f7a9Sdjm 	for (k = kexalgs; k->name != NULL; k++) {
883235f7a9Sdjm 		if (ret != NULL)
893235f7a9Sdjm 			ret[rlen++] = sep;
903235f7a9Sdjm 		nlen = strlen(k->name);
913235f7a9Sdjm 		if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) {
923235f7a9Sdjm 			free(ret);
933235f7a9Sdjm 			return NULL;
943235f7a9Sdjm 		}
953235f7a9Sdjm 		ret = tmp;
963235f7a9Sdjm 		memcpy(ret + rlen, k->name, nlen + 1);
973235f7a9Sdjm 		rlen += nlen;
983235f7a9Sdjm 	}
993235f7a9Sdjm 	return ret;
1003235f7a9Sdjm }
1013235f7a9Sdjm 
1023235f7a9Sdjm static const struct kexalg *
1033235f7a9Sdjm kex_alg_by_name(const char *name)
1043235f7a9Sdjm {
1053235f7a9Sdjm 	const struct kexalg *k;
1063235f7a9Sdjm 
1073235f7a9Sdjm 	for (k = kexalgs; k->name != NULL; k++) {
1083235f7a9Sdjm 		if (strcmp(k->name, name) == 0)
1093235f7a9Sdjm 			return k;
1103235f7a9Sdjm 	}
1113235f7a9Sdjm 	return NULL;
1123235f7a9Sdjm }
1133235f7a9Sdjm 
1143235f7a9Sdjm int
1153235f7a9Sdjm kex_name_valid(const char *name)
1163235f7a9Sdjm {
1173235f7a9Sdjm 	return kex_alg_by_name(name) != NULL;
1183235f7a9Sdjm }
1193235f7a9Sdjm 
1203235f7a9Sdjm u_int
1213235f7a9Sdjm kex_type_from_name(const char *name)
1223235f7a9Sdjm {
1233235f7a9Sdjm 	const struct kexalg *k;
1243235f7a9Sdjm 
1253235f7a9Sdjm 	if ((k = kex_alg_by_name(name)) == NULL)
1263235f7a9Sdjm 		return 0;
1273235f7a9Sdjm 	return k->type;
1283235f7a9Sdjm }
1293235f7a9Sdjm 
1303235f7a9Sdjm int
1313235f7a9Sdjm kex_hash_from_name(const char *name)
1323235f7a9Sdjm {
1333235f7a9Sdjm 	const struct kexalg *k;
1343235f7a9Sdjm 
1353235f7a9Sdjm 	if ((k = kex_alg_by_name(name)) == NULL)
1363235f7a9Sdjm 		return -1;
1373235f7a9Sdjm 	return k->hash_alg;
1383235f7a9Sdjm }
1393235f7a9Sdjm 
1403235f7a9Sdjm int
1413235f7a9Sdjm kex_nid_from_name(const char *name)
1423235f7a9Sdjm {
1433235f7a9Sdjm 	const struct kexalg *k;
1443235f7a9Sdjm 
1453235f7a9Sdjm 	if ((k = kex_alg_by_name(name)) == NULL)
1463235f7a9Sdjm 		return -1;
1473235f7a9Sdjm 	return k->ec_nid;
1483235f7a9Sdjm }
1493235f7a9Sdjm 
1503235f7a9Sdjm /* Validate KEX method name list */
1513235f7a9Sdjm int
1523235f7a9Sdjm kex_names_valid(const char *names)
1533235f7a9Sdjm {
1543235f7a9Sdjm 	char *s, *cp, *p;
1553235f7a9Sdjm 
1563235f7a9Sdjm 	if (names == NULL || strcmp(names, "") == 0)
1573235f7a9Sdjm 		return 0;
1583235f7a9Sdjm 	if ((s = cp = strdup(names)) == NULL)
1593235f7a9Sdjm 		return 0;
1603235f7a9Sdjm 	for ((p = strsep(&cp, ",")); p && *p != '\0';
1613235f7a9Sdjm 	    (p = strsep(&cp, ","))) {
1623235f7a9Sdjm 		if (kex_alg_by_name(p) == NULL) {
1633235f7a9Sdjm 			error("Unsupported KEX algorithm \"%.100s\"", p);
1643235f7a9Sdjm 			free(s);
1653235f7a9Sdjm 			return 0;
1663235f7a9Sdjm 		}
1673235f7a9Sdjm 	}
1683235f7a9Sdjm 	debug3("kex names ok: [%s]", names);
1693235f7a9Sdjm 	free(s);
1703235f7a9Sdjm 	return 1;
1713235f7a9Sdjm }
1723235f7a9Sdjm 
1733235f7a9Sdjm /* returns non-zero if proposal contains any algorithm from algs */
1743235f7a9Sdjm int
1753235f7a9Sdjm kex_has_any_alg(const char *proposal, const char *algs)
1763235f7a9Sdjm {
1773235f7a9Sdjm 	char *cp;
1783235f7a9Sdjm 
1793235f7a9Sdjm 	if ((cp = match_list(proposal, algs, NULL)) == NULL)
1803235f7a9Sdjm 		return 0;
1813235f7a9Sdjm 	free(cp);
1823235f7a9Sdjm 	return 1;
1833235f7a9Sdjm }
1843235f7a9Sdjm 
1853235f7a9Sdjm /*
1863235f7a9Sdjm  * Concatenate algorithm names, avoiding duplicates in the process.
1873235f7a9Sdjm  * Caller must free returned string.
1883235f7a9Sdjm  */
1893235f7a9Sdjm char *
1903235f7a9Sdjm kex_names_cat(const char *a, const char *b)
1913235f7a9Sdjm {
1923235f7a9Sdjm 	char *ret = NULL, *tmp = NULL, *cp, *p;
1933235f7a9Sdjm 	size_t len;
1943235f7a9Sdjm 
1953235f7a9Sdjm 	if (a == NULL || *a == '\0')
1963235f7a9Sdjm 		return strdup(b);
1973235f7a9Sdjm 	if (b == NULL || *b == '\0')
1983235f7a9Sdjm 		return strdup(a);
1993235f7a9Sdjm 	if (strlen(b) > 1024*1024)
2003235f7a9Sdjm 		return NULL;
2013235f7a9Sdjm 	len = strlen(a) + strlen(b) + 2;
2023235f7a9Sdjm 	if ((tmp = cp = strdup(b)) == NULL ||
2033235f7a9Sdjm 	    (ret = calloc(1, len)) == NULL) {
2043235f7a9Sdjm 		free(tmp);
2053235f7a9Sdjm 		return NULL;
2063235f7a9Sdjm 	}
2073235f7a9Sdjm 	strlcpy(ret, a, len);
2083235f7a9Sdjm 	for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
2093235f7a9Sdjm 		if (kex_has_any_alg(ret, p))
2103235f7a9Sdjm 			continue; /* Algorithm already present */
2113235f7a9Sdjm 		if (strlcat(ret, ",", len) >= len ||
2123235f7a9Sdjm 		    strlcat(ret, p, len) >= len) {
2133235f7a9Sdjm 			free(tmp);
2143235f7a9Sdjm 			free(ret);
2153235f7a9Sdjm 			return NULL; /* Shouldn't happen */
2163235f7a9Sdjm 		}
2173235f7a9Sdjm 	}
2183235f7a9Sdjm 	free(tmp);
2193235f7a9Sdjm 	return ret;
2203235f7a9Sdjm }
2213235f7a9Sdjm 
2223235f7a9Sdjm /*
2233235f7a9Sdjm  * Assemble a list of algorithms from a default list and a string from a
2243235f7a9Sdjm  * configuration file. The user-provided string may begin with '+' to
2253235f7a9Sdjm  * indicate that it should be appended to the default, '-' that the
2263235f7a9Sdjm  * specified names should be removed, or '^' that they should be placed
2273235f7a9Sdjm  * at the head.
2283235f7a9Sdjm  */
2293235f7a9Sdjm int
2303235f7a9Sdjm kex_assemble_names(char **listp, const char *def, const char *all)
2313235f7a9Sdjm {
2323235f7a9Sdjm 	char *cp, *tmp, *patterns;
2333235f7a9Sdjm 	char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL;
2343235f7a9Sdjm 	int r = SSH_ERR_INTERNAL_ERROR;
2353235f7a9Sdjm 
2363235f7a9Sdjm 	if (listp == NULL || def == NULL || all == NULL)
2373235f7a9Sdjm 		return SSH_ERR_INVALID_ARGUMENT;
2383235f7a9Sdjm 
2393235f7a9Sdjm 	if (*listp == NULL || **listp == '\0') {
2403235f7a9Sdjm 		if ((*listp = strdup(def)) == NULL)
2413235f7a9Sdjm 			return SSH_ERR_ALLOC_FAIL;
2423235f7a9Sdjm 		return 0;
2433235f7a9Sdjm 	}
2443235f7a9Sdjm 
2453235f7a9Sdjm 	list = *listp;
2463235f7a9Sdjm 	*listp = NULL;
2473235f7a9Sdjm 	if (*list == '+') {
2483235f7a9Sdjm 		/* Append names to default list */
2493235f7a9Sdjm 		if ((tmp = kex_names_cat(def, list + 1)) == NULL) {
2503235f7a9Sdjm 			r = SSH_ERR_ALLOC_FAIL;
2513235f7a9Sdjm 			goto fail;
2523235f7a9Sdjm 		}
2533235f7a9Sdjm 		free(list);
2543235f7a9Sdjm 		list = tmp;
2553235f7a9Sdjm 	} else if (*list == '-') {
2563235f7a9Sdjm 		/* Remove names from default list */
2573235f7a9Sdjm 		if ((*listp = match_filter_denylist(def, list + 1)) == NULL) {
2583235f7a9Sdjm 			r = SSH_ERR_ALLOC_FAIL;
2593235f7a9Sdjm 			goto fail;
2603235f7a9Sdjm 		}
2613235f7a9Sdjm 		free(list);
2623235f7a9Sdjm 		/* filtering has already been done */
2633235f7a9Sdjm 		return 0;
2643235f7a9Sdjm 	} else if (*list == '^') {
2653235f7a9Sdjm 		/* Place names at head of default list */
2663235f7a9Sdjm 		if ((tmp = kex_names_cat(list + 1, def)) == NULL) {
2673235f7a9Sdjm 			r = SSH_ERR_ALLOC_FAIL;
2683235f7a9Sdjm 			goto fail;
2693235f7a9Sdjm 		}
2703235f7a9Sdjm 		free(list);
2713235f7a9Sdjm 		list = tmp;
2723235f7a9Sdjm 	} else {
2733235f7a9Sdjm 		/* Explicit list, overrides default - just use "list" as is */
2743235f7a9Sdjm 	}
2753235f7a9Sdjm 
2763235f7a9Sdjm 	/*
2773235f7a9Sdjm 	 * The supplied names may be a pattern-list. For the -list case,
2783235f7a9Sdjm 	 * the patterns are applied above. For the +list and explicit list
2793235f7a9Sdjm 	 * cases we need to do it now.
2803235f7a9Sdjm 	 */
2813235f7a9Sdjm 	ret = NULL;
2823235f7a9Sdjm 	if ((patterns = opatterns = strdup(list)) == NULL) {
2833235f7a9Sdjm 		r = SSH_ERR_ALLOC_FAIL;
2843235f7a9Sdjm 		goto fail;
2853235f7a9Sdjm 	}
2863235f7a9Sdjm 	/* Apply positive (i.e. non-negated) patterns from the list */
2873235f7a9Sdjm 	while ((cp = strsep(&patterns, ",")) != NULL) {
2883235f7a9Sdjm 		if (*cp == '!') {
2893235f7a9Sdjm 			/* negated matches are not supported here */
2903235f7a9Sdjm 			r = SSH_ERR_INVALID_ARGUMENT;
2913235f7a9Sdjm 			goto fail;
2923235f7a9Sdjm 		}
2933235f7a9Sdjm 		free(matching);
2943235f7a9Sdjm 		if ((matching = match_filter_allowlist(all, cp)) == NULL) {
2953235f7a9Sdjm 			r = SSH_ERR_ALLOC_FAIL;
2963235f7a9Sdjm 			goto fail;
2973235f7a9Sdjm 		}
2983235f7a9Sdjm 		if ((tmp = kex_names_cat(ret, matching)) == NULL) {
2993235f7a9Sdjm 			r = SSH_ERR_ALLOC_FAIL;
3003235f7a9Sdjm 			goto fail;
3013235f7a9Sdjm 		}
3023235f7a9Sdjm 		free(ret);
3033235f7a9Sdjm 		ret = tmp;
3043235f7a9Sdjm 	}
3053235f7a9Sdjm 	if (ret == NULL || *ret == '\0') {
3063235f7a9Sdjm 		/* An empty name-list is an error */
3073235f7a9Sdjm 		/* XXX better error code? */
3083235f7a9Sdjm 		r = SSH_ERR_INVALID_ARGUMENT;
3093235f7a9Sdjm 		goto fail;
3103235f7a9Sdjm 	}
3113235f7a9Sdjm 
3123235f7a9Sdjm 	/* success */
3133235f7a9Sdjm 	*listp = ret;
3143235f7a9Sdjm 	ret = NULL;
3153235f7a9Sdjm 	r = 0;
3163235f7a9Sdjm 
3173235f7a9Sdjm  fail:
3183235f7a9Sdjm 	free(matching);
3193235f7a9Sdjm 	free(opatterns);
3203235f7a9Sdjm 	free(list);
3213235f7a9Sdjm 	free(ret);
3223235f7a9Sdjm 	return r;
3233235f7a9Sdjm }
324