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