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