]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Support OpenSSH encrypt-then-MAC protocol extension.
authorSimon Tatham <anakin@pobox.com>
Sun, 26 Apr 2015 22:30:32 +0000 (23:30 +0100)
committerSimon Tatham <anakin@pobox.com>
Sun, 26 Apr 2015 22:30:32 +0000 (23:30 +0100)
This causes the initial length field of the SSH-2 binary packet to be
unencrypted (with the knock-on effect that now the packet length not
including MAC must be congruent to 4 rather than 0 mod the cipher
block size), and then the MAC is applied over the unencrypted length
field and encrypted ciphertext (prefixed by the sequence number as
usual). At the cost of exposing some information about the packet
lengths to an attacker (but rarely anything they couldn't have
inferred from the TCP headers anyway), this closes down any
possibility of a MITM using the client as a decryption oracle, unless
they can _first_ fake a correct MAC.

ETM mode is enabled by means of selecting a different MAC identifier,
all the current ones of which are constructed by appending
"-etm@openssh.com" to the name of a MAC that already existed.

We currently prefer the original SSH-2 binary packet protocol (i.e. we
list all the ETM-mode MACs last in our KEXINIT), on the grounds that
it's better tested and more analysed, so at the moment the new mode is
only activated if a server refuses to speak anything else.

ssh.c
ssh.h
sshmd5.c
sshsh256.c
sshsha.c

