version 1.159, 2020/07/05 23:59:45 |
version 1.160, 2020/10/18 11:32:01 |
|
|
goto out; |
goto out; |
} |
} |
if ((r = sshbuf_consume(b, KEX_COOKIE_LEN)) != 0) { /* skip cookie */ |
if ((r = sshbuf_consume(b, KEX_COOKIE_LEN)) != 0) { /* skip cookie */ |
error("%s: consume cookie: %s", __func__, ssh_err(r)); |
error_fr(r, "consume cookie"); |
goto out; |
goto out; |
} |
} |
/* extract kex init proposal strings */ |
/* extract kex init proposal strings */ |
for (i = 0; i < PROPOSAL_MAX; i++) { |
for (i = 0; i < PROPOSAL_MAX; i++) { |
if ((r = sshbuf_get_cstring(b, &(proposal[i]), NULL)) != 0) { |
if ((r = sshbuf_get_cstring(b, &(proposal[i]), NULL)) != 0) { |
error("%s: parse proposal %u: %s", __func__, |
error_fr(r, "parse proposal %u", i); |
i, ssh_err(r)); |
|
goto out; |
goto out; |
} |
} |
debug2("%s: %s", proposal_names[i], proposal[i]); |
debug2("%s: %s", proposal_names[i], proposal[i]); |
|
|
/* first kex follows / reserved */ |
/* first kex follows / reserved */ |
if ((r = sshbuf_get_u8(b, &v)) != 0 || /* first_kex_follows */ |
if ((r = sshbuf_get_u8(b, &v)) != 0 || /* first_kex_follows */ |
(r = sshbuf_get_u32(b, &i)) != 0) { /* reserved */ |
(r = sshbuf_get_u32(b, &i)) != 0) { /* reserved */ |
error("%s: parse: %s", __func__, ssh_err(r)); |
error_fr(r, "parse"); |
goto out; |
goto out; |
} |
} |
if (first_kex_follows != NULL) |
if (first_kex_follows != NULL) |
|
|
(r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || |
(r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || |
(r = sshpkt_put_cstring(ssh, algs)) != 0 || |
(r = sshpkt_put_cstring(ssh, algs)) != 0 || |
(r = sshpkt_send(ssh)) != 0) { |
(r = sshpkt_send(ssh)) != 0) { |
error("%s: compose: %s", __func__, ssh_err(r)); |
error_fr(r, "compose"); |
goto out; |
goto out; |
} |
} |
/* success */ |
/* success */ |
|
|
if (strcmp(name, "server-sig-algs") == 0) { |
if (strcmp(name, "server-sig-algs") == 0) { |
/* Ensure no \0 lurking in value */ |
/* Ensure no \0 lurking in value */ |
if (memchr(val, '\0', vlen) != NULL) { |
if (memchr(val, '\0', vlen) != NULL) { |
error("%s: nul byte in %s", __func__, name); |
error_f("nul byte in %s", name); |
return SSH_ERR_INVALID_FORMAT; |
return SSH_ERR_INVALID_FORMAT; |
} |
} |
debug("%s: %s=<%s>", __func__, name, val); |
debug_f("%s=<%s>", name, val); |
kex->server_sig_algs = val; |
kex->server_sig_algs = val; |
val = NULL; |
val = NULL; |
} else |
} else |
debug("%s: %s (unrecognised)", __func__, name); |
debug_f("%s (unrecognised)", name); |
free(name); |
free(name); |
free(val); |
free(val); |
} |
} |
|
|
int r; |
int r; |
|
|
if (kex == NULL) { |
if (kex == NULL) { |
error("%s: no hex", __func__); |
error_f("no hex"); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
if (kex->flags & KEX_INIT_SENT) |
if (kex->flags & KEX_INIT_SENT) |
|
|
|
|
/* generate a random cookie */ |
/* generate a random cookie */ |
if (sshbuf_len(kex->my) < KEX_COOKIE_LEN) { |
if (sshbuf_len(kex->my) < KEX_COOKIE_LEN) { |
error("%s: bad kex length: %zu < %d", __func__, |
error_f("bad kex length: %zu < %d", |
sshbuf_len(kex->my), KEX_COOKIE_LEN); |
sshbuf_len(kex->my), KEX_COOKIE_LEN); |
return SSH_ERR_INVALID_FORMAT; |
return SSH_ERR_INVALID_FORMAT; |
} |
} |
if ((cookie = sshbuf_mutable_ptr(kex->my)) == NULL) { |
if ((cookie = sshbuf_mutable_ptr(kex->my)) == NULL) { |
error("%s: buffer error", __func__); |
error_f("buffer error"); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
arc4random_buf(cookie, KEX_COOKIE_LEN); |
arc4random_buf(cookie, KEX_COOKIE_LEN); |
|
|
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXINIT)) != 0 || |
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXINIT)) != 0 || |
(r = sshpkt_putb(ssh, kex->my)) != 0 || |
(r = sshpkt_putb(ssh, kex->my)) != 0 || |
(r = sshpkt_send(ssh)) != 0) { |
(r = sshpkt_send(ssh)) != 0) { |
error("%s: compose reply: %s", __func__, ssh_err(r)); |
error_fr(r, "compose reply"); |
return r; |
return r; |
} |
} |
debug("SSH2_MSG_KEXINIT sent"); |
debug("SSH2_MSG_KEXINIT sent"); |
|
|
|
|
debug("SSH2_MSG_KEXINIT received"); |
debug("SSH2_MSG_KEXINIT received"); |
if (kex == NULL) { |
if (kex == NULL) { |
error("%s: no hex", __func__); |
error_f("no hex"); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL); |
ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, NULL); |
|
|
/* discard packet */ |
/* discard packet */ |
for (i = 0; i < KEX_COOKIE_LEN; i++) { |
for (i = 0; i < KEX_COOKIE_LEN; i++) { |
if ((r = sshpkt_get_u8(ssh, NULL)) != 0) { |
if ((r = sshpkt_get_u8(ssh, NULL)) != 0) { |
error("%s: discard cookie: %s", __func__, ssh_err(r)); |
error_fr(r, "discard cookie"); |
return r; |
return r; |
} |
} |
} |
} |
for (i = 0; i < PROPOSAL_MAX; i++) { |
for (i = 0; i < PROPOSAL_MAX; i++) { |
if ((r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { |
if ((r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { |
error("%s: discard proposal: %s", __func__, ssh_err(r)); |
error_fr(r, "discard proposal"); |
return r; |
return r; |
} |
} |
} |
} |
|
|
if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) |
if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) |
return (kex->kex[kex->kex_type])(ssh); |
return (kex->kex[kex->kex_type])(ssh); |
|
|
error("%s: unknown kex type %u", __func__, kex->kex_type); |
error_f("unknown kex type %u", kex->kex_type); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
|
|
|
|
kex_start_rekex(struct ssh *ssh) |
kex_start_rekex(struct ssh *ssh) |
{ |
{ |
if (ssh->kex == NULL) { |
if (ssh->kex == NULL) { |
error("%s: no kex", __func__); |
error_f("no kex"); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
if (ssh->kex->done == 0) { |
if (ssh->kex->done == 0) { |
error("%s: requested twice", __func__); |
error_f("requested twice"); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
ssh->kex->done = 0; |
ssh->kex->done = 0; |
|
|
if (name == NULL) |
if (name == NULL) |
return SSH_ERR_NO_CIPHER_ALG_MATCH; |
return SSH_ERR_NO_CIPHER_ALG_MATCH; |
if ((enc->cipher = cipher_by_name(name)) == NULL) { |
if ((enc->cipher = cipher_by_name(name)) == NULL) { |
error("%s: unsupported cipher %s", __func__, name); |
error_f("unsupported cipher %s", name); |
free(name); |
free(name); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
|
|
if (name == NULL) |
if (name == NULL) |
return SSH_ERR_NO_MAC_ALG_MATCH; |
return SSH_ERR_NO_MAC_ALG_MATCH; |
if (mac_setup(mac, name) < 0) { |
if (mac_setup(mac, name) < 0) { |
error("%s: unsupported MAC %s", __func__, name); |
error_f("unsupported MAC %s", name); |
free(name); |
free(name); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
|
|
if (strcmp(name, "none") == 0) { |
if (strcmp(name, "none") == 0) { |
comp->type = COMP_NONE; |
comp->type = COMP_NONE; |
} else { |
} else { |
error("%s: unsupported compression scheme %s", __func__, name); |
error_f("unsupported compression scheme %s", name); |
free(name); |
free(name); |
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
|
|
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 ((kexalg = kex_alg_by_name(k->name)) == NULL) { |
error("%s: unsupported KEX method %s", __func__, 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 = kexalg->type; |
|
|
return SSH_ERR_NO_HOSTKEY_ALG_MATCH; |
return SSH_ERR_NO_HOSTKEY_ALG_MATCH; |
k->hostkey_type = sshkey_type_from_name(k->hostkey_alg); |
k->hostkey_type = sshkey_type_from_name(k->hostkey_alg); |
if (k->hostkey_type == KEY_UNSPEC) { |
if (k->hostkey_type == KEY_UNSPEC) { |
error("%s: unsupported hostkey algorithm %s", __func__, |
error_f("unsupported hostkey algorithm %s", k->hostkey_alg); |
k->hostkey_alg); |
|
return SSH_ERR_INTERNAL_ERROR; |
return SSH_ERR_INTERNAL_ERROR; |
} |
} |
k->hostkey_nid = sshkey_ecdsa_nid_from_name(k->hostkey_alg); |
k->hostkey_nid = sshkey_ecdsa_nid_from_name(k->hostkey_alg); |
|
|
kex->session_id_len) != 0 || |
kex->session_id_len) != 0 || |
ssh_digest_final(hashctx, digest, mdsz) != 0) { |
ssh_digest_final(hashctx, digest, mdsz) != 0) { |
r = SSH_ERR_LIBCRYPTO_ERROR; |
r = SSH_ERR_LIBCRYPTO_ERROR; |
error("%s: KEX hash failed", __func__); |
error_f("KEX hash failed"); |
goto out; |
goto out; |
} |
} |
ssh_digest_free(hashctx); |
ssh_digest_free(hashctx); |
|
|
ssh_digest_update(hashctx, hash, hashlen) != 0 || |
ssh_digest_update(hashctx, hash, hashlen) != 0 || |
ssh_digest_update(hashctx, digest, have) != 0 || |
ssh_digest_update(hashctx, digest, have) != 0 || |
ssh_digest_final(hashctx, digest + have, mdsz) != 0) { |
ssh_digest_final(hashctx, digest + have, mdsz) != 0) { |
error("%s: KDF failed", __func__); |
error_f("KDF failed"); |
r = SSH_ERR_LIBCRYPTO_ERROR; |
r = SSH_ERR_LIBCRYPTO_ERROR; |
goto out; |
goto out; |
} |
} |
|
|
*prvp = NULL; |
*prvp = NULL; |
if (kex->load_host_public_key == NULL || |
if (kex->load_host_public_key == NULL || |
kex->load_host_private_key == NULL) { |
kex->load_host_private_key == NULL) { |
error("%s: missing hostkey loader", __func__); |
error_f("missing hostkey loader"); |
return SSH_ERR_INVALID_ARGUMENT; |
return SSH_ERR_INVALID_ARGUMENT; |
} |
} |
*pubp = kex->load_host_public_key(kex->hostkey_type, |
*pubp = kex->load_host_public_key(kex->hostkey_type, |
|
|
struct kex *kex = ssh->kex; |
struct kex *kex = ssh->kex; |
|
|
if (kex->verify_host_key == NULL) { |
if (kex->verify_host_key == NULL) { |
error("%s: missing hostkey verifier", __func__); |
error_f("missing hostkey verifier"); |
return SSH_ERR_INVALID_ARGUMENT; |
return SSH_ERR_INVALID_ARGUMENT; |
} |
} |
if (server_host_key->type != kex->hostkey_type || |
if (server_host_key->type != kex->hostkey_type || |
|
|
msg, strlen(msg)) != strlen(msg) || |
msg, strlen(msg)) != strlen(msg) || |
atomicio(vwrite, ssh_packet_get_connection_out(ssh), |
atomicio(vwrite, ssh_packet_get_connection_out(ssh), |
crnl, strlen(crnl)) != strlen(crnl)) |
crnl, strlen(crnl)) != strlen(crnl)) |
error("%s: write: %.100s", __func__, strerror(errno)); |
error_f("write: %.100s", strerror(errno)); |
} |
} |
|
|
/* |
/* |
|
|
version_addendum == NULL ? "" : " ", |
version_addendum == NULL ? "" : " ", |
version_addendum == NULL ? "" : version_addendum)) != 0) { |
version_addendum == NULL ? "" : version_addendum)) != 0) { |
oerrno = errno; |
oerrno = errno; |
error("%s: sshbuf_putf: %s", __func__, ssh_err(r)); |
error_fr(r, "sshbuf_putf"); |
goto out; |
goto out; |
} |
} |
|
|
|
|
sshbuf_mutable_ptr(our_version), |
sshbuf_mutable_ptr(our_version), |
sshbuf_len(our_version)) != sshbuf_len(our_version)) { |
sshbuf_len(our_version)) != sshbuf_len(our_version)) { |
oerrno = errno; |
oerrno = errno; |
debug("%s: write: %.100s", __func__, strerror(errno)); |
debug_f("write: %.100s", strerror(errno)); |
r = SSH_ERR_SYSTEM_ERROR; |
r = SSH_ERR_SYSTEM_ERROR; |
goto out; |
goto out; |
} |
} |
if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ |
if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ |
oerrno = errno; |
oerrno = errno; |
error("%s: sshbuf_consume_end: %s", __func__, ssh_err(r)); |
error_fr(r, "sshbuf_consume_end"); |
goto out; |
goto out; |
} |
} |
our_version_string = sshbuf_dup_string(our_version); |
our_version_string = sshbuf_dup_string(our_version); |
if (our_version_string == NULL) { |
if (our_version_string == NULL) { |
error("%s: sshbuf_dup_string failed", __func__); |
error_f("sshbuf_dup_string failed"); |
r = SSH_ERR_ALLOC_FAIL; |
r = SSH_ERR_ALLOC_FAIL; |
goto out; |
goto out; |
} |
} |
|
|
if (n >= SSH_MAX_PRE_BANNER_LINES) { |
if (n >= SSH_MAX_PRE_BANNER_LINES) { |
send_error(ssh, "No SSH identification string " |
send_error(ssh, "No SSH identification string " |
"received."); |
"received."); |
error("%s: No SSH version received in first %u lines " |
error_f("No SSH version received in first %u lines " |
"from server", __func__, SSH_MAX_PRE_BANNER_LINES); |
"from server", SSH_MAX_PRE_BANNER_LINES); |
r = SSH_ERR_INVALID_FORMAT; |
r = SSH_ERR_INVALID_FORMAT; |
goto out; |
goto out; |
} |
} |
|
|
goto out; |
goto out; |
} else if (r == -1) { |
} else if (r == -1) { |
oerrno = errno; |
oerrno = errno; |
error("%s: %s", |
error_f("%s", strerror(errno)); |
__func__, strerror(errno)); |
|
r = SSH_ERR_SYSTEM_ERROR; |
r = SSH_ERR_SYSTEM_ERROR; |
goto out; |
goto out; |
} |
} |
|
|
len = atomicio(read, ssh_packet_get_connection_in(ssh), |
len = atomicio(read, ssh_packet_get_connection_in(ssh), |
&c, 1); |
&c, 1); |
if (len != 1 && errno == EPIPE) { |
if (len != 1 && errno == EPIPE) { |
error("%s: Connection closed by remote host", |
error_f("Connection closed by remote host"); |
__func__); |
|
r = SSH_ERR_CONN_CLOSED; |
r = SSH_ERR_CONN_CLOSED; |
goto out; |
goto out; |
} else if (len != 1) { |
} else if (len != 1) { |
oerrno = errno; |
oerrno = errno; |
error("%s: read: %.100s", |
error_f("read: %.100s", strerror(errno)); |
__func__, strerror(errno)); |
|
r = SSH_ERR_SYSTEM_ERROR; |
r = SSH_ERR_SYSTEM_ERROR; |
goto out; |
goto out; |
} |
} |
|
|
if (c == '\n') |
if (c == '\n') |
break; |
break; |
if (c == '\0' || expect_nl) { |
if (c == '\0' || expect_nl) { |
error("%s: banner line contains invalid " |
error_f("banner line contains invalid " |
"characters", __func__); |
"characters"); |
goto invalid; |
goto invalid; |
} |
} |
if ((r = sshbuf_put_u8(peer_version, c)) != 0) { |
if ((r = sshbuf_put_u8(peer_version, c)) != 0) { |
oerrno = errno; |
oerrno = errno; |
error("%s: sshbuf_put: %s", |
error_fr(r, "sshbuf_put"); |
__func__, ssh_err(r)); |
|
goto out; |
goto out; |
} |
} |
if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) { |
if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) { |
error("%s: banner line too long", __func__); |
error_f("banner line too long"); |
goto invalid; |
goto invalid; |
} |
} |
} |
} |
|
|
break; |
break; |
/* If not, then just log the line and continue */ |
/* If not, then just log the line and continue */ |
if ((cp = sshbuf_dup_string(peer_version)) == NULL) { |
if ((cp = sshbuf_dup_string(peer_version)) == NULL) { |
error("%s: sshbuf_dup_string failed", __func__); |
error_f("sshbuf_dup_string failed"); |
r = SSH_ERR_ALLOC_FAIL; |
r = SSH_ERR_ALLOC_FAIL; |
goto out; |
goto out; |
} |
} |
/* Do not accept lines before the SSH ident from a client */ |
/* Do not accept lines before the SSH ident from a client */ |
if (ssh->kex->server) { |
if (ssh->kex->server) { |
error("%s: client sent invalid protocol identifier " |
error_f("client sent invalid protocol identifier " |
"\"%.256s\"", __func__, cp); |
"\"%.256s\"", cp); |
free(cp); |
free(cp); |
goto invalid; |
goto invalid; |
} |
} |
debug("%s: banner line %zu: %s", __func__, n, cp); |
debug_f("banner line %zu: %s", n, cp); |
free(cp); |
free(cp); |
} |
} |
peer_version_string = sshbuf_dup_string(peer_version); |
peer_version_string = sshbuf_dup_string(peer_version); |
if (peer_version_string == NULL) |
if (peer_version_string == NULL) |
error("%s: sshbuf_dup_string failed", __func__); |
error_f("sshbuf_dup_string failed"); |
/* XXX must be same size for sscanf */ |
/* XXX must be same size for sscanf */ |
if ((remote_version = calloc(1, sshbuf_len(peer_version))) == NULL) { |
if ((remote_version = calloc(1, sshbuf_len(peer_version))) == NULL) { |
error("%s: calloc failed", __func__); |
error_f("calloc failed"); |
r = SSH_ERR_ALLOC_FAIL; |
r = SSH_ERR_ALLOC_FAIL; |
goto out; |
goto out; |
} |
} |