1*9469f4f1Schristos /* $NetBSD: kex-names.c,v 1.3 2024/09/24 21:32:18 christos Exp $ */ 2*9469f4f1Schristos /* $OpenBSD: kex-names.c,v 1.4 2024/09/09 02:39:57 djm Exp $ */ 3*9469f4f1Schristos 418e68bb4Schristos /* 518e68bb4Schristos * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. 618e68bb4Schristos * 718e68bb4Schristos * Redistribution and use in source and binary forms, with or without 818e68bb4Schristos * modification, are permitted provided that the following conditions 918e68bb4Schristos * are met: 1018e68bb4Schristos * 1. Redistributions of source code must retain the above copyright 1118e68bb4Schristos * notice, this list of conditions and the following disclaimer. 1218e68bb4Schristos * 2. Redistributions in binary form must reproduce the above copyright 1318e68bb4Schristos * notice, this list of conditions and the following disclaimer in the 1418e68bb4Schristos * documentation and/or other materials provided with the distribution. 1518e68bb4Schristos * 1618e68bb4Schristos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1718e68bb4Schristos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1818e68bb4Schristos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1918e68bb4Schristos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2018e68bb4Schristos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2118e68bb4Schristos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2218e68bb4Schristos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2318e68bb4Schristos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2418e68bb4Schristos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2518e68bb4Schristos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2618e68bb4Schristos */ 2718e68bb4Schristos 281c7715ddSchristos #include "includes.h" 29*9469f4f1Schristos __RCSID("$NetBSD: kex-names.c,v 1.3 2024/09/24 21:32:18 christos Exp $"); 3018e68bb4Schristos 3118e68bb4Schristos #include <stdio.h> 3218e68bb4Schristos #include <stdlib.h> 3318e68bb4Schristos #include <string.h> 3418e68bb4Schristos #include <unistd.h> 3518e68bb4Schristos #include <signal.h> 3618e68bb4Schristos 3718e68bb4Schristos #ifdef WITH_OPENSSL 3818e68bb4Schristos #include <openssl/crypto.h> 3918e68bb4Schristos #include <openssl/evp.h> 4018e68bb4Schristos #endif 4118e68bb4Schristos 4218e68bb4Schristos #include "kex.h" 4318e68bb4Schristos #include "log.h" 4418e68bb4Schristos #include "match.h" 4518e68bb4Schristos #include "digest.h" 4618e68bb4Schristos #include "misc.h" 4718e68bb4Schristos 4818e68bb4Schristos #include "ssherr.h" 4918e68bb4Schristos #include "xmalloc.h" 5018e68bb4Schristos 5118e68bb4Schristos struct kexalg { 521c7715ddSchristos const char *name; 5318e68bb4Schristos u_int type; 5418e68bb4Schristos int ec_nid; 5518e68bb4Schristos int hash_alg; 5618e68bb4Schristos }; 5718e68bb4Schristos static const struct kexalg kexalgs[] = { 5818e68bb4Schristos #ifdef WITH_OPENSSL 5918e68bb4Schristos { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, 6018e68bb4Schristos { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, 6118e68bb4Schristos { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, 6218e68bb4Schristos { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, 6318e68bb4Schristos { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 }, 6418e68bb4Schristos { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, 6518e68bb4Schristos { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 }, 6618e68bb4Schristos { KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2, 6718e68bb4Schristos NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, 6818e68bb4Schristos { KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1, 6918e68bb4Schristos SSH_DIGEST_SHA384 }, 7018e68bb4Schristos { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1, 7118e68bb4Schristos SSH_DIGEST_SHA512 }, 7218e68bb4Schristos #endif 7318e68bb4Schristos { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, 7418e68bb4Schristos { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, 7518e68bb4Schristos { KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0, 7618e68bb4Schristos SSH_DIGEST_SHA512 }, 77*9469f4f1Schristos { KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0, 78*9469f4f1Schristos SSH_DIGEST_SHA512 }, 79*9469f4f1Schristos { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0, 80*9469f4f1Schristos SSH_DIGEST_SHA256 }, 8118e68bb4Schristos { NULL, 0, -1, -1}, 8218e68bb4Schristos }; 8318e68bb4Schristos 8418e68bb4Schristos char * 8518e68bb4Schristos kex_alg_list(char sep) 8618e68bb4Schristos { 8718e68bb4Schristos char *ret = NULL, *tmp; 8818e68bb4Schristos size_t nlen, rlen = 0; 8918e68bb4Schristos const struct kexalg *k; 9018e68bb4Schristos 9118e68bb4Schristos for (k = kexalgs; k->name != NULL; k++) { 9218e68bb4Schristos if (ret != NULL) 9318e68bb4Schristos ret[rlen++] = sep; 9418e68bb4Schristos nlen = strlen(k->name); 9518e68bb4Schristos if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { 9618e68bb4Schristos free(ret); 9718e68bb4Schristos return NULL; 9818e68bb4Schristos } 9918e68bb4Schristos ret = tmp; 10018e68bb4Schristos memcpy(ret + rlen, k->name, nlen + 1); 10118e68bb4Schristos rlen += nlen; 10218e68bb4Schristos } 10318e68bb4Schristos return ret; 10418e68bb4Schristos } 10518e68bb4Schristos 10618e68bb4Schristos static const struct kexalg * 10718e68bb4Schristos kex_alg_by_name(const char *name) 10818e68bb4Schristos { 10918e68bb4Schristos const struct kexalg *k; 11018e68bb4Schristos 11118e68bb4Schristos for (k = kexalgs; k->name != NULL; k++) { 11218e68bb4Schristos if (strcmp(k->name, name) == 0) 11318e68bb4Schristos return k; 11418e68bb4Schristos } 11518e68bb4Schristos return NULL; 11618e68bb4Schristos } 11718e68bb4Schristos 11818e68bb4Schristos int 11918e68bb4Schristos kex_name_valid(const char *name) 12018e68bb4Schristos { 12118e68bb4Schristos return kex_alg_by_name(name) != NULL; 12218e68bb4Schristos } 12318e68bb4Schristos 12418e68bb4Schristos u_int 12518e68bb4Schristos kex_type_from_name(const char *name) 12618e68bb4Schristos { 12718e68bb4Schristos const struct kexalg *k; 12818e68bb4Schristos 12918e68bb4Schristos if ((k = kex_alg_by_name(name)) == NULL) 13018e68bb4Schristos return 0; 13118e68bb4Schristos return k->type; 13218e68bb4Schristos } 13318e68bb4Schristos 13418e68bb4Schristos int 13518e68bb4Schristos kex_hash_from_name(const char *name) 13618e68bb4Schristos { 13718e68bb4Schristos const struct kexalg *k; 13818e68bb4Schristos 13918e68bb4Schristos if ((k = kex_alg_by_name(name)) == NULL) 14018e68bb4Schristos return -1; 14118e68bb4Schristos return k->hash_alg; 14218e68bb4Schristos } 14318e68bb4Schristos 14418e68bb4Schristos int 14518e68bb4Schristos kex_nid_from_name(const char *name) 14618e68bb4Schristos { 14718e68bb4Schristos const struct kexalg *k; 14818e68bb4Schristos 14918e68bb4Schristos if ((k = kex_alg_by_name(name)) == NULL) 15018e68bb4Schristos return -1; 15118e68bb4Schristos return k->ec_nid; 15218e68bb4Schristos } 15318e68bb4Schristos 15418e68bb4Schristos /* Validate KEX method name list */ 15518e68bb4Schristos int 15618e68bb4Schristos kex_names_valid(const char *names) 15718e68bb4Schristos { 15818e68bb4Schristos char *s, *cp, *p; 15918e68bb4Schristos 16018e68bb4Schristos if (names == NULL || strcmp(names, "") == 0) 16118e68bb4Schristos return 0; 16218e68bb4Schristos if ((s = cp = strdup(names)) == NULL) 16318e68bb4Schristos return 0; 16418e68bb4Schristos for ((p = strsep(&cp, ",")); p && *p != '\0'; 16518e68bb4Schristos (p = strsep(&cp, ","))) { 16618e68bb4Schristos if (kex_alg_by_name(p) == NULL) { 16718e68bb4Schristos error("Unsupported KEX algorithm \"%.100s\"", p); 16818e68bb4Schristos free(s); 16918e68bb4Schristos return 0; 17018e68bb4Schristos } 17118e68bb4Schristos } 17218e68bb4Schristos debug3("kex names ok: [%s]", names); 17318e68bb4Schristos free(s); 17418e68bb4Schristos return 1; 17518e68bb4Schristos } 17618e68bb4Schristos 17718e68bb4Schristos /* returns non-zero if proposal contains any algorithm from algs */ 17818e68bb4Schristos int 17918e68bb4Schristos kex_has_any_alg(const char *proposal, const char *algs) 18018e68bb4Schristos { 18118e68bb4Schristos char *cp; 18218e68bb4Schristos 18318e68bb4Schristos if ((cp = match_list(proposal, algs, NULL)) == NULL) 18418e68bb4Schristos return 0; 18518e68bb4Schristos free(cp); 18618e68bb4Schristos return 1; 18718e68bb4Schristos } 18818e68bb4Schristos 18918e68bb4Schristos /* 19018e68bb4Schristos * Concatenate algorithm names, avoiding duplicates in the process. 19118e68bb4Schristos * Caller must free returned string. 19218e68bb4Schristos */ 19318e68bb4Schristos char * 19418e68bb4Schristos kex_names_cat(const char *a, const char *b) 19518e68bb4Schristos { 19618e68bb4Schristos char *ret = NULL, *tmp = NULL, *cp, *p; 19718e68bb4Schristos size_t len; 19818e68bb4Schristos 19918e68bb4Schristos if (a == NULL || *a == '\0') 20018e68bb4Schristos return strdup(b); 20118e68bb4Schristos if (b == NULL || *b == '\0') 20218e68bb4Schristos return strdup(a); 20318e68bb4Schristos if (strlen(b) > 1024*1024) 20418e68bb4Schristos return NULL; 20518e68bb4Schristos len = strlen(a) + strlen(b) + 2; 20618e68bb4Schristos if ((tmp = cp = strdup(b)) == NULL || 20718e68bb4Schristos (ret = calloc(1, len)) == NULL) { 20818e68bb4Schristos free(tmp); 20918e68bb4Schristos return NULL; 21018e68bb4Schristos } 21118e68bb4Schristos strlcpy(ret, a, len); 21218e68bb4Schristos for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { 21318e68bb4Schristos if (kex_has_any_alg(ret, p)) 21418e68bb4Schristos continue; /* Algorithm already present */ 21518e68bb4Schristos if (strlcat(ret, ",", len) >= len || 21618e68bb4Schristos strlcat(ret, p, len) >= len) { 21718e68bb4Schristos free(tmp); 21818e68bb4Schristos free(ret); 21918e68bb4Schristos return NULL; /* Shouldn't happen */ 22018e68bb4Schristos } 22118e68bb4Schristos } 22218e68bb4Schristos free(tmp); 22318e68bb4Schristos return ret; 22418e68bb4Schristos } 22518e68bb4Schristos 22618e68bb4Schristos /* 22718e68bb4Schristos * Assemble a list of algorithms from a default list and a string from a 22818e68bb4Schristos * configuration file. The user-provided string may begin with '+' to 22918e68bb4Schristos * indicate that it should be appended to the default, '-' that the 23018e68bb4Schristos * specified names should be removed, or '^' that they should be placed 23118e68bb4Schristos * at the head. 23218e68bb4Schristos */ 23318e68bb4Schristos int 23418e68bb4Schristos kex_assemble_names(char **listp, const char *def, const char *all) 23518e68bb4Schristos { 23618e68bb4Schristos char *cp, *tmp, *patterns; 23718e68bb4Schristos char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL; 23818e68bb4Schristos int r = SSH_ERR_INTERNAL_ERROR; 23918e68bb4Schristos 24018e68bb4Schristos if (listp == NULL || def == NULL || all == NULL) 24118e68bb4Schristos return SSH_ERR_INVALID_ARGUMENT; 24218e68bb4Schristos 24318e68bb4Schristos if (*listp == NULL || **listp == '\0') { 24418e68bb4Schristos if ((*listp = strdup(def)) == NULL) 24518e68bb4Schristos return SSH_ERR_ALLOC_FAIL; 24618e68bb4Schristos return 0; 24718e68bb4Schristos } 24818e68bb4Schristos 24918e68bb4Schristos list = *listp; 25018e68bb4Schristos *listp = NULL; 25118e68bb4Schristos if (*list == '+') { 25218e68bb4Schristos /* Append names to default list */ 25318e68bb4Schristos if ((tmp = kex_names_cat(def, list + 1)) == NULL) { 25418e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 25518e68bb4Schristos goto fail; 25618e68bb4Schristos } 25718e68bb4Schristos free(list); 25818e68bb4Schristos list = tmp; 25918e68bb4Schristos } else if (*list == '-') { 26018e68bb4Schristos /* Remove names from default list */ 26118e68bb4Schristos if ((*listp = match_filter_denylist(def, list + 1)) == NULL) { 26218e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 26318e68bb4Schristos goto fail; 26418e68bb4Schristos } 26518e68bb4Schristos free(list); 26618e68bb4Schristos /* filtering has already been done */ 26718e68bb4Schristos return 0; 26818e68bb4Schristos } else if (*list == '^') { 26918e68bb4Schristos /* Place names at head of default list */ 27018e68bb4Schristos if ((tmp = kex_names_cat(list + 1, def)) == NULL) { 27118e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 27218e68bb4Schristos goto fail; 27318e68bb4Schristos } 27418e68bb4Schristos free(list); 27518e68bb4Schristos list = tmp; 27618e68bb4Schristos } else { 27718e68bb4Schristos /* Explicit list, overrides default - just use "list" as is */ 27818e68bb4Schristos } 27918e68bb4Schristos 28018e68bb4Schristos /* 28118e68bb4Schristos * The supplied names may be a pattern-list. For the -list case, 28218e68bb4Schristos * the patterns are applied above. For the +list and explicit list 28318e68bb4Schristos * cases we need to do it now. 28418e68bb4Schristos */ 28518e68bb4Schristos ret = NULL; 28618e68bb4Schristos if ((patterns = opatterns = strdup(list)) == NULL) { 28718e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 28818e68bb4Schristos goto fail; 28918e68bb4Schristos } 29018e68bb4Schristos /* Apply positive (i.e. non-negated) patterns from the list */ 29118e68bb4Schristos while ((cp = strsep(&patterns, ",")) != NULL) { 29218e68bb4Schristos if (*cp == '!') { 29318e68bb4Schristos /* negated matches are not supported here */ 29418e68bb4Schristos r = SSH_ERR_INVALID_ARGUMENT; 29518e68bb4Schristos goto fail; 29618e68bb4Schristos } 29718e68bb4Schristos free(matching); 29818e68bb4Schristos if ((matching = match_filter_allowlist(all, cp)) == NULL) { 29918e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 30018e68bb4Schristos goto fail; 30118e68bb4Schristos } 30218e68bb4Schristos if ((tmp = kex_names_cat(ret, matching)) == NULL) { 30318e68bb4Schristos r = SSH_ERR_ALLOC_FAIL; 30418e68bb4Schristos goto fail; 30518e68bb4Schristos } 30618e68bb4Schristos free(ret); 30718e68bb4Schristos ret = tmp; 30818e68bb4Schristos } 30918e68bb4Schristos if (ret == NULL || *ret == '\0') { 31018e68bb4Schristos /* An empty name-list is an error */ 31118e68bb4Schristos /* XXX better error code? */ 31218e68bb4Schristos r = SSH_ERR_INVALID_ARGUMENT; 31318e68bb4Schristos goto fail; 31418e68bb4Schristos } 31518e68bb4Schristos 31618e68bb4Schristos /* success */ 31718e68bb4Schristos *listp = ret; 31818e68bb4Schristos ret = NULL; 31918e68bb4Schristos r = 0; 32018e68bb4Schristos 32118e68bb4Schristos fail: 32218e68bb4Schristos free(matching); 32318e68bb4Schristos free(opatterns); 32418e68bb4Schristos free(list); 32518e68bb4Schristos free(ret); 32618e68bb4Schristos return r; 32718e68bb4Schristos } 328