Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'ssh/transport2.c')
-rw-r--r--ssh/transport2.c2407
1 files changed, 2407 insertions, 0 deletions
diff --git a/ssh/transport2.c b/ssh/transport2.c
new file mode 100644
index 00000000..ce6318aa
--- /dev/null
+++ b/ssh/transport2.c
@@ -0,0 +1,2407 @@
+/*
+ * Packet protocol layer for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "server.h"
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = {
+ #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type },
+ HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM)
+};
+
+const static ssh2_macalg *const macs[] = {
+ &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+};
+const static ssh2_macalg *const buggymacs[] = {
+ &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
+};
+
+static ssh_compressor *ssh_comp_none_init(void)
+{
+ return NULL;
+}
+static void ssh_comp_none_cleanup(ssh_compressor *handle)
+{
+}
+static ssh_decompressor *ssh_decomp_none_init(void)
+{
+ return NULL;
+}
+static void ssh_decomp_none_cleanup(ssh_decompressor *handle)
+{
+}
+static void ssh_comp_none_block(ssh_compressor *handle,
+ const unsigned char *block, int len,
+ unsigned char **outblock, int *outlen,
+ int minlen)
+{
+}
+static bool ssh_decomp_none_block(ssh_decompressor *handle,
+ const unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ return false;
+}
+static const ssh_compression_alg ssh_comp_none = {
+ .name = "none",
+ .delayed_name = NULL,
+ .compress_new = ssh_comp_none_init,
+ .compress_free = ssh_comp_none_cleanup,
+ .compress = ssh_comp_none_block,
+ .decompress_new = ssh_decomp_none_init,
+ .decompress_free = ssh_decomp_none_cleanup,
+ .decompress = ssh_decomp_none_block,
+ .text_name = NULL,
+};
+const static ssh_compression_alg *const compressions[] = {
+ &ssh_zlib, &ssh_comp_none
+};
+
+static void ssh2_transport_free(PacketProtocolLayer *);
+static void ssh2_transport_process_queue(PacketProtocolLayer *);
+static bool ssh2_transport_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg);
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl);
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
+static void ssh2_transport_higher_layer_packet_callback(void *context);
+
+static const PacketProtocolLayerVtable ssh2_transport_vtable = {
+ .free = ssh2_transport_free,
+ .process_queue = ssh2_transport_process_queue,
+ .get_specials = ssh2_transport_get_specials,
+ .special_cmd = ssh2_transport_special_cmd,
+ .reconfigure = ssh2_transport_reconfigure,
+ .queued_data_size = ssh2_transport_queued_data_size,
+ .name = NULL, /* no protocol name for this layer */
+};
+
+#ifndef NO_GSSAPI
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+ bool definitely_rekeying);
+#endif
+
+static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
+ unsigned long rekey_time);
+static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
+ struct ssh2_transport_state *s, const char *type, const char *name,
+ const void *alg);
+
+static const char *const kexlist_descr[NKEXLIST] = {
+ "key exchange algorithm",
+ "host key algorithm",
+ "client-to-server cipher",
+ "server-to-client cipher",
+ "client-to-server MAC",
+ "server-to-client MAC",
+ "client-to-server compression method",
+ "server-to-client compression method"
+};
+
+static int weak_algorithm_compare(void *av, void *bv);
+static int ca_blob_compare(void *av, void *bv);
+
+PacketProtocolLayer *ssh2_transport_new(
+ Conf *conf, const char *host, int port, const char *fullhostname,
+ const char *client_greeting, const char *server_greeting,
+ struct ssh_connection_shared_gss_state *shgss,
+ struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
+ const SshServerConfig *ssc)
+{
+ struct ssh2_transport_state *s = snew(struct ssh2_transport_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh2_transport_vtable;
+
+ s->conf = conf_copy(conf);
+ s->savedhost = dupstr(host);
+ s->savedport = port;
+ s->fullhostname = dupstr(fullhostname);
+ s->shgss = shgss;
+ s->client_greeting = dupstr(client_greeting);
+ s->server_greeting = dupstr(server_greeting);
+ s->stats = stats;
+ s->hostkeyblob = strbuf_new();
+ s->host_cas = newtree234(ca_blob_compare);
+
+ pq_in_init(&s->pq_in_higher);
+ pq_out_init(&s->pq_out_higher);
+ s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher;
+ s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback;
+ s->ic_pq_out_higher.ctx = &s->ppl;
+
+ s->higher_layer = higher_layer;
+ s->higher_layer->selfptr = &s->higher_layer;
+ ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher);
+
+#ifndef NO_GSSAPI
+ s->gss_cred_expiry = GSS_NO_EXPIRATION;
+ s->shgss->srv_name = GSS_C_NO_NAME;
+ s->shgss->ctx = NULL;
+#endif
+ s->thc = ssh_transient_hostkey_cache_new();
+ s->gss_kex_used = false;
+
+ s->outgoing_kexinit = strbuf_new();
+ s->incoming_kexinit = strbuf_new();
+ if (ssc) {
+ s->ssc = ssc;
+ s->client_kexinit = s->incoming_kexinit;
+ s->server_kexinit = s->outgoing_kexinit;
+ s->cstrans = &s->in;
+ s->sctrans = &s->out;
+ s->out.mkkey_adjust = 1;
+ } else {
+ s->client_kexinit = s->outgoing_kexinit;
+ s->server_kexinit = s->incoming_kexinit;
+ s->cstrans = &s->out;
+ s->sctrans = &s->in;
+ s->in.mkkey_adjust = 1;
+ }
+
+ s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare);
+
+ ssh2_transport_set_max_data_size(s);
+
+ return &s->ppl;
+}
+
+static void ssh2_transport_free(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ /*
+ * As our last act before being freed, move any outgoing packets
+ * off our higher layer's output queue on to our own output queue.
+ * We might be being freed while the SSH connection is still alive
+ * (because we're initiating shutdown from our end), in which case
+ * we don't want those last few packets to get lost.
+ *
+ * (If our owner were to have already destroyed our output pq
+ * before wanting to free us, then it would have to reset our
+ * publicly visible out_pq field to NULL to inhibit this attempt.
+ * But that's not how I expect the shutdown sequence to go in
+ * practice.)
+ */
+ if (s->ppl.out_pq)
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+ conf_free(s->conf);
+
+ ssh_ppl_free(s->higher_layer);
+
+ pq_in_clear(&s->pq_in_higher);
+ pq_out_clear(&s->pq_out_higher);
+
+ sfree(s->savedhost);
+ sfree(s->fullhostname);
+ sfree(s->client_greeting);
+ sfree(s->server_greeting);
+ sfree(s->keystr);
+ strbuf_free(s->hostkeyblob);
+ {
+ host_ca *hca;
+ while ( (hca = delpos234(s->host_cas, 0)) )
+ host_ca_free(hca);
+ freetree234(s->host_cas);
+ }
+ if (s->hkey && !s->hostkeys) {
+ ssh_key_free(s->hkey);
+ s->hkey = NULL;
+ }
+ for (size_t i = 0; i < NKEXLIST; i++)
+ sfree(s->kexlists[i].algs);
+ if (s->f) mp_free(s->f);
+ if (s->p) mp_free(s->p);
+ if (s->g) mp_free(s->g);
+ if (s->ebuf) strbuf_free(s->ebuf);
+ if (s->fbuf) strbuf_free(s->fbuf);
+ if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret);
+ if (s->dh_ctx)
+ dh_cleanup(s->dh_ctx);
+ if (s->rsa_kex_key_needs_freeing) {
+ ssh_rsakex_freekey(s->rsa_kex_key);
+ sfree(s->rsa_kex_key);
+ }
+ if (s->ecdh_key)
+ ecdh_key_free(s->ecdh_key);
+ if (s->exhash)
+ ssh_hash_free(s->exhash);
+ strbuf_free(s->outgoing_kexinit);
+ strbuf_free(s->incoming_kexinit);
+ ssh_transient_hostkey_cache_free(s->thc);
+
+ freetree234(s->weak_algorithms_consented_to);
+
+ expire_timer_context(s);
+ sfree(s);
+}
+
+/*
+ * SSH-2 key derivation (RFC 4253 section 7.2).
+ */
+static void ssh2_mkkey(
+ struct ssh2_transport_state *s, strbuf *out,
+ strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen)
+{
+ int hlen = s->kex_alg->hash->hlen;
+ int keylen_padded;
+ unsigned char *key;
+ ssh_hash *h;
+
+ if (keylen == 0)
+ return;
+
+ /*
+ * Round the requested amount of key material up to a multiple of
+ * the length of the hash we're using to make it. This makes life
+ * simpler because then we can just write each hash output block
+ * straight into the output buffer without fiddling about
+ * truncating the last one. Since it's going into a strbuf, and
+ * strbufs are always smemclr()ed on free, there's no need to
+ * worry about leaving extra potentially-sensitive data in memory
+ * that the caller didn't ask for.
+ */
+ keylen_padded = ((keylen + hlen - 1) / hlen) * hlen;
+
+ strbuf_clear(out);
+ key = strbuf_append(out, keylen_padded);
+
+ /* First hlen bytes. */
+ h = ssh_hash_new(s->kex_alg->hash);
+ if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+ put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
+ put_data(h, H, hlen);
+ put_byte(h, chr);
+ put_data(h, s->session_id, s->session_id_len);
+ ssh_hash_digest(h, key);
+
+ /* Subsequent blocks of hlen bytes. */
+ if (keylen_padded > hlen) {
+ int offset;
+
+ ssh_hash_reset(h);
+ if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+ put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
+ put_data(h, H, hlen);
+
+ for (offset = hlen; offset < keylen_padded; offset += hlen) {
+ put_data(h, key + offset - hlen, hlen);
+ ssh_hash_digest_nondestructive(h, key + offset);
+ }
+
+ }
+
+ ssh_hash_free(h);
+}
+
+/*
+ * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
+ * If the algorithm is already in the list, return a pointer to its
+ * entry, otherwise return an entry from the end of the list.
+ *
+ * 'name' is expected to be a ptrlen which it's safe to keep a copy
+ * of.
+ */
+static struct kexinit_algorithm *ssh2_kexinit_addalg_pl(
+ struct kexinit_algorithm_list *list, ptrlen name)
+{
+ for (size_t i = 0; i < list->nalgs; i++)
+ if (ptrlen_eq_ptrlen(list->algs[i].name, name))
+ return &list->algs[i];
+
+ sgrowarray(list->algs, list->algsize, list->nalgs);
+ struct kexinit_algorithm *entry = &list->algs[list->nalgs++];
+ entry->name = name;
+ return entry;
+}
+
+static struct kexinit_algorithm *ssh2_kexinit_addalg(
+ struct kexinit_algorithm_list *list, const char *name)
+{
+ return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name));
+}
+
+bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
+{
+ static const char *const ssh2_disconnect_reasons[] = {
+ NULL,
+ "host not allowed to connect",
+ "protocol error",
+ "key exchange failed",
+ "host authentication failed",
+ "MAC error",
+ "compression error",
+ "service not available",
+ "protocol version not supported",
+ "host key not verifiable",
+ "connection lost",
+ "by application",
+ "too many connections",
+ "auth cancelled by user",
+ "no more auth methods available",
+ "illegal user name",
+ };
+
+ PktIn *pktin;
+ ptrlen msg;
+ int reason;
+
+ while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+ switch (pktin->type) {
+ case SSH2_MSG_DISCONNECT:
+ reason = get_uint32(pktin);
+ msg = get_string(pktin);
+
+ ssh_remote_error(
+ ppl->ssh, "Remote side sent disconnect message\n"
+ "type %d (%s):\n\"%.*s\"", reason,
+ ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+ ssh2_disconnect_reasons[reason] : "unknown"),
+ PTRLEN_PRINTF(msg));
+ /* don't try to pop the queue, because we've been freed! */
+ return true; /* indicate that we've been freed */
+
+ case SSH2_MSG_DEBUG:
+ /* XXX maybe we should actually take notice of the return value */
+ get_bool(pktin);
+ msg = get_string(pktin);
+ ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
+ pq_pop(ppl->in_pq);
+ break;
+
+ case SSH2_MSG_IGNORE:
+ /* Do nothing, because we're ignoring it! Duhh. */
+ pq_pop(ppl->in_pq);
+ break;
+
+ case SSH2_MSG_EXT_INFO: {
+ /*
+ * The BPP enforces that these turn up only at legal
+ * points in the protocol. In particular, it will not pass
+ * an EXT_INFO on to us if it arrives before encryption is
+ * enabled (which is when a MITM could inject one
+ * maliciously).
+ *
+ * However, one of the criteria for legality is that a
+ * server is permitted to send this message immediately
+ * _before_ USERAUTH_SUCCESS. So we may receive this
+ * message not yet knowing whether it's legal to have sent
+ * it - we won't know until the BPP processes the next
+ * packet.
+ *
+ * But that should be OK, because firstly, an
+ * out-of-sequence EXT_INFO that's still within the
+ * encrypted session is only a _protocol_ violation, not
+ * an attack; secondly, any data we set in response to
+ * such an illegal EXT_INFO won't have a chance to affect
+ * the session before the BPP aborts it anyway.
+ */
+ uint32_t nexts = get_uint32(pktin);
+ for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) {
+ ptrlen extname = get_string(pktin);
+ ptrlen extvalue = get_string(pktin);
+ if (ptrlen_eq_string(extname, "server-sig-algs")) {
+ /*
+ * Server has sent a list of signature algorithms
+ * it will potentially accept for user
+ * authentication keys. Check in particular
+ * whether the RFC 8332 improved versions of
+ * ssh-rsa are in the list, and set flags in the
+ * BPP if so.
+ *
+ * TODO: another thing we _could_ do here is to
+ * record a full list of the algorithm identifiers
+ * we've seen, whether we understand them
+ * ourselves or not. Then we could use that as a
+ * pre-filter during userauth, to skip keys in the
+ * SSH agent if we already know the server can't
+ * possibly accept them. (Even if the key
+ * algorithm is one that the agent and the server
+ * both understand but we do not.)
+ */
+ ptrlen algname;
+ while (get_commasep_word(&extvalue, &algname)) {
+ if (ptrlen_eq_string(algname, "rsa-sha2-256"))
+ ppl->bpp->ext_info_rsa_sha256_ok = true;
+ if (ptrlen_eq_string(algname, "rsa-sha2-512"))
+ ppl->bpp->ext_info_rsa_sha512_ok = true;
+ }
+ }
+ }
+ pq_pop(ppl->in_pq);
+ break;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s)
+{
+ PktIn *pktin;
+
+ while (1) {
+ if (ssh2_common_filter_queue(&s->ppl))
+ return true;
+ if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+ return false;
+
+ /* Pass on packets to the next layer if they're outside
+ * the range reserved for the transport protocol. */
+ if (pktin->type >= 50) {
+ /* ... except that we shouldn't tolerate higher-layer
+ * packets coming from the server before we've seen
+ * the first NEWKEYS. */
+ if (!s->higher_layer_ok) {
+ ssh_proto_error(s->ppl.ssh, "Received premature higher-"
+ "layer packet, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return true;
+ }
+
+ pq_pop(s->ppl.in_pq);
+ pq_push(&s->pq_in_higher, pktin);
+ } else {
+ /* Anything else is a transport-layer packet that the main
+ * process_queue coroutine should handle. */
+ return false;
+ }
+ }
+}
+
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
+{
+ if (ssh2_transport_filter_queue(s))
+ return NULL; /* we've been freed */
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_write_kexinit_lists(
+ BinarySink *pktout,
+ struct kexinit_algorithm_list kexlists[NKEXLIST],
+ Conf *conf, const SshServerConfig *ssc, int remote_bugs,
+ const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
+ ssh_transient_hostkey_cache *thc, tree234 *host_cas,
+ ssh_key *const *our_hostkeys, int our_nhostkeys,
+ bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
+{
+ int i, j, k;
+ bool warn;
+
+ int n_preferred_kex;
+ const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */
+ int n_preferred_hk;
+ int preferred_hk[HK_MAX];
+ int n_preferred_ciphers;
+ const ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ const ssh_compression_alg *preferred_comp;
+ const ssh2_macalg *const *maclist;
+ int nmacs;
+
+ struct kexinit_algorithm *alg;
+
+ /*
+ * Set up the preferred key exchange. (NULL => warn below here)
+ */
+ n_preferred_kex = 0;
+ if (can_gssapi_keyex) {
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex;
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex;
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex;
+ }
+ for (i = 0; i < KEX_MAX; i++) {
+ switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
+ case KEX_DHGEX:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_gex;
+ break;
+ case KEX_DHGROUP18:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group18;
+ break;
+ case KEX_DHGROUP17:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group17;
+ break;
+ case KEX_DHGROUP16:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group16;
+ break;
+ case KEX_DHGROUP15:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group15;
+ break;
+ case KEX_DHGROUP14:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group14;
+ break;
+ case KEX_DHGROUP1:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group1;
+ break;
+ case KEX_RSA:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_rsa_kex;
+ break;
+ case KEX_ECDH:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_ecdh_kex;
+ break;
+ case KEX_NTRU_HYBRID:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_ntru_hybrid_kex;
+ break;
+ case KEX_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < KEX_MAX - 1) {
+ preferred_kex[n_preferred_kex++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up the preferred host key types. These are just the ids
+ * in the enum in putty.h, so 'warn below here' is indicated
+ * by HK_WARN.
+ */
+ n_preferred_hk = 0;
+ for (i = 0; i < HK_MAX; i++) {
+ int id = conf_get_int_int(conf, CONF_ssh_hklist, i);
+ /* As above, don't bother with HK_WARN if it's last in the
+ * list */
+ if (id != HK_WARN || i < HK_MAX - 1)
+ preferred_hk[n_preferred_hk++] = id;
+ }
+
+ /*
+ * Set up the preferred ciphers. (NULL => warn below here)
+ */
+ n_preferred_ciphers = 0;
+ for (i = 0; i < CIPHER_MAX; i++) {
+ switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+ case CIPHER_BLOWFISH:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish;
+ break;
+ case CIPHER_DES:
+ if (conf_get_bool(conf, CONF_ssh2_des_cbc))
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_des;
+ break;
+ case CIPHER_3DES:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des;
+ break;
+ case CIPHER_AES:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes;
+ break;
+ case CIPHER_ARCFOUR:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour;
+ break;
+ case CIPHER_CHACHA20:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
+ break;
+ case CIPHER_AESGCM:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < CIPHER_MAX - 1) {
+ preferred_ciphers[n_preferred_ciphers++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up preferred compression.
+ */
+ if (conf_get_bool(conf, CONF_compression))
+ preferred_comp = &ssh_zlib;
+ else
+ preferred_comp = &ssh_comp_none;
+
+ for (i = 0; i < NKEXLIST; i++)
+ kexlists[i].nalgs = 0;
+ /* List key exchange algorithms. */
+ warn = false;
+ for (i = 0; i < n_preferred_kex; i++) {
+ const ssh_kexes *k = preferred_kex[i];
+ if (!k) warn = true;
+ else for (j = 0; j < k->nkexes; j++) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX],
+ k->list[j]->name);
+ alg->u.kex.kex = k->list[j];
+ alg->u.kex.warn = warn;
+ }
+ }
+ /* List server host key algorithms. */
+ if (our_hostkeys) {
+ /*
+ * In server mode, we just list the algorithms that match the
+ * host keys we actually have.
+ */
+ for (i = 0; i < our_nhostkeys; i++) {
+ const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
+
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ keyalg->ssh_id);
+ alg->u.hk.hostkey = keyalg;
+ alg->u.hk.hkflags = 0;
+ alg->u.hk.warn = false;
+
+ uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg);
+ static const uint32_t try_flags[] = {
+ SSH_AGENT_RSA_SHA2_256,
+ SSH_AGENT_RSA_SHA2_512,
+ };
+ for (size_t i = 0; i < lenof(try_flags); i++) {
+ if (try_flags[i] & ~supported_flags)
+ continue; /* these flags not supported */
+
+ alg = ssh2_kexinit_addalg(
+ &kexlists[KEXLIST_HOSTKEY],
+ ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i]));
+
+ alg->u.hk.hostkey = keyalg;
+ alg->u.hk.hkflags = try_flags[i];
+ alg->u.hk.warn = false;
+ }
+ }
+ } else if (first_time) {
+ /*
+ * In the first key exchange, we list all the algorithms we're
+ * prepared to cope with, but (if configured to) we prefer
+ * those algorithms for which we have a host key for this
+ * host.
+ *
+ * If the host key algorithm is below the warning
+ * threshold, we warn even if we did already have a key
+ * for it, on the basis that if the user has just
+ * reconfigured that host key type to be warned about,
+ * they surely _do_ want to be alerted that a server
+ * they're actually connecting to is using it.
+ */
+
+ bool accept_certs = false;
+ {
+ host_ca_enum *handle = enum_host_ca_start();
+ if (handle) {
+ strbuf *name = strbuf_new();
+ while (strbuf_clear(name), enum_host_ca_next(handle, name)) {
+ host_ca *hca = host_ca_load(name->s);
+ if (!hca)
+ continue;
+
+ if (hca->ca_public_key &&
+ cert_expr_match_str(hca->validity_expression,
+ hk_host, hk_port)) {
+ accept_certs = true;
+ add234(host_cas, hca);
+ } else {
+ host_ca_free(hca);
+ }
+ }
+ enum_host_ca_finish(handle);
+ strbuf_free(name);
+ }
+ }
+
+ if (accept_certs) {
+ /* Add all the certificate algorithms first, in preference order */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (!a->alg->is_certificate)
+ continue;
+ if (a->id != preferred_hk[i])
+ continue;
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+
+ /* Next, add algorithms we already know a key for (unless
+ * configured not to do that) */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (a->alg->is_certificate && accept_certs)
+ continue; /* already added this one */
+ if (a->id != preferred_hk[i])
+ continue;
+ if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
+ have_ssh_host_key(hk_host, hk_port,
+ a->alg->cache_id)) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+
+ /* And finally, everything else */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (a->alg->is_certificate)
+ continue;
+ if (a->id != preferred_hk[i])
+ continue;
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+#ifndef NO_GSSAPI
+ } else if (transient_hostkey_mode) {
+ /*
+ * If we've previously done a GSSAPI KEX, then we list
+ * precisely the algorithms for which a previous GSS key
+ * exchange has delivered us a host key, because we expect
+ * one of exactly those keys to be used in any subsequent
+ * non-GSS-based rekey.
+ *
+ * An exception is if this is the key exchange we
+ * triggered for the purposes of populating that cache -
+ * in which case the cache will currently be empty, which
+ * isn't helpful!
+ */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+ continue;
+ if (ssh_transient_hostkey_cache_has(
+ thc, ssh2_hostkey_algs[j].alg)) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ ssh2_hostkey_algs[j].alg->ssh_id);
+ alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+#endif
+ } else {
+ /*
+ * In subsequent key exchanges, we list only the host key
+ * algorithm that was selected in the first key exchange,
+ * so that we keep getting the same host key and hence
+ * don't have to interrupt the user's session to ask for
+ * reverification.
+ */
+ assert(hk_prev);
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id);
+ alg->u.hk.hostkey = hk_prev;
+ alg->u.hk.warn = false;
+ }
+ if (can_gssapi_keyex) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null");
+ alg->u.hk.hostkey = NULL;
+ }
+ /* List encryption algorithms (client->server then server->client). */
+ for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
+ warn = false;
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(&kexlists[K], "none");
+ alg->u.cipher.cipher = NULL;
+ alg->u.cipher.warn = warn;
+#endif /* FUZZING */
+ for (i = 0; i < n_preferred_ciphers; i++) {
+ const ssh2_ciphers *c = preferred_ciphers[i];
+ if (!c) warn = true;
+ else for (j = 0; j < c->nciphers; j++) {
+ alg = ssh2_kexinit_addalg(&kexlists[k],
+ c->list[j]->ssh2_id);
+ alg->u.cipher.cipher = c->list[j];
+ alg->u.cipher.warn = warn;
+ }
+ }
+ }
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (remote_bugs & BUG_SSH2_HMAC) {
+ maclist = buggymacs;
+ nmacs = lenof(buggymacs);
+ } else {
+ maclist = macs;
+ nmacs = lenof(macs);
+ }
+
+ /* List MAC algorithms (client->server then server->client). */
+ for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(kexlists[j], "none");
+ alg->u.mac.mac = NULL;
+ alg->u.mac.etm = false;
+#endif /* FUZZING */
+ for (i = 0; i < nmacs; i++) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name);
+ alg->u.mac.mac = maclist[i];
+ alg->u.mac.etm = false;
+ }
+ for (i = 0; i < nmacs; i++) {
+ /* For each MAC, there may also be an ETM version,
+ * which we list second. */
+ if (maclist[i]->etm_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name);
+ alg->u.mac.mac = maclist[i];
+ alg->u.mac.etm = true;
+ }
+ }
+ }
+
+ /* List client->server compression algorithms,
+ * then server->client compression algorithms. (We use the
+ * same set twice.) */
+ for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
+ assert(lenof(compressions) > 1);
+ /* Prefer non-delayed versions */
+ alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name);
+ alg->u.comp.comp = preferred_comp;
+ alg->u.comp.delayed = false;
+ if (preferred_comp->delayed_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j],
+ preferred_comp->delayed_name);
+ alg->u.comp.comp = preferred_comp;
+ alg->u.comp.delayed = true;
+ }
+ for (i = 0; i < lenof(compressions); i++) {
+ const ssh_compression_alg *c = compressions[i];
+ alg = ssh2_kexinit_addalg(&kexlists[j], c->name);
+ alg->u.comp.comp = c;
+ alg->u.comp.delayed = false;
+ if (c->delayed_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name);
+ alg->u.comp.comp = c;
+ alg->u.comp.delayed = true;
+ }
+ }
+ }
+
+ /*
+ * Finally, format the lists into text and write them into the
+ * outgoing KEXINIT packet.
+ */
+ for (i = 0; i < NKEXLIST; i++) {
+ strbuf *list = strbuf_new();
+ if (ssc && ssc->kex_override[i].ptr) {
+ put_datapl(list, ssc->kex_override[i]);
+ } else {
+ for (j = 0; j < kexlists[i].nalgs; j++)
+ add_to_commasep_pl(list, kexlists[i].algs[j].name);
+ }
+ if (i == KEXLIST_KEX && first_time) {
+ if (our_hostkeys) /* we're the server */
+ add_to_commasep(list, "ext-info-s");
+ else /* we're the client */
+ add_to_commasep(list, "ext-info-c");
+ }
+ put_stringsb(pktout, list);
+ }
+ /* List client->server languages. Empty list. */
+ put_stringz(pktout, "");
+ /* List server->client languages. Empty list. */
+ put_stringz(pktout, "");
+}
+
+struct server_hostkeys {
+ int *indices;
+ size_t n, size;
+};
+
+static bool ssh2_scan_kexinits(
+ ptrlen client_kexinit, ptrlen server_kexinit,
+ struct kexinit_algorithm_list kexlists[NKEXLIST],
+ const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
+ transport_direction *cs, transport_direction *sc,
+ bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
+ Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
+ struct server_hostkeys *server_hostkeys, unsigned *hkflags,
+ bool *can_send_ext_info)
+{
+ BinarySource client[1], server[1];
+ int i;
+ bool guess_correct;
+ ptrlen clists[NKEXLIST], slists[NKEXLIST];
+ const struct kexinit_algorithm *selected[NKEXLIST];
+
+ BinarySource_BARE_INIT_PL(client, client_kexinit);
+ BinarySource_BARE_INIT_PL(server, server_kexinit);
+
+ /* 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;
+ bool cfirst, sfirst;
+ int 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 < kexlists[i].nalgs; j++) {
+ if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) {
+ selected[i] = &kexlists[i].algs[j];
+ break;
+ }
+ }
+ if (!selected[i]) {
+ /*
+ * In the client, this should never happen! But in the
+ * server, where we allow manual override on the command
+ * line of the exact KEXINIT strings, it can happen
+ * because the command line contained a typo. So we
+ * produce a reasonably useful message instead of an
+ * assertion failure.
+ */
+ ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
+ "any supported algorithm",
+ kexlist_descr[i], PTRLEN_PRINTF(found));
+ return false;
+ }
+
+ /*
+ * 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;
+ *hkflags = alg->u.hk.hkflags;
+ *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:
+ unreachable("Bad list index in scan_kexinits");
+ }
+ }
+
+ /*
+ * Check whether the other side advertised support for EXT_INFO.
+ */
+ {
+ ptrlen extinfo_advert =
+ (server_hostkeys ? PTRLEN_LITERAL("ext-info-c") :
+ PTRLEN_LITERAL("ext-info-s"));
+ ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] :
+ slists[KEXLIST_KEX]);
+ for (ptrlen word; get_commasep_word(&list, &word) ;)
+ if (ptrlen_eq_ptrlen(word, extinfo_advert))
+ *can_send_ext_info = true;
+ }
+
+ 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 ssh2_hostkey_algs[] array.
+ */
+ ptrlen list = slists[KEXLIST_HOSTKEY];
+ for (ptrlen word; get_commasep_word(&list, &word) ;) {
+ for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
+ if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) {
+ sgrowarray(server_hostkeys->indices, server_hostkeys->size,
+ server_hostkeys->n);
+ server_hostkeys->indices[server_hostkeys->n++] = i;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+ if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT))
+ return false; /* bug flag not enabled => no need to delay */
+ if (s->incoming_kexinit->len)
+ return false; /* already got a remote KEXINIT we can filter against */
+ return true;
+}
+
+static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+ strbuf *pktout = strbuf_new();
+ BinarySource osrc[1], isrc[1];
+ BinarySource_BARE_INIT(
+ osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len);
+ BinarySource_BARE_INIT(
+ isrc, s->incoming_kexinit->u, s->incoming_kexinit->len);
+
+ /* Skip the packet type bytes from both packets */
+ get_byte(osrc);
+ get_byte(isrc);
+
+ /* Copy our cookie into the real output packet; skip their cookie */
+ put_datapl(pktout, get_data(osrc, 16));
+ get_data(isrc, 16);
+
+ /*
+ * Now we expect NKEXLIST+2 name-lists. We write into the outgoing
+ * packet a subset of our intended outgoing one, containing only
+ * names mentioned in the incoming out.
+ *
+ * NKEXLIST+2 because for this purpose we treat the 'languages'
+ * lists the same as the rest. In the rest of this code base we
+ * ignore those.
+ */
+ strbuf *out = strbuf_new();
+ for (size_t i = 0; i < NKEXLIST+2; i++) {
+ strbuf_clear(out);
+ ptrlen olist = get_string(osrc), ilist = get_string(isrc);
+ for (ptrlen oword; get_commasep_word(&olist, &oword) ;) {
+ ptrlen ilist_copy = ilist;
+ bool add = false;
+ for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) {
+ if (ptrlen_eq_ptrlen(oword, iword)) {
+ /* Found this word in the incoming list. */
+ add = true;
+ break;
+ }
+ }
+
+ if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) {
+ /* Special case: this will _never_ match anything from the
+ * server, and we need it to enable SHA-2 based RSA.
+ *
+ * If this ever turns out to confuse any server all by
+ * itself then I suppose we'll need an even more
+ * draconian bug flag to exclude that too. (Obv, such
+ * a server wouldn't be able to speak SHA-2 RSA
+ * anyway.) */
+ add = true;
+ }
+
+ if (add)
+ add_to_commasep_pl(out, oword);
+ }
+ put_stringpl(pktout, ptrlen_from_strbuf(out));
+ }
+ strbuf_free(out);
+
+ /*
+ * Finally, copy the remaining parts of our intended KEXINIT.
+ */
+ put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */
+ put_uint32(pktout, get_uint32(osrc)); /* reserved word */
+
+ /*
+ * Dump this data into s->outgoing_kexinit in place of what we had
+ * there before. We need to remember the KEXINIT we _really_ sent,
+ * not the one we'd have liked to send, since the host key
+ * signature will be validated against the former.
+ */
+ strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */
+ put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout));
+ strbuf_free(pktout);
+}
+
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s)
+{
+ put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret));
+ assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
+ ssh_hash_final(s->exhash, s->exchange_hash);
+ s->exhash = NULL;
+
+#if 0
+ debug("Exchange hash is:\n");
+ dmemdump(s->exchange_hash, s->kex_alg->hash->hlen);
+#endif
+}
+
+static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+ PktIn *pktin;
+ PktOut *pktout;
+
+ /* Filter centrally handled messages off the front of the queue on
+ * every entry to this coroutine, no matter where we're resuming
+ * from, even if we're _not_ looping on pq_pop. That way we can
+ * still proactively handle those messages even if we're waiting
+ * for a user response. */
+ if (ssh2_transport_filter_queue(s))
+ return; /* we've been freed */
+
+ crBegin(s->crState);
+
+ s->in.cipher = s->out.cipher = NULL;
+ s->in.mac = s->out.mac = NULL;
+ s->in.comp = s->out.comp = NULL;
+
+ s->got_session_id = false;
+ s->need_gss_transient_hostkey = false;
+ s->warned_about_no_gss_transient_hostkey = false;
+
+ begin_key_exchange:
+
+#ifndef NO_GSSAPI
+ if (s->need_gss_transient_hostkey) {
+ /*
+ * This flag indicates a special case in which we must not do
+ * GSS key exchange even if we could. (See comments below,
+ * where the flag was set on the previous key exchange.)
+ */
+ s->can_gssapi_keyex = false;
+ } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) {
+ /*
+ * We always check if we have GSS creds before we come up with
+ * the kex algorithm list, otherwise future rekeys will fail
+ * when creds expire. To make this so, this code section must
+ * follow the begin_key_exchange label above, otherwise this
+ * section would execute just once per-connection.
+ *
+ * Update GSS state unless the reason we're here is that a
+ * timer just checked the GSS state and decided that we should
+ * rekey to update delegated credentials. In that case, the
+ * state is "fresh".
+ */
+ if (s->rekey_class != RK_GSS_UPDATE)
+ ssh2_transport_gss_update(s, true);
+
+ /* Do GSSAPI KEX when capable */
+ s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE;
+
+ /*
+ * But not when failure is likely. [ GSS implementations may
+ * attempt (and fail) to use a ticket that is almost expired
+ * when retrieved from the ccache that actually expires by the
+ * time the server receives it. ]
+ *
+ * Note: The first time always try KEXGSS if we can, failures
+ * will be very rare, and disabling the initial GSS KEX is
+ * worse. Some day GSS libraries will ignore cached tickets
+ * whose lifetime is critically short, and will instead use
+ * fresh ones.
+ */
+ if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0)
+ s->can_gssapi_keyex = false;
+ s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd);
+ } else {
+ s->can_gssapi_keyex = false;
+ }
+#endif
+
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX;
+
+ /*
+ * Construct our KEXINIT packet, in a strbuf so we can refer to it
+ * later.
+ */
+ strbuf_clear(s->outgoing_kexinit);
+ put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
+ random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
+ ssh2_write_kexinit_lists(
+ BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
+ s->conf, s->ssc, s->ppl.remote_bugs,
+ s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas,
+ s->hostkeys, s->nhostkeys,
+ !s->got_session_id, s->can_gssapi_keyex,
+ s->gss_kex_used && !s->need_gss_transient_hostkey);
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ put_bool(s->outgoing_kexinit, false);
+ put_uint32(s->outgoing_kexinit, 0); /* reserved */
+
+ /*
+ * Send our KEXINIT, most of the time.
+ *
+ * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to
+ * have seen at least one KEXINIT from the server first, so that
+ * we can filter our own KEXINIT down to contain only algorithms
+ * the server mentioned.
+ *
+ * But we only need to do this on the _first_ key exchange, when
+ * we've never seen a KEXINIT from the server before. In rekeys,
+ * we still have the server's previous KEXINIT lying around, so we
+ * can filter based on that.
+ *
+ * (And a good thing too, since the way you _initiate_ a rekey is
+ * by sending your KEXINIT, so we'd have no way to prod the server
+ * into sending its first!)
+ */
+ s->kexinit_delayed = delay_outgoing_kexinit(s);
+ if (!s->kexinit_delayed) {
+ if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) {
+ /* Filter based on the KEXINIT from the previous exchange */
+ filter_outgoing_kexinit(s);
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+ put_data(pktout, s->outgoing_kexinit->u + 1,
+ s->outgoing_kexinit->len - 1); /* omit type byte */
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ /*
+ * Flag that KEX is in progress.
+ */
+ s->kex_in_progress = true;
+
+ /*
+ * Wait for the other side's KEXINIT, and save it.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting KEXINIT, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx, pktin->type));
+ return;
+ }
+ strbuf_clear(s->incoming_kexinit);
+ put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
+ put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
+
+ /*
+ * If we've delayed sending our KEXINIT so as to filter it down to
+ * only things the server won't choke on, send ours now.
+ */
+ if (s->kexinit_delayed) {
+ filter_outgoing_kexinit(s);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+ put_data(pktout, s->outgoing_kexinit->u + 1,
+ s->outgoing_kexinit->len - 1); /* omit type byte */
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ /*
+ * Work through the two KEXINIT packets in parallel to find the
+ * selected algorithm identifiers.
+ */
+ {
+ struct server_hostkeys hks = { NULL, 0, 0 };
+
+ if (!ssh2_scan_kexinits(
+ ptrlen_from_strbuf(s->client_kexinit),
+ ptrlen_from_strbuf(s->server_kexinit),
+ s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
+ s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
+ &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks,
+ &s->hkflags, &s->can_send_ext_info)) {
+ sfree(hks.indices);
+ return; /* false means a fatal error function was called */
+ }
+
+ /*
+ * 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;
+
+ for (int i = 0; i < hks.n; i++) {
+ int j = hks.indices[i];
+ if (ssh2_hostkey_algs[j].alg != s->hostkey_alg &&
+ ssh2_hostkey_algs[j].alg->cache_id &&
+ !have_ssh_host_key(s->savedhost, s->savedport,
+ ssh2_hostkey_algs[j].alg->cache_id)) {
+ s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
+ }
+ }
+
+ sfree(hks.indices);
+ }
+
+ if (s->warn_kex) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "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 =
+ &ssh2_hostkey_algs[s->uncert_hostkeys[j]];
+ bool 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);
+ sfree(old_ba);
+ } else {
+ betteralgs = dupstr(hktype->alg->ssh_id);
+ }
+ }
+ }
+ if (betteralgs) {
+ /* Use the special warning prompt that lets us provide
+ * a list of better algorithms */
+ s->spr = seat_confirm_weak_cached_hostkey(
+ ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs,
+ ssh2_transport_dialog_callback, s);
+ sfree(betteralgs);
+ } else {
+ /* If none exist, use the more general 'weak crypto'
+ * warning prompt */
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "host key type", s->hostkey_alg->ssh_id,
+ s->hostkey_alg);
+ }
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "host key warning");
+ return;
+ }
+ }
+
+ if (s->warn_cscipher) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "client-to-server cipher", s->out.cipher->ssh2_id,
+ s->out.cipher);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
+ return;
+ }
+ }
+
+ if (s->warn_sccipher) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "server-to-client cipher", s->in.cipher->ssh2_id,
+ s->in.cipher);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "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);
+
+ /*
+ * Actually perform the key exchange.
+ */
+ s->exhash = ssh_hash_new(s->kex_alg->hash);
+ if (s->kex_shared_secret)
+ strbuf_free(s->kex_shared_secret);
+ s->kex_shared_secret = strbuf_new_nm();
+ 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);
+ s->crStateKex = 0;
+ while (1) {
+ bool aborted = false;
+ ssh2kex_coroutine(s, &aborted);
+ if (aborted)
+ return; /* disaster: our entire state has been freed */
+ if (!s->crStateKex)
+ break; /* kex phase has terminated normally */
+ crReturnV;
+ }
+
+ /*
+ * The exchange hash from the very first key exchange is also
+ * the session id, used in session key construction and
+ * authentication.
+ */
+ if (!s->got_session_id) {
+ assert(sizeof(s->exchange_hash) <= sizeof(s->session_id));
+ memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash));
+ s->session_id_len = s->kex_alg->hash->hlen;
+ assert(s->session_id_len <= sizeof(s->session_id));
+ s->got_session_id = true;
+ }
+
+ /*
+ * Send SSH2_MSG_NEWKEYS.
+ */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS);
+ pq_push(s->ppl.out_pq, pktout);
+ /* Start counting down the outgoing-data limit for these cipher keys. */
+ dts_reset(&s->stats->out, s->max_data_size);
+
+ /*
+ * Force the BPP to synchronously marshal all packets up to and
+ * including that NEWKEYS into wire format, before we switch over
+ * to new crypto.
+ */
+ ssh_bpp_handle_output(s->ppl.bpp);
+
+ /*
+ * We've sent outgoing NEWKEYS, so create and initialise outgoing
+ * session keys.
+ */
+ {
+ strbuf *cipher_key = strbuf_new_nm();
+ strbuf *cipher_iv = strbuf_new_nm();
+ strbuf *mac_key = strbuf_new_nm();
+
+ if (s->out.cipher) {
+ ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
+ 'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
+ ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
+ 'C' + s->out.mkkey_adjust,
+ s->out.cipher->padded_keybytes);
+ }
+ if (s->out.mac) {
+ ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
+ 'E' + s->out.mkkey_adjust, s->out.mac->keylen);
+ }
+
+ ssh2_bpp_new_outgoing_crypto(
+ s->ppl.bpp,
+ s->out.cipher, cipher_key->u, cipher_iv->u,
+ s->out.mac, s->out.etm_mode, mac_key->u,
+ s->out.comp, s->out.comp_delayed);
+
+ strbuf_free(cipher_key);
+ strbuf_free(cipher_iv);
+ strbuf_free(mac_key);
+ }
+
+ /*
+ * If that was our first key exchange, this is the moment to send
+ * our EXT_INFO, if we're sending one.
+ */
+ if (!s->post_newkeys_ext_info) {
+ s->post_newkeys_ext_info = true; /* never do this again */
+ if (s->can_send_ext_info) {
+ strbuf *extinfo = strbuf_new();
+ uint32_t n_exts = 0;
+
+ if (s->ssc) {
+ /* Server->client EXT_INFO lists our supported user
+ * key algorithms. */
+ n_exts++;
+ put_stringz(extinfo, "server-sig-algs");
+ strbuf *list = strbuf_new();
+ for (size_t i = 0; i < n_keyalgs; i++)
+ add_to_commasep(list, all_keyalgs[i]->ssh_id);
+ put_stringsb(extinfo, list);
+ } else {
+ /* Client->server EXT_INFO is currently not sent, but here's
+ * where we should put things in it if we ever want to. */
+ }
+
+ /* Only send EXT_INFO if it's non-empty */
+ if (n_exts) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO);
+ put_uint32(pktout, n_exts);
+ put_datapl(pktout, ptrlen_from_strbuf(extinfo));
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ strbuf_free(extinfo);
+ }
+ }
+
+ /*
+ * Now our end of the key exchange is complete, we can send all
+ * our queued higher-layer packets. Transfer the whole of the next
+ * layer's outgoing queue on to our own.
+ */
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+ ssh_sendbuffer_changed(s->ppl.ssh);
+
+ /*
+ * Expect SSH2_MSG_NEWKEYS from server.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_NEWKEYS) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting SSH_MSG_NEWKEYS, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ /* Start counting down the incoming-data limit for these cipher keys. */
+ dts_reset(&s->stats->in, s->max_data_size);
+
+ /*
+ * We've seen incoming NEWKEYS, so create and initialise
+ * incoming session keys.
+ */
+ {
+ strbuf *cipher_key = strbuf_new_nm();
+ strbuf *cipher_iv = strbuf_new_nm();
+ strbuf *mac_key = strbuf_new_nm();
+
+ if (s->in.cipher) {
+ ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
+ 'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
+ ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
+ 'C' + s->in.mkkey_adjust,
+ s->in.cipher->padded_keybytes);
+ }
+ if (s->in.mac) {
+ ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
+ 'E' + s->in.mkkey_adjust, s->in.mac->keylen);
+ }
+
+ ssh2_bpp_new_incoming_crypto(
+ s->ppl.bpp,
+ s->in.cipher, cipher_key->u, cipher_iv->u,
+ s->in.mac, s->in.etm_mode, mac_key->u,
+ s->in.comp, s->in.comp_delayed);
+
+ strbuf_free(cipher_key);
+ strbuf_free(cipher_iv);
+ strbuf_free(mac_key);
+ }
+
+ /*
+ * Free shared secret.
+ */
+ strbuf_free(s->kex_shared_secret);
+ s->kex_shared_secret = NULL;
+
+ /*
+ * Update the specials menu to list the remaining uncertified host
+ * keys.
+ */
+ seat_update_specials_menu(s->ppl.seat);
+
+ /*
+ * Key exchange is over. Loop straight back round if we have a
+ * deferred rekey reason.
+ */
+ if (s->deferred_rekey_reason) {
+ ppl_logevent("%s", s->deferred_rekey_reason);
+ pktin = NULL;
+ s->deferred_rekey_reason = NULL;
+ goto begin_key_exchange;
+ }
+
+ /*
+ * Otherwise, schedule a timer for our next rekey.
+ */
+ s->kex_in_progress = false;
+ s->last_rekey = GETTICKCOUNT();
+ (void) ssh2_transport_timer_update(s, 0);
+
+ /*
+ * Now we're encrypting. Get the next-layer protocol started if it
+ * hasn't already, and then sit here waiting for reasons to go
+ * back to the start and do a repeat key exchange. One of those
+ * reasons is that we receive KEXINIT from the other end; the
+ * other is if we find rekey_reason is non-NULL, i.e. we've
+ * decided to initiate a rekey ourselves for some reason.
+ */
+ if (!s->higher_layer_ok) {
+ if (!s->hostkeys) {
+ /* We're the client, so send SERVICE_REQUEST. */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
+ put_stringz(pktout, s->higher_layer->vt->name);
+ pq_push(s->ppl.out_pq, pktout);
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
+ ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
+ "'%s' protocol", s->higher_layer->vt->name);
+ return;
+ }
+ } else {
+ ptrlen service_name;
+
+ /* We're the server, so expect SERVICE_REQUEST. */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_SERVICE_REQUEST) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting SERVICE_REQUEST, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ service_name = get_string(pktin);
+ if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) {
+ ssh_proto_error(s->ppl.ssh, "Client requested service "
+ "'%.*s' when we only support '%s'",
+ PTRLEN_PRINTF(service_name),
+ s->higher_layer->vt->name);
+ return;
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT);
+ put_stringz(pktout, s->higher_layer->vt->name);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ s->higher_layer_ok = true;
+ queue_idempotent_callback(&s->higher_layer->ic_process_queue);
+ }
+
+ s->rekey_class = RK_NONE;
+ do {
+ crReturnV;
+
+ /* Pass through outgoing packets from the higher layer. */
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+ ssh_sendbuffer_changed(s->ppl.ssh);
+
+ /* Wait for either a KEXINIT, or something setting
+ * s->rekey_class. This call to ssh2_transport_pop also has
+ * the side effect of transferring incoming packets _to_ the
+ * higher layer (via filter_queue). */
+ if ((pktin = ssh2_transport_pop(s)) != NULL) {
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected transport-"
+ "layer packet outside a key exchange, "
+ "type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ pq_push_front(s->ppl.in_pq, pktin);
+ ppl_logevent("Remote side initiated key re-exchange");
+ s->rekey_class = RK_SERVER;
+ }
+
+ if (s->rekey_class == RK_POST_USERAUTH) {
+ /*
+ * userauth has seen a USERAUTH_SUCCESS. This may be the
+ * moment to do an immediate rekey with different
+ * parameters. But it may not; so here we turn that rekey
+ * class into either RK_NONE or RK_NORMAL.
+ *
+ * Currently the only reason for this is if we've done a
+ * GSS key exchange and don't have anything in our
+ * transient hostkey cache, in which case we should make
+ * an attempt to populate the cache now.
+ */
+ if (s->need_gss_transient_hostkey) {
+ s->rekey_reason = "populating transient host key cache";
+ s->rekey_class = RK_NORMAL;
+ } else {
+ /* No need to rekey at this time. */
+ s->rekey_class = RK_NONE;
+ }
+ }
+
+ if (!s->rekey_class) {
+ /* If we don't yet have any other reason to rekey, check
+ * if we've hit our data limit in either direction. */
+ if (s->stats->in.expired) {
+ s->rekey_reason = "too much data received";
+ s->rekey_class = RK_NORMAL;
+ } else if (s->stats->out.expired) {
+ s->rekey_reason = "too much data sent";
+ s->rekey_class = RK_NORMAL;
+ }
+ }
+
+ if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) {
+ /*
+ * Special case: if the server bug is set that doesn't
+ * allow rekeying, we give a different log message and
+ * continue waiting. (If such a server _initiates_ a
+ * rekey, we process it anyway!)
+ */
+ if ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+ ppl_logevent("Remote bug prevents key re-exchange (%s)",
+ s->rekey_reason);
+ /* Reset the counters, so that at least this message doesn't
+ * hit the event log _too_ often. */
+ dts_reset(&s->stats->in, s->max_data_size);
+ dts_reset(&s->stats->out, s->max_data_size);
+ (void) ssh2_transport_timer_update(s, 0);
+ s->rekey_class = RK_NONE;
+ } else {
+ ppl_logevent("Initiating key re-exchange (%s)",
+ s->rekey_reason);
+ }
+ }
+ } while (s->rekey_class == RK_NONE);
+
+ /* Once we exit the above loop, we really are rekeying. */
+ goto begin_key_exchange;
+
+ crFinishV;
+}
+
+static void ssh2_transport_higher_layer_packet_callback(void *context)
+{
+ PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+ ssh_ppl_process_queue(ppl);
+}
+
+static void ssh2_transport_timer(void *ctx, unsigned long now)
+{
+ struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx;
+ unsigned long mins;
+ unsigned long ticks;
+
+ if (s->kex_in_progress || now != s->next_rekey)
+ return;
+
+ mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+ if (mins == 0)
+ return;
+
+ /* Rekey if enough time has elapsed */
+ ticks = mins * 60 * TICKSPERSEC;
+ if (now - s->last_rekey > ticks - 30*TICKSPERSEC) {
+ s->rekey_reason = "timeout";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ return;
+ }
+
+#ifndef NO_GSSAPI
+ /*
+ * Rekey now if we have a new cred or context expires this cycle,
+ * but not if this is unsafe.
+ */
+ if (conf_get_int(s->conf, CONF_gssapirekey)) {
+ ssh2_transport_gss_update(s, false);
+ if ((s->gss_status & GSS_KEX_CAPABLE) != 0 &&
+ (s->gss_status & GSS_CTXT_MAYFAIL) == 0 &&
+ (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) {
+ s->rekey_reason = "GSS credentials updated";
+ s->rekey_class = RK_GSS_UPDATE;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ return;
+ }
+ }
+#endif
+
+ /* Try again later. */
+ (void) ssh2_transport_timer_update(s, 0);
+}
+
+/*
+ * The rekey_time is zero except when re-configuring.
+ *
+ * We either schedule the next timer and return false, or return true
+ * to run the callback now, which will call us again to re-schedule on
+ * completion.
+ */
+static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
+ unsigned long rekey_time)
+{
+ unsigned long mins;
+ unsigned long ticks;
+
+ mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+ ticks = mins * 60 * TICKSPERSEC;
+
+ /* Handle change from previous setting */
+ if (rekey_time != 0 && rekey_time != mins) {
+ unsigned long next;
+ unsigned long now = GETTICKCOUNT();
+
+ mins = rekey_time;
+ ticks = mins * 60 * TICKSPERSEC;
+ next = s->last_rekey + ticks;
+
+ /* If overdue, caller will rekey synchronously now */
+ if (now - s->last_rekey > ticks)
+ return true;
+ ticks = next - now;
+ }
+
+#ifndef NO_GSSAPI
+ if (s->gss_kex_used) {
+ /*
+ * If we've used GSSAPI key exchange, then we should
+ * periodically check whether we need to do another one to
+ * pass new credentials to the server.
+ */
+ unsigned long gssmins;
+
+ /* Check cascade conditions more frequently if configured */
+ gssmins = sanitise_rekey_time(
+ conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+ if (gssmins > 0) {
+ if (gssmins < mins)
+ ticks = (mins = gssmins) * 60 * TICKSPERSEC;
+
+ if ((s->gss_status & GSS_KEX_CAPABLE) != 0) {
+ /*
+ * Run next timer even sooner if it would otherwise be
+ * too close to the context expiration time
+ */
+ if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 &&
+ s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME)
+ ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC;
+ }
+ }
+ }
+#endif
+
+ /* Schedule the next timer */
+ s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s);
+ return false;
+}
+
+void ssh2_transport_dialog_callback(void *vctx, SeatPromptResult spr)
+{
+ struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx;
+ s->spr = spr;
+ ssh_ppl_process_queue(&s->ppl);
+}
+
+#ifndef NO_GSSAPI
+/*
+ * This is called at the beginning of each SSH rekey to determine
+ * whether we are GSS capable, and if we did GSS key exchange, and are
+ * delegating credentials, it is also called periodically to determine
+ * whether we should rekey in order to delegate (more) fresh
+ * credentials. This is called "credential cascading".
+ *
+ * On Windows, with SSPI, we may not get the credential expiration, as
+ * Windows automatically renews from cached passwords, so the
+ * credential effectively never expires. Since we still want to
+ * cascade when the local TGT is updated, we use the expiration of a
+ * newly obtained context as a proxy for the expiration of the TGT.
+ */
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+ bool definitely_rekeying)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ int gss_stat;
+ time_t gss_cred_expiry;
+ unsigned long mins;
+ Ssh_gss_buf gss_sndtok;
+ Ssh_gss_buf gss_rcvtok;
+ Ssh_gss_ctx gss_ctx;
+
+ s->gss_status = 0;
+
+ /*
+ * Nothing to do if no GSSAPI libraries are configured or GSSAPI
+ * auth is not enabled.
+ */
+ if (s->shgss->libs->nlibraries == 0)
+ return;
+ if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) &&
+ !conf_get_bool(s->conf, CONF_try_gssapi_kex))
+ return;
+
+ /* Import server name and cache it */
+ if (s->shgss->srv_name == GSS_C_NO_NAME) {
+ gss_stat = s->shgss->lib->import_name(
+ s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+ if (gss_stat != SSH_GSS_OK) {
+ if (gss_stat == SSH_GSS_BAD_HOST_NAME)
+ ppl_logevent("GSSAPI import name failed - Bad service name;"
+ " won't use GSS key exchange");
+ else
+ ppl_logevent("GSSAPI import name failed;"
+ " won't use GSS key exchange");
+ return;
+ }
+ }
+
+ /*
+ * Do we (still) have credentials? Capture the credential
+ * expiration when available
+ */
+ gss_stat = s->shgss->lib->acquire_cred(
+ s->shgss->lib, &gss_ctx, &gss_cred_expiry);
+ if (gss_stat != SSH_GSS_OK)
+ return;
+
+ SSH_GSS_CLEAR_BUF(&gss_sndtok);
+ SSH_GSS_CLEAR_BUF(&gss_rcvtok);
+
+ /*
+ * When acquire_cred yields no useful expiration, get a proxy for
+ * the cred expiration from the context expiration.
+ */
+ gss_stat = s->shgss->lib->init_sec_context(
+ s->shgss->lib, &gss_ctx, s->shgss->srv_name,
+ 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok,
+ (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL),
+ &s->gss_ctxt_lifetime);
+
+ /* This context was for testing only. */
+ if (gss_ctx)
+ s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx);
+
+ if (gss_stat != SSH_GSS_OK &&
+ gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+ /*
+ * No point in verbosely interrupting the user to tell them we
+ * couldn't get GSS credentials, if this was only a check
+ * between key exchanges to see if fresh ones were available.
+ * When we do do a rekey, this message (if displayed) will
+ * appear among the standard rekey blurb, but when we're not,
+ * it shouldn't pop up all the time regardless.
+ */
+ if (definitely_rekeying)
+ ppl_logevent("No GSSAPI security context available");
+
+ return;
+ }
+
+ if (gss_sndtok.length)
+ s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok);
+
+ s->gss_status |= GSS_KEX_CAPABLE;
+
+ /*
+ * When rekeying to cascade, avoid doing this too close to the
+ * context expiration time, since the key exchange might fail.
+ */
+ if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME)
+ s->gss_status |= GSS_CTXT_MAYFAIL;
+
+ /*
+ * If we're not delegating credentials, rekeying is not used to
+ * refresh them. We must avoid setting GSS_CRED_UPDATED or
+ * GSS_CTXT_EXPIRES when credential delegation is disabled.
+ */
+ if (!conf_get_bool(s->conf, CONF_gssapifwd))
+ return;
+
+ if (s->gss_cred_expiry != GSS_NO_EXPIRATION &&
+ difftime(gss_cred_expiry, s->gss_cred_expiry) > 0)
+ s->gss_status |= GSS_CRED_UPDATED;
+
+ mins = sanitise_rekey_time(
+ conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+ if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60)
+ s->gss_status |= GSS_CTXT_EXPIRES;
+}
+#endif /* NO_GSSAPI */
+
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ assert(s->got_session_id);
+ return make_ptrlen(s->session_id, s->session_id_len);
+}
+
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ s->rekey_reason = NULL; /* will be filled in later */
+ s->rekey_class = RK_POST_USERAUTH;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static bool ssh2_transport_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+ bool need_separator = false;
+ bool toret = false;
+
+ if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) {
+ need_separator = true;
+ toret = true;
+ }
+
+ /*
+ * Don't bother offering rekey-based specials if we've decided the
+ * remote won't cope with it, since we wouldn't bother sending it
+ * if asked anyway.
+ */
+ if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+ if (need_separator) {
+ add_special(ctx, NULL, SS_SEP, 0);
+ need_separator = false;
+ }
+
+ add_special(ctx, "Repeat key exchange", SS_REKEY, 0);
+ toret = true;
+
+ if (s->n_uncert_hostkeys) {
+ int i;
+
+ add_special(ctx, NULL, SS_SEP, 0);
+ add_special(ctx, "Cache new host key type", SS_SUBMENU, 0);
+ for (i = 0; i < s->n_uncert_hostkeys; i++) {
+ const ssh_keyalg *alg =
+ ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg;
+
+ add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]);
+ }
+ add_special(ctx, NULL, SS_EXITMENU, 0);
+ }
+ }
+
+ return toret;
+}
+
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ if (code == SS_REKEY) {
+ if (!s->kex_in_progress) {
+ s->rekey_reason = "at user request";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ }
+ } else if (code == SS_XCERT) {
+ if (!s->kex_in_progress) {
+ s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg;
+ s->rekey_reason = "cross-certifying new host key";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ }
+ } else {
+ /* Send everything else to the next layer up. This includes
+ * SS_PING/SS_NOP, which we _could_ handle here - but it's
+ * better to put them in the connection layer, so they'll
+ * still work in bare connection mode. */
+ ssh_ppl_special_cmd(s->higher_layer, code, arg);
+ }
+}
+
+/* Safely convert rekey_time to unsigned long minutes */
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def)
+{
+ if (rekey_time < 0 || rekey_time > MAX_TICK_MINS)
+ rekey_time = def;
+ return (unsigned long)rekey_time;
+}
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s)
+{
+ s->max_data_size = parse_blocksize(
+ conf_get_str(s->conf, CONF_ssh_rekey_data));
+}
+
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh2_transport_state *s;
+ const char *rekey_reason = NULL;
+ bool rekey_mandatory = false;
+ unsigned long old_max_data_size, rekey_time;
+ int i;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ rekey_time = sanitise_rekey_time(
+ conf_get_int(conf, CONF_ssh_rekey_time), 60);
+ if (ssh2_transport_timer_update(s, rekey_time))
+ rekey_reason = "timeout shortened";
+
+ old_max_data_size = s->max_data_size;
+ ssh2_transport_set_max_data_size(s);
+ if (old_max_data_size != s->max_data_size &&
+ s->max_data_size != 0) {
+ if (s->max_data_size < old_max_data_size) {
+ unsigned long diff = old_max_data_size - s->max_data_size;
+
+ dts_consume(&s->stats->out, diff);
+ dts_consume(&s->stats->in, diff);
+ if (s->stats->out.expired || s->stats->in.expired)
+ rekey_reason = "data limit lowered";
+ } else {
+ unsigned long diff = s->max_data_size - old_max_data_size;
+ if (s->stats->out.running)
+ s->stats->out.remaining += diff;
+ if (s->stats->in.running)
+ s->stats->in.remaining += diff;
+ }
+ }
+
+ if (conf_get_bool(s->conf, CONF_compression) !=
+ conf_get_bool(conf, CONF_compression)) {
+ rekey_reason = "compression setting changed";
+ rekey_mandatory = true;
+ }
+
+ for (i = 0; i < CIPHER_MAX; i++)
+ if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
+ conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+ rekey_reason = "cipher settings changed";
+ rekey_mandatory = true;
+ }
+ if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) !=
+ conf_get_bool(conf, CONF_ssh2_des_cbc)) {
+ rekey_reason = "cipher settings changed";
+ rekey_mandatory = true;
+ }
+
+ conf_free(s->conf);
+ s->conf = conf_copy(conf);
+
+ if (rekey_reason) {
+ if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) {
+ s->rekey_reason = rekey_reason;
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ } else if (rekey_mandatory) {
+ s->deferred_rekey_reason = rekey_reason;
+ }
+ }
+
+ /* Also pass the configuration along to our higher layer */
+ ssh_ppl_reconfigure(s->higher_layer, conf);
+}
+
+static int weak_algorithm_compare(void *av, void *bv)
+{
+ uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv;
+ return a < b ? -1 : a > b ? +1 : 0;
+}
+
+static int ca_blob_compare(void *av, void *bv)
+{
+ host_ca *a = (host_ca *)av, *b = (host_ca *)bv;
+ strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key;
+ /* Ordering by public key is arbitrary here, so do whatever is easiest */
+ if (apk->len < bpk->len)
+ return -1;
+ if (apk->len > bpk->len)
+ return +1;
+ return memcmp(apk->u, bpk->u, apk->len);
+}
+
+/*
+ * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the
+ * tree234 s->weak_algorithms_consented_to to ensure we ask at most
+ * once about any given crypto primitive.
+ */
+static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
+ struct ssh2_transport_state *s, const char *type, const char *name,
+ const void *alg)
+{
+ if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL))
+ return SPR_OK;
+ add234(s->weak_algorithms_consented_to, (void *)alg);
+
+ return seat_confirm_weak_crypto_primitive(
+ ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s);
+}
+
+static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ return (ssh_ppl_default_queued_data_size(ppl) +
+ ssh_ppl_queued_data_size(s->higher_layer));
+}