RSA key authentication in ssh1 works; SSH2 is nearly there
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 7 Sep 2000 16:33:49 +0000 (16:33 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 7 Sep 2000 16:33:49 +0000 (16:33 +0000)
git-svn-id: svn://svn.tartarus.org/sgt/putty@572 cda61777-01e9-0310-a592-d414129be87e

12 files changed:
Makefile
putty.h
ssh.c
ssh.h
sshbn.c
sshdes.c
sshdss.c
sshrsa.c
sshsha.c
win_res.h
win_res.rc
windlg.c

index dc671ff..9149356 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -58,7 +58,7 @@ MOBJS = misc.$(OBJ) version.$(OBJ)
 ##-- objects putty pscp
 OBJS1 = sshcrc.$(OBJ) sshdes.$(OBJ) sshmd5.$(OBJ) sshrsa.$(OBJ) sshrand.$(OBJ)
 OBJS2 = sshsha.$(OBJ) sshblowf.$(OBJ) noise.$(OBJ) sshdh.$(OBJ) sshdss.$(OBJ)
-OBJS3 = sshbn.$(OBJ)
+OBJS3 = sshbn.$(OBJ) sshpubk.$(OBJ)
 ##-- resources putty
 PRESRC = win_res.$(RES)
 ##-- resources puttytel
@@ -144,6 +144,7 @@ sshblowf.$(OBJ): sshblowf.c ssh.h
 sshdh.$(OBJ): sshdh.c ssh.h
 sshdss.$(OBJ): sshdss.c ssh.h
 sshbn.$(OBJ): sshbn.c ssh.h
+sshpubk.$(OBJ): sshpubk.c ssh.h
 scp.$(OBJ): scp.c putty.h scp.h
 version.$(OBJ): version.c
 be_all.$(OBJ): be_all.c
diff --git a/putty.h b/putty.h
index 3b2257a..a813511 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -135,6 +135,7 @@ typedef struct {
     /* SSH options */
     int nopty;
     enum { CIPHER_3DES, CIPHER_BLOWFISH, CIPHER_DES } cipher;
+    char keyfile[FILENAME_MAX];
     int try_tis_auth;
     /* Telnet options */
     char termtype[32];
diff --git a/ssh.c b/ssh.c
index d327471..7511e08 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -23,6 +23,9 @@
 #define SSH1_SMSG_PUBLIC_KEY   2
 #define SSH1_CMSG_SESSION_KEY  3
 #define SSH1_CMSG_USER         4
+#define SSH1_CMSG_AUTH_RSA      6
+#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7
+#define SSH1_CMSG_AUTH_RSA_RESPONSE 8
 #define SSH1_CMSG_AUTH_PASSWORD        9
 #define SSH1_CMSG_REQUEST_PTY  10
 #define SSH1_CMSG_WINDOW_SIZE  11
 #define SSH2_MSG_NEWKEYS                21
 #define SSH2_MSG_KEXDH_INIT             30
 #define SSH2_MSG_KEXDH_REPLY            31
+#define SSH2_MSG_USERAUTH_REQUEST            50
+#define SSH2_MSG_USERAUTH_FAILURE            51
+#define SSH2_MSG_USERAUTH_SUCCESS            52
+#define SSH2_MSG_USERAUTH_BANNER             53
+#define SSH2_MSG_USERAUTH_PK_OK              60
+#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ   60
+#define SSH2_MSG_GLOBAL_REQUEST                  80
+#define SSH2_MSG_REQUEST_SUCCESS                 81
+#define SSH2_MSG_REQUEST_FAILURE                 82
+#define SSH2_MSG_CHANNEL_OPEN                    90
+#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION       91
+#define SSH2_MSG_CHANNEL_OPEN_FAILURE            92
+#define SSH2_MSG_CHANNEL_WINDOW_ADJUST           93
+#define SSH2_MSG_CHANNEL_DATA                    94
+#define SSH2_MSG_CHANNEL_EXTENDED_DATA           95
+#define SSH2_MSG_CHANNEL_EOF                     96
+#define SSH2_MSG_CHANNEL_CLOSE                   97
+#define SSH2_MSG_CHANNEL_REQUEST                 98
+#define SSH2_MSG_CHANNEL_SUCCESS                 99
+#define SSH2_MSG_CHANNEL_FAILURE                 100
+
+#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED    1
+#define SSH2_OPEN_CONNECT_FAILED                 2
+#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE           3
+#define SSH2_OPEN_RESOURCE_SHORTAGE              4
+#define SSH2_EXTENDED_DATA_STDERR                1
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -67,7 +96,7 @@
     (cp)[2] = (unsigned char)((value) >> 8); \
     (cp)[3] = (unsigned char)(value); }
 
-enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR };
+enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
 
 /* Coroutine mechanics for the sillier bits of the code */
 #define crBegin1       static int crLine = 0;
@@ -86,6 +115,7 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR };
 #define crStop(z)      do{ crLine = 0; return (z); }while(0)
 #define crStopV                do{ crLine = 0; return; }while(0)
 #define crWaitUntil(c) do { crReturn(0); } while (!(c))
+#define crWaitUntilV(c)        do { crReturnV; } while (!(c))
 
 extern struct ssh_cipher ssh_3des;
 extern struct ssh_cipher ssh_3des_ssh2;
@@ -347,16 +377,8 @@ next_packet:
         /* FIXME */
     }
 #endif
-    debug(("Got initblk:"));
-    for (i = 0; i < cipherblk; i++)
-        debug(("  %02x", (unsigned char)pktin.data[i]));
-    debug(("\r\n"));
     if (sccipher)
         sccipher->decrypt(pktin.data, cipherblk);
-    debug(("Decrypted initblk:"));
-    for (i = 0; i < cipherblk; i++)
-        debug(("  %02x", (unsigned char)pktin.data[i]));
-    debug(("\r\n"));
 
     /*
      * Now get the length and padding figures.
@@ -401,10 +423,12 @@ next_packet:
     if (sccipher)
         sccipher->decrypt(pktin.data + cipherblk, packetlen - cipherblk);
 
+#if 0
     debug(("Got packet len=%d pad=%d\r\n", len, pad));
     for (i = 0; i < packetlen; i++)
         debug(("  %02x", (unsigned char)pktin.data[i]));
     debug(("\r\n"));
+#endif
 
     /*
      * Check the MAC.
@@ -416,9 +440,8 @@ next_packet:
     pktin.savedpos = 6;
     pktin.type = pktin.data[5];
 
-    /*
-     * FIXME: handle IGNORE and DEBUG messages.
-     */
+    if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
+        goto next_packet;              /* FIXME: print DEBUG message */
 
     crFinish(0);
 }
@@ -494,6 +517,8 @@ static void send_packet(int pkttype, ...)
     unsigned char *p, *argp, argchar;
     unsigned long argint;
     int pktlen, argtype, arglen;
+    Bignum bn;
+    int i;
 
     pktlen = 0;
     va_start(args, pkttype);
@@ -517,6 +542,13 @@ static void send_packet(int pkttype, ...)
            arglen = strlen(argp);
            pktlen += 4 + arglen;
            break;
+         case PKT_BIGNUM:
+           bn = va_arg(args, Bignum);
+            i = 16 * bn[0] - 1;
+            while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 )
+                i--;
+            pktlen += 2 + (i+7)/8;
+           break;
          default:
            assert(0);
        }
@@ -552,6 +584,21 @@ static void send_packet(int pkttype, ...)
            memcpy(p + 4, argp, arglen);
            p += 4 + arglen;
            break;
+         case PKT_BIGNUM:
+           bn = va_arg(args, Bignum);
+            i = 16 * bn[0] - 1;
+            while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 )
+                i--;
+            *p++ = (i >> 8) & 0xFF;
+            *p++ = i & 0xFF;
+            i = (i + 7) / 8;
+            while (i-- > 0) {
+                if (i % 2)
+                    *p++ = bn[i/2+1] >> 8;
+                else
+                    *p++ = bn[i/2+1] & 0xFF;
+            }
+           break;
        }
     }
     va_end(args);
@@ -694,6 +741,178 @@ fflush(fp);
     SHA_Bytes(s, str, len);
 }
 
