kex-names.c revision 1.4 1 1.2 christos /* $NetBSD: kex-names.c,v 1.4 2025/10/11 15:45:06 christos Exp $ */
2 1.4 christos /* $OpenBSD: kex-names.c,v 1.6 2025/09/02 11:08:34 djm 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.4 2025/10/11 15:45:06 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 <unistd.h>
35 1.1 christos #include <signal.h>
36 1.1 christos
37 1.1 christos #ifdef WITH_OPENSSL
38 1.1 christos #include <openssl/crypto.h>
39 1.1 christos #include <openssl/evp.h>
40 1.1 christos #endif
41 1.1 christos
42 1.1 christos #include "kex.h"
43 1.1 christos #include "log.h"
44 1.1 christos #include "match.h"
45 1.1 christos #include "digest.h"
46 1.1 christos #include "misc.h"
47 1.1 christos
48 1.1 christos #include "ssherr.h"
49 1.1 christos #include "xmalloc.h"
50 1.1 christos
51 1.1 christos struct kexalg {
52 1.2 christos const char *name;
53 1.1 christos u_int type;
54 1.1 christos int ec_nid;
55 1.1 christos int hash_alg;
56 1.4 christos int pq_alg;
57 1.1 christos };
58 1.1 christos static const struct kexalg kexalgs[] = {
59 1.1 christos #ifdef WITH_OPENSSL
60 1.4 christos { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
61 1.4 christos { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
62 1.4 christos { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
63 1.4 christos { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
64 1.4 christos { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
65 1.4 christos { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
66 1.4 christos { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
67 1.1 christos { KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2,
68 1.4 christos NID_X9_62_prime256v1, SSH_DIGEST_SHA256, KEX_NOT_PQ },
69 1.1 christos { KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1,
70 1.4 christos SSH_DIGEST_SHA384, KEX_NOT_PQ },
71 1.1 christos { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
72 1.4 christos SSH_DIGEST_SHA512, KEX_NOT_PQ },
73 1.1 christos #endif
74 1.4 christos { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0,
75 1.4 christos SSH_DIGEST_SHA256, KEX_NOT_PQ },
76 1.4 christos { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0,
77 1.4 christos SSH_DIGEST_SHA256, KEX_NOT_PQ },
78 1.1 christos { KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0,
79 1.4 christos SSH_DIGEST_SHA512, KEX_IS_PQ },
80 1.3 christos { KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0,
81 1.4 christos SSH_DIGEST_SHA512, KEX_IS_PQ },
82 1.3 christos { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
83 1.4 christos SSH_DIGEST_SHA256, KEX_IS_PQ },
84 1.4 christos { NULL, 0, -1, -1, 0},
85 1.1 christos };
86 1.1 christos
87 1.1 christos char *
88 1.1 christos kex_alg_list(char sep)
89 1.1 christos {
90 1.4 christos char *ret = NULL;
91 1.1 christos const struct kexalg *k;
92 1.4 christos char sep_str[2] = {sep, '\0'};
93 1.4 christos
94 1.4 christos for (k = kexalgs; k->name != NULL; k++)
95 1.4 christos xextendf(&ret, sep_str, "%s", k->name);
96 1.1 christos
97 1.1 christos return ret;
98 1.1 christos }
99 1.1 christos
100 1.1 christos static const struct kexalg *
101 1.1 christos kex_alg_by_name(const char *name)
102 1.1 christos {
103 1.1 christos const struct kexalg *k;
104 1.1 christos
105 1.1 christos for (k = kexalgs; k->name != NULL; k++) {
106 1.1 christos if (strcmp(k->name, name) == 0)
107 1.1 christos return k;
108 1.1 christos }
109 1.1 christos return NULL;
110 1.1 christos }
111 1.1 christos
112 1.1 christos int
113 1.1 christos kex_name_valid(const char *name)
114 1.1 christos {
115 1.1 christos return kex_alg_by_name(name) != NULL;
116 1.1 christos }
117 1.1 christos
118 1.4 christos int
119 1.4 christos kex_is_pq_from_name(const char *name)
120 1.4 christos {
121 1.4 christos const struct kexalg *k;
122 1.4 christos
123 1.4 christos if ((k = kex_alg_by_name(name)) == NULL)
124 1.4 christos return 0;
125 1.4 christos return k->pq_alg == KEX_IS_PQ;
126 1.4 christos }
127 1.4 christos
128 1.1 christos u_int
129 1.1 christos kex_type_from_name(const char *name)
130 1.1 christos {
131 1.1 christos const struct kexalg *k;
132 1.1 christos
133 1.1 christos if ((k = kex_alg_by_name(name)) == NULL)
134 1.1 christos return 0;
135 1.1 christos return k->type;
136 1.1 christos }
137 1.1 christos
138 1.1 christos int
139 1.1 christos kex_hash_from_name(const char *name)
140 1.1 christos {
141 1.1 christos const struct kexalg *k;
142 1.1 christos
143 1.1 christos if ((k = kex_alg_by_name(name)) == NULL)
144 1.1 christos return -1;
145 1.1 christos return k->hash_alg;
146 1.1 christos }
147 1.1 christos
148 1.1 christos int
149 1.1 christos kex_nid_from_name(const char *name)
150 1.1 christos {
151 1.1 christos const struct kexalg *k;
152 1.1 christos
153 1.1 christos if ((k = kex_alg_by_name(name)) == NULL)
154 1.1 christos return -1;
155 1.1 christos return k->ec_nid;
156 1.1 christos }
157 1.1 christos
158 1.1 christos /* Validate KEX method name list */
159 1.1 christos int
160 1.1 christos kex_names_valid(const char *names)
161 1.1 christos {
162 1.1 christos char *s, *cp, *p;
163 1.1 christos
164 1.1 christos if (names == NULL || strcmp(names, "") == 0)
165 1.1 christos return 0;
166 1.1 christos if ((s = cp = strdup(names)) == NULL)
167 1.1 christos return 0;
168 1.1 christos for ((p = strsep(&cp, ",")); p && *p != '\0';
169 1.1 christos (p = strsep(&cp, ","))) {
170 1.1 christos if (kex_alg_by_name(p) == NULL) {
171 1.1 christos error("Unsupported KEX algorithm \"%.100s\"", p);
172 1.1 christos free(s);
173 1.1 christos return 0;
174 1.1 christos }
175 1.1 christos }
176 1.1 christos debug3("kex names ok: [%s]", names);
177 1.1 christos free(s);
178 1.1 christos return 1;
179 1.1 christos }
180 1.1 christos
181 1.1 christos /* returns non-zero if proposal contains any algorithm from algs */
182 1.1 christos int
183 1.1 christos kex_has_any_alg(const char *proposal, const char *algs)
184 1.1 christos {
185 1.1 christos char *cp;
186 1.1 christos
187 1.1 christos if ((cp = match_list(proposal, algs, NULL)) == NULL)
188 1.1 christos return 0;
189 1.1 christos free(cp);
190 1.1 christos return 1;
191 1.1 christos }
192 1.1 christos
193 1.1 christos /*
194 1.1 christos * Concatenate algorithm names, avoiding duplicates in the process.
195 1.1 christos * Caller must free returned string.
196 1.1 christos */
197 1.1 christos char *
198 1.1 christos kex_names_cat(const char *a, const char *b)
199 1.1 christos {
200 1.1 christos char *ret = NULL, *tmp = NULL, *cp, *p;
201 1.1 christos size_t len;
202 1.1 christos
203 1.1 christos if (a == NULL || *a == '\0')
204 1.1 christos return strdup(b);
205 1.1 christos if (b == NULL || *b == '\0')
206 1.1 christos return strdup(a);
207 1.1 christos if (strlen(b) > 1024*1024)
208 1.1 christos return NULL;
209 1.1 christos len = strlen(a) + strlen(b) + 2;
210 1.1 christos if ((tmp = cp = strdup(b)) == NULL ||
211 1.1 christos (ret = calloc(1, len)) == NULL) {
212 1.1 christos free(tmp);
213 1.1 christos return NULL;
214 1.1 christos }
215 1.1 christos strlcpy(ret, a, len);
216 1.1 christos for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
217 1.1 christos if (kex_has_any_alg(ret, p))
218 1.1 christos continue; /* Algorithm already present */
219 1.1 christos if (strlcat(ret, ",", len) >= len ||
220 1.1 christos strlcat(ret, p, len) >= len) {
221 1.1 christos free(tmp);
222 1.1 christos free(ret);
223 1.1 christos return NULL; /* Shouldn't happen */
224 1.1 christos }
225 1.1 christos }
226 1.1 christos free(tmp);
227 1.1 christos return ret;
228 1.1 christos }
229 1.1 christos
230 1.1 christos /*
231 1.1 christos * Assemble a list of algorithms from a default list and a string from a
232 1.1 christos * configuration file. The user-provided string may begin with '+' to
233 1.1 christos * indicate that it should be appended to the default, '-' that the
234 1.1 christos * specified names should be removed, or '^' that they should be placed
235 1.1 christos * at the head.
236 1.1 christos */
237 1.1 christos int
238 1.1 christos kex_assemble_names(char **listp, const char *def, const char *all)
239 1.1 christos {
240 1.1 christos char *cp, *tmp, *patterns;
241 1.1 christos char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL;
242 1.1 christos int r = SSH_ERR_INTERNAL_ERROR;
243 1.1 christos
244 1.1 christos if (listp == NULL || def == NULL || all == NULL)
245 1.1 christos return SSH_ERR_INVALID_ARGUMENT;
246 1.1 christos
247 1.1 christos if (*listp == NULL || **listp == '\0') {
248 1.1 christos if ((*listp = strdup(def)) == NULL)
249 1.1 christos return SSH_ERR_ALLOC_FAIL;
250 1.1 christos return 0;
251 1.1 christos }
252 1.1 christos
253 1.1 christos list = *listp;
254 1.1 christos *listp = NULL;
255 1.1 christos if (*list == '+') {
256 1.1 christos /* Append names to default list */
257 1.1 christos if ((tmp = kex_names_cat(def, list + 1)) == NULL) {
258 1.1 christos r = SSH_ERR_ALLOC_FAIL;
259 1.1 christos goto fail;
260 1.1 christos }
261 1.1 christos free(list);
262 1.1 christos list = tmp;
263 1.1 christos } else if (*list == '-') {
264 1.1 christos /* Remove names from default list */
265 1.1 christos if ((*listp = match_filter_denylist(def, list + 1)) == NULL) {
266 1.1 christos r = SSH_ERR_ALLOC_FAIL;
267 1.1 christos goto fail;
268 1.1 christos }
269 1.1 christos free(list);
270 1.1 christos /* filtering has already been done */
271 1.1 christos return 0;
272 1.1 christos } else if (*list == '^') {
273 1.1 christos /* Place names at head of default list */
274 1.1 christos if ((tmp = kex_names_cat(list + 1, def)) == NULL) {
275 1.1 christos r = SSH_ERR_ALLOC_FAIL;
276 1.1 christos goto fail;
277 1.1 christos }
278 1.1 christos free(list);
279 1.1 christos list = tmp;
280 1.1 christos } else {
281 1.1 christos /* Explicit list, overrides default - just use "list" as is */
282 1.1 christos }
283 1.1 christos
284 1.1 christos /*
285 1.1 christos * The supplied names may be a pattern-list. For the -list case,
286 1.1 christos * the patterns are applied above. For the +list and explicit list
287 1.1 christos * cases we need to do it now.
288 1.1 christos */
289 1.1 christos ret = NULL;
290 1.1 christos if ((patterns = opatterns = strdup(list)) == NULL) {
291 1.1 christos r = SSH_ERR_ALLOC_FAIL;
292 1.1 christos goto fail;
293 1.1 christos }
294 1.1 christos /* Apply positive (i.e. non-negated) patterns from the list */
295 1.1 christos while ((cp = strsep(&patterns, ",")) != NULL) {
296 1.1 christos if (*cp == '!') {
297 1.1 christos /* negated matches are not supported here */
298 1.1 christos r = SSH_ERR_INVALID_ARGUMENT;
299 1.1 christos goto fail;
300 1.1 christos }
301 1.1 christos free(matching);
302 1.1 christos if ((matching = match_filter_allowlist(all, cp)) == NULL) {
303 1.1 christos r = SSH_ERR_ALLOC_FAIL;
304 1.1 christos goto fail;
305 1.1 christos }
306 1.1 christos if ((tmp = kex_names_cat(ret, matching)) == NULL) {
307 1.1 christos r = SSH_ERR_ALLOC_FAIL;
308 1.1 christos goto fail;
309 1.1 christos }
310 1.1 christos free(ret);
311 1.1 christos ret = tmp;
312 1.1 christos }
313 1.1 christos if (ret == NULL || *ret == '\0') {
314 1.1 christos /* An empty name-list is an error */
315 1.1 christos /* XXX better error code? */
316 1.1 christos r = SSH_ERR_INVALID_ARGUMENT;
317 1.1 christos goto fail;
318 1.1 christos }
319 1.1 christos
320 1.1 christos /* success */
321 1.1 christos *listp = ret;
322 1.1 christos ret = NULL;
323 1.1 christos r = 0;
324 1.1 christos
325 1.1 christos fail:
326 1.1 christos free(matching);
327 1.1 christos free(opatterns);
328 1.1 christos free(list);
329 1.1 christos free(ret);
330 1.1 christos return r;
331 1.1 christos }
332