diff --git a/ssh.c b/ssh.c
index 0cc27c93e41fade32d0e9be12397cba3f263e7dc..5ed63cc24decc2f27b1658cdeff4efde87fcc929 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -768,6 +768,7 @@ struct ssh_tag {
     const struct ssh2_cipher *cscipher, *sccipher;
     void *cs_cipher_ctx, *sc_cipher_ctx;
     const struct ssh_mac *csmac, *scmac;
+    int csmac_etm, scmac_etm;
     void *cs_mac_ctx, *sc_mac_ctx;
     const struct ssh_compress *cscomp, *sccomp;
     void *cs_comp_ctx, *sc_comp_ctx;
@@ -1566,7 +1567,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     st->maclen = ssh->scmac ? ssh->scmac->len : 0;
 
     if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
-       ssh->scmac) {
+       ssh->scmac && !ssh->scmac_etm) {
        /*
         * When dealing with a CBC-mode cipher, we want to avoid the
         * possibility of an attacker's tweaking the ciphertext stream
@@ -1578,6 +1579,11 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
         * length, so we just read data and check the MAC repeatedly,
         * and when the MAC passes, see if the length we've got is
         * plausible.
+         *
+         * This defence is unnecessary in OpenSSH ETM mode, because
+         * the whole point of ETM mode is that the attacker can't
+         * tweak the ciphertext stream at all without the MAC
+         * detecting it before we decrypt anything.
         */
 
        /* May as well allocate the whole lot now. */
@@ -1632,6 +1638,71 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        st->pktin->data = sresize(st->pktin->data,
                                  st->pktin->maxlen + APIEXTRA,
                                  unsigned char);
+    } else if (ssh->scmac && ssh->scmac_etm) {
+       st->pktin->data = snewn(4 + APIEXTRA, unsigned char);
+
+        /*
+         * OpenSSH encrypt-then-MAC mode: the packet length is
+         * unencrypted.
+         */
+       for (st->i = st->len = 0; st->i < 4; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+       st->len = toint(GET_32BIT(st->pktin->data));
+
+       /*
+        * _Completely_ silly lengths should be stomped on before they
+        * do us any more damage.
+        */
+       if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT ||
+           st->len % st->cipherblk != 0) {
+           bombout(("Incoming packet length field was garbled"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /*
+        * So now we can work out the total packet length.
+        */
+       st->packetlen = st->len + 4;
+
+       /*
+        * Allocate memory for the rest of the packet.
+        */
+       st->pktin->maxlen = st->packetlen + st->maclen;
+       st->pktin->data = sresize(st->pktin->data,
+                                 st->pktin->maxlen + APIEXTRA,
+                                 unsigned char);
+
+       /*
+        * Read the remainder of the packet.
+        */
+       for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+
+       /*
+        * Check the MAC.
+        */
+       if (ssh->scmac
+           && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data,
+                                  st->len + 4, st->incoming_sequence)) {
+           bombout(("Incorrect MAC received on packet"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /* Decrypt everything between the length field and the MAC. */
+       if (ssh->sccipher)
+           ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+                                  st->pktin->data + 4,
+                                  st->packetlen - 4);
     } else {
        st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
 
@@ -2146,7 +2217,7 @@ static struct Packet *ssh2_pkt_init(int pkt_type)
  */
 static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
 {
-    int cipherblk, maclen, padding, i;
+    int cipherblk, maclen, padding, unencrypted_prefix, i;
 
     if (ssh->logctx)
         ssh2_log_outgoing_packet(ssh, pkt);
@@ -2187,10 +2258,12 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
     cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
     padding = 4;
+    unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0;
     if (pkt->length + padding < pkt->forcepad)
        padding = pkt->forcepad - pkt->length;
     padding +=
-       (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+       (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+        % cipherblk;
     assert(padding <= 255);
     maclen = ssh->csmac ? ssh->csmac->len : 0;
     ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
@@ -2198,16 +2271,30 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     for (i = 0; i < padding; i++)
        pkt->data[pkt->length + i] = random_byte();
     PUT_32BIT(pkt->data, pkt->length + padding - 4);
-    if (ssh->csmac)
-       ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
-                            pkt->length + padding,
-                            ssh->v2_outgoing_sequence);
-    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
-
-    if (ssh->cscipher)
-       ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
-                              pkt->data, pkt->length + padding);
+    if (ssh->csmac && ssh->csmac_etm) {
+        /*
+         * OpenSSH-defined encrypt-then-MAC protocol.
+         */
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data + 4, pkt->length + padding - 4);
+        ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                             pkt->length + padding,
+                             ssh->v2_outgoing_sequence);
+    } else {
+        /*
+         * SSH-2 standard protocol.
+         */
+        if (ssh->csmac)
+            ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                                 pkt->length + padding,
+                                 ssh->v2_outgoing_sequence);
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data, pkt->length + padding);
+    }
 
+    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
     pkt->encrypted_len = pkt->length + padding;
 
     /* Ready-to-send packet starts at pkt->data. We return length. */
@@ -6072,6 +6159,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        const struct ssh2_cipher *sccipher_tobe;
        const struct ssh_mac *csmac_tobe;
        const struct ssh_mac *scmac_tobe;
+        int csmac_etm_tobe, scmac_etm_tobe;
        const struct ssh_compress *cscomp_tobe;
        const struct ssh_compress *sccomp_tobe;
        char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
@@ -6255,8 +6343,15 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        /* List MAC algorithms (client->server then server->client). */
        for (j = 0; j < 2; j++) {
            ssh2_pkt_addstring_start(s->pktout);
-           for (i = 0; i < s->nmacs; i++)
+           for (i = 0; i < s->nmacs; i++) {
                ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->name);
+            }
+           for (i = 0; i < s->nmacs; i++) {
+                /* For each MAC, there may also be an ETM version,
+                 * which we list second. */
+                if (s->maclist[i]->etm_name)
+                    ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->etm_name);
+            }
        }
        /* List client->server compression algorithms,
         * then server->client compression algorithms. (We use the
@@ -6434,9 +6529,25 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->csmac_tobe = s->maclist[i];
-               break;
+               s->csmac_etm_tobe = FALSE;
+                break;
            }
        }
+        if (!s->csmac_tobe) {
+            for (i = 0; i < s->nmacs; i++) {
+                if (s->maclist[i]->etm_name &&
+                    in_commasep_string(s->maclist[i]->etm_name, str, len)) {
+                    s->csmac_tobe = s->maclist[i];
+                    s->csmac_etm_tobe = TRUE;
+                    break;
+                }
+            }
+        }
+        if (!s->csmac_tobe) {
+            bombout(("Couldn't agree a client-to-server MAC"
+                     " (available: %.*s)", len, str));
+           crStopV;
+        }
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client mac */
         if (!str) {
             bombout(("KEXINIT packet was incomplete"));
@@ -6445,9 +6556,25 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->scmac_tobe = s->maclist[i];
-               break;
+               s->scmac_etm_tobe = FALSE;
+                break;
            }
        }
+        if (!s->scmac_tobe) {
+            for (i = 0; i < s->nmacs; i++) {
+                if (s->maclist[i]->etm_name &&
+                    in_commasep_string(s->maclist[i]->etm_name, str, len)) {
+                    s->scmac_tobe = s->maclist[i];
+                    s->scmac_etm_tobe = TRUE;
+                    break;
+                }
+            }
+        }
+        if (!s->scmac_tobe) {
+            bombout(("Couldn't agree a server-to-client MAC"
+                     " (available: %.*s)", len, str));
+           crStopV;
+        }
        ssh_pkt_getstring(pktin, &str, &len);  /* client->server compression */
         if (!str) {
             bombout(("KEXINIT packet was incomplete"));
@@ -7003,6 +7130,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (ssh->cs_mac_ctx)
        ssh->csmac->free_context(ssh->cs_mac_ctx);
     ssh->csmac = s->csmac_tobe;
+    ssh->csmac_etm = s->csmac_etm_tobe;
     ssh->cs_mac_ctx = ssh->csmac->make_context();
 
     if (ssh->cs_comp_ctx)
@@ -7034,8 +7162,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
     logeventf(ssh, "Initialised %.200s client->server encryption",
              ssh->cscipher->text_name);
-    logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
-             ssh->csmac->text_name);
+    logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s",
+             ssh->csmac->text_name,
+              ssh->csmac_etm ? " (in ETM mode)" : "");
     if (ssh->cscomp->text_name)
        logeventf(ssh, "Initialised %s compression",
                  ssh->cscomp->text_name);