+/*
+ * SSH2 packet construction functions.
+ */
+void ssh2_pkt_adddata(void *data, int len) {
+    pktout.length += len;
+    if (pktout.maxlen < pktout.length) {
+        pktout.maxlen = pktout.length + 256;
+       pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) :
+                       realloc(pktout.data, pktout.maxlen+APIEXTRA));
+        if (!pktout.data)
+            fatalbox("Out of memory");
+    }
+    memcpy(pktout.data+pktout.length-len, data, len);
+}
+void ssh2_pkt_addbyte(unsigned char byte) {
+    ssh2_pkt_adddata(&byte, 1);
+}
+void ssh2_pkt_init(int pkt_type) {
+    pktout.length = 5;
+    ssh2_pkt_addbyte((unsigned char)pkt_type);
+}
+void ssh2_pkt_addbool(unsigned char value) {
+    ssh2_pkt_adddata(&value, 1);
+}
+void ssh2_pkt_adduint32(unsigned long value) {
+    unsigned char x[4];
+    PUT_32BIT(x, value);
+    ssh2_pkt_adddata(x, 4);
+}
+void ssh2_pkt_addstring_start(void) {
+    ssh2_pkt_adduint32(0);
+    pktout.savedpos = pktout.length;
+}
+void ssh2_pkt_addstring_str(char *data) {
+    ssh2_pkt_adddata(data, strlen(data));
+    PUT_32BIT(pktout.data + pktout.savedpos - 4,
+              pktout.length - pktout.savedpos);
+}
+void ssh2_pkt_addstring_data(char *data, int len) {
+    ssh2_pkt_adddata(data, len);
+    PUT_32BIT(pktout.data + pktout.savedpos - 4,
+              pktout.length - pktout.savedpos);
+}
+void ssh2_pkt_addstring(char *data) {
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_str(data);
+}
+char *ssh2_mpint_fmt(Bignum b, int *len) {
+    unsigned char *p;
+    int i, n = b[0];
+    p = malloc(n * 2 + 1);
+    if (!p)
+        fatalbox("out of memory");
+    p[0] = 0;
+    for (i = 0; i < n; i++) {
+        p[i*2+1] = (b[n-i] >> 8) & 0xFF;
+        p[i*2+2] = (b[n-i]     ) & 0xFF;
+    }
+    i = 0;
+    while (p[i] == 0 && (p[i+1] & 0x80) == 0)
+        i++;
+    memmove(p, p+i, n*2+1-i);
+    *len = n*2+1-i;
+    return p;
+}
+void ssh2_pkt_addmp(Bignum b) {
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_data(p, len);
+    free(p);
+}
+void ssh2_pkt_send(void) {
+    int cipherblk, maclen, padding, i;
+    static unsigned long outgoing_sequence = 0;
+
+    /*
+     * Add padding. At least four bytes, and must also bring total
+     * length (minus MAC) up to a multiple of the block size.
+     */
+    cipherblk = cipher ? cipher->blksize : 8;   /* block size */
+    cipherblk = cipherblk < 8 ? 8 : cipherblk;   /* or 8 if blksize < 8 */
+    padding = 4;
+    padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
+    pktout.data[4] = padding;
+    for (i = 0; i < padding; i++)
+        pktout.data[pktout.length + i] = random_byte();
+    PUT_32BIT(pktout.data, pktout.length + padding - 4);
+    if (csmac)
+        csmac->generate(pktout.data, pktout.length + padding,
+                        outgoing_sequence);
+    outgoing_sequence++;               /* whether or not we MACed */
+
+#if 0
+    debug(("Sending packet len=%d\r\n", pktout.length+padding));
+    for (i = 0; i < pktout.length+padding; i++)
+        debug(("  %02x", (unsigned char)pktout.data[i]));
+    debug(("\r\n"));
+#endif
+
+    if (cscipher)
+        cscipher->encrypt(pktout.data, pktout.length + padding);
+    maclen = csmac ? csmac->len : 0;
+
+    s_write(pktout.data, pktout.length + padding + maclen);
+}
+
+#if 0
+void bndebug(char *string, Bignum b) {
+    unsigned char *p;
+    int i, len;
+    p = ssh2_mpint_fmt(b, &len);
+    debug(("%s", string));
+    for (i = 0; i < len; i++)
+        debug((" %02x", p[i]));
+    debug(("\r\n"));
+    free(p);
+}
+#endif
+
+void sha_mpint(SHA_State *s, Bignum b) {
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    sha_string(s, p, len);
+    free(p);
+}
+
+/*
+ * SSH2 packet decode functions.
+ */
+unsigned long ssh2_pkt_getuint32(void) {
+    unsigned long value;
+    if (pktin.length - pktin.savedpos < 4)
+        return 0;                      /* arrgh, no way to decline (FIXME?) */
+    value = GET_32BIT(pktin.data+pktin.savedpos);
+    pktin.savedpos += 4;
+    return value;
+}
+void ssh2_pkt_getstring(char **p, int *length) {
+    *p = NULL;
+    if (pktin.length - pktin.savedpos < 4)
+        return;
+    *length = GET_32BIT(pktin.data+pktin.savedpos);
+    pktin.savedpos += 4;
+    if (pktin.length - pktin.savedpos < *length)
+        return;
+    *p = pktin.data+pktin.savedpos;
+    pktin.savedpos += *length;
+}
+Bignum ssh2_pkt_getmp(void) {
+    char *p;
+    int i, j, length;
+    Bignum b;
+
+    ssh2_pkt_getstring(&p, &length);
+    if (!p)
+        return NULL;
+    if (p[0] & 0x80)
+        fatalbox("internal error: Can't handle negative mpints");
+    b = newbn((length+1)/2);
+    for (i = 0; i < length; i++) {
+        j = length - 1 - i;
+        if (j & 1)
+            b[j/2+1] |= ((unsigned char)p[i]) << 8;
+        else
+            b[j/2+1] |= ((unsigned char)p[i]);
+    }
+    return b;
+}
+
 static int do_ssh_init(void) {
     char c, *vsp;
     char version[10];
@@ -739,7 +958,7 @@ static int do_ssh_init(void) {
     vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
 
-    if (ssh_versioncmp(version, "1.99") >= 0) {
+    if (ssh_versioncmp(version, "2.0" /* FIXME: "1.99" */ ) >= 0) {
         /*
          * This is a v2 server. Begin v2 protocol.
          */
@@ -780,12 +999,13 @@ static int do_ssh_init(void) {
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 {
     int i, j, len;
-    unsigned char session_id[16];
     unsigned char *rsabuf, *keystr1, *keystr2;
     unsigned char cookie[8];
     struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
+    static int tried_publickey;
+    static unsigned char session_id[16];
     int cipher_type;
 
     crBegin;
@@ -799,8 +1019,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     memcpy(cookie, pktin.body, 8);
 
-    i = makekey(pktin.body+8, &servkey, &keystr1);
-    j = makekey(pktin.body+8+i, &hostkey, &keystr2);
+    i = makekey(pktin.body+8, &servkey, &keystr1, 0);
+    j = makekey(pktin.body+8+i, &hostkey, &keystr2, 0);
 
     /*
      * Hash the host key and print the hash in the log box. Just as
@@ -969,17 +1189,20 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     crWaitUntil(ispkt);
 
+    tried_publickey = 0;
+
     while (pktin.type == SSH1_SMSG_FAILURE) {
        static char password[100];
        static int pos;
        static char c;
         static int pwpkt_type;
-
         /*
          * Show password prompt, having first obtained it via a TIS
          * exchange if we're doing TIS authentication.
          */
         pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+        if (*cfg.keyfile && !tried_publickey)
+            pwpkt_type = SSH1_CMSG_AUTH_RSA;
 
        if (IS_SCP) {
            char prompt[200];
@@ -997,59 +1220,133 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             }
        } else {
 
-        if (pktin.type == SSH1_SMSG_FAILURE &&
-            cfg.try_tis_auth &&
-            (supported_auths_mask & (1<<SSH1_AUTH_TIS))) {
-            pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
-           logevent("Requested TIS authentication");
-           send_packet(SSH1_CMSG_AUTH_TIS, PKT_END);
-            crWaitUntil(ispkt);
-            if (pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
-                logevent("TIS authentication declined");
-                c_write("TIS authentication refused.\r\n", 29);
-            } else {
-                int challengelen = ((pktin.body[0] << 24) |
-                                    (pktin.body[1] << 16) |
-                                    (pktin.body[2] << 8) |
-                                    (pktin.body[3]));
-                logevent("Received TIS challenge");
-                c_write(pktin.body+4, challengelen);
+            if (pktin.type == SSH1_SMSG_FAILURE &&
+                cfg.try_tis_auth &&
+                (supported_auths_mask & (1<<SSH1_AUTH_TIS))) {
+                pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+                logevent("Requested TIS authentication");
+                send_packet(SSH1_CMSG_AUTH_TIS, PKT_END);
+                crWaitUntil(ispkt);
+                if (pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+                    logevent("TIS authentication declined");
+                    c_write("TIS authentication refused.\r\n", 29);
+                } else {
+                    int challengelen = ((pktin.body[0] << 24) |
+                                        (pktin.body[1] << 16) |
+                                        (pktin.body[2] << 8) |
+                                        (pktin.body[3]));
+                    logevent("Received TIS challenge");
+                    c_write(pktin.body+4, challengelen);
+                }
+            }
+            if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD)
+                c_write("password: ", 10);
+            if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+                c_write("Trying public key authentication.\r\n", 35);
+                if (!rsakey_encrypted(cfg.keyfile)) {
+                    c_write("No passphrase required.\r\n", 25);
+                    goto tryauth;
+                }
+                c_write("passphrase: ", 12);
             }
-        }
-        if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD)
-            c_write("password: ", 10);
 
-       pos = 0;
-       while (pos >= 0) {
-           crWaitUntil(!ispkt);
-           while (inlen--) switch (c = *in++) {
-             case 10: case 13:
-               password[pos] = 0;
-               pos = -1;
-               break;
-             case 8: case 127:
-               if (pos > 0)
-                   pos--;
-               break;
-             case 21: case 27:
-               pos = 0;
-               break;
-             case 3: case 4:
-               random_save_seed();
-               exit(0);
-               break;
-             default:
-               if (((c >= ' ' && c <= '~') ||
-                     ((unsigned char)c >= 160)) && pos < 40)
-                   password[pos++] = c;
-               break;
-           }
-       }
-       c_write("\r\n", 2);
+            pos = 0;
+            while (pos >= 0) {
+                crWaitUntil(!ispkt);
+                while (inlen--) switch (c = *in++) {
+                  case 10: case 13:
+                    password[pos] = 0;
+                    pos = -1;
+                    break;
+                  case 8: case 127:
+                    if (pos > 0)
+                        pos--;
+                    break;
+                  case 21: case 27:
+                    pos = 0;
+                    break;
+                  case 3: case 4:
+                    random_save_seed();
+                    exit(0);
+                    break;
+                  default:
+                    if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < sizeof(password))
+                        password[pos++] = c;
+                    break;
+                }
+            }
+            c_write("\r\n", 2);
 
        }
 
-       send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+        tryauth:
+       if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+            /*
+             * Try public key authentication with the specified
+             * key file.
+             */
+            static struct RSAKey pubkey;
+            static Bignum challenge, response;
+            static int i;
+            static unsigned char buffer[32];
+
+            tried_publickey = 1;
+            i = loadrsakey(cfg.keyfile, &pubkey, password);
+            if (i == 0) {
+                c_write("Couldn't load public key from ", 30);
+                c_write(cfg.keyfile, strlen(cfg.keyfile));
+                c_write(".\r\n", 3);
+                continue;              /* go and try password */
+            }
+            if (i == -1) {
+                c_write("Wrong passphrase.\r\n", 19);
+                tried_publickey = 0;
+                continue;              /* try again */
+            }
+
+            /*
+             * Send a public key attempt.
+             */
+            send_packet(SSH1_CMSG_AUTH_RSA,
+                        PKT_BIGNUM, pubkey.modulus, PKT_END);
+
+            crWaitUntil(ispkt);
+            if (pktin.type == SSH1_SMSG_FAILURE) {
+                c_write("Server refused our public key.\r\n", 32);
+                continue;              /* go and try password */
+            }
+            if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE)
+                fatalbox("Bizarre response to offer of public key");
+            ssh1_read_bignum(pktin.body, &challenge);
+            response = rsadecrypt(challenge, &pubkey);
+            freebn(pubkey.private_exponent);   /* burn the evidence */
+
+            for (i = 0; i < 32; i += 2) {
+                buffer[i] = response[16-i/2] >> 8;
+                buffer[i+1] = response[16-i/2] & 0xFF;
+            }
+
+            MD5Init(&md5c);
+            MD5Update(&md5c, buffer, 32);
+            MD5Update(&md5c, session_id, 16);
+            MD5Final(buffer, &md5c);
+
+            send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE,
+                        PKT_DATA, buffer, 16, PKT_END);
+
+            crWaitUntil(ispkt);
+            if (pktin.type == SSH1_SMSG_FAILURE) {
+                c_write("Failed to authenticate with our public key.\r\n", 45);
+                continue;              /* go and try password */
+            } else if (pktin.type != SSH1_SMSG_SUCCESS) {
+                fatalbox("Bizarre response to RSA authentication response");
+            }
+
+            break;                     /* we're through! */
+        } else {
+            send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+        }
        logevent("Sent password");
        memset(password, 0, strlen(password));
        crWaitUntil(ispkt);
