]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - ssh.c
Implement client-initiated rekeys after an hour, or after 1Gb of
[PuTTY.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index 1d8e6b8d80ad7d13e6086633c35d383e98b3bcc4..522d3467945d814349fc6139ae051572dc2e76fd 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -517,6 +517,7 @@ struct Packet {
     unsigned char *body;
     long savedpos;
     long maxlen;
+    long encrypted_len;                       /* for SSH2 total-size counting */
 
     /*
      * State associated with packet logging
@@ -543,6 +544,9 @@ static void ssh_do_close(Ssh ssh);
 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;
@@ -703,8 +707,19 @@ struct ssh_tag {
      * 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. */
@@ -1099,6 +1114,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
                               st->pktin->data + st->cipherblk,
                               st->packetlen - st->cipherblk);
 
+    st->pktin->encrypted_len = st->packetlen;
+
     /*
      * Check the MAC.
      */
@@ -1592,6 +1609,8 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
        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;
 }
@@ -1636,6 +1655,13 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
     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);
 }
 
@@ -1653,6 +1679,7 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct 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);
 }
 
@@ -1718,6 +1745,13 @@ static void ssh_pkt_defersend(Ssh ssh)
     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;
 }
 
 /*
@@ -4286,6 +4320,11 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
         */
        ssh->queueing = TRUE;
 
+       /*
+        * Flag that KEX is in progress.
+        */
+       ssh->kex_in_progress = TRUE;
+
        /*
         * Construct and send our key exchange packet.
         */
@@ -4637,11 +4676,62 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
     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
@@ -4658,76 +4748,51 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
        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) {
@@ -4735,6 +4800,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
        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
@@ -4753,11 +4824,21 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
      * 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);
@@ -6822,12 +6903,31 @@ static void ssh2_protocol_setup(Ssh ssh)
     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;
@@ -6943,6 +7043,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
 
     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;
@@ -7021,6 +7125,7 @@ static void ssh_free(void *handle)
     }
     if (ssh->s)
        ssh_do_close(ssh);
+    expire_timer_context(ssh);
     sfree(ssh);
     if (ssh->pinger)
        pinger_free(ssh->pinger);
@@ -7139,6 +7244,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 {
     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},
@@ -7180,7 +7286,6 @@ static const struct telnet_special *ssh_get_specials(void *handle)
        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);
@@ -7234,6 +7339,11 @@ static void ssh_special(void *handle, Telnet_Special code)
            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;