#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#ifdef WITH_OPENSSL
#include <openssl/crypto.h>
#include <openssl/evp.h>
#endif
#include "kex.h"
#include "log.h"
#include "match.h"
#include "digest.h"
#include "misc.h"
#include "ssherr.h"
struct kexalg {
char *name;
u_int type;
int ec_nid;
int hash_alg;
int pq_alg;
};
static const struct kexalg kexalgs[] = {
#ifdef WITH_OPENSSL
{ KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
{ KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
{ KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
{ KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
{ KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
{ KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
{ KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
{ KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2,
NID_X9_62_prime256v1, SSH_DIGEST_SHA256, KEX_NOT_PQ },
{ KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1,
SSH_DIGEST_SHA384, KEX_NOT_PQ },
{ KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
SSH_DIGEST_SHA512, KEX_NOT_PQ },
#endif
{ KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0,
SSH_DIGEST_SHA256, KEX_NOT_PQ },
{ KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0,
SSH_DIGEST_SHA256, KEX_NOT_PQ },
{ KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0,
SSH_DIGEST_SHA512, KEX_IS_PQ },
{ KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0,
SSH_DIGEST_SHA512, KEX_IS_PQ },
{ KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
SSH_DIGEST_SHA256, KEX_IS_PQ },
{ NULL, 0, -1, -1, 0},
};
char *
kex_alg_list(char sep)
{
char *ret = NULL;
const struct kexalg *k;
char sep_str[2] = {sep, '\0'};
for (k = kexalgs; k->name != NULL; k++)
xextendf(&ret, sep_str, "%s", k->name);
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;
}
int
kex_name_valid(const char *name)
{
return kex_alg_by_name(name) != NULL;
}
int
kex_is_pq_from_name(const char *name)
{
const struct kexalg *k;
if ((k = kex_alg_by_name(name)) == NULL)
return 0;
return k->pq_alg == KEX_IS_PQ;
}
u_int
kex_type_from_name(const char *name)
{
const struct kexalg *k;
if ((k = kex_alg_by_name(name)) == NULL)
return 0;
return k->type;
}
int
kex_hash_from_name(const char *name)
{
const struct kexalg *k;
if ((k = kex_alg_by_name(name)) == NULL)
return -1;
return k->hash_alg;
}
int
kex_nid_from_name(const char *name)
{
const struct kexalg *k;
if ((k = kex_alg_by_name(name)) == NULL)
return -1;
return k->ec_nid;
}
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;
}
int
kex_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;
}
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 (kex_has_any_alg(ret, p))
continue;
if (strlcat(ret, ",", len) >= len ||
strlcat(ret, p, len) >= len) {
free(tmp);
free(ret);
return NULL;
}
}
free(tmp);
return ret;
}
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 == '+') {
if ((tmp = kex_names_cat(def, list + 1)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
free(list);
list = tmp;
} else if (*list == '-') {
if ((*listp = match_filter_denylist(def, list + 1)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
free(list);
return 0;
} else if (*list == '^') {
if ((tmp = kex_names_cat(list + 1, def)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
free(list);
list = tmp;
} else {
}
ret = NULL;
if ((patterns = opatterns = strdup(list)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
while ((cp = strsep(&patterns, ",")) != NULL) {
if (*cp == '!') {
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') {
r = SSH_ERR_INVALID_ARGUMENT;
goto fail;
}
*listp = ret;
ret = NULL;
r = 0;
fail:
free(matching);
free(opatterns);
free(list);
free(ret);
return r;
}