@@ -1135,165 +1432,6 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
 }
 
 /*
- * SSH2 packet construction functions.
- */
-void ssh2_pkt_adddata(void *data, int len) {
-    pktout.length += len;
-    if (pktout.maxlen < pktout.length) {
-        pktout.maxlen = pktout.length + 256;
-       pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) :
-                       realloc(pktout.data, pktout.maxlen+APIEXTRA));
-        if (!pktout.data)
-            fatalbox("Out of memory");
-    }
-    memcpy(pktout.data+pktout.length-len, data, len);
-}
-void ssh2_pkt_addbyte(unsigned char byte) {
-    ssh2_pkt_adddata(&byte, 1);
-}
-void ssh2_pkt_init(int pkt_type) {
-    pktout.length = 5;
-    ssh2_pkt_addbyte((unsigned char)pkt_type);
-}
-void ssh2_pkt_addbool(unsigned char value) {
-    ssh2_pkt_adddata(&value, 1);
-}
-void ssh2_pkt_adduint32(unsigned long value) {
-    unsigned char x[4];
-    PUT_32BIT(x, value);
-    ssh2_pkt_adddata(x, 4);
-}
-void ssh2_pkt_addstring_start(void) {
-    ssh2_pkt_adduint32(0);
-    pktout.savedpos = pktout.length;
-}
-void ssh2_pkt_addstring_str(char *data) {
-    ssh2_pkt_adddata(data, strlen(data));
-    PUT_32BIT(pktout.data + pktout.savedpos - 4,
-              pktout.length - pktout.savedpos);
-}
-void ssh2_pkt_addstring_data(char *data, int len) {
-    ssh2_pkt_adddata(data, len);
-    PUT_32BIT(pktout.data + pktout.savedpos - 4,
-              pktout.length - pktout.savedpos);
-}
-void ssh2_pkt_addstring(char *data) {
-    ssh2_pkt_addstring_start();
-    ssh2_pkt_addstring_str(data);
-}
-char *ssh2_mpint_fmt(Bignum b, int *len) {
-    unsigned char *p;
-    int i, n = b[0];
-    p = malloc(n * 2 + 1);
-    if (!p)
-        fatalbox("out of memory");
-    p[0] = 0;
-    for (i = 0; i < n; i++) {
-        p[i*2+1] = (b[n-i] >> 8) & 0xFF;
-        p[i*2+2] = (b[n-i]     ) & 0xFF;
-    }
-    i = 0;
-    while (p[i] == 0 && (p[i+1] & 0x80) == 0)
-        i++;
-    memmove(p, p+i, n*2+1-i);
-    *len = n*2+1-i;
-    return p;
-}
-void ssh2_pkt_addmp(Bignum b) {
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    ssh2_pkt_addstring_start();
-    ssh2_pkt_addstring_data(p, len);
-    free(p);
-}
-void ssh2_pkt_send(void) {
-    int cipherblk, maclen, padding, i;
-    static unsigned long outgoing_sequence = 0;
-
-    /*
-     * Add padding. At least four bytes, and must also bring total
-     * length (minus MAC) up to a multiple of the block size.
-     */
-    cipherblk = cipher ? cipher->blksize : 8;   /* block size */
-    cipherblk = cipherblk < 8 ? 8 : cipherblk;   /* or 8 if blksize < 8 */
-    padding = 4;
-    padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
-    pktout.data[4] = padding;
-    for (i = 0; i < padding; i++)
-        pktout.data[pktout.length + i] = random_byte();
-    PUT_32BIT(pktout.data, pktout.length + padding - 4);
-    if (csmac)
-        csmac->generate(pktout.data, pktout.length + padding,
-                        outgoing_sequence);
-    outgoing_sequence++;               /* whether or not we MACed */
-    if (cscipher)
-        cscipher->encrypt(pktout.data, pktout.length + padding);
-    maclen = csmac ? csmac->len : 0;
-#if 0
-    debug(("Sending packet len=%d\r\n", pktout.length+padding+maclen));
-    for (i = 0; i < pktout.length+padding+maclen; i++)
-        debug(("  %02x", (unsigned char)pktout.data[i]));
-    debug(("\r\n"));
-#endif
-    s_write(pktout.data, pktout.length + padding + maclen);
-}
-
-void sha_mpint(SHA_State *s, Bignum b) {
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    sha_string(s, p, len);
-    free(p);
-}
-
-/*
- * SSH2 packet decode functions.
- */
-void ssh2_pkt_getstring(char **p, int *length) {
-    *p = NULL;
-    if (pktin.length - pktin.savedpos < 4)
-        return;
-    *length = GET_32BIT(pktin.data+pktin.savedpos);
-    pktin.savedpos += 4;
-    if (pktin.length - pktin.savedpos < *length)
-        return;
-    *p = pktin.data+pktin.savedpos;
-    pktin.savedpos += *length;
-}
-Bignum ssh2_pkt_getmp(void) {
-    char *p;
-    int i, j, length;
-    Bignum b;
-
-    ssh2_pkt_getstring(&p, &length);
-    if (!p)
-        return NULL;
-    if (p[0] & 0x80)
-        fatalbox("internal error: Can't handle negative mpints");
-    b = newbn((length+1)/2);
-    for (i = 0; i < length; i++) {
-        j = length - 1 - i;
-        if (j & 1)
-            b[j/2+1] |= ((unsigned char)p[i]) << 8;
-        else
-            b[j/2+1] |= ((unsigned char)p[i]);
-    }
-    return b;
-}
-
-void bndebug(char *string, Bignum b) {
-    unsigned char *p;
-    int i, len;
-    p = ssh2_mpint_fmt(b, &len);
-    debug(("%s", string));
-    for (i = 0; i < len; i++)
-        debug(("  %02x", p[i]));
-    debug(("\r\n"));
-    free(p);
-}
-
-/*
  * Utility routine for decoding comma-separated strings in KEXINIT.
  */
 int in_commasep_string(char *needle, char *haystack, int haylen) {
@@ -1341,9 +1479,9 @@ void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) {
 }
 
 /*
- * Handle the SSH2 key exchange phase.
+ * Handle the SSH2 transport layer.
  */
