Zero length passwords no longer cause an assertion failure :-)
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 29f2a61..7b4bff4 100644 (file)
--- a/ssh.c
+++ b/ssh.c
 
 #define SSH2_EXTENDED_DATA_STDERR                 1    /* 0x1 */
 
+/*
+ * Various remote-bug flags.
+ */
+#define BUG_CHOKES_ON_SSH1_IGNORE                 1
+#define BUG_SSH2_HMAC                             2
+
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
     ((unsigned long)(unsigned char)(cp)[1] << 16) | \
@@ -173,9 +179,7 @@ const static struct ssh2_ciphers *ciphers[] = {
 };
 
 const static struct ssh_kex *kex_algs[] = {
-#ifdef DO_DIFFIE_HELLMAN_GEX
     &ssh_diffiehellman_gex,
-#endif
     &ssh_diffiehellman };
 
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
@@ -196,10 +200,12 @@ static int ssh_comp_none_block(unsigned char *block, int len,
                               unsigned char **outblock, int *outlen) {
     return 0;
 }
+static int ssh_comp_none_disable(void) { return 0; }
 const static struct ssh_compress ssh_comp_none = {
     "none",
     ssh_comp_none_init, ssh_comp_none_block,
-    ssh_comp_none_init, ssh_comp_none_block
+    ssh_comp_none_init, ssh_comp_none_block,
+    ssh_comp_none_disable
 };
 extern const struct ssh_compress ssh_zlib;
 const static struct ssh_compress *compressions[] = {
@@ -252,6 +258,7 @@ static unsigned char session_key[32];
 static int ssh1_compressing;
 static int ssh_agentfwd_enabled;
 static int ssh_X11_fwd_enabled;
+static int ssh_remote_bugs;
 static const struct ssh_cipher *cipher = NULL;
 static const struct ssh2_cipher *cscipher = NULL;
 static const struct ssh2_cipher *sccipher = NULL;
@@ -659,15 +666,16 @@ static int s_wrpkt_prepare(void) {
 
     pktout.body[-1] = pktout.type;
 
+#if 0
+    debug(("Packet payload pre-compression:\n"));
+    for (i = -1; i < pktout.length; i++)
+        debug(("  %02x", (unsigned char)pktout.body[i]));
+    debug(("\r\n"));
+#endif
+
     if (ssh1_compressing) {
        unsigned char *compblk;
        int complen;
-#if 0
-       debug(("Packet payload pre-compression:\n"));
-       for (i = -1; i < pktout.length; i++)
-           debug(("  %02x", (unsigned char)pktout.body[i]));
-       debug(("\r\n"));
-#endif
        zlib_compress_block(pktout.body-1, pktout.length+1,
                            &compblk, &complen);
        ssh1_pktout_size(complen-1);
@@ -1090,6 +1098,41 @@ static Bignum ssh2_pkt_getmp(void) {
     return b;
 }
 
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(char *vstring) {
+    char *imp;                         /* pointer to implementation part */
+    imp = vstring;
+    imp += strcspn(imp, "-");
+    imp += strcspn(imp, "-");
+
+    ssh_remote_bugs = 0;
+
+    if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+        !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+        !strcmp(imp, "1.2.22")) {
+        /*
+         * These versions don't support SSH1_MSG_IGNORE, so we have
+         * to use a different defence against password length
+         * sniffing.
+         */
+        ssh_remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+        logevent("We believe remote version has SSH1 ignore bug");
+    }
+
+    if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
+        !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
+        !strncmp(imp, "2.1 ", 4)) {
+        /*
+         * These versions have the HMAC bug.
+         */
+        ssh_remote_bugs |= BUG_SSH2_HMAC;
+        logevent("We believe remote version has SSH2 HMAC bug");
+    }
+}
+
 static int do_ssh_init(unsigned char c) {
     static char *vsp;
     static char version[10];
@@ -1137,6 +1180,7 @@ static int do_ssh_init(unsigned char c) {
 
     *vsp = 0;
     sprintf(vlog, "Server version: %s", vstring);
+    ssh_detect_bugs(vstring);
     vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
 
@@ -1835,38 +1879,65 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                  *    N+7. This won't obscure the order of
                  *    magnitude of the password length, but it will
                  *    introduce a bit of extra uncertainty.
+                 * 
+                 * A few servers (the old 1.2.18 through 1.2.22)
+                 * can't deal with SSH1_MSG_IGNORE. For these
+                 * servers, we need an alternative defence. We make
+                 * use of the fact that the password is interpreted
+                 * as a C string: so we can append a NUL, then some
+                 * random data.
                  */
-                int bottom, top, pwlen, i;
-                char *randomstr;
-
-                pwlen = strlen(password);
-                if (pwlen < 16) {
-                    bottom = 1;
-                    top = 15;
+                if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
+                    char string[64];
+                    char *s;
+                    int len;
+
+                    len = strlen(password);
+                    if (len < sizeof(string)) {
+                        s = string;
+                        strcpy(string, password);
+                        len++;         /* cover the zero byte */
+                        while (len < sizeof(string)) {
+                            string[len++] = (char)random_byte();
+                        }
+                    } else {
+                        s = password;
+                    }
+                    send_packet(pwpkt_type, PKT_INT, len,
+                                PKT_DATA, s, len, PKT_END);
                 } else {
-                    bottom = pwlen &~ 7;
-                    top = bottom + 7;
-                }
+                    int bottom, top, pwlen, i;
+                    char *randomstr;
+
+                    pwlen = strlen(password);
+                    if (pwlen < 16) {
+                        bottom = 0;    /* zero length passwords are OK! :-) */
+                        top = 15;
+                    } else {
+                        bottom = pwlen &~ 7;
+                        top = bottom + 7;
+                    }
 
-                assert(pwlen >= bottom && pwlen <= top);
+                    assert(pwlen >= bottom && pwlen <= top);
 
-                randomstr = smalloc(top+1);
+                    randomstr = smalloc(top+1);
 
-                for (i = bottom; i <= top; i++) {
-                    if (i == pwlen)
-                        defer_packet(pwpkt_type, PKT_STR, password, PKT_END);
-                    else {
-                        for (j = 0; j < i; j++) {
-                            do {
-                                randomstr[j] = random_byte();
-                            } while (randomstr[j] == '\0');
+                    for (i = bottom; i <= top; i++) {
+                        if (i == pwlen)
+                            defer_packet(pwpkt_type, PKT_STR, password, PKT_END);
+                        else {
+                            for (j = 0; j < i; j++) {
+                                do {
+                                    randomstr[j] = random_byte();
+                                } while (randomstr[j] == '\0');
+                            }
+                            randomstr[i] = '\0';
+                            defer_packet(SSH1_MSG_IGNORE,
+                                         PKT_STR, randomstr, PKT_END);
                         }
-                        randomstr[i] = '\0';
-                        defer_packet(SSH1_MSG_IGNORE,
-                                     PKT_STR, randomstr, PKT_END);
                     }
+                    ssh_pkt_defersend();
                 }
-                ssh_pkt_defersend();
             } else {
                 send_packet(pwpkt_type, PKT_STR, password, PKT_END);
             }
@@ -2258,7 +2329,7 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, char *keyspace
  */
 static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 {
-    static int i, j, len, nbits;
+    static int i, j, len, nbits, pbits;
     static char *str;
     static Bignum p, g, e, f, K;
     static int kex_init_value, kex_reply_value;
@@ -2307,7 +2378,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /*
      * Be prepared to work around the buggy MAC problem.
      */
-    if (cfg.buggymac)
+    if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
         maclist = buggymacs, nmacs = lenof(buggymacs);
     else
         maclist = macs, nmacs = lenof(macs);
@@ -2481,30 +2552,34 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
+     * Work out the number of bits of key we will need from the key
+     * exchange. We start with the maximum key length of either
+     * cipher...
+     */
+    {
+        int csbits, scbits;
+
+       csbits = cscipher_tobe->keylen;
+       scbits = sccipher_tobe->keylen;
+       nbits = (csbits > scbits ? csbits : scbits);
+    }
+    /* The keys only have 160-bit entropy, since they're based on
+     * a SHA-1 hash. So cap the key size at 160 bits. */
+    if (nbits > 160) nbits = 160;
+
+    /*
      * If we're doing Diffie-Hellman group exchange, start by
      * requesting a group.
      */
     if (kex == &ssh_diffiehellman_gex) {
-        int csbits, scbits;
-
         logevent("Doing Diffie-Hellman group exchange");
         /*
-         * Work out number of bits. We start with the maximum key
-         * length of either cipher...
-         */
-        csbits = cscipher_tobe->keylen;
-        scbits = sccipher_tobe->keylen;
-        nbits = (csbits > scbits ? csbits : scbits);
-        /* The keys only have 160-bit entropy, since they're based on
-         * a SHA-1 hash. So cap the key size at 160 bits. */
-        if (nbits > 160) nbits = 160;
-        /*
-         * ... and then work out how big a DH group we will need to
-         * allow that much data.
-         */
-        nbits = 512 << ((nbits-1) / 64);
+         * Work out how big a DH group we will need to allow that
+         * much data.
+        */
+        pbits = 512 << ((nbits-1) / 64);
         ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
-        ssh2_pkt_adduint32(nbits);
+        ssh2_pkt_adduint32(pbits);
         ssh2_pkt_send();
 
         crWaitUntil(ispkt);
@@ -2527,7 +2602,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /*
      * Now generate and send e for Diffie-Hellman.
      */
-    e = dh_create_e();
+    e = dh_create_e(nbits*2);
     ssh2_pkt_init(kex_init_value);
     ssh2_pkt_addmp(e);
     ssh2_pkt_send();
@@ -2545,7 +2620,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 
     sha_string(&exhash, hostkeydata, hostkeylen);
     if (kex == &ssh_diffiehellman_gex) {
-        sha_uint32(&exhash, nbits);
+        sha_uint32(&exhash, pbits);
         sha_mpint(&exhash, p);
         sha_mpint(&exhash, g);
     }