unsigned char *body;
long savedpos;
long maxlen;
+ long encrypted_len; /* for SSH2 total-size counting */
/*
* State associated with packet logging
static unsigned long ssh_pkt_getuint32(struct Packet *pkt);
static int ssh2_pkt_getbool(struct Packet *pkt);
static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
+static void ssh2_timer(void *ctx, long now);
+static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin);
struct rdpkt1_state_tag {
long len, pad, biglen, to_read;
* This module deals with sending keepalives.
*/
Pinger pinger;
+
+ /*
+ * Track incoming and outgoing data sizes and time, for
+ * size-based rekeys.
+ */
+ unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
+ int kex_in_progress;
+ long next_rekey;
};
+#define MAX_DATA_BEFORE_REKEY (0x40000000UL)
+#define REKEY_TIMEOUT (3600 * TICKSPERSEC)
+
#define logevent(s) logevent(ssh->frontend, s)
/* logevent, only printf-formatted. */
st->pktin->data + st->cipherblk,
st->packetlen - st->cipherblk);
+ st->pktin->encrypted_len = st->packetlen;
+
/*
* Check the MAC.
*/
ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
pkt->data, pkt->length + padding);
+ pkt->encrypted_len = pkt->length + padding;
+
/* Ready-to-send packet starts at pkt->data. We return length. */
return pkt->length + padding + maclen;
}
backlog = sk_write(ssh->s, (char *)pkt->data, len);
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
+
+ ssh->outgoing_data_size += pkt->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ ssh->outgoing_data_size > MAX_DATA_BEFORE_REKEY)
+ do_ssh2_transport(ssh, "Initiating key re-exchange "
+ "(too much data sent)", -1, NULL);
+
ssh_free_packet(pkt);
}
}
memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len);
ssh->deferred_len += len;
+ ssh->deferred_data_size += pkt->encrypted_len;
ssh_free_packet(pkt);
}
ssh->deferred_send_data = NULL;
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
+
+ ssh->outgoing_data_size += ssh->deferred_data_size;
+ if (!ssh->kex_in_progress &&
+ ssh->outgoing_data_size > MAX_DATA_BEFORE_REKEY)
+ do_ssh2_transport(ssh, "Initiating key re-exchange "
+ "(too much data sent)", -1, NULL);
+ ssh->deferred_data_size = 0;
}
/*
*/
ssh->queueing = TRUE;
+ /*
+ * Flag that KEX is in progress.
+ */
+ ssh->kex_in_progress = TRUE;
+
/*
* Construct and send our key exchange packet.
*/
sfree(s->keystr);
ssh->hostkey->freekey(s->hkey);
+ /*
+ * The exchange hash from the very first key exchange is also
+ * the session id, used in session key construction and
+ * authentication.
+ */
+ if (s->first_kex)
+ memcpy(ssh->v2_session_id, s->exchange_hash,
+ sizeof(s->exchange_hash));
+
/*
* Send SSH2_MSG_NEWKEYS.
*/
s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS);
ssh2_pkt_send_noqueue(ssh, s->pktout);
+ ssh->outgoing_data_size = 0; /* start counting from here */
+
+ /*
+ * We've sent client NEWKEYS, so create and initialise
+ * client-to-servere session keys.
+ */
+ if (ssh->cs_cipher_ctx)
+ ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+ ssh->cscipher = s->cscipher_tobe;
+ ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+
+ if (ssh->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ ssh->csmac = s->csmac_tobe;
+ ssh->cs_mac_ctx = ssh->csmac->make_context();
+
+ if (ssh->cs_comp_ctx)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ ssh->cscomp = s->cscomp_tobe;
+ ssh->cs_comp_ctx = ssh->cscomp->compress_init();
+
+ /*
+ * Set IVs on client-to-server keys. Here we use the exchange
+ * hash from the _first_ key exchange.
+ */
+ {
+ unsigned char keyspace[40];
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'C',keyspace);
+ ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'A',keyspace);
+ ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'E',keyspace);
+ ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
+ }
+
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
+ ssh->csmac->text_name);
+ if (ssh->cscomp->text_name)
+ logeventf(ssh, "Initialised %s compression",
+ ssh->cscomp->text_name);
/*
* Now our end of the key exchange is complete, we can send all
bombout(("expected new-keys packet from server"));
crStop(0);
}
+ ssh->incoming_data_size = 0; /* start counting from here */
/*
- * Create and initialise session keys.
+ * We've seen server NEWKEYS, so create and initialise
+ * server-to-client session keys.
*/
- if (ssh->cs_cipher_ctx)
- ssh->cscipher->free_context(ssh->cs_cipher_ctx);
- ssh->cscipher = s->cscipher_tobe;
- ssh->cs_cipher_ctx = ssh->cscipher->make_context();
-
if (ssh->sc_cipher_ctx)
ssh->sccipher->free_context(ssh->sc_cipher_ctx);
ssh->sccipher = s->sccipher_tobe;
ssh->sc_cipher_ctx = ssh->sccipher->make_context();
- if (ssh->cs_mac_ctx)
- ssh->csmac->free_context(ssh->cs_mac_ctx);
- ssh->csmac = s->csmac_tobe;
- ssh->cs_mac_ctx = ssh->csmac->make_context();
-
if (ssh->sc_mac_ctx)
ssh->scmac->free_context(ssh->sc_mac_ctx);
ssh->scmac = s->scmac_tobe;
ssh->sc_mac_ctx = ssh->scmac->make_context();
- if (ssh->cs_comp_ctx)
- ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
- ssh->cscomp = s->cscomp_tobe;
- ssh->cs_comp_ctx = ssh->cscomp->compress_init();
-
if (ssh->sc_comp_ctx)
ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
ssh->sccomp = s->sccomp_tobe;
ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
/*
- * Set IVs after keys. Here we use the exchange hash from the
- * _first_ key exchange.
+ * Set IVs on server-to-client keys. Here we use the exchange
+ * hash from the _first_ key exchange.
*/
{
unsigned char keyspace[40];
- if (s->first_kex)
- memcpy(ssh->v2_session_id, s->exchange_hash,
- sizeof(s->exchange_hash));
- ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'C',keyspace);
- ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'D',keyspace);
ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'A',keyspace);
- ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'B',keyspace);
ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'E',keyspace);
- ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'F',keyspace);
ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
}
- logeventf(ssh, "Initialised %.200s client->server encryption",
- ssh->cscipher->text_name);
logeventf(ssh, "Initialised %.200s server->client encryption",
ssh->sccipher->text_name);
- logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
- ssh->csmac->text_name);
logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
ssh->scmac->text_name);
- if (ssh->cscomp->text_name)
- logeventf(ssh, "Initialised %s compression",
- ssh->cscomp->text_name);
if (ssh->sccomp->text_name)
logeventf(ssh, "Initialised %s decompression",
ssh->sccomp->text_name);
+
+ /*
+ * Free key exchange data.
+ */
freebn(s->f);
freebn(s->K);
if (ssh->kex == &ssh_diffiehellman_gex) {
freebn(s->p);
}
+ /*
+ * Key exchange is over. Schedule a timer for our next rekey.
+ */
+ ssh->kex_in_progress = FALSE;
+ ssh->next_rekey = schedule_timer(REKEY_TIMEOUT, ssh2_timer, ssh);
+
/*
* If this is the first key exchange phase, we must pass the
* SSH2_MSG_NEWKEYS packet to the next layer, not because it
* function so that other things can run on top of the
* transport. If we ever see a KEXINIT, we must go back to the
* start.
+ *
+ * We _also_ go back to the start if we see pktin==NULL and
+ * inlen==-1, because this is a special signal meaning
+ * `initiate client-driven rekey', and `in' contains a message
+ * giving the reason for the rekey.
*/
- while (!(pktin && pktin->type == SSH2_MSG_KEXINIT)) {
+ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
+ (!pktin && inlen == -1))) {
crReturn(1);
}
- logevent("Server initiated key re-exchange");
+ if (pktin) {
+ logevent("Server initiated key re-exchange");
+ } else {
+ logevent((char *)in);
+ }
goto begin_key_exchange;
crFinish(1);
ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
}
+static void ssh2_timer(void *ctx, long now)
+{
+ Ssh ssh = (Ssh)ctx;
+
+ if (!ssh->kex_in_progress &&
+ now - ssh->next_rekey >= 0) {
+ do_ssh2_transport(ssh, "Initiating key re-exchange (timeout)",
+ -1, NULL);
+ }
+}
+
static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen,
struct Packet *pktin)
{
if (ssh->state == SSH_STATE_CLOSED)
return;
+ if (pktin) {
+ ssh->incoming_data_size += pktin->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ ssh->incoming_data_size > MAX_DATA_BEFORE_REKEY)
+ do_ssh2_transport(ssh, "Initiating key re-exchange "
+ "(too much data received)", -1, NULL);
+ }
+
if (pktin && ssh->packet_dispatch[pktin->type]) {
ssh->packet_dispatch[pktin->type](ssh, pktin);
return;
ssh->pinger = NULL;
+ ssh->incoming_data_size = ssh->outgoing_data_size =
+ ssh->deferred_data_size = 0L;
+ ssh->kex_in_progress = FALSE;
+
p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
if (p != NULL)
return p;
}
if (ssh->s)
ssh_do_close(ssh);
+ expire_timer_context(ssh);
sfree(ssh);
if (ssh->pinger)
pinger_free(ssh->pinger);
{
static const struct telnet_special ignore_special[] = {
{"IGNORE message", TS_NOP},
+ {"Repeat key exchange", TS_REKEY},
};
static const struct telnet_special ssh2_session_specials[] = {
{NULL, TS_SEP},
if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
ADD_SPECIALS(ignore_special);
} else if (ssh->version == 2) {
- /* XXX add rekey, when implemented */
ADD_SPECIALS(ignore_special);
if (ssh->mainchan)
ADD_SPECIALS(ssh2_session_specials);
ssh2_pkt_addstring_start(pktout);
ssh2_pkt_send_noqueue(ssh, pktout);
}
+ } else if (code == TS_REKEY) {
+ if (!ssh->kex_in_progress && ssh->version == 2) {
+ do_ssh2_transport(ssh, "Initiating key re-exchange at"
+ " user request", -1, NULL);
+ }
} else if (code == TS_BRK) {
if (ssh->state == SSH_STATE_CLOSED
|| ssh->state == SSH_STATE_PREPACKET) return;