-static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
+static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 {
     static int i, len;
     static char *str;
@@ -1354,13 +1492,15 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
     static struct ssh_mac *scmac_tobe = NULL;
     static struct ssh_compress *cscomp_tobe = NULL;
     static struct ssh_compress *sccomp_tobe = NULL;
-    static char *hostkeydata, *sigdata;
+    static char *hostkeydata, *sigdata, *keystr;
     static int hostkeylen, siglen;
     static unsigned char exchange_hash[20];
     static unsigned char keyspace[40];
 
     crBegin;
+    random_init();
 
+    begin_key_exchange:
     /*
      * Construct and send our key exchange packet.
      */
@@ -1441,8 +1581,9 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
      * Now examine the other side's KEXINIT to see what we're up
      * to.
      */
-    if (pktin.type != SSH2_MSG_KEXINIT)
+    if (pktin.type != SSH2_MSG_KEXINIT) {
         fatalbox("expected key exchange packet from server");
+    }
     kex = NULL; hostkey = NULL; cscipher_tobe = NULL; sccipher_tobe = NULL;
     csmac_tobe = NULL; scmac_tobe = NULL; cscomp_tobe = NULL; sccomp_tobe = NULL;
     pktin.savedpos += 16;              /* skip garbage cookie */
@@ -1502,14 +1643,6 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
             break;
         }
     }
-    debug(("key exchange is %s\r\n", kex ? kex->name : NULL));
-    debug(("host key alg is %s\r\n", hostkey ? hostkey->name : NULL));
-    debug(("cscipher alg is %s\r\n", cscipher_tobe ? cscipher_tobe->name : NULL));
-    debug(("sccipher alg is %s\r\n", sccipher_tobe ? sccipher_tobe->name : NULL));
-    debug(("csmac alg is %s\r\n", csmac_tobe ? csmac_tobe->name : NULL));
-    debug(("scmac alg is %s\r\n", scmac_tobe ? scmac_tobe->name : NULL));
-    debug(("cscomp alg is %s\r\n", cscomp_tobe ? cscomp_tobe->name : NULL));
-    debug(("sccomp alg is %s\r\n", sccomp_tobe ? sccomp_tobe->name : NULL));
 
     /*
      * Currently we only support Diffie-Hellman and DSS, so let's
@@ -1522,21 +1655,19 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
      * Now we begin the fun. Generate and send e for Diffie-Hellman.
      */
     e = dh_create_e();
-    bndebug("e=", e);
     ssh2_pkt_init(SSH2_MSG_KEXDH_INIT);
     ssh2_pkt_addmp(e);
     ssh2_pkt_send();
 
     crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_KEXDH_REPLY)
+    if (pktin.type != SSH2_MSG_KEXDH_REPLY) {
         fatalbox("expected key exchange packet from server");
+    }
     ssh2_pkt_getstring(&hostkeydata, &hostkeylen);
     f = ssh2_pkt_getmp();
-    bndebug("f=", f);
     ssh2_pkt_getstring(&sigdata, &siglen);
 
     K = dh_find_K(f);
-    bndebug("K=", K);
 
     sha_string(&exhash, hostkeydata, hostkeylen);
     sha_mpint(&exhash, e);
@@ -1544,29 +1675,37 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
     sha_mpint(&exhash, K);
     SHA_Final(&exhash, exchange_hash);
 
+#if 0
     debug(("Exchange hash is:\r\n"));
     for (i = 0; i < 20; i++)
         debug((" %02x", exchange_hash[i]));
     debug(("\r\n"));
+#endif
+
+    hostkey->setkey(hostkeydata, hostkeylen);
+    if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20))
+        fatalbox("Server failed host key check");
 
     /*
-     * FIXME: verify host key. This bit will be moderately
-     * unpleasant, because of having to rewrite it to work
-     * alongside the old scheme.
+     * Expect SSH2_MSG_NEWKEYS from server.
      */
+    crWaitUntil(ispkt);
+    if (pktin.type != SSH2_MSG_NEWKEYS)
+        fatalbox("expected new-keys packet from server");
 
     /*
-     * FIXME: verify signature of exchange hash.
+     * Authenticate remote host: verify host key. (We've already
+     * checked the signature of the exchange hash.)
      */
+    keystr = hostkey->fmtkey();
+    verify_ssh_host_key(savedhost, keystr);
+    free(keystr);
 
     /*
-     * Send SSH2_MSG_NEWKEYS. Expect it from server.
+     * Send SSH2_MSG_NEWKEYS.
      */
     ssh2_pkt_init(SSH2_MSG_NEWKEYS);
     ssh2_pkt_send();
-    crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_NEWKEYS)
-        fatalbox("expected new-keys packet from server");
 
     /*
      * Create and initialise session keys.
@@ -1588,31 +1727,291 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
     ssh2_mkkey(K, exchange_hash, 'F', keyspace); scmac->setsckey(keyspace);
 
     /*
-     * Now we're encrypting. Send a test packet (FIXME).
+     * Now we're encrypting. Begin returning 1 to the protocol main
+     * 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.
      */
-    crWaitUntil(!ispkt);
-    ssh2_pkt_init(SSH2_MSG_IGNORE);
-    ssh2_pkt_addstring("oo-er");
-    ssh2_pkt_send();
-
-    crWaitUntil(0);
+    do {
+        crReturn(1);
+    } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT));
+    goto begin_key_exchange;
 
     crFinish(1);
 }
 