@@ -7069,6 +7198,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (ssh->sc_mac_ctx)
        ssh->scmac->free_context(ssh->sc_mac_ctx);
     ssh->scmac = s->scmac_tobe;
+    ssh->scmac_etm = s->scmac_etm_tobe;
     ssh->sc_mac_ctx = ssh->scmac->make_context();
 
     if (ssh->sc_comp_ctx)
@@ -7099,8 +7229,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     logeventf(ssh, "Initialised %.200s server->client encryption",
              ssh->sccipher->text_name);
-    logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
-             ssh->scmac->text_name);
+    logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s",
+             ssh->scmac->text_name,
+              ssh->scmac_etm ? " (in ETM mode)" : "");
     if (ssh->sccomp->text_name)
        logeventf(ssh, "Initialised %s decompression",
                  ssh->sccomp->text_name);
diff --git a/ssh.h b/ssh.h
index 29f12d892740863587f864d53f48ccb0d37fd823..7f4523993da71be63431e313e76477ae4bb0c59d 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -296,7 +296,7 @@ struct ssh_mac {
     void (*bytes) (void *, unsigned char const *, int);
     void (*genresult) (void *, unsigned char *);
     int (*verresult) (void *, unsigned char const *);
-    char *name;
+    char *name, *etm_name;
     int len;
     char *text_name;
 };
index 2fdb5900703327cf065a5455b38b40d66e084821..af139690121af1123fbe556747d2dded037256b7 100644 (file)
--- a/sshmd5.c
+++ b/sshmd5.c
@@ -334,7 +334,7 @@ const struct ssh_mac ssh_hmac_md5 = {
     hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16,
     hmacmd5_generate, hmacmd5_verify,
     hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult,
-    "hmac-md5",
+    "hmac-md5", "hmac-md5-etm@openssh.com",
     16,
     "HMAC-MD5"
 };
index 60c5217f580c329a83544e7e44d7d74c2ca18d7d..7ea25fbe1193c7ed1d062c798b63bc41c3fd2d77 100644 (file)
@@ -323,7 +323,7 @@ const struct ssh_mac ssh_hmac_sha256 = {
     sha256_generate, sha256_verify,
     hmacsha256_start, hmacsha256_bytes,
     hmacsha256_genresult, hmacsha256_verresult,
-    "hmac-sha2-256",
+    "hmac-sha2-256", "hmac-sha2-256-etm@openssh.com",
     32,
     "HMAC-SHA-256"
 };
index a5b3a60c8465493578b08c9b1c74b5e3c1e27287..f22035fe187070c782a20a60cff45723782d9517 100644 (file)
--- a/sshsha.c
+++ b/sshsha.c
@@ -400,7 +400,7 @@ const struct ssh_mac ssh_hmac_sha1 = {
     sha1_make_context, sha1_free_context, sha1_key,
     sha1_generate, sha1_verify,
     hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
-    "hmac-sha1",
+    "hmac-sha1", "hmac-sha1-etm@openssh.com",
     20,
     "HMAC-SHA1"
 };
@@ -410,7 +410,7 @@ const struct ssh_mac ssh_hmac_sha1_96 = {
     sha1_96_generate, sha1_96_verify,
     hmacsha1_start, hmacsha1_bytes,
     hmacsha1_96_genresult, hmacsha1_96_verresult,
-    "hmac-sha1-96",
+    "hmac-sha1-96", "hmac-sha1-96-etm@openssh.com",
     12,
     "HMAC-SHA1-96"
 };
@@ -419,7 +419,7 @@ const struct ssh_mac ssh_hmac_sha1_buggy = {
     sha1_make_context, sha1_free_context, sha1_key_buggy,
     sha1_generate, sha1_verify,
     hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
-    "hmac-sha1",
+    "hmac-sha1", NULL,
     20,
     "bug-compatible HMAC-SHA1"
 };
@@ -429,7 +429,7 @@ const struct ssh_mac ssh_hmac_sha1_96_buggy = {
     sha1_96_generate, sha1_96_verify,
     hmacsha1_start, hmacsha1_bytes,
     hmacsha1_96_genresult, hmacsha1_96_verresult,
-    "hmac-sha1-96",
+    "hmac-sha1-96", NULL,
     12,
     "bug-compatible HMAC-SHA1-96"
 };