mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
Factor KEXINIT analysis out into its own function.
The function takes the two KEXINIT packets in their string form, together with a list of mappings from names to known algorithm implementations, and returns the selected one of each kind, along with all the other necessary auxiliary stuff.
This commit is contained in:
parent
3df80af868
commit
7de8801e73
1
ssh.h
1
ssh.h
@ -1479,6 +1479,7 @@ int first_in_commasep_string(char const *needle, char const *haystack,
|
|||||||
int haylen);
|
int haylen);
|
||||||
int in_commasep_string(char const *needle, char const *haystack, int haylen);
|
int in_commasep_string(char const *needle, char const *haystack, int haylen);
|
||||||
void add_to_commasep(strbuf *buf, const char *data);
|
void add_to_commasep(strbuf *buf, const char *data);
|
||||||
|
int get_commasep_word(ptrlen *list, ptrlen *word);
|
||||||
|
|
||||||
int verify_ssh_manual_host_key(
|
int verify_ssh_manual_host_key(
|
||||||
Conf *conf, const char *fingerprint, ssh_key *key);
|
Conf *conf, const char *fingerprint, ssh_key *key);
|
||||||
|
543
ssh2transport.c
543
ssh2transport.c
@ -146,6 +146,14 @@ typedef enum RekeyClass {
|
|||||||
RK_GSS_UPDATE
|
RK_GSS_UPDATE
|
||||||
} RekeyClass;
|
} RekeyClass;
|
||||||
|
|
||||||
|
typedef struct transport_direction {
|
||||||
|
const struct ssh2_cipheralg *cipher;
|
||||||
|
const struct ssh2_macalg *mac;
|
||||||
|
int etm_mode;
|
||||||
|
const struct ssh_compression_alg *comp;
|
||||||
|
int comp_delayed;
|
||||||
|
} transport_direction;
|
||||||
|
|
||||||
struct ssh2_transport_state {
|
struct ssh2_transport_state {
|
||||||
int crState;
|
int crState;
|
||||||
|
|
||||||
@ -201,13 +209,7 @@ struct ssh2_transport_state {
|
|||||||
Bignum p, g, e, f, K;
|
Bignum p, g, e, f, K;
|
||||||
strbuf *client_kexinit, *server_kexinit;
|
strbuf *client_kexinit, *server_kexinit;
|
||||||
int kex_init_value, kex_reply_value;
|
int kex_init_value, kex_reply_value;
|
||||||
struct {
|
transport_direction in, out;
|
||||||
const struct ssh2_cipheralg *cipher;
|
|
||||||
const struct ssh2_macalg *mac;
|
|
||||||
int etm_mode;
|
|
||||||
const struct ssh_compression_alg *comp;
|
|
||||||
int comp_delayed;
|
|
||||||
} in, out;
|
|
||||||
ptrlen hostkeydata, sigdata;
|
ptrlen hostkeydata, sigdata;
|
||||||
char *keystr, *fingerprint;
|
char *keystr, *fingerprint;
|
||||||
ssh_key *hkey; /* actual host key */
|
ssh_key *hkey; /* actual host key */
|
||||||
@ -901,6 +903,217 @@ static void ssh2_write_kexinit_lists(
|
|||||||
put_stringz(pktout, "");
|
put_stringz(pktout, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ssh2_scan_kexinits(
|
||||||
|
ptrlen client_kexinit, ptrlen server_kexinit,
|
||||||
|
struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST],
|
||||||
|
const struct ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
|
||||||
|
transport_direction *cs, transport_direction *sc,
|
||||||
|
int *warn_kex, int *warn_hk, int *warn_cscipher, int *warn_sccipher,
|
||||||
|
Ssh *ssh, int *ignore_guess_cs_packet, int *ignore_guess_sc_packet,
|
||||||
|
int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST])
|
||||||
|
{
|
||||||
|
BinarySource client[1], server[1];
|
||||||
|
int i;
|
||||||
|
int guess_correct;
|
||||||
|
ptrlen clists[NKEXLIST], slists[NKEXLIST];
|
||||||
|
const struct kexinit_algorithm *selected[NKEXLIST];
|
||||||
|
|
||||||
|
BinarySource_BARE_INIT(client, client_kexinit.ptr, client_kexinit.len);
|
||||||
|
BinarySource_BARE_INIT(server, server_kexinit.ptr, server_kexinit.len);
|
||||||
|
|
||||||
|
/* Skip packet type bytes and random cookies. */
|
||||||
|
get_data(client, 1 + 16);
|
||||||
|
get_data(server, 1 + 16);
|
||||||
|
|
||||||
|
guess_correct = TRUE;
|
||||||
|
|
||||||
|
/* Find the matching string in each list, and map it to its
|
||||||
|
* kexinit_algorithm structure. */
|
||||||
|
for (i = 0; i < NKEXLIST; i++) {
|
||||||
|
ptrlen clist, slist, cword, sword, found;
|
||||||
|
int cfirst, sfirst, j;
|
||||||
|
|
||||||
|
clists[i] = get_string(client);
|
||||||
|
slists[i] = get_string(server);
|
||||||
|
if (get_err(client) || get_err(server)) {
|
||||||
|
/* Report a better error than the spurious "Couldn't
|
||||||
|
* agree" that we'd generate if we pressed on regardless
|
||||||
|
* and treated the empty get_string() result as genuine */
|
||||||
|
ssh_proto_error(ssh, "KEXINIT packet was incomplete");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cfirst = TRUE, clist = clists[i];
|
||||||
|
get_commasep_word(&clist, &cword); cfirst = FALSE)
|
||||||
|
for (sfirst = TRUE, slist = slists[i];
|
||||||
|
get_commasep_word(&slist, &sword); sfirst = FALSE)
|
||||||
|
if (ptrlen_eq_ptrlen(cword, sword)) {
|
||||||
|
found = cword;
|
||||||
|
goto found_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No matching string found in the two lists. Delay reporting
|
||||||
|
* a fatal error until below, because sometimes it turns out
|
||||||
|
* not to be fatal. */
|
||||||
|
selected[i] = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* However, even if a failure to agree on any algorithm at all
|
||||||
|
* is not completely fatal (e.g. because it's the MAC
|
||||||
|
* negotiation for a cipher that comes with a built-in MAC),
|
||||||
|
* it still invalidates the guessed key exchange packet. (RFC
|
||||||
|
* 4253 section 7, not contradicted by OpenSSH's
|
||||||
|
* PROTOCOL.chacha20poly1305 or as far as I can see by their
|
||||||
|
* code.)
|
||||||
|
*/
|
||||||
|
guess_correct = FALSE;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
found_match:
|
||||||
|
|
||||||
|
selected[i] = NULL;
|
||||||
|
for (j = 0; j < MAXKEXLIST; j++) {
|
||||||
|
if (ptrlen_eq_string(found, kexlists[i][j].name)) {
|
||||||
|
selected[i] = &kexlists[i][j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(selected[i]); /* kexlists[] must cover one of the inputs */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the kex or host key algorithm is not the first one in
|
||||||
|
* both sides' lists, that means the guessed key exchange
|
||||||
|
* packet (if any) is officially wrong.
|
||||||
|
*/
|
||||||
|
if ((i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) && !(cfirst || sfirst))
|
||||||
|
guess_correct = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip language strings in both KEXINITs, and read the flags
|
||||||
|
* saying whether a guessed KEX packet follows.
|
||||||
|
*/
|
||||||
|
get_string(client);
|
||||||
|
get_string(client);
|
||||||
|
get_string(server);
|
||||||
|
get_string(server);
|
||||||
|
if (ignore_guess_cs_packet)
|
||||||
|
*ignore_guess_cs_packet = get_bool(client) && !guess_correct;
|
||||||
|
if (ignore_guess_sc_packet)
|
||||||
|
*ignore_guess_sc_packet = get_bool(server) && !guess_correct;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now transcribe the selected algorithm set into the output data.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < NKEXLIST; i++) {
|
||||||
|
const struct kexinit_algorithm *alg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we've already selected a cipher which requires a
|
||||||
|
* particular MAC, then just select that. This is the case in
|
||||||
|
* which it's not a fatal error if the actual MAC string lists
|
||||||
|
* didn't include any matching error.
|
||||||
|
*/
|
||||||
|
if (i == KEXLIST_CSMAC && cs->cipher &&
|
||||||
|
cs->cipher->required_mac) {
|
||||||
|
cs->mac = cs->cipher->required_mac;
|
||||||
|
cs->etm_mode = !!(cs->mac->etm_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == KEXLIST_SCMAC && sc->cipher &&
|
||||||
|
sc->cipher->required_mac) {
|
||||||
|
sc->mac = sc->cipher->required_mac;
|
||||||
|
sc->etm_mode = !!(sc->mac->etm_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
alg = selected[i];
|
||||||
|
if (!alg) {
|
||||||
|
/*
|
||||||
|
* Otherwise, any match failure _is_ a fatal error.
|
||||||
|
*/
|
||||||
|
ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
|
||||||
|
kexlist_descr[i], PTRLEN_PRINTF(slists[i]));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case KEXLIST_KEX:
|
||||||
|
*kex_alg = alg->u.kex.kex;
|
||||||
|
*warn_kex = alg->u.kex.warn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_HOSTKEY:
|
||||||
|
/*
|
||||||
|
* Ignore an unexpected/inappropriate offer of "null",
|
||||||
|
* we offer "null" when we're willing to use GSS KEX,
|
||||||
|
* but it is only acceptable when GSSKEX is actually
|
||||||
|
* selected.
|
||||||
|
*/
|
||||||
|
if (alg->u.hk.hostkey == NULL &&
|
||||||
|
(*kex_alg)->main_type != KEXTYPE_GSS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*hostkey_alg = alg->u.hk.hostkey;
|
||||||
|
*warn_hk = alg->u.hk.warn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_CSCIPHER:
|
||||||
|
cs->cipher = alg->u.cipher.cipher;
|
||||||
|
*warn_cscipher = alg->u.cipher.warn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_SCCIPHER:
|
||||||
|
sc->cipher = alg->u.cipher.cipher;
|
||||||
|
*warn_sccipher = alg->u.cipher.warn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_CSMAC:
|
||||||
|
cs->mac = alg->u.mac.mac;
|
||||||
|
cs->etm_mode = alg->u.mac.etm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_SCMAC:
|
||||||
|
sc->mac = alg->u.mac.mac;
|
||||||
|
sc->etm_mode = alg->u.mac.etm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_CSCOMP:
|
||||||
|
cs->comp = alg->u.comp.comp;
|
||||||
|
cs->comp_delayed = alg->u.comp.delayed;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEXLIST_SCCOMP:
|
||||||
|
sc->comp = alg->u.comp.comp;
|
||||||
|
sc->comp_delayed = alg->u.comp.delayed;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(FALSE && "Bad list index in scan_kexinits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server_hostkeys) {
|
||||||
|
/*
|
||||||
|
* Finally, make an auxiliary pass over the server's host key
|
||||||
|
* list to find all the host key algorithms offered by the
|
||||||
|
* server which we know about at all, whether we selected each
|
||||||
|
* one or not. We return these as a list of indices into the
|
||||||
|
* constant hostkey_algs[] array.
|
||||||
|
*/
|
||||||
|
*n_server_hostkeys = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < lenof(hostkey_algs); i++)
|
||||||
|
if (in_commasep_string(hostkey_algs[i].alg->ssh_id,
|
||||||
|
slists[KEXLIST_HOSTKEY].ptr,
|
||||||
|
slists[KEXLIST_HOSTKEY].len))
|
||||||
|
server_hostkeys[(*n_server_hostkeys)++] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||||
{
|
{
|
||||||
struct ssh2_transport_state *s =
|
struct ssh2_transport_state *s =
|
||||||
@ -1026,238 +1239,146 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
|||||||
put_data(s->server_kexinit, get_ptr(pktin), get_avail(pktin));
|
put_data(s->server_kexinit, get_ptr(pktin), get_avail(pktin));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now examine the other side's KEXINIT to see what we're up
|
* Work through the two KEXINIT packets in parallel to find the
|
||||||
* to.
|
* selected algorithm identifiers.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
ptrlen str;
|
int nhk, hks[MAXKEXLIST], i, j;
|
||||||
int i, j;
|
|
||||||
|
|
||||||
s->kex_alg = NULL;
|
if (!ssh2_scan_kexinits(
|
||||||
s->hostkey_alg = NULL;
|
ptrlen_from_strbuf(s->client_kexinit),
|
||||||
s->in.cipher = s->out.cipher = NULL;
|
ptrlen_from_strbuf(s->server_kexinit),
|
||||||
s->in.mac = s->out.mac = NULL;
|
s->kexlists, &s->kex_alg, &s->hostkey_alg, &s->out, &s->in,
|
||||||
s->in.comp = s->out.comp = NULL;
|
&s->warn_kex, &s->warn_hk, &s->warn_cscipher,
|
||||||
s->in.comp_delayed = s->out.comp_delayed = FALSE;
|
&s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks))
|
||||||
s->warn_kex = s->warn_hk = FALSE;
|
return; /* FALSE means a fatal error function was called */
|
||||||
s->warn_cscipher = s->warn_sccipher = FALSE;
|
|
||||||
|
|
||||||
get_data(pktin, 16); /* skip garbage cookie */
|
/*
|
||||||
|
* In addition to deciding which host key we're actually going
|
||||||
|
* to use, we should make a list of the host keys offered by
|
||||||
|
* the server which we _don't_ have cached. These will be
|
||||||
|
* offered as cross-certification options by ssh_get_specials.
|
||||||
|
*
|
||||||
|
* We also count the key we're currently using for KEX as one
|
||||||
|
* we've already got, because by the time this menu becomes
|
||||||
|
* visible, it will be.
|
||||||
|
*/
|
||||||
|
s->n_uncert_hostkeys = 0;
|
||||||
|
|
||||||
s->guessok = FALSE;
|
for (i = 0; i < nhk; i++) {
|
||||||
for (i = 0; i < NKEXLIST; i++) {
|
j = hks[i];
|
||||||
str = get_string(pktin);
|
if (hostkey_algs[j].alg != s->hostkey_alg &&
|
||||||
if (get_err(pktin)) {
|
!have_ssh_host_key(s->savedhost, s->savedport,
|
||||||
ssh_proto_error(s->ppl.ssh, "KEXINIT packet was incomplete");
|
hostkey_algs[j].alg->cache_id)) {
|
||||||
return;
|
s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* If we've already selected a cipher which requires a
|
s->exhash = ssh_hash_new(s->kex_alg->hash);
|
||||||
* particular MAC, then just select that, and don't even
|
put_stringz(s->exhash, s->client_greeting);
|
||||||
* bother looking through the server's KEXINIT string for
|
put_stringz(s->exhash, s->server_greeting);
|
||||||
* MACs. */
|
put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
|
||||||
if (i == KEXLIST_CSMAC && s->out.cipher &&
|
put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len);
|
||||||
s->out.cipher->required_mac) {
|
|
||||||
s->out.mac = s->out.cipher->required_mac;
|
|
||||||
s->out.etm_mode = !!(s->out.mac->etm_name);
|
|
||||||
goto matched;
|
|
||||||
}
|
|
||||||
if (i == KEXLIST_SCMAC && s->in.cipher &&
|
|
||||||
s->in.cipher->required_mac) {
|
|
||||||
s->in.mac = s->in.cipher->required_mac;
|
|
||||||
s->in.etm_mode = !!(s->in.mac->etm_name);
|
|
||||||
goto matched;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < MAXKEXLIST; j++) {
|
if (s->warn_kex) {
|
||||||
struct kexinit_algorithm *alg = &s->kexlists[i][j];
|
s->dlgret = seat_confirm_weak_crypto_primitive(
|
||||||
if (alg->name == NULL) break;
|
s->ppl.seat, "key-exchange algorithm", s->kex_alg->name,
|
||||||
if (in_commasep_string(alg->name, str.ptr, str.len)) {
|
ssh2_transport_dialog_callback, s);
|
||||||
/* We've found a matching algorithm. */
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
||||||
if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) {
|
if (s->dlgret == 0) {
|
||||||
/* Check if we might need to ignore first kex pkt */
|
ssh_user_close(s->ppl.ssh, "User aborted at kex warning");
|
||||||
if (j != 0 ||
|
|
||||||
!first_in_commasep_string(alg->name,
|
|
||||||
str.ptr, str.len))
|
|
||||||
s->guessok = FALSE;
|
|
||||||
}
|
|
||||||
if (i == KEXLIST_KEX) {
|
|
||||||
s->kex_alg = alg->u.kex.kex;
|
|
||||||
s->warn_kex = alg->u.kex.warn;
|
|
||||||
} else if (i == KEXLIST_HOSTKEY) {
|
|
||||||
/*
|
|
||||||
* Ignore an unexpected/inappropriate offer of "null",
|
|
||||||
* we offer "null" when we're willing to use GSS KEX,
|
|
||||||
* but it is only acceptable when GSSKEX is actually
|
|
||||||
* selected.
|
|
||||||
*/
|
|
||||||
if (alg->u.hk.hostkey == NULL &&
|
|
||||||
s->kex_alg->main_type != KEXTYPE_GSS)
|
|
||||||
continue;
|
|
||||||
s->hostkey_alg = alg->u.hk.hostkey;
|
|
||||||
s->warn_hk = alg->u.hk.warn;
|
|
||||||
} else if (i == KEXLIST_CSCIPHER) {
|
|
||||||
s->out.cipher = alg->u.cipher.cipher;
|
|
||||||
s->warn_cscipher = alg->u.cipher.warn;
|
|
||||||
} else if (i == KEXLIST_SCCIPHER) {
|
|
||||||
s->in.cipher = alg->u.cipher.cipher;
|
|
||||||
s->warn_sccipher = alg->u.cipher.warn;
|
|
||||||
} else if (i == KEXLIST_CSMAC) {
|
|
||||||
s->out.mac = alg->u.mac.mac;
|
|
||||||
s->out.etm_mode = alg->u.mac.etm;
|
|
||||||
} else if (i == KEXLIST_SCMAC) {
|
|
||||||
s->in.mac = alg->u.mac.mac;
|
|
||||||
s->in.etm_mode = alg->u.mac.etm;
|
|
||||||
} else if (i == KEXLIST_CSCOMP) {
|
|
||||||
s->out.comp = alg->u.comp.comp;
|
|
||||||
s->out.comp_delayed = alg->u.comp.delayed;
|
|
||||||
} else if (i == KEXLIST_SCCOMP) {
|
|
||||||
s->in.comp = alg->u.comp.comp;
|
|
||||||
s->in.comp_delayed = alg->u.comp.delayed;
|
|
||||||
}
|
|
||||||
goto matched;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ssh_sw_abort(s->ppl.ssh, "Couldn't agree a %s (available: %.*s)",
|
|
||||||
kexlist_descr[i], PTRLEN_PRINTF(str));
|
|
||||||
return;
|
return;
|
||||||
matched:;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (i == KEXLIST_HOSTKEY &&
|
if (s->warn_hk) {
|
||||||
!s->gss_kex_used &&
|
int j, k;
|
||||||
s->kex_alg->main_type != KEXTYPE_GSS) {
|
char *betteralgs;
|
||||||
int j;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In addition to deciding which host key we're
|
* Change warning box wording depending on why we chose a
|
||||||
* actually going to use, we should make a list of the
|
* warning-level host key algorithm. If it's because
|
||||||
* host keys offered by the server which we _don't_
|
* that's all we have *cached*, list the host keys we
|
||||||
* have cached. These will be offered as cross-
|
* could usefully cross-certify. Otherwise, use the same
|
||||||
* certification options by ssh_get_specials.
|
* standard wording as any other weak crypto primitive.
|
||||||
*
|
*/
|
||||||
* We also count the key we're currently using for KEX
|
betteralgs = NULL;
|
||||||
* as one we've already got, because by the time this
|
for (j = 0; j < s->n_uncert_hostkeys; j++) {
|
||||||
* menu becomes visible, it will be.
|
const struct ssh_signkey_with_user_pref_id *hktype =
|
||||||
*/
|
&hostkey_algs[s->uncert_hostkeys[j]];
|
||||||
s->n_uncert_hostkeys = 0;
|
int better = FALSE;
|
||||||
|
for (k = 0; k < HK_MAX; k++) {
|
||||||
for (j = 0; j < lenof(hostkey_algs); j++) {
|
int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k);
|
||||||
if (hostkey_algs[j].alg != s->hostkey_alg &&
|
if (id == HK_WARN) {
|
||||||
in_commasep_string(hostkey_algs[j].alg->ssh_id,
|
break;
|
||||||
str.ptr, str.len) &&
|
} else if (id == hktype->id) {
|
||||||
!have_ssh_host_key(s->savedhost, s->savedport,
|
better = TRUE;
|
||||||
hostkey_algs[j].alg->cache_id)) {
|
break;
|
||||||
s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
|
}
|
||||||
}
|
}
|
||||||
|
if (better) {
|
||||||
|
if (betteralgs) {
|
||||||
|
char *old_ba = betteralgs;
|
||||||
|
betteralgs = dupcat(betteralgs, ",",
|
||||||
|
hktype->alg->ssh_id,
|
||||||
|
(const char *)NULL);
|
||||||
|
sfree(old_ba);
|
||||||
|
} else {
|
||||||
|
betteralgs = dupstr(hktype->alg->ssh_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (betteralgs) {
|
||||||
get_string(pktin); /* client->server language */
|
|
||||||
get_string(pktin); /* server->client language */
|
|
||||||
s->ignorepkt = get_bool(pktin) && !s->guessok;
|
|
||||||
|
|
||||||
s->exhash = ssh_hash_new(s->kex_alg->hash);
|
|
||||||
put_stringz(s->exhash, s->client_greeting);
|
|
||||||
put_stringz(s->exhash, s->server_greeting);
|
|
||||||
put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
|
|
||||||
put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len);
|
|
||||||
|
|
||||||
if (s->warn_kex) {
|
|
||||||
s->dlgret = seat_confirm_weak_crypto_primitive(
|
|
||||||
s->ppl.seat, "key-exchange algorithm", s->kex_alg->name,
|
|
||||||
ssh2_transport_dialog_callback, s);
|
|
||||||
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
||||||
if (s->dlgret == 0) {
|
|
||||||
ssh_user_close(s->ppl.ssh, "User aborted at kex warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->warn_hk) {
|
|
||||||
int j, k;
|
|
||||||
char *betteralgs;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Change warning box wording depending on why we chose a
|
|
||||||
* warning-level host key algorithm. If it's because
|
|
||||||
* that's all we have *cached*, list the host keys we
|
|
||||||
* could usefully cross-certify. Otherwise, use the same
|
|
||||||
* standard wording as any other weak crypto primitive.
|
|
||||||
*/
|
|
||||||
betteralgs = NULL;
|
|
||||||
for (j = 0; j < s->n_uncert_hostkeys; j++) {
|
|
||||||
const struct ssh_signkey_with_user_pref_id *hktype =
|
|
||||||
&hostkey_algs[s->uncert_hostkeys[j]];
|
|
||||||
int better = FALSE;
|
|
||||||
for (k = 0; k < HK_MAX; k++) {
|
|
||||||
int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k);
|
|
||||||
if (id == HK_WARN) {
|
|
||||||
break;
|
|
||||||
} else if (id == hktype->id) {
|
|
||||||
better = TRUE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (better) {
|
|
||||||
if (betteralgs) {
|
|
||||||
char *old_ba = betteralgs;
|
|
||||||
betteralgs = dupcat(betteralgs, ",",
|
|
||||||
hktype->alg->ssh_id,
|
|
||||||
(const char *)NULL);
|
|
||||||
sfree(old_ba);
|
|
||||||
} else {
|
|
||||||
betteralgs = dupstr(hktype->alg->ssh_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (betteralgs) {
|
|
||||||
/* Use the special warning prompt that lets us provide
|
/* Use the special warning prompt that lets us provide
|
||||||
* a list of better algorithms */
|
* a list of better algorithms */
|
||||||
s->dlgret = seat_confirm_weak_cached_hostkey(
|
s->dlgret = seat_confirm_weak_cached_hostkey(
|
||||||
s->ppl.seat, s->hostkey_alg->ssh_id, betteralgs,
|
s->ppl.seat, s->hostkey_alg->ssh_id, betteralgs,
|
||||||
ssh2_transport_dialog_callback, s);
|
ssh2_transport_dialog_callback, s);
|
||||||
sfree(betteralgs);
|
sfree(betteralgs);
|
||||||
} else {
|
} else {
|
||||||
/* If none exist, use the more general 'weak crypto'
|
/* If none exist, use the more general 'weak crypto'
|
||||||
* warning prompt */
|
* warning prompt */
|
||||||
s->dlgret = seat_confirm_weak_crypto_primitive(
|
s->dlgret = seat_confirm_weak_crypto_primitive(
|
||||||
s->ppl.seat, "host key type", s->hostkey_alg->ssh_id,
|
s->ppl.seat, "host key type", s->hostkey_alg->ssh_id,
|
||||||
ssh2_transport_dialog_callback, s);
|
ssh2_transport_dialog_callback, s);
|
||||||
}
|
|
||||||
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
||||||
if (s->dlgret == 0) {
|
|
||||||
ssh_user_close(s->ppl.ssh, "User aborted at host key warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
||||||
if (s->warn_cscipher) {
|
if (s->dlgret == 0) {
|
||||||
s->dlgret = seat_confirm_weak_crypto_primitive(
|
ssh_user_close(s->ppl.ssh, "User aborted at host key warning");
|
||||||
s->ppl.seat, "client-to-server cipher", s->out.cipher->name,
|
return;
|
||||||
ssh2_transport_dialog_callback, s);
|
|
||||||
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
||||||
if (s->dlgret == 0) {
|
|
||||||
ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->warn_sccipher) {
|
|
||||||
s->dlgret = seat_confirm_weak_crypto_primitive(
|
|
||||||
s->ppl.seat, "server-to-client cipher", s->in.cipher->name,
|
|
||||||
ssh2_transport_dialog_callback, s);
|
|
||||||
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
||||||
if (s->dlgret == 0) {
|
|
||||||
ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->ignorepkt) /* first_kex_packet_follows */
|
|
||||||
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s->warn_cscipher) {
|
||||||
|
s->dlgret = seat_confirm_weak_crypto_primitive(
|
||||||
|
s->ppl.seat, "client-to-server cipher", s->out.cipher->name,
|
||||||
|
ssh2_transport_dialog_callback, s);
|
||||||
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
||||||
|
if (s->dlgret == 0) {
|
||||||
|
ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->warn_sccipher) {
|
||||||
|
s->dlgret = seat_confirm_weak_crypto_primitive(
|
||||||
|
s->ppl.seat, "server-to-client cipher", s->in.cipher->name,
|
||||||
|
ssh2_transport_dialog_callback, s);
|
||||||
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
||||||
|
if (s->dlgret == 0) {
|
||||||
|
ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the other side has sent an initial key exchange packet that
|
||||||
|
* we must treat as a wrong guess, wait for it, and discard it.
|
||||||
|
*/
|
||||||
|
if (s->ignorepkt)
|
||||||
|
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
|
||||||
|
|
||||||
if (s->kex_alg->main_type == KEXTYPE_DH) {
|
if (s->kex_alg->main_type == KEXTYPE_DH) {
|
||||||
/*
|
/*
|
||||||
* Work out the number of bits of key we will need from the
|
* Work out the number of bits of key we will need from the
|
||||||
|
32
sshcommon.c
32
sshcommon.c
@ -596,6 +596,38 @@ void add_to_commasep(strbuf *buf, const char *data)
|
|||||||
put_data(buf, data, strlen(data));
|
put_data(buf, data, strlen(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_commasep_word(ptrlen *list, ptrlen *word)
|
||||||
|
{
|
||||||
|
const char *comma;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Discard empty list elements, should there be any, because we
|
||||||
|
* never want to return one as if it was a real string. (This
|
||||||
|
* introduces a mild tolerance of badly formatted data in lists we
|
||||||
|
* receive, but I think that's acceptable.)
|
||||||
|
*/
|
||||||
|
while (list->len > 0 && *(const char *)list->ptr == ',') {
|
||||||
|
list->ptr = (const char *)list->ptr + 1;
|
||||||
|
list->len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!list->len)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
comma = memchr(list->ptr, ',', list->len);
|
||||||
|
if (!comma) {
|
||||||
|
*word = *list;
|
||||||
|
list->len = 0;
|
||||||
|
} else {
|
||||||
|
size_t wordlen = comma - (const char *)list->ptr;
|
||||||
|
word->ptr = list->ptr;
|
||||||
|
word->len = wordlen;
|
||||||
|
list->ptr = (const char *)list->ptr + wordlen + 1;
|
||||||
|
list->len -= wordlen + 1;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
/* ----------------------------------------------------------------------
|
||||||
* Functions for translating SSH packet type codes into their symbolic
|
* Functions for translating SSH packet type codes into their symbolic
|
||||||
* string names.
|
* string names.
|
||||||
|
Loading…
Reference in New Issue
Block a user