-static void ssh2_protocol(unsigned char *in, int inlen, int ispkt) {
+/*
+ * Handle the SSH2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
+{
+    static unsigned long their_channel;
+    static unsigned long remote_winsize;
+    static unsigned long remote_maxpkt;
+
     crBegin;
 
-    random_init();
+    /*
+     * Request userauth protocol, and await a response to it.
+     */
+    ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+    ssh2_pkt_addstring("ssh-userauth");
+    ssh2_pkt_send();
+    crWaitUntilV(ispkt);
+    if (pktin.type != SSH2_MSG_SERVICE_ACCEPT)
+        fatalbox("Server refused user authentication protocol");
+
+    /*
+     * FIXME: currently we support only password authentication.
+     * (This places us technically in violation of the SSH2 spec.
+     * We must fix this.)
+     */
+    while (1) {
+        /*
+         * Get a username and a password.
+         */
+       static char username[100];
+       static char password[100];
+       static int pos = 0;
+       static char c;
 
-    while (!do_ssh2_kex(in, inlen, ispkt)) {
+       if (!IS_SCP && !*cfg.username) {
+           c_write("login as: ", 10);
+           while (pos >= 0) {
+               crWaitUntilV(!ispkt);
+               while (inlen--) switch (c = *in++) {
+                 case 10: case 13:
+                   username[pos] = 0;
+                   pos = -1;
+                   break;
+                 case 8: case 127:
+                   if (pos > 0) {
+                       c_write("\b \b", 3);
+                       pos--;
+                   }
+                   break;
+                 case 21: case 27:
+                   while (pos > 0) {
+                       c_write("\b \b", 3);
+                       pos--;
+                   }
+                   break;
+                 case 3: case 4:
+                   random_save_seed();
+                   exit(0);
+                   break;
+                 default:
+                   if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < 40) {
+                       username[pos++] = c;
+                       c_write(&c, 1);
+                   }
+                   break;
+               }
+           }
+           c_write("\r\n", 2);
+           username[strcspn(username, "\n\r")] = '\0';
+       } else {
+           char stuff[200];
+           strncpy(username, cfg.username, 99);
+           username[99] = '\0';
+           if (!IS_SCP) {
+               sprintf(stuff, "Using username \"%s\".\r\n", username);
+               c_write(stuff, strlen(stuff));
+           }
+       }
+
+       if (IS_SCP) {
+           char prompt[200];
+           sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
+           if (!ssh_get_password(prompt, password, sizeof(password))) {
+                /*
+                 * get_password failed to get a password (for
+                 * example because one was supplied on the command
+                 * line which has already failed to work).
+                 * Terminate.
+                 */
+                logevent("No more passwords to try");
+                ssh_state = SSH_STATE_CLOSED;
+                crReturnV;
+            }
+       } else {
+            c_write("password: ", 10);
+
+            pos = 0;
+            while (pos >= 0) {
+                crWaitUntilV(!ispkt);
+                while (inlen--) switch (c = *in++) {
+                  case 10: case 13:
+                    password[pos] = 0;
+                    pos = -1;
+                    break;
+                  case 8: case 127:
+                    if (pos > 0)
+                        pos--;
+                    break;
+                  case 21: case 27:
+                    pos = 0;
+                    break;
+                  case 3: case 4:
+                    random_save_seed();
+                    exit(0);
+                    break;
+                  default:
+                    if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < 40)
+                        password[pos++] = c;
+                    break;
+                }
+            }
+            c_write("\r\n", 2);
+       }
+
+        ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+        ssh2_pkt_addstring(username);
+        ssh2_pkt_addstring("ssh-connection");   /* service requested */
+        ssh2_pkt_addstring("password");
+        ssh2_pkt_addbool(FALSE);
+        ssh2_pkt_addstring(password);
+        ssh2_pkt_send();
+
+        crWaitUntilV(ispkt);
+        if (pktin.type != SSH2_MSG_USERAUTH_SUCCESS) {
+           c_write("Access denied\r\n", 15);
+           logevent("Authentication refused");
+        } else
+            break;
+    }
+
+    /*
+     * Now we're authenticated for the connection protocol. The
+     * connection protocol will automatically have started at this
+     * point; there's no need to send SERVICE_REQUEST.
+     */
+
+    /*
+     * So now create a channel with a session in it.
+     */
+    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+    ssh2_pkt_addstring("session");
+    ssh2_pkt_adduint32(100);           /* as good as any */
+    ssh2_pkt_adduint32(0xFFFFFFFFUL);  /* very big window which we ignore */
+    ssh2_pkt_adduint32(0xFFFFFFFFUL);  /* very big max pkt size */
+    ssh2_pkt_send();
+    crWaitUntilV(ispkt);
+    if (pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+        fatalbox("Server refused to open a session");
+        /* FIXME: error data comes back in FAILURE packet */
+    }
+    if (ssh2_pkt_getuint32() != 100) {
+        fatalbox("Server's channel confirmation cited wrong channel");
+    }
+    their_channel = ssh2_pkt_getuint32();
+    remote_winsize = ssh2_pkt_getuint32();
+    remote_maxpkt = ssh2_pkt_getuint32();
+    logevent("Opened channel for session");
+
+    /*
+     * Now allocate a pty for the session.
+     */
+    ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(their_channel); /* recipient channel */
+    ssh2_pkt_addstring("pty-req");
+    ssh2_pkt_addbool(1);               /* want reply */
+    ssh2_pkt_addstring(cfg.termtype);
+    ssh2_pkt_adduint32(cols);
+    ssh2_pkt_adduint32(rows);
+    ssh2_pkt_adduint32(0);             /* pixel width */
+    ssh2_pkt_adduint32(0);             /* pixel height */
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_data("\0", 1);  /* TTY_OP_END, no special options */
+    ssh2_pkt_send();
+
+    do {                               /* FIXME: pay attention to these */
+        crWaitUntilV(ispkt);
+    } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+    if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+        if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+            fatalbox("Server got confused by pty request");
+        }
+        c_write("Server refused to allocate pty\r\n", 32);
+    } else {
+        logevent("Allocated pty");
+    }
+
+    /*
+     * Start a shell.
+     */
+    ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(their_channel); /* recipient channel */
+    ssh2_pkt_addstring("shell");
+    ssh2_pkt_addbool(1);               /* want reply */
+    ssh2_pkt_send();
+    do {                               /* FIXME: pay attention to these */
+        crWaitUntilV(ispkt);
+    } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+    if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+        if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+            fatalbox("Server got confused by shell request");
+        }
+        fatalbox("Server refused to start a shell");
+    } else {
+        logevent("Started a shell");
+    }
+
+    /*
+     * Transfer data!
+     */
+    while (1) {
        crReturnV;
+       if (ispkt) {
+           if (pktin.type == SSH2_MSG_CHANNEL_DATA ||
+                pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+                char *data;
+                int length;
+                if (ssh2_pkt_getuint32() != 100)
+                    continue;          /* wrong channel */
+                if (pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+                    ssh2_pkt_getuint32() != SSH2_EXTENDED_DATA_STDERR)
+                    continue;          /* extended but not stderr */
+                ssh2_pkt_getstring(&data, &length);
+                if (data)
+                    c_write(data, length);
+           } else if (pktin.type == SSH2_MSG_DISCONNECT) {
+                ssh_state = SSH_STATE_CLOSED;
+               logevent("Received disconnect request");
+           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+                continue;              /* exit status et al; ignore (FIXME?) */
+           } else if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                continue;              /* ignore for now (FIXME!) */
+           } else {
+               fatalbox("Strange packet received: type %d", pktin.type);
+           }
+       } else {
+            /* FIXME: for now, ignore window size */
+            ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+            ssh2_pkt_adduint32(their_channel);
+            ssh2_pkt_addstring_start();
+            ssh2_pkt_addstring_data(in, inlen);
+            ssh2_pkt_send();
+       }
     }
 
     crFinishV;
 }
 
 /*
+ * Handle the top-level SSH2 protocol.
+ */
+static void ssh2_protocol(unsigned char *in, int inlen, int ispkt)
+{
+    if (do_ssh2_transport(in, inlen, ispkt) == 0)
+        return;
+    do_ssh2_authconn(in, inlen, ispkt);
+}
+
+/*
  * Called to set up the connection. Will arrange for WM_NETEVENT
  * messages to be passed to the specified window, whose window
  * procedure should then call telnet_msg().
@@ -1904,4 +2303,3 @@ Backend ssh_backend = {
     ssh_size,
     ssh_special
 };
-
diff --git a/ssh.h b/ssh.h
index 84e3c20..c3233fe 100644 (file)
--- a/ssh.h
+++ b/ssh.h
 #define APIEXTRA 0
 #endif
 
+/*
+ * A Bignum is stored as a sequence of `unsigned short' words. The
+ * first tells how many remain; the remaining ones are digits, LS
+ * first.
+ */
+typedef unsigned short *Bignum;
+
 struct RSAKey {
     int bits;
     int bytes;
@@ -25,14 +32,19 @@ struct RSAKey {
     unsigned long exponent;
     unsigned char *modulus;
 #else
-    void *modulus;
-    void *exponent;
+    Bignum modulus;
+    Bignum exponent;
+    Bignum private_exponent;
 #endif
 };
 
 int makekey(unsigned char *data, struct RSAKey *result,
-           unsigned char **keystr);
+           unsigned char **keystr, int order);
+int makeprivate(unsigned char *data, struct RSAKey *result);
 void rsaencrypt(unsigned char *data, int length, struct RSAKey *key);
+Bignum rsadecrypt(Bignum input, struct RSAKey *key);
+void rsasign(unsigned char *data, int length, struct RSAKey *key);
+void rsasanitise(struct RSAKey *key);
 int rsastr_len(struct RSAKey *key);
 void rsastr_fmt(char *str, struct RSAKey *key);
 
