Home | History | Annotate | Line # | Download | only in dist
      1  1.2  christos /*	$NetBSD: kex-names.c,v 1.5 2026/04/08 18:58:40 christos Exp $	*/
      2  1.5  christos /* $OpenBSD: kex-names.c,v 1.7 2026/02/14 00:18:34 jsg Exp $ */
      3  1.3  christos 
      4  1.1  christos /*
      5  1.1  christos  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
      6  1.1  christos  *
      7  1.1  christos  * Redistribution and use in source and binary forms, with or without
      8  1.1  christos  * modification, are permitted provided that the following conditions
      9  1.1  christos  * are met:
     10  1.1  christos  * 1. Redistributions of source code must retain the above copyright
     11  1.1  christos  *    notice, this list of conditions and the following disclaimer.
     12  1.1  christos  * 2. Redistributions in binary form must reproduce the above copyright
     13  1.1  christos  *    notice, this list of conditions and the following disclaimer in the
     14  1.1  christos  *    documentation and/or other materials provided with the distribution.
     15  1.1  christos  *
     16  1.1  christos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  1.1  christos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  1.1  christos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  1.1  christos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  1.1  christos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     21  1.1  christos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     22  1.1  christos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     23  1.1  christos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  1.1  christos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     25  1.1  christos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  1.1  christos  */
     27  1.1  christos 
     28  1.2  christos #include "includes.h"
     29  1.2  christos __RCSID("$NetBSD: kex-names.c,v 1.5 2026/04/08 18:58:40 christos Exp $");
     30  1.1  christos 
     31  1.1  christos #include <stdio.h>
     32  1.1  christos #include <stdlib.h>
     33  1.1  christos #include <string.h>
     34  1.1  christos #include <signal.h>
     35  1.1  christos 
     36  1.1  christos #ifdef WITH_OPENSSL
     37  1.1  christos #include <openssl/crypto.h>
     38  1.1  christos #include <openssl/evp.h>
     39  1.1  christos #endif
     40  1.1  christos 
     41  1.1  christos #include "kex.h"
     42  1.1  christos #include "log.h"
     43  1.1  christos #include "match.h"
     44  1.1  christos #include "digest.h"
     45  1.1  christos #include "misc.h"
     46  1.1  christos 
     47  1.1  christos #include "ssherr.h"
     48  1.1  christos 
     49  1.1  christos struct kexalg {
     50  1.2  christos 	const char *name;
     51  1.1  christos 	u_int type;
     52  1.1  christos 	int ec_nid;
     53  1.1  christos 	int hash_alg;
     54  1.4  christos 	int pq_alg;
     55  1.1  christos };
     56  1.1  christos static const struct kexalg kexalgs[] = {
     57  1.1  christos #ifdef WITH_OPENSSL
     58  1.4  christos 	{ KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
     59  1.4  christos 	{ KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
     60  1.4  christos 	{ KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
     61  1.4  christos 	{ KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
     62  1.4  christos 	{ KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
     63  1.4  christos 	{ KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
     64  1.4  christos 	{ KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
     65  1.1  christos 	{ KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2,
     66  1.4  christos 	    NID_X9_62_prime256v1, SSH_DIGEST_SHA256, KEX_NOT_PQ },
     67  1.1  christos 	{ KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1,
     68  1.4  christos 	    SSH_DIGEST_SHA384, KEX_NOT_PQ },
     69  1.1  christos 	{ KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
     70  1.4  christos 	    SSH_DIGEST_SHA512, KEX_NOT_PQ },
     71  1.1  christos #endif
     72  1.4  christos 	{ KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0,
     73  1.4  christos 	    SSH_DIGEST_SHA256, KEX_NOT_PQ },
     74  1.4  christos 	{ KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0,
     75  1.4  christos 	    SSH_DIGEST_SHA256, KEX_NOT_PQ },
     76  1.1  christos 	{ KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0,
     77  1.4  christos 	    SSH_DIGEST_SHA512, KEX_IS_PQ },
     78  1.3  christos 	{ KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0,
     79  1.4  christos 	    SSH_DIGEST_SHA512, KEX_IS_PQ },
     80  1.3  christos 	{ KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
     81  1.4  christos 	    SSH_DIGEST_SHA256, KEX_IS_PQ },
     82  1.4  christos 	{ NULL, 0, -1, -1, 0},
     83  1.1  christos };
     84  1.1  christos 
     85  1.1  christos char *
     86  1.1  christos kex_alg_list(char sep)
     87  1.1  christos {
     88  1.4  christos 	char *ret = NULL;
     89  1.1  christos 	const struct kexalg *k;
     90  1.4  christos 	char sep_str[2] = {sep, '\0'};
     91  1.4  christos 
     92  1.4  christos 	for (k = kexalgs; k->name != NULL; k++)
     93  1.4  christos 		xextendf(&ret, sep_str, "%s", k->name);
     94  1.1  christos 
     95  1.1  christos 	return ret;
     96  1.1  christos }
     97  1.1  christos 
     98  1.1  christos static const struct kexalg *
     99  1.1  christos kex_alg_by_name(const char *name)
    100  1.1  christos {
    101  1.1  christos 	const struct kexalg *k;
    102  1.1  christos 
    103  1.1  christos 	for (k = kexalgs; k->name != NULL; k++) {
    104  1.1  christos 		if (strcmp(k->name, name) == 0)
    105  1.1  christos 			return k;
    106  1.1  christos 	}
    107  1.1  christos 	return NULL;
    108  1.1  christos }
    109  1.1  christos 
    110  1.1  christos int
    111  1.1  christos kex_name_valid(const char *name)
    112  1.1  christos {
    113  1.1  christos 	return kex_alg_by_name(name) != NULL;
    114  1.1  christos }
    115  1.1  christos 
    116  1.4  christos int
    117  1.4  christos kex_is_pq_from_name(const char *name)
    118  1.4  christos {
    119  1.4  christos 	const struct kexalg *k;
    120  1.4  christos 
    121  1.4  christos 	if ((k = kex_alg_by_name(name)) == NULL)
    122  1.4  christos 		return 0;
    123  1.4  christos 	return k->pq_alg == KEX_IS_PQ;
    124  1.4  christos }
    125  1.4  christos 
    126  1.1  christos u_int
    127  1.1  christos kex_type_from_name(const char *name)
    128  1.1  christos {
    129  1.1  christos 	const struct kexalg *k;
    130  1.1  christos 
    131  1.1  christos 	if ((k = kex_alg_by_name(name)) == NULL)
    132  1.1  christos 		return 0;
    133  1.1  christos 	return k->type;
    134  1.1  christos }
    135  1.1  christos 
    136  1.1  christos int
    137  1.1  christos kex_hash_from_name(const char *name)
    138  1.1  christos {
    139  1.1  christos 	const struct kexalg *k;
    140  1.1  christos 
    141  1.1  christos 	if ((k = kex_alg_by_name(name)) == NULL)
    142  1.1  christos 		return -1;
    143  1.1  christos 	return k->hash_alg;
    144  1.1  christos }
    145  1.1  christos 
    146  1.1  christos int
    147  1.1  christos kex_nid_from_name(const char *name)
    148  1.1  christos {
    149  1.1  christos 	const struct kexalg *k;
    150  1.1  christos 
    151  1.1  christos 	if ((k = kex_alg_by_name(name)) == NULL)
    152  1.1  christos 		return -1;
    153  1.1  christos 	return k->ec_nid;
    154  1.1  christos }
    155  1.1  christos 
    156  1.1  christos /* Validate KEX method name list */
    157  1.1  christos int
    158  1.1  christos kex_names_valid(const char *names)
    159  1.1  christos {
    160  1.1  christos 	char *s, *cp, *p;
    161  1.1  christos 
    162  1.1  christos 	if (names == NULL || strcmp(names, "") == 0)
    163  1.1  christos 		return 0;
    164  1.1  christos 	if ((s = cp = strdup(names)) == NULL)
    165  1.1  christos 		return 0;
    166  1.1  christos 	for ((p = strsep(&cp, ",")); p && *p != '\0';
    167  1.1  christos 	    (p = strsep(&cp, ","))) {
    168  1.1  christos 		if (kex_alg_by_name(p) == NULL) {
    169  1.1  christos 			error("Unsupported KEX algorithm \"%.100s\"", p);
    170  1.1  christos 			free(s);
    171  1.1  christos 			return 0;
    172  1.1  christos 		}
    173  1.1  christos 	}
    174  1.1  christos 	debug3("kex names ok: [%s]", names);
    175  1.1  christos 	free(s);
    176  1.1  christos 	return 1;
    177  1.1  christos }
    178  1.1  christos 
    179  1.1  christos /* returns non-zero if proposal contains any algorithm from algs */
    180  1.1  christos int
    181  1.1  christos kex_has_any_alg(const char *proposal, const char *algs)
    182  1.1  christos {
    183  1.1  christos 	char *cp;
    184  1.1  christos 
    185  1.1  christos 	if ((cp = match_list(proposal, algs, NULL)) == NULL)
    186  1.1  christos 		return 0;
    187  1.1  christos 	free(cp);
    188  1.1  christos 	return 1;
    189  1.1  christos }
    190  1.1  christos 
    191  1.1  christos /*
    192  1.1  christos  * Concatenate algorithm names, avoiding duplicates in the process.
    193  1.1  christos  * Caller must free returned string.
    194  1.1  christos  */
    195  1.1  christos char *
    196  1.1  christos kex_names_cat(const char *a, const char *b)
    197  1.1  christos {
    198  1.1  christos 	char *ret = NULL, *tmp = NULL, *cp, *p;
    199  1.1  christos 	size_t len;
    200  1.1  christos 
    201  1.1  christos 	if (a == NULL || *a == '\0')
    202  1.1  christos 		return strdup(b);
    203  1.1  christos 	if (b == NULL || *b == '\0')
    204  1.1  christos 		return strdup(a);
    205  1.1  christos 	if (strlen(b) > 1024*1024)
    206  1.1  christos 		return NULL;
    207  1.1  christos 	len = strlen(a) + strlen(b) + 2;
    208  1.1  christos 	if ((tmp = cp = strdup(b)) == NULL ||
    209  1.1  christos 	    (ret = calloc(1, len)) == NULL) {
    210  1.1  christos 		free(tmp);
    211  1.1  christos 		return NULL;
    212  1.1  christos 	}
    213  1.1  christos 	strlcpy(ret, a, len);
    214  1.1  christos 	for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
    215  1.1  christos 		if (kex_has_any_alg(ret, p))
    216  1.1  christos 			continue; /* Algorithm already present */
    217  1.1  christos 		if (strlcat(ret, ",", len) >= len ||
    218  1.1  christos 		    strlcat(ret, p, len) >= len) {
    219  1.1  christos 			free(tmp);
    220  1.1  christos 			free(ret);
    221  1.1  christos 			return NULL; /* Shouldn't happen */
    222  1.1  christos 		}
    223  1.1  christos 	}
    224  1.1  christos 	free(tmp);
    225  1.1  christos 	return ret;
    226  1.1  christos }
    227  1.1  christos 
    228  1.1  christos /*
    229  1.1  christos  * Assemble a list of algorithms from a default list and a string from a
    230  1.1  christos  * configuration file. The user-provided string may begin with '+' to
    231  1.1  christos  * indicate that it should be appended to the default, '-' that the
    232  1.1  christos  * specified names should be removed, or '^' that they should be placed
    233  1.1  christos  * at the head.
    234  1.1  christos  */
    235  1.1  christos int
    236  1.1  christos kex_assemble_names(char **listp, const char *def, const char *all)
    237  1.1  christos {
    238  1.1  christos 	char *cp, *tmp, *patterns;
    239  1.1  christos 	char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL;
    240  1.1  christos 	int r = SSH_ERR_INTERNAL_ERROR;
    241  1.1  christos 
    242  1.1  christos 	if (listp == NULL || def == NULL || all == NULL)
    243  1.1  christos 		return SSH_ERR_INVALID_ARGUMENT;
    244  1.1  christos 
    245  1.1  christos 	if (*listp == NULL || **listp == '\0') {
    246  1.1  christos 		if ((*listp = strdup(def)) == NULL)
    247  1.1  christos 			return SSH_ERR_ALLOC_FAIL;
    248  1.1  christos 		return 0;
    249  1.1  christos 	}
    250  1.1  christos 
    251  1.1  christos 	list = *listp;
    252  1.1  christos 	*listp = NULL;
    253  1.1  christos 	if (*list == '+') {
    254  1.1  christos 		/* Append names to default list */
    255  1.1  christos 		if ((tmp = kex_names_cat(def, list + 1)) == NULL) {
    256  1.1  christos 			r = SSH_ERR_ALLOC_FAIL;
    257  1.1  christos 			goto fail;
    258  1.1  christos 		}
    259  1.1  christos 		free(list);
    260  1.1  christos 		list = tmp;
    261  1.1  christos 	} else if (*list == '-') {
    262  1.1  christos 		/* Remove names from default list */
    263  1.1  christos 		if ((*listp = match_filter_denylist(def, list + 1)) == NULL) {
    264  1.1  christos 			r = SSH_ERR_ALLOC_FAIL;
    265  1.1  christos 			goto fail;
    266  1.1  christos 		}
    267  1.1  christos 		free(list);
    268  1.1  christos 		/* filtering has already been done */
    269  1.1  christos 		return 0;
    270  1.1  christos 	} else if (*list == '^') {
    271  1.1  christos 		/* Place names at head of default list */
    272  1.1  christos 		if ((tmp = kex_names_cat(list + 1, def)) == NULL) {
    273  1.1  christos 			r = SSH_ERR_ALLOC_FAIL;
    274  1.1  christos 			goto fail;
    275  1.1  christos 		}
    276  1.1  christos 		free(list);
    277  1.1  christos 		list = tmp;
    278  1.1  christos 	} else {
    279  1.1  christos 		/* Explicit list, overrides default - just use "list" as is */
    280  1.1  christos 	}
    281  1.1  christos 
    282  1.1  christos 	/*
    283  1.1  christos 	 * The supplied names may be a pattern-list. For the -list case,
    284  1.1  christos 	 * the patterns are applied above. For the +list and explicit list
    285  1.1  christos 	 * cases we need to do it now.
    286  1.1  christos 	 */
    287  1.1  christos 	ret = NULL;
    288  1.1  christos 	if ((patterns = opatterns = strdup(list)) == NULL) {
    289  1.1  christos 		r = SSH_ERR_ALLOC_FAIL;
    290  1.1  christos 		goto fail;
    291  1.1  christos 	}
    292  1.1  christos 	/* Apply positive (i.e. non-negated) patterns from the list */
    293  1.1  christos 	while ((cp = strsep(&patterns, ",")) != NULL) {
    294  1.1  christos 		if (*cp == '!') {
    295  1.1  christos 			/* negated matches are not supported here */
    296  1.1  christos 			r = SSH_ERR_INVALID_ARGUMENT;
    297  1.1  christos 			goto fail;
    298  1.1  christos 		}
    299  1.1  christos 		free(matching);
    300  1.1  christos 		if ((matching = match_filter_allowlist(all, cp)) == NULL) {
    301  1.1  christos 			r = SSH_ERR_ALLOC_FAIL;
    302  1.1  christos 			goto fail;
    303  1.1  christos 		}
    304  1.1  christos 		if ((tmp = kex_names_cat(ret, matching)) == NULL) {
    305  1.1  christos 			r = SSH_ERR_ALLOC_FAIL;
    306  1.1  christos 			goto fail;
    307  1.1  christos 		}
    308  1.1  christos 		free(ret);
    309  1.1  christos 		ret = tmp;
    310  1.1  christos 	}
    311  1.1  christos 	if (ret == NULL || *ret == '\0') {
    312  1.1  christos 		/* An empty name-list is an error */
    313  1.1  christos 		/* XXX better error code? */
    314  1.1  christos 		r = SSH_ERR_INVALID_ARGUMENT;
    315  1.1  christos 		goto fail;
    316  1.1  christos 	}
    317  1.1  christos 
    318  1.1  christos 	/* success */
    319  1.1  christos 	*listp = ret;
    320  1.1  christos 	ret = NULL;
    321  1.1  christos 	r = 0;
    322  1.1  christos 
    323  1.1  christos  fail:
    324  1.1  christos 	free(matching);
    325  1.1  christos 	free(opatterns);
    326  1.1  christos 	free(list);
    327  1.1  christos 	free(ret);
    328  1.1  christos 	return r;
    329  1.1  christos }
    330