version 1.185, 2024/01/08 00:34:33 |
version 1.186, 2024/05/17 00:30:23 |
|
|
"languages stoc", |
"languages stoc", |
}; |
}; |
|
|
struct kexalg { |
|
char *name; |
|
u_int type; |
|
int ec_nid; |
|
int hash_alg; |
|
}; |
|
static const struct kexalg kexalgs[] = { |
|
#ifdef WITH_OPENSSL |
|
{ KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, |
|
{ KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, |
|
{ KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 }, |
|
{ KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 }, |
|
{ KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 }, |
|
{ KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, |
|
{ KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 }, |
|
{ KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2, |
|
NID_X9_62_prime256v1, SSH_DIGEST_SHA256 }, |
|
{ KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1, |
|
SSH_DIGEST_SHA384 }, |
|
{ KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1, |
|
SSH_DIGEST_SHA512 }, |
|
#endif |
|
{ KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, |
|
{ KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 }, |
|
{ KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0, |
|
SSH_DIGEST_SHA512 }, |
|
{ NULL, 0, -1, -1}, |
|
}; |
|
|
|
char * |
|
kex_alg_list(char sep) |
|
{ |
|
char *ret = NULL, *tmp; |
|
size_t nlen, rlen = 0; |
|
const struct kexalg *k; |
|
|
|
for (k = kexalgs; k->name != NULL; k++) { |
|
if (ret != NULL) |
|
ret[rlen++] = sep; |
|
nlen = strlen(k->name); |
|
if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { |
|
free(ret); |
|
return NULL; |
|
} |
|
ret = tmp; |
|
memcpy(ret + rlen, k->name, nlen + 1); |
|
rlen += nlen; |
|
} |
|
return ret; |
|
} |
|
|
|
static const struct kexalg * |
|
kex_alg_by_name(const char *name) |
|
{ |
|
const struct kexalg *k; |
|
|
|
for (k = kexalgs; k->name != NULL; k++) { |
|
if (strcmp(k->name, name) == 0) |
|
return k; |
|
} |
|
return NULL; |
|
} |
|
|
|
/* Validate KEX method name list */ |
|
int |
|
kex_names_valid(const char *names) |
|
{ |
|
char *s, *cp, *p; |
|
|
|
if (names == NULL || strcmp(names, "") == 0) |
|
return 0; |
|
if ((s = cp = strdup(names)) == NULL) |
|
return 0; |
|
for ((p = strsep(&cp, ",")); p && *p != '\0'; |
|
(p = strsep(&cp, ","))) { |
|
if (kex_alg_by_name(p) == NULL) { |
|
error("Unsupported KEX algorithm \"%.100s\"", p); |
|
free(s); |
|
return 0; |
|
} |
|
} |
|
debug3("kex names ok: [%s]", names); |
|
free(s); |
|
return 1; |
|
} |
|
|
|
/* returns non-zero if proposal contains any algorithm from algs */ |
|
static int |
|
has_any_alg(const char *proposal, const char *algs) |
|
{ |
|
char *cp; |
|
|
|
if ((cp = match_list(proposal, algs, NULL)) == NULL) |
|
return 0; |
|
free(cp); |
|
return 1; |
|
} |
|
|
|
/* |
/* |
* Concatenate algorithm names, avoiding duplicates in the process. |
|
* Caller must free returned string. |
|
*/ |
|
char * |
|
kex_names_cat(const char *a, const char *b) |
|
{ |
|
char *ret = NULL, *tmp = NULL, *cp, *p; |
|
size_t len; |
|
|
|
if (a == NULL || *a == '\0') |
|
return strdup(b); |
|
if (b == NULL || *b == '\0') |
|
return strdup(a); |
|
if (strlen(b) > 1024*1024) |
|
return NULL; |
|
len = strlen(a) + strlen(b) + 2; |
|
if ((tmp = cp = strdup(b)) == NULL || |
|
(ret = calloc(1, len)) == NULL) { |
|
free(tmp); |
|
return NULL; |
|
} |
|
strlcpy(ret, a, len); |
|
for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { |
|
if (has_any_alg(ret, p)) |
|
continue; /* Algorithm already present */ |
|
if (strlcat(ret, ",", len) >= len || |
|
strlcat(ret, p, len) >= len) { |
|
free(tmp); |
|
free(ret); |
|
return NULL; /* Shouldn't happen */ |
|
} |
|
} |
|
free(tmp); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Assemble a list of algorithms from a default list and a string from a |
|
* configuration file. The user-provided string may begin with '+' to |
|
* indicate that it should be appended to the default, '-' that the |
|
* specified names should be removed, or '^' that they should be placed |
|
* at the head. |
|
*/ |
|
int |
|
kex_assemble_names(char **listp, const char *def, const char *all) |
|
{ |
|
char *cp, *tmp, *patterns; |
|
char *list = NULL, *ret = NULL, *matching = NULL, *opatterns = NULL; |
|
int r = SSH_ERR_INTERNAL_ERROR; |
|
|
|
if (listp == NULL || def == NULL || all == NULL) |
|
return SSH_ERR_INVALID_ARGUMENT; |
|
|
|
if (*listp == NULL || **listp == '\0') { |
|
if ((*listp = strdup(def)) == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
|
return 0; |
|
} |
|
|
|
list = *listp; |
|
*listp = NULL; |
|
if (*list == '+') { |
|
/* Append names to default list */ |
|
if ((tmp = kex_names_cat(def, list + 1)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
free(list); |
|
list = tmp; |
|
} else if (*list == '-') { |
|
/* Remove names from default list */ |
|
if ((*listp = match_filter_denylist(def, list + 1)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
free(list); |
|
/* filtering has already been done */ |
|
return 0; |
|
} else if (*list == '^') { |
|
/* Place names at head of default list */ |
|
if ((tmp = kex_names_cat(list + 1, def)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
free(list); |
|
list = tmp; |
|
} else { |
|
/* Explicit list, overrides default - just use "list" as is */ |
|
} |
|
|
|
/* |
|
* The supplied names may be a pattern-list. For the -list case, |
|
* the patterns are applied above. For the +list and explicit list |
|
* cases we need to do it now. |
|
*/ |
|
ret = NULL; |
|
if ((patterns = opatterns = strdup(list)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
/* Apply positive (i.e. non-negated) patterns from the list */ |
|
while ((cp = strsep(&patterns, ",")) != NULL) { |
|
if (*cp == '!') { |
|
/* negated matches are not supported here */ |
|
r = SSH_ERR_INVALID_ARGUMENT; |
|
goto fail; |
|
} |
|
free(matching); |
|
if ((matching = match_filter_allowlist(all, cp)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
if ((tmp = kex_names_cat(ret, matching)) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto fail; |
|
} |
|
free(ret); |
|
ret = tmp; |
|
} |
|
if (ret == NULL || *ret == '\0') { |
|
/* An empty name-list is an error */ |
|
/* XXX better error code? */ |
|
r = SSH_ERR_INVALID_ARGUMENT; |
|
goto fail; |
|
} |
|
|
|
/* success */ |
|
*listp = ret; |
|
ret = NULL; |
|
r = 0; |
|
|
|
fail: |
|
free(matching); |
|
free(opatterns); |
|
free(list); |
|
free(ret); |
|
return r; |
|
} |
|
|
|
/* |
|
* Fill out a proposal array with dynamically allocated values, which may |
* Fill out a proposal array with dynamically allocated values, which may |
* be modified as required for compatibility reasons. |
* be modified as required for compatibility reasons. |
* Any of the options may be NULL, in which case the default is used. |
* Any of the options may be NULL, in which case the default is used. |
|
|
(alg = strsep(&algs, ","))) { |
(alg = strsep(&algs, ","))) { |
if ((sigalg = sshkey_sigalg_by_name(alg)) == NULL) |
if ((sigalg = sshkey_sigalg_by_name(alg)) == NULL) |
continue; |
continue; |
if (!has_any_alg(sigalg, sigalgs)) |
if (!kex_has_any_alg(sigalg, sigalgs)) |
continue; |
continue; |
/* Don't add an algorithm twice. */ |
/* Don't add an algorithm twice. */ |
if (ssh->kex->server_sig_algs != NULL && |
if (ssh->kex->server_sig_algs != NULL && |
has_any_alg(sigalg, ssh->kex->server_sig_algs)) |
kex_has_any_alg(sigalg, ssh->kex->server_sig_algs)) |
continue; |
continue; |
xextendf(&ssh->kex->server_sig_algs, ",", "%s", sigalg); |
xextendf(&ssh->kex->server_sig_algs, ",", "%s", sigalg); |
} |
} |
|
|
static int |
static int |
choose_kex(struct kex *k, char *client, char *server) |
choose_kex(struct kex *k, char *client, char *server) |
{ |
{ |
const struct kexalg *kexalg; |
|
|
|
k->name = match_list(client, server, NULL); |
k->name = match_list(client, server, NULL); |
|
|
debug("kex: algorithm: %s", k->name ? k->name : "(no match)"); |
debug("kex: algorithm: %s", k->name ? k->name : "(no match)"); |
if (k->name == NULL) |
if (k->name == NULL) |
return SSH_ERR_NO_KEX_ALG_MATCH; |
return SSH_ERR_NO_KEX_ALG_MATCH; |
if ((kexalg = kex_alg_by_name(k->name)) == NULL) { |
if (!kex_name_valid(k->name)) { |
error_f("unsupported KEX method %s", k->name); |
error_f("unsupported KEX method %s", k->name); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
k->kex_type = kexalg->type; |
k->kex_type = kex_type_from_name(k->name); |
k->hash_alg = kexalg->hash_alg; |
k->hash_alg = kex_hash_from_name(k->name); |
k->ec_nid = kexalg->ec_nid; |
k->ec_nid = kex_nid_from_name(k->name); |
return 0; |
return 0; |
} |
} |
|
|
|
|
static int |
static int |
kexalgs_contains(char **peer, const char *ext) |
kexalgs_contains(char **peer, const char *ext) |
{ |
{ |
return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); |
return kex_has_any_alg(peer[PROPOSAL_KEX_ALGS], ext); |
} |
} |
|
|
static int |
static int |
|
|
|
|
/* Check whether client supports rsa-sha2 algorithms */ |
/* Check whether client supports rsa-sha2 algorithms */ |
if (kex->server && (kex->flags & KEX_INITIAL)) { |
if (kex->server && (kex->flags & KEX_INITIAL)) { |
if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], |
if (kex_has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], |
"rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com")) |
"rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com")) |
kex->flags |= KEX_RSA_SHA2_256_SUPPORTED; |
kex->flags |= KEX_RSA_SHA2_256_SUPPORTED; |
if (has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], |
if (kex_has_any_alg(peer[PROPOSAL_SERVER_HOST_KEY_ALGS], |
"rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com")) |
"rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com")) |
kex->flags |= KEX_RSA_SHA2_512_SUPPORTED; |
kex->flags |= KEX_RSA_SHA2_512_SUPPORTED; |
} |
} |