@@ -71,6 +83,7 @@ typedef struct {
 void SHA_Init(SHA_State *s);
 void SHA_Bytes(SHA_State *s, void *p, int len);
 void SHA_Final(SHA_State *s, unsigned char *output);
+void SHA_Simple(void *p, int len, unsigned char *output);
 
 struct ssh_cipher {
     void (*sesskey)(unsigned char *key);   /* for ssh 1 */
@@ -94,10 +107,20 @@ struct ssh_mac {
 };
 
 struct ssh_kex {
+    /*
+     * Plugging in another KEX algorithm requires structural chaos,
+     * so it's hard to abstract them into nice little structures
+     * like this. Hence, for the moment, this is just a
+     * placeholder. I claim justification in the fact that OpenSSH
+     * does this too :-)
+     */
     char *name;
 };
 
 struct ssh_hostkey {
+    void (*setkey)(char *data, int len);
+    char *(*fmtkey)(void);
+    int (*verifysig)(char *sig, int siglen, char *data, int datalen);
     char *name;
 };
 
@@ -114,16 +137,20 @@ void random_add_noise(void *noise, int length);
 
 void logevent (char *);
 
-/*
- * A Bignum is stored as a sequence of `unsigned short' words. The
- * first tells how many remain; the remaining ones are digits, LS
- * first.
- */
-typedef unsigned short *Bignum;
-
 Bignum newbn(int length);
+Bignum copybn(Bignum b);
 void freebn(Bignum b);
 void modpow(Bignum base, Bignum exp, Bignum mod, Bignum result);
+void modmul(Bignum a, Bignum b, Bignum mod, Bignum result);
+void decbn(Bignum n);
+extern Bignum Zero, One;
+int ssh1_read_bignum(unsigned char *data, Bignum *result);
 
 Bignum dh_create_e(void);
 Bignum dh_find_K(Bignum f);
+
+int loadrsakey(char *filename, struct RSAKey *key, char *passphrase);
+int rsakey_encrypted(char *filename);
+
+void des3_decrypt_pubkey(unsigned char *key,
+                         unsigned char *blk, int len);
\ No newline at end of file
diff --git a/sshbn.c b/sshbn.c
index a9eae85..97ae357 100644 (file)
--- a/sshbn.c
+++ b/sshbn.c
@@ -6,36 +6,17 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <stdio.h> /* FIXME */
+#include <stdarg.h> /* FIXME */
+#include <windows.h> /* FIXME */
+#include "putty.h" /* FIXME */
+
 #include "ssh.h"
 
-static unsigned short Zero[1] = { 0 };
+unsigned short bnZero[1] = { 0 };
+unsigned short bnOne[2] = { 1, 1 };
 
-#if defined TESTMODE || defined RSADEBUG
-#ifndef DLVL
-#define DLVL 10000
-#endif
-#define debug(x) bndebug(#x,x)
-static int level = 0;
-static void bndebug(char *name, Bignum b) {
-    int i;
-    int w = 50-level-strlen(name)-5*b[0];
-    if (level >= DLVL)
-       return;
-    if (w < 0) w = 0;
-    dprintf("%*s%s%*s", level, "", name, w, "");
-    for (i=b[0]; i>0; i--)
-       dprintf(" %04x", b[i]);
-    dprintf("\n");
-}
-#define dmsg(x) do {if(level<DLVL){dprintf("%*s",level,"");printf x;}} while(0)
-#define enter(x) do { dmsg(x); level += 4; } while(0)
-#define leave(x) do { level -= 4; dmsg(x); } while(0)
-#else
-#define debug(x)
-#define dmsg(x)
-#define enter(x)
-#define leave(x)
-#endif
+Bignum Zero = bnZero, One = bnOne;
 
 Bignum newbn(int length) {
     Bignum b = malloc((length+1)*sizeof(unsigned short));
@@ -46,6 +27,14 @@ Bignum newbn(int length) {
     return b;
 }
 
+Bignum copybn(Bignum orig) {
+    Bignum b = malloc((orig[0]+1)*sizeof(unsigned short));
+    if (!b)
+       abort();                       /* FIXME */
+    memcpy(b, orig, (orig[0]+1)*sizeof(*b));
+    return b;
+}
+
 void freebn(Bignum b) {
     /*
      * Burn the evidence, just in case.
@@ -83,11 +72,13 @@ static void bigmul(unsigned short *a, unsigned short *b, unsigned short *c,
 
 /*
  * Compute a = a % m.
- * Input in first 2*len words of a and first len words of m.
- * Output in first 2*len words of a (of which first len words will be zero).
+ * Input in first len2 words of a and first len words of m.
+ * Output in first len2 words of a
+ * (of which first len2-len words will be zero).
  * The MSW of m MUST have its high bit set.
  */
-static void bigmod(unsigned short *a, unsigned short *m, int len)
+static void bigmod(unsigned short *a, unsigned short *m,
+                   int len, int len2)
 {
     unsigned short m0, m1;
     unsigned int h;
@@ -103,7 +94,7 @@ static void bigmod(unsigned short *a, unsigned short *m, int len)
     m0 = m[0];
     m1 = m[1];
 
-    for (i = 0; i <= len; i++) {
+    for (i = 0; i <= len2-len; i++) {
        unsigned long t;
        unsigned int q, r, c;
 
@@ -204,10 +195,10 @@ void modpow(Bignum base, Bignum exp, Bignum mod, Bignum result)
     while (i < exp[0]) {
        while (j >= 0) {
            bigmul(a + mlen, a + mlen, b, mlen);
-           bigmod(b, m, mlen);
+           bigmod(b, m, mlen, mlen*2);
            if ((exp[exp[0] - i] & (1 << j)) != 0) {
                bigmul(b + mlen, n, a, mlen);
-               bigmod(a, m, mlen);
+               bigmod(a, m, mlen, mlen*2);
            } else {
                unsigned short *t;
                t = a;  a = b;  b = t;
@@ -222,7 +213,7 @@ void modpow(Bignum base, Bignum exp, Bignum mod, Bignum result)
        for (i = mlen - 1; i < 2*mlen - 1; i++)
            a[i] = (a[i] << mshift) | (a[i+1] >> (16-mshift));
        a[2*mlen-1] = a[2*mlen-1] << mshift;
-       bigmod(a, m, mlen);
+       bigmod(a, m, mlen, mlen*2);
        for (i = 2*mlen - 1; i >= mlen; i--)
            a[i] = (a[i] >> mshift) | (a[i-1] << (16-mshift));
     }
@@ -237,3 +228,115 @@ void modpow(Bignum base, Bignum exp, Bignum mod, Bignum result)
     for (i = 0; i < mlen; i++) m[i] = 0; free(m);
     for (i = 0; i < mlen; i++) n[i] = 0; free(n);
 }
+
+/*
+ * Compute (p * q) % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ */
+void modmul(Bignum p, Bignum q, Bignum mod, Bignum result)
+{
+    unsigned short *a, *n, *m, *o;
+    int mshift;
+    int pqlen, mlen, i, j;
+
+    /* Allocate m of size mlen, copy mod to m */
+    /* We use big endian internally */
+    mlen = mod[0];
+    m = malloc(mlen * sizeof(unsigned short));
+    for (j = 0; j < mlen; j++) m[j] = mod[mod[0] - j];
+
+    /* Shift m left to make msb bit set */
+    for (mshift = 0; mshift < 15; mshift++)
+       if ((m[0] << mshift) & 0x8000) break;
+    if (mshift) {
+       for (i = 0; i < mlen - 1; i++)
+           m[i] = (m[i] << mshift) | (m[i+1] >> (16-mshift));
+       m[mlen-1] = m[mlen-1] << mshift;
+    }
+
+    pqlen = (p[0] > q[0] ? p[0] : q[0]);
+
+    /* Allocate n of size pqlen, copy p to n */
+    n = malloc(pqlen * sizeof(unsigned short));
+    i = pqlen - p[0];
+    for (j = 0; j < i; j++) n[j] = 0;
+    for (j = 0; j < p[0]; j++) n[i+j] = p[p[0] - j];
+
+    /* Allocate o of size pqlen, copy q to o */
+    o = malloc(pqlen * sizeof(unsigned short));
+    i = pqlen - q[0];
+    for (j = 0; j < i; j++) o[j] = 0;
+    for (j = 0; j < q[0]; j++) o[i+j] = q[q[0] - j];
+
+    /* Allocate a of size 2*pqlen for result */
+    a = malloc(2 * pqlen * sizeof(unsigned short));
+
+    /* Main computation */
+    bigmul(n, o, a, pqlen);
+    bigmod(a, m, mlen, 2*pqlen);
+
+    /* Fixup result in case the modulus was shifted */
+    if (mshift) {
+       for (i = 2*pqlen - mlen - 1; i < 2*pqlen - 1; i++)
+           a[i] = (a[i] << mshift) | (a[i+1] >> (16-mshift));
+       a[2*pqlen-1] = a[2*pqlen-1] << mshift;
+       bigmod(a, m, mlen, pqlen*2);
+       for (i = 2*pqlen - 1; i >= 2*pqlen - mlen; i--)
+           a[i] = (a[i] >> mshift) | (a[i-1] << (16-mshift));
+    }
+
+    /* Copy result to buffer */
+    for (i = 0; i < mlen; i++)
+       result[result[0] - i] = a[i+2*pqlen-mlen];
+
+    /* Free temporary arrays */
+    for (i = 0; i < 2*pqlen; i++) a[i] = 0; free(a);
+    for (i = 0; i < mlen; i++) m[i] = 0; free(m);
+    for (i = 0; i < pqlen; i++) n[i] = 0; free(n);
+    for (i = 0; i < pqlen; i++) o[i] = 0; free(o);
+}
+
+/*
+ * Decrement a number.
+ */
+void decbn(Bignum bn) {
+    int i = 1;
+    while (i < bn[0] && bn[i] == 0)
+        bn[i++] = 0xFFFF;
+    bn[i]--;
+}
+
+/*
+ * Read an ssh1-format bignum from a data buffer. Return the number
+ * of bytes consumed.
+ */
+int ssh1_read_bignum(unsigned char *data, Bignum *result) {
+    unsigned char *p = data;
+    Bignum bn;
+    int i;
+    int w, b;
+
+    w = 0;
+    for (i=0; i<2; i++)
+        w = (w << 8) + *p++;
+
+    b = (w+7)/8;                       /* bits -> bytes */
+    w = (w+15)/16;                    /* bits -> words */
+
+    bn = newbn(w);
+
+    for (i=1; i<=w; i++)
+        bn[i] = 0;
+    for (i=b; i-- ;) {
+        unsigned char byte = *p++;
+        if (i & 1)
+            bn[1+i/2] |= byte<<8;
+        else
+            bn[1+i/2] |= byte;
+    }
+
+    *result = bn;
+
+    return p - data;
+}
index 1c82193..bc1fe59 100644 (file)
--- a/sshdes.c
+++ b/sshdes.c
@@ -1,4 +1,5 @@
 #include <assert.h>
+#include <stdio.h> /* FIXME */
 #include <stdarg.h> /* FIXME */
 #include <windows.h> /* FIXME */
 #include "putty.h" /* FIXME */
@@ -769,6 +770,18 @@ static void des3_ssh2_decrypt_blk(unsigned char *blk, int len) {
     des_cbc3_decrypt(blk, blk, len, sckeys);
 }
 
+void des3_decrypt_pubkey(unsigned char *key,
+                         unsigned char *blk, int len) {
+    DESContext ourkeys[3];
+    des_key_setup(GET_32BIT_MSB_FIRST(key),
+                  GET_32BIT_MSB_FIRST(key+4), &ourkeys[0]);
+    des_key_setup(GET_32BIT_MSB_FIRST(key+8),
+                  GET_32BIT_MSB_FIRST(key+12), &ourkeys[1]);
+    des_key_setup(GET_32BIT_MSB_FIRST(key),
+                  GET_32BIT_MSB_FIRST(key+4), &ourkeys[2]);
+    des_3cbc_decrypt(blk, blk, len, ourkeys);
+}
+
 struct ssh_cipher ssh_3des_ssh2 = {
     NULL,
     des3_csiv, des3_cskey,
index c651140..ce18f47 100644 (file)
--- a/sshdss.c
+++ b/sshdss.c
@@ -1,5 +1,189 @@
+#include <stdio.h>
+#include <stdlib.h>
+
 #include "ssh.h"
 
+#define GET_32BIT(cp) \
+    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+    ((unsigned long)(unsigned char)(cp)[3]))
+
+static void getstring(char **data, int *datalen, char **p, int *length) {
+    *p = NULL;
+    if (*datalen < 4)
+        return;
+    *length = GET_32BIT(*data);
+    *datalen -= 4; *data += 4;
+    if (*datalen < *length)
+        return;
+    *p = *data;
+    *data += *length; *datalen -= *length;
+}
+static Bignum getmp(char **data, int *datalen) {
+    char *p;
+    int i, j, length;
+    Bignum b;
+
+    getstring(data, datalen, &p, &length);
+    if (!p)
+        return NULL;
+    if (p[0] & 0x80)
+        return NULL;                   /* negative mp */
+    b = newbn((length+1)/2);
+    for (i = 0; i < length; i++) {
+        j = length - 1 - i;
+        if (j & 1)
+            b[j/2+1] |= ((unsigned char)p[i]) << 8;
+        else
+            b[j/2+1] |= ((unsigned char)p[i]);
+    }
+    return b;
+}
+
+static Bignum get160(char **data, int *datalen) {
+    char *p;
+    int i, j, length;
+    Bignum b;
+
+    p = *data;
+    *data += 20; *datalen -= 20;
+
+    length = 20;
+    while (length > 0 && !p[0])
+        p++, length--;
+    b = newbn((length+1)/2);
+    for (i = 0; i < length; i++) {
+        j = length - 1 - i;
+        if (j & 1)
+            b[j/2+1] |= ((unsigned char)p[i]) << 8;
+        else
+            b[j/2+1] |= ((unsigned char)p[i]);
+    }
+    return b;
+}
+
+static Bignum dss_p, dss_q, dss_g, dss_y;
+
+static void dss_setkey(char *data, int len) {
+    char *p;
+    int slen;
+    getstring(&data, &len, &p, &slen);
+    if (!p || memcmp(p, "ssh-dss", 7)) {
+        dss_p = NULL;
+        return;
+    }
+    dss_p = getmp(&data, &len);
+    dss_q = getmp(&data, &len);
+    dss_g = getmp(&data, &len);
+    dss_y = getmp(&data, &len);
+}
+
+static char *dss_fmtkey(void) {
+    char *p;
+    int len;
+    int i;
+    if (!dss_p)
+        return NULL;
+    len = 7 + 4 + 1;                   /* "ssh-dss", punctuation, \0 */
+    len += 4 * (dss_p[0] + dss_q[0] + dss_g[0] + dss_y[0]);   /* digits */
+    p = malloc(len);
+    if (!p) return NULL;
+    strcpy(p, "ssh-dss:");
+    for (i = dss_p[0]; i > 0; i--) sprintf(p+strlen(p), "%04X", dss_p[i]);
+    strcat(p, "/");
+    for (i = dss_q[0]; i > 0; i--) sprintf(p+strlen(p), "%04X", dss_q[i]);
+    strcat(p, "/");
+    for (i = dss_g[0]; i > 0; i--) sprintf(p+strlen(p), "%04X", dss_g[i]);
+    strcat(p, "/");
+    for (i = dss_y[0]; i > 0; i--) sprintf(p+strlen(p), "%04X", dss_y[i]);
+    return p;
+}
+
+static int dss_verifysig(char *sig, int siglen, char *data, int datalen) {
+    char *p;
+    int i, slen;
+    char hash[20];
+    Bignum qm2, r, s, w, i1, i2, i3, u1, u2, sha, v;
+    int ret;
+
+    if (!dss_p)
+        return 0;
+
+    getstring(&sig, &siglen, &p, &slen);
+    if (!p || memcmp(p, "ssh-dss", 7)) {
+        return 0;
+    }
+    sig += 4, siglen -= 4;             /* skip yet another length field */
+    r = get160(&sig, &siglen);
+    s = get160(&sig, &siglen);
+    if (!r || !s)
+        return 0;
+
+    /*
+     * Step 1. w <- s^-1 mod q.
+     */
+    w = newbn(dss_q[0]);
+    qm2 = copybn(dss_q);
+    decbn(qm2); decbn(qm2);
+    /* Now qm2 is q-2, and by Fermat's Little Theorem, s^qm2 == s^-1 (mod q).
+     * This is a silly way to do it; may fix it later. */
+    modpow(s, qm2, dss_q, w);
+
+    /*
+     * Step 2. u1 <- SHA(message) * w mod q.
+     */
+    u1 = newbn(dss_q[0]);
+    SHA_Simple(data, datalen, hash);
+    p = hash; slen = 20; sha = get160(&p, &slen);
+    modmul(sha, w, dss_q, u1);
+
+    /*
+     * Step 3. u2 <- r * w mod q.
+     */
+    u2 = newbn(dss_q[0]);
+    modmul(r, w, dss_q, u2);
+
+    /*
+     * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
+     */
+    i1 = newbn(dss_p[0]);
+    i2 = newbn(dss_p[0]);
+    i3 = newbn(dss_p[0]);
+    v = newbn(dss_q[0]);
+    modpow(dss_g, u1, dss_p, i1);
+    modpow(dss_y, u2, dss_p, i2);
+    modmul(i1, i2, dss_p, i3);
+    modmul(i3, One, dss_q, v);
+
+    /*
+     * Step 5. v should now be equal to r.
+     */
+
+    ret = 1;
+    for (i = 1; i <= v[0] || i <= r[0]; i++) {
+        if ((i > v[0] && r[i] != 0) ||
+            (i > r[0] && v[i] != 0) ||
+            (i <= v[0] && i <= r[0] && r[i] != v[i]))
+            ret = 0;
+    }
+
+    freebn(w);
+    freebn(qm2);
+    freebn(sha);
+    freebn(i1);
+    freebn(i2);
+    freebn(i3);
+    freebn(v);
+    freebn(r);
+    freebn(s);
+
+    return ret;
+}
+
 struct ssh_hostkey ssh_dss = {
+    dss_setkey,
+    dss_fmtkey,
+    dss_verifysig,
     "ssh-dss"
 };
index bf54e40..bd92c7b 100644 (file)
--- a/sshrsa.c
+++ b/sshrsa.c
@@ -39,49 +39,35 @@ static void bndebug(char *name, Bignum b) {
 #include "ssh.h"
 
 int makekey(unsigned char *data, struct RSAKey *result,
-           unsigned char **keystr) {
+           unsigned char **keystr, int order) {
     unsigned char *p = data;
-    Bignum bn[2];
-    int i, j;
-    int w, b;
+    int i;
 
     result->bits = 0;
     for (i=0; i<4; i++)
        result->bits = (result->bits << 8) + *p++;
 
-    for (j=0; j<2; j++) {
-
-       w = 0;
-       for (i=0; i<2; i++)
-           w = (w << 8) + *p++;
-
-       result->bytes = b = (w+7)/8;   /* bits -> bytes */
-       w = (w+15)/16;                 /* bits -> words */
-
-       bn[j] = newbn(w);
+    /*
+     * order=0 means exponent then modulus (the keys sent by the
+     * server). order=1 means modulus then exponent (the keys
+     * stored in a keyfile).
+     */
 
-       if (keystr) *keystr = p;       /* point at key string, second time */
-
-       for (i=1; i<=w; i++)
-           bn[j][i] = 0;
-       for (i=b; i-- ;) {
-           unsigned char byte = *p++;
-           if (i & 1)
-               bn[j][1+i/2] |= byte<<8;
-           else
-               bn[j][1+i/2] |= byte;
-       }
-
-       debug(bn[j]);
-
-    }
-
-    result->exponent = bn[0];
-    result->modulus = bn[1];
+    if (order == 0)
+        p += ssh1_read_bignum(p, &result->exponent);
+    result->bytes = (((p[0] << 8) + p[1]) + 7) / 8;
+    if (keystr) *keystr = p+2;
+    p += ssh1_read_bignum(p, &result->modulus);
+    if (order == 1)
+        p += ssh1_read_bignum(p, &result->exponent);
 
     return p - data;
 }
 
+int makeprivate(unsigned char *data, struct RSAKey *result) {
+    return ssh1_read_bignum(data, &result->private_exponent);
+}
+
 void rsaencrypt(unsigned char *data, int length, struct RSAKey *key) {
     Bignum b1, b2;
     int w, i;
@@ -136,6 +122,13 @@ void rsaencrypt(unsigned char *data, int length, struct RSAKey *key) {
     freebn(b2);
 }
 
+Bignum rsadecrypt(Bignum input, struct RSAKey *key) {
+    Bignum ret;
+    ret = newbn(key->modulus[0]);
+    modpow(input, key->private_exponent, key->modulus, ret);
+    return ret;
+}
+
 int rsastr_len(struct RSAKey *key) {
     Bignum md, ex;
 
index 7c10a9a..d47b1be 100644 (file)
--- a/sshsha.c
+++ b/sshsha.c
@@ -1,7 +1,3 @@
-#include <stdarg.h> /* FIXME */
-#include <windows.h> /* FIXME */
-#include "putty.h" /* FIXME */
-
 /*
  * SHA1 hash algorithm. Used in SSH2 as a MAC, and the transform is
  * also used as a `stirring' function for the PuTTY random number
@@ -181,11 +177,6 @@ static void sha1_key(SHA_State *s1, SHA_State *s2,
                      unsigned char *key, int len) {
     unsigned char foo[64];
     int i;
-    {int j;
-        debug(("Key supplied is:\r\n"));
-        for (j=0; j<len; j++) debug(("  %02X", key[j]));
-        debug(("\r\n"));
-    }
 
     memset(foo, 0x36, 64);
     for (i = 0; i < len && i < 64; i++)
@@ -231,32 +222,12 @@ static void sha1_do_hmac(SHA_State *s1, SHA_State *s2,
 }
 
 static void sha1_generate(unsigned char *blk, int len, unsigned long seq) {
-    {int i;
-        debug(("Gen HMAC on block len=%d seq=%d:\r\n", len, seq));
-        for (i=0; i<len; i++) debug(("  %02X", blk[i]));
-        debug(("\r\n"));
-    }
     sha1_do_hmac(&sha1_cs_mac_s1, &sha1_cs_mac_s2, blk, len, seq, blk+len);
-    {int i;
-        debug(("We compute HMAC as:\r\n"));
-        for (i=0; i<20; i++) debug(("  %02X", blk[len+i]));
-        debug(("\r\n"));
-    }
 }
 
 static int sha1_verify(unsigned char *blk, int len, unsigned long seq) {
     unsigned char correct[20];
-    {int i;
-        debug(("HMAC on block len=%d seq=%d:\r\n", len, seq));
-        for (i=0; i<len; i++) debug(("  %02X", blk[i]));
-        debug(("\r\n"));
-    }
     sha1_do_hmac(&sha1_sc_mac_s1, &sha1_sc_mac_s2, blk, len, seq, correct);
-    {int i;
-        debug(("We compute HMAC as:\r\n"));
-        for (i=0; i<20; i++) debug(("  %02X", correct[i]));
-        debug(("\r\n"));
-    }
     return !memcmp(correct, blk+len, 20);
 }
 
index 5d6675e..7f4955a 100644 (file)
--- a/win_res.h
+++ b/win_res.h
 #define IDC3_CIPHERBLOWF 1021
 #define IDC3_CIPHERDES  1022
 #define IDC3_AUTHTIS    1023
+#define IDC3_PKSTATIC   1024
+#define IDC3_PKEDIT     1025
+#define IDC3_PKBUTTON   1026
 
 #define IDC4_MBSTATIC   1001
 #define IDC4_MBWINDOWS  1002
index a9d0e60..9fb515e 100644 (file)
@@ -236,6 +236,12 @@ BEGIN
     
     
     AUTOCHECKBOX "Attempt TIS authentication", IDC3_AUTHTIS, 3, 55, 162, 8
+    
+    
+    LTEXT "Public key file:", IDC3_PKSTATIC, 3, 75, 119, 8
+    EDITTEXT IDC3_PKEDIT,  3, 83, 119, 12, ES_AUTOHSCROLL
+    
+    PUSHBUTTON "C&hange...", IDC3_PKBUTTON,  129, 83, 35, 12
 END
 
 IDD_PANEL4 DIALOG DISCARDABLE 6, 30, 168, 163
index 23bf2cb..f6c342e 100644 (file)
--- a/windlg.c
+++ b/windlg.c
@@ -152,6 +152,7 @@ static void save_settings (char *section, int do_host) {
     wpps (sesskey, "Cipher", cfg.cipher == CIPHER_BLOWFISH ? "blowfish" :
                              cfg.cipher == CIPHER_DES ? "des" : "3des");
     wppi (sesskey, "AuthTIS", cfg.try_tis_auth);
+    wpps (sesskey, "PublicKeyFile", cfg.keyfile);
     wppi (sesskey, "RFCEnviron", cfg.rfc_environ);
     wppi (sesskey, "BackspaceIsDelete", cfg.bksp_is_delete);
     wppi (sesskey, "RXVTHomeEnd", cfg.rxvt_homeend);
@@ -293,6 +294,7 @@ static void load_settings (char *section, int do_host) {
            cfg.cipher = CIPHER_3DES;
     }
     gppi (sesskey, "AuthTIS", 0, &cfg.try_tis_auth);
+    gpps (sesskey, "PublicKeyFile", "", cfg.keyfile, sizeof(cfg.keyfile));
     gppi (sesskey, "RFCEnviron", 0, &cfg.rfc_environ);
     gppi (sesskey, "BackspaceIsDelete", 1, &cfg.bksp_is_delete);
     gppi (sesskey, "RXVTHomeEnd", 0, &cfg.rxvt_homeend);
@@ -995,6 +997,9 @@ static int CALLBACK TelnetProc (HWND hwnd, UINT msg,
 
 static int CALLBACK SshProc (HWND hwnd, UINT msg,
                             WPARAM wParam, LPARAM lParam) {
+    OPENFILENAME of;
+    char filename[sizeof(cfg.keyfile)];
+
     switch (msg) {
       case WM_INITDIALOG:
        SetDlgItemText (hwnd, IDC3_TTEDIT, cfg.termtype);
@@ -1006,6 +1011,7 @@ static int CALLBACK SshProc (HWND hwnd, UINT msg,
 
                          IDC3_CIPHER3DES);
        CheckDlgButton (hwnd, IDC3_AUTHTIS, cfg.try_tis_auth);
+       SetDlgItemText (hwnd, IDC3_PKEDIT, cfg.keyfile);
        break;
       case WM_COMMAND:
        switch (LOWORD(wParam)) {
@@ -1042,6 +1048,36 @@ static int CALLBACK SshProc (HWND hwnd, UINT msg,
                HIWORD(wParam) == BN_DOUBLECLICKED)
                cfg.try_tis_auth = IsDlgButtonChecked (hwnd, IDC3_AUTHTIS);
            break;
+         case IDC3_PKEDIT:
+           if (HIWORD(wParam) == EN_CHANGE)
+               GetDlgItemText (hwnd, IDC3_PKEDIT, cfg.keyfile,
+                               sizeof(cfg.keyfile)-1);
+           break;
+         case IDC3_PKBUTTON:
+            /*
+             * FIXME: this crashes. Find out why.
+             */
+            memset(&of, 0, sizeof(of));
+#ifdef OPENFILENAME_SIZE_VERSION_400
+            of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+            of.lStructSize = sizeof(of);
+#endif
+            of.hwndOwner = hwnd;
+            of.lpstrFilter = "All Files\0*\0\0\0";
+            of.lpstrCustomFilter = NULL;
+            of.nFilterIndex = 1;
+            of.lpstrFile = filename; strcpy(filename, cfg.keyfile);
+            of.nMaxFile = sizeof(filename);
+            of.lpstrFileTitle = NULL;
+            of.lpstrInitialDir = NULL;
+            of.lpstrTitle = "Select Public Key File";
+            of.Flags = 0;
+            if (GetOpenFileName(&of)) {
+                strcpy(cfg.keyfile, filename);
+                SetDlgItemText (hwnd, IDC3_PKEDIT, cfg.keyfile);
+            }
+           break;
        }
        break;
     }