Rationalised host key storage. Also started code reorg: persistent-state
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 27 Sep 2000 15:21:04 +0000 (15:21 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 27 Sep 2000 15:21:04 +0000 (15:21 +0000)
routines have been moved out into a replaceable module winstore.c.

git-svn-id: svn://svn.tartarus.org/sgt/putty@639 cda61777-01e9-0310-a592-d414129be87e

Makefile
noise.c
putty.h
ssh.c
ssh.h
sshdss.c
sshrsa.c
storage.h [new file with mode: 0644]
windlg.c
window.c
winstore.c [new file with mode: 0644]

index 62e34ea..7d92720 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -67,7 +67,7 @@ PLOBJS = plink.$(OBJ) windlg.$(OBJ)
 ##-- objects pscp
 SOBJS = scp.$(OBJ) windlg.$(OBJ) be_none.$(OBJ)
 ##-- objects putty puttytel pscp plink
-MOBJS = misc.$(OBJ) version.$(OBJ)
+MOBJS = misc.$(OBJ) version.$(OBJ) winstore.$(OBJ)
 ##-- objects putty pscp plink
 OBJS1 = sshcrc.$(OBJ) sshdes.$(OBJ) sshmd5.$(OBJ) sshrsa.$(OBJ) sshrand.$(OBJ)
 OBJS2 = sshsha.$(OBJ) sshblowf.$(OBJ) noise.$(OBJ) sshdh.$(OBJ) sshdss.$(OBJ)
@@ -182,8 +182,9 @@ plink.rsp: makefile
        echo $(SOCK2) >> plink.rsp
 
 ##-- dependencies
-window.$(OBJ): window.c putty.h win_res.h
-windlg.$(OBJ): windlg.c putty.h ssh.h win_res.h
+window.$(OBJ): window.c putty.h win_res.h storage.h
+windlg.$(OBJ): windlg.c putty.h ssh.h win_res.h storage.h
+winstore.$(OBJ): winstore.c putty.h storage.h
 terminal.$(OBJ): terminal.c putty.h
 sizetip.$(OBJ): sizetip.c putty.h
 telnet.$(OBJ): telnet.c putty.h
@@ -191,7 +192,7 @@ raw.$(OBJ): raw.c putty.h
 xlat.$(OBJ): xlat.c putty.h
 ldisc.$(OBJ): ldisc.c putty.h
 misc.$(OBJ): misc.c putty.h
-noise.$(OBJ): noise.c putty.h ssh.h
+noise.$(OBJ): noise.c putty.h ssh.h storage.h
 ssh.$(OBJ): ssh.c ssh.h putty.h tree234.h
 sshcrc.$(OBJ): sshcrc.c ssh.h
 sshdes.$(OBJ): sshdes.c ssh.h
diff --git a/noise.c b/noise.c
index 383e6c3..88b13a9 100644 (file)
--- a/noise.c
+++ b/noise.c
@@ -8,41 +8,7 @@
 
 #include "putty.h"
 #include "ssh.h"
-
-static char seedpath[2*MAX_PATH+10] = "\0";
-
-/*
- * Find the random seed file path and store it in `seedpath'.
- */
-static void get_seedpath(void) {
-    HKEY rkey;
-    DWORD type, size;
-
-    size = sizeof(seedpath);
-
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey)==ERROR_SUCCESS) {
-       int ret = RegQueryValueEx(rkey, "RandSeedFile",
-                                 0, &type, seedpath, &size);
-       if (ret != ERROR_SUCCESS || type != REG_SZ)
-           seedpath[0] = '\0';
-       RegCloseKey(rkey);
-    } else
-       seedpath[0] = '\0';
-
-    if (!seedpath[0]) {
-       int len, ret;
-
-       len = GetEnvironmentVariable("HOMEDRIVE", seedpath, sizeof(seedpath));
-       ret = GetEnvironmentVariable("HOMEPATH", seedpath+len,
-                                     sizeof(seedpath)-len);
-       if (ret == 0) {                /* probably win95; store in \WINDOWS */
-           GetWindowsDirectory(seedpath, sizeof(seedpath));
-           len = strlen(seedpath);
-       } else
-           len += ret;
-       strcpy(seedpath+len, "\\PUTTY.RND");
-    }
-}
+#include "storage.h"
 
 /*
  * This function is called once, at PuTTY startup, and will do some
@@ -52,7 +18,6 @@ static void get_seedpath(void) {
 
 void noise_get_heavy(void (*func) (void *, int)) {
     HANDLE srch;
-    HANDLE seedf;
     WIN32_FIND_DATA finddata;
     char winpath[MAX_PATH+3];
 
@@ -66,55 +31,15 @@ void noise_get_heavy(void (*func) (void *, int)) {
        FindClose(srch);
     }
 
-    if (!seedpath[0])
-       get_seedpath();
-
-    seedf = CreateFile(seedpath, GENERIC_READ,
-                      FILE_SHARE_READ | FILE_SHARE_WRITE,
-                      NULL, OPEN_EXISTING, 0, NULL);
-
-    if (seedf != INVALID_HANDLE_VALUE) {
-       while (1) {
-           char buf[1024];
-           DWORD len;
-
-           if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
-               func(buf, len);
-           else
-               break;
-       }
-       CloseHandle(seedf);
-    }
+    read_random_seed(func);
 }
 
 void random_save_seed(void) {
-    HANDLE seedf;
-
-    if (!seedpath[0])
-       get_seedpath();
-
-    seedf = CreateFile(seedpath, GENERIC_WRITE, 0,
-                      NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    int len;
+    void *data;
 
-    if (seedf != INVALID_HANDLE_VALUE) {
-       int len;
-       DWORD lenwritten;
-       void *data;
-
-       random_get_savedata(&data, &len);
-       WriteFile(seedf, data, len, &lenwritten, NULL);
-       CloseHandle(seedf);
-    }
-}
-
-/*
- * This function is called from `putty -cleanup'. It removes the
- * random seed file.
- */
-void random_destroy_seed(void) {
-    if (!seedpath[0])
-       get_seedpath();
-    remove(seedpath);
+    random_get_savedata(&data, &len);
+    write_random_seed(data, len);
 }
 
 /*
diff --git a/putty.h b/putty.h
index 016c56c..07990a6 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -270,7 +270,8 @@ void do_defaults (char *);
 void logevent (char *);
 void showeventlog (HWND);
 void showabout (HWND);
-void verify_ssh_host_key(char *host, char *keystr);
+void verify_ssh_host_key(char *host, char *keytype,
+                         char *keystr, char *fingerprint);
 void get_sesslist(int allocate);
 void registry_cleanup(void);
 
diff --git a/ssh.c b/ssh.c
index cdb12bf..9fbcf46 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -1142,11 +1142,13 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
          * First format the key into a string.
          */
         int len = rsastr_len(&hostkey);
+        char fingerprint[100];
         char *keystr = malloc(len);
         if (!keystr)
             fatalbox("Out of memory");
         rsastr_fmt(keystr, &hostkey);
-        verify_ssh_host_key(savedhost, keystr);
+        rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
+        verify_ssh_host_key(savedhost, "rsa", keystr, fingerprint);
         free(keystr);
     }
 
@@ -1824,7 +1826,7 @@ static int do_ssh2_transport(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, *keystr;
+    static char *hostkeydata, *sigdata, *keystr, *fingerprint;
     static int hostkeylen, siglen;
     static unsigned char exchange_hash[20];
     static unsigned char keyspace[40];
@@ -2053,7 +2055,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      * checked the signature of the exchange hash.)
      */
     keystr = hostkey->fmtkey();
-    verify_ssh_host_key(savedhost, keystr);
+    fingerprint = hostkey->fingerprint();
+    verify_ssh_host_key(savedhost, hostkey->keytype, keystr, fingerprint);
+    logevent("Host key fingerprint is:");
+    logevent(fingerprint);
+    free(fingerprint);
     free(keystr);
 
     /*
diff --git a/ssh.h b/ssh.h
index 19a514c..6a49863 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -123,8 +123,10 @@ struct ssh_kex {
 struct ssh_hostkey {
     void (*setkey)(char *data, int len);
     char *(*fmtkey)(void);
+    char *(*fingerprint)(void);
     int (*verifysig)(char *sig, int siglen, char *data, int datalen);
     char *name;
+    char *keytype;                     /* for host key cache */
 };
 
 struct ssh_compress {
index ce18f47..86a1617 100644 (file)
--- a/sshdss.c
+++ b/sshdss.c
@@ -9,6 +9,12 @@
     ((unsigned long)(unsigned char)(cp)[2] << 8) | \
     ((unsigned long)(unsigned char)(cp)[3]))
 
+#define PUT_32BIT(cp, value) { \
+    (cp)[0] = (unsigned char)((value) >> 24); \
+    (cp)[1] = (unsigned char)((value) >> 16); \
+    (cp)[2] = (unsigned char)((value) >> 8); \
+    (cp)[3] = (unsigned char)(value); }
+
 static void getstring(char **data, int *datalen, char **p, int *length) {
     *p = NULL;
     if (*datalen < 4)
@@ -81,25 +87,70 @@ static void dss_setkey(char *data, int len) {
 
 static char *dss_fmtkey(void) {
     char *p;
-    int len;
-    int i;
+    int len, i, pos, nibbles;
+    static const char hex[] = "0123456789abcdef";
     if (!dss_p)
         return NULL;
-    len = 7 + 4 + 1;                   /* "ssh-dss", punctuation, \0 */
+    len = 8 + 4 + 1;                   /* 4 x "0x", 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]);
+
+    pos = 0;
+    pos += sprintf(p+pos, "0x");
+    nibbles = (3 + ssh1_bignum_bitcount(dss_p))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        p[pos++] = hex[(bignum_byte(dss_p, i/2) >> (4*(i%2))) & 0xF];
+    pos += sprintf(p+pos, "0x");
+    nibbles = (3 + ssh1_bignum_bitcount(dss_q))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        p[pos++] = hex[(bignum_byte(dss_q, i/2) >> (4*(i%2))) & 0xF];
+    pos += sprintf(p+pos, "0x");
+    nibbles = (3 + ssh1_bignum_bitcount(dss_g))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        p[pos++] = hex[(bignum_byte(dss_g, i/2) >> (4*(i%2))) & 0xF];
+    pos += sprintf(p+pos, "0x");
+    nibbles = (3 + ssh1_bignum_bitcount(dss_y))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        p[pos++] = hex[(bignum_byte(dss_y, i/2) >> (4*(i%2))) & 0xF];
+    p[pos] = '\0';
     return p;
 }
 
+static char *dss_fingerprint(void) {
+    struct MD5Context md5c;
+    unsigned char digest[16], lenbuf[4];
+    char buffer[16*3+40];
+    char *ret;
+    int numlen, i;
+
+    MD5Init(&md5c);
+    MD5Update(&md5c, "\0\0\0\7ssh-dss", 11);
+
+#define ADD_BIGNUM(bignum) \
+    numlen = (ssh1_bignum_bitcount(bignum)+8)/8; \
+    PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
+    for (i = numlen; i-- ;) { \
+        unsigned char c = bignum_byte(bignum, i); \
+        MD5Update(&md5c, &c, 1); \
+    }
+    ADD_BIGNUM(dss_p);
+    ADD_BIGNUM(dss_q);
+    ADD_BIGNUM(dss_g);
+    ADD_BIGNUM(dss_y);
+#undef ADD_BIGNUM
+
+    MD5Final(digest, &md5c);
+
+    sprintf(buffer, "%d ", ssh1_bignum_bitcount(dss_p));
+    for (i = 0; i < 16; i++)
+        sprintf(buffer+strlen(buffer), "%s%02x", i?":":"", digest[i]);
+    ret = malloc(strlen(buffer)+1);
+    if (ret)
+        strcpy(ret, buffer);
+    return ret;
+}
+
 static int dss_verifysig(char *sig, int siglen, char *data, int datalen) {
     char *p;
     int i, slen;
@@ -184,6 +235,8 @@ static int dss_verifysig(char *sig, int siglen, char *data, int datalen) {
 struct ssh_hostkey ssh_dss = {
     dss_setkey,
     dss_fmtkey,
+    dss_fingerprint,
     dss_verifysig,
-    "ssh-dss"
+    "ssh-dss",
+    "dss"
 };
index 5ea4cc7..d39f846 100644 (file)
--- a/sshrsa.c
+++ b/sshrsa.c
@@ -106,25 +106,29 @@ int rsastr_len(struct RSAKey *key) {
 
     md = key->modulus;
     ex = key->exponent;
-    return 4 * (ex[0]+md[0]) + 10;
+    return 4 * (ex[0]+md[0]) + 20;
 }
 
 void rsastr_fmt(char *str, struct RSAKey *key) {
     Bignum md, ex;
-    int len = 0, i;
+    int len = 0, i, nibbles;
+    static const char hex[] = "0123456789abcdef";
 
     md = key->modulus;
     ex = key->exponent;
 
-    for (i=1; i<=ex[0]; i++) {
-       sprintf(str+len, "%04x", ex[i]);
-       len += strlen(str+len);
-    }
-    str[len++] = '/';
-    for (i=1; i<=md[0]; i++) {
-       sprintf(str+len, "%04x", md[i]);
-       len += strlen(str+len);
-    }
+    len += sprintf(str+len, "0x");
+
+    nibbles = (3 + ssh1_bignum_bitcount(ex))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        str[len++] = hex[(bignum_byte(ex, i/2) >> (4*(i%2))) & 0xF];
+
+    len += sprintf(str+len, ",0x");
+
+    nibbles = (3 + ssh1_bignum_bitcount(md))/4; if (nibbles<1) nibbles=1;
+    for (i=nibbles; i-- ;)
+        str[len++] = hex[(bignum_byte(md, i/2) >> (4*(i%2))) & 0xF];
+
     str[len] = '\0';
 }
 
diff --git a/storage.h b/storage.h
new file mode 100644 (file)
index 0000000..b5ac3b5
--- /dev/null
+++ b/storage.h
@@ -0,0 +1,88 @@
+/*
+ * storage.h: interface defining functions for storage and recovery
+ * of PuTTY's persistent data.
+ */
+
+#ifndef PUTTY_STORAGE_H
+#define PUTTY_STORAGE_H
+
+/* ----------------------------------------------------------------------
+ * Functions to save and restore PuTTY sessions. Note that this is
+ * only the low-level code to do the reading and writing. The
+ * higher-level code that translates a Config structure into a set
+ * of (key,value) pairs is elsewhere, since it doesn't (mostly)
+ * change between platforms.
+ */
+
+/*
+ * Write a saved session. The caller is expected to call
+ * open_setting_w() to get a `void *' handle, then pass that to a
+ * number of calls to write_setting_s() and write_setting_i(), and
+ * then close it using close_settings_w(). At the end of this call
+ * sequence the settings should have been written to the PuTTY
+ * persistent storage area.
+ */
+void *open_settings_w(char *sessionname);
+void write_setting_s(void *handle, char *key, char *value);
+void write_setting_i(void *handle, char *key, int value);
+void *close_settings_w(void *handle);
+
+/*
+ * Read a saved session. The caller is expected to call
+ * open_setting_r() to get a `void *' handle, then pass that to a
+ * number of calls to read_setting_s() and read_setting_i(), and
+ * then close it using close_settings_r().
+ * 
+ * read_setting_s() writes into the provided buffer and returns a
+ * pointer to the same buffer.
+ * 
+ * If a particular string setting is not present in the session,
+ * read_setting_s() can return NULL, in which case the caller
+ * should invent a sensible default. If an integer setting is not
+ * present, read_setting_i() returns its provided default.
+ */
+void *open_settings_r(char *sessionname);
+char *read_setting_s(void *handle, char *key, char *buffer, int buflen);
+int read_setting_i(void *handle, char *key, int defvalue);
+void *close_settings_r(void *handle);
+
+/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's host key database.
+ */
+
+/*
+ * See if a host key matches the database entry. Return values can
+ * be 0 (entry matches database), 1 (entry is absent in database),
+ * or 2 (entry exists in database and is different).
+ */
+int verify_host_key(char *hostname, char *keytype, char *key);
+
+/*
+ * Write a host key into the database, overwriting any previous
+ * entry that might have been there.
+ */
+void store_host_key(char *hostname, char *keytype, char *key);
+
+/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's random number seed file.
+ */
+
+typedef void (*noise_consumer_t)(void *data, size_t len);
+
+/*
+ * Read PuTTY's random seed file and pass its contents to a noise
+ * consumer function.
+ */
+void read_random_seed(noise_consumer_t consumer);
+
+/*
+ * Write PuTTY's random seed file from a given chunk of noise.
+ */
+void write_random_seed(void *data, size_t len);
+
+/* ----------------------------------------------------------------------
+ * Cleanup function: remove all of PuTTY's persistent state.
+ */
+void cleanup_all(void);
+
+#endif
index 71cc634..71f46ba 100644 (file)
--- a/windlg.c
+++ b/windlg.c
@@ -14,6 +14,7 @@
 #include "ssh.h"
 #include "putty.h"
 #include "win_res.h"
+#include "storage.h"
 
 #define NPANELS 8
 #define MAIN_NPANELS 8
@@ -1644,128 +1645,70 @@ void showabout (HWND hwnd) {
     }
 }
 
-void verify_ssh_host_key(char *host, char *keystr) {
-    char *otherstr, *mungedhost;
-    int len;
-    HKEY rkey;
-
-    len = 1 + strlen(keystr);
-
-    /*
-     * Now read a saved key in from the registry and see what it
-     * says.
-     */
-    otherstr = smalloc(len);
-    mungedhost = smalloc(3*strlen(host)+1);
-    if (!otherstr || !mungedhost)
-       fatalbox("Out of memory");
-
-    mungestr(host, mungedhost);
-
-    if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
-                    &rkey) != ERROR_SUCCESS) {
-       if (MessageBox(NULL, "PuTTY was unable to open the host key cache\n"
-                      "in the registry. There is thus no way to tell\n"
-                      "if the remote host is what you think it is.\n"
-                      "Connect anyway?", "PuTTY Problem",
-                      MB_ICONWARNING | MB_YESNO) == IDNO)
-           exit(0);
-    } else {
-       DWORD readlen = len;
-       DWORD type;
-       int ret;
-
-       ret = RegQueryValueEx(rkey, mungedhost, NULL,
-                             &type, otherstr, &readlen);
-
-       if (ret == ERROR_MORE_DATA ||
-           (ret == ERROR_SUCCESS && type == REG_SZ &&
-            strcmp(otherstr, keystr))) {
-           if (MessageBox(NULL,
-                          "This host's host key is different from the\n"
-                          "one cached in the registry! Someone may be\n"
-                          "impersonating this host for malicious reasons;\n"
-                          "alternatively, the host key may have changed\n"
-                          "due to sloppy system administration.\n"
-                          "Replace key in registry and connect?",
-                          "PuTTY: Security Warning",
-                          MB_ICONWARNING | MB_YESNO) == IDNO)
-               exit(0);
-           RegSetValueEx(rkey, mungedhost, 0, REG_SZ, keystr,
-                         strlen(keystr)+1);
-       } else if (ret != ERROR_SUCCESS || type != REG_SZ) {
-           if (MessageBox(NULL,
-                          "This host's host key is not cached in the\n"
-                          "registry. Do you want to add it to the cache\n"
-                          "and carry on connecting?",
-                          "PuTTY: New Host",
-                          MB_ICONWARNING | MB_YESNO) == IDNO)
-               exit(0);
-           RegSetValueEx(rkey, mungedhost, 0, REG_SZ, keystr,
-                         strlen(keystr)+1);
-       }
+void verify_ssh_host_key(char *host, char *keytype,
+                         char *keystr, char *fingerprint) {
+    int ret;
 
-       RegCloseKey(rkey);
-    }
-}
+    static const char absentmsg[] =
+        "The server's host key is not cached in the registry. You\n"
+        "have no guarantee that the server is the computer you\n"
+        "think it is.\n"
+        "The server's key fingerprint is:\n"
+        "%s\n"
+        "If you trust this host, hit Yes to add the key to\n"
+        "PuTTY's cache and carry on connecting.\n"
+        "If you do not trust this host, hit No to abandon the\n"
+        "connection.\n";
+
+    static const char wrongmsg[] =
+        "WARNING - POTENTIAL SECURITY BREACH!\n"
+        "\n"
+        "The server's host key does not match the one PuTTY has\n"
+        "cached in the registry. This means that either the\n"
+        "server administrator has changed the host key, or you\n"
+        "have actually connected to another computer pretending\n"
+        "to be the server.\n"
+        "The new key fingerprint is:\n"
+        "%s\n"
+        "If you were expecting this change and trust the new key,\n"
+        "hit Yes to update PuTTY's cache and continue connecting.\n"
+        "If you want to carry on connecting but without updating\n"
+        "the cache, hit No.\n"
+        "If you want to abandon the connection completely, hit\n"
+        "Cancel. Hitting Cancel is the ONLY guaranteed safe\n"
+        "choice.\n";
+
+    static const char mbtitle[] = "PuTTY Security Alert";
 
-/*
- * Recursively delete a registry key and everything under it.
- */
-static void registry_recursive_remove(HKEY key) {
-    DWORD i;
-    char name[MAX_PATH+1];
-    HKEY subkey;
-
-    i = 0;
-    while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
-        if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
-            registry_recursive_remove(subkey);
-            RegCloseKey(subkey);
-        }
-        RegDeleteKey(key, name);
-    }
-}
-
-/*
- * Destroy all registry information associated with PuTTY.
- */
-void registry_cleanup(void) {
-    HKEY key;
-    int ret;
-    char name[MAX_PATH+1];
+    
+    char message[160+                  /* sensible fingerprint max size */
+                 (sizeof(absentmsg) > sizeof(wrongmsg) ?
+                  sizeof(absentmsg) : sizeof(wrongmsg))];
 
     /*
-     * Open the main PuTTY registry key and remove everything in it.
+     * Verify the key against the registry.
      */
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == ERROR_SUCCESS) {
-        registry_recursive_remove(key);
-        RegCloseKey(key);
+    ret = verify_host_key(host, keytype, keystr);
+
+    if (ret == 0)                      /* success - key matched OK */
+        return;
+    if (ret == 2) {                    /* key was different */
+        int mbret;
+        sprintf(message, wrongmsg, fingerprint);
+        mbret = MessageBox(NULL, message, mbtitle,
+                           MB_ICONWARNING | MB_YESNOCANCEL);
+        if (mbret == IDYES)
+            store_host_key(host, keytype, keystr);
+        if (mbret == IDCANCEL)
+            exit(0);
     }
-    /*
-     * Now open the parent key and remove the PuTTY main key. Once
-     * we've done that, see if the parent key has any other
-     * children.
-     */
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
-                   &key) == ERROR_SUCCESS) {
-        RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
-        ret = RegEnumKey(key, 0, name, sizeof(name));
-        RegCloseKey(key);
-        /*
-         * If the parent key had no other children, we must delete
-         * it in its turn. That means opening the _grandparent_
-         * key.
-         */
-        if (ret != ERROR_SUCCESS) {
-            if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
-                           &key) == ERROR_SUCCESS) {
-                RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
-                RegCloseKey(key);
-            }
-        }
+    if (ret == 1) {                    /* key was absent */
+        int mbret;
+        sprintf(message, absentmsg, fingerprint);
+        mbret = MessageBox(NULL, message, mbtitle,
+                           MB_ICONWARNING | MB_YESNO);
+        if (mbret == IDNO)
+            exit(0);
+        store_host_key(host, keytype, keystr);
     }
-    /*
-     * Now we're done.
-     */
 }
index 91b4dc5..4c83e9b 100644 (file)
--- a/window.c
+++ b/window.c
@@ -13,6 +13,7 @@
 
 #define PUTTY_DO_GLOBALS                      /* actually _define_ globals */
 #include "putty.h"
+#include "storage.h"
 #include "win_res.h"
 
 #define IDM_SHOWLOG   0x0010
@@ -176,8 +177,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
                                "to continue?",
                                "PuTTY Warning",
                                MB_YESNO | MB_ICONWARNING) == IDYES) {
-                    random_destroy_seed();
-                    registry_cleanup();
+                    cleanup_all();
                 }
                 exit(0);
            }
diff --git a/winstore.c b/winstore.c
new file mode 100644 (file)
index 0000000..bc3ab18
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * winstore.c: Windows-specific implementation of the interface
+ * defined in storage.h.
+ */
+
+#include <windows.h>
+#include <stdio.h>
+#include "putty.h"
+#include "storage.h"
+
+static char seedpath[2*MAX_PATH+10] = "\0";
+
+static char hex[16] = "0123456789ABCDEF";
+
+static void mungestr(char *in, char *out) {
+    int candot = 0;
+
+    while (*in) {
+       if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
+           *in == '%' || *in < ' ' || *in > '~' || (*in == '.' && !candot)) {
+           *out++ = '%';
+           *out++ = hex[((unsigned char)*in) >> 4];
+           *out++ = hex[((unsigned char)*in) & 15];
+       } else
+           *out++ = *in;
+       in++;
+       candot = 1;
+    }
+    *out = '\0';
+    return;
+}
+
+static void unmungestr(char *in, char *out) {
+    while (*in) {
+       if (*in == '%' && in[1] && in[2]) {
+           int i, j;
+
+           i = in[1] - '0'; i -= (i > 9 ? 7 : 0);
+           j = in[2] - '0'; j -= (j > 9 ? 7 : 0);
+
+           *out++ = (i<<4) + j;
+           in += 3;
+       } else
+           *out++ = *in++;
+    }
+    *out = '\0';
+    return;
+}
+
+void *open_settings_w(char *sessionname);
+void write_setting_s(void *handle, char *key, char *value);
+void write_setting_i(void *handle, char *key, int value);
+void *close_settings_w(void *handle);
+
+void *open_settings_r(char *sessionname);
+char *read_setting_s(void *handle, char *key, char *buffer, int buflen);
+int read_setting_i(void *handle, char *key, int defvalue);
+void *close_settings_r(void *handle);
+
+static void hostkey_regname(char *buffer, char *hostname, char *keytype) {
+    strcpy(buffer, keytype);
+    strcat(buffer, "@");
+    mungestr(hostname, buffer + strlen(buffer));
+}
+
+int verify_host_key(char *hostname, char *keytype, char *key) {
+    char *otherstr, *regname;
+    int len;
+    HKEY rkey;
+    DWORD readlen;
+    DWORD type;
+    int ret, compare;
+
+    len = 1 + strlen(key);
+
+    /*
+     * Now read a saved key in from the registry and see what it
+     * says.
+     */
+    otherstr = smalloc(len);
+    regname = smalloc(3*(strlen(hostname)+strlen(keytype))+5);
+    if (!otherstr || !regname)
+       fatalbox("Out of memory");
+
+    hostkey_regname(regname, hostname, keytype);
+
+    if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
+                    &rkey) != ERROR_SUCCESS)
+        return 1;                      /* key does not exist in registry */
+
+    readlen = len;
+    ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);
+
+    if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
+        !strcmp(keytype, "rsa")) {
+        /*
+         * Key didn't exist. If the key type is RSA, we'll try
+         * another trick, which is to look up the _old_ key format
+         * under just the hostname and translate that.
+         */
+        char *justhost = regname + 1 + strlen(keytype);
+        char *oldstyle = smalloc(len + 10);   /* safety margin */
+        readlen = len;
+        ret = RegQueryValueEx(rkey, justhost, NULL, &type,
+                              oldstyle, &readlen);
+        
+        if (ret == ERROR_SUCCESS && type == REG_SZ) {
+            /*
+             * The old format is two old-style bignums separated by
+             * a slash. An old-style bignum is made of groups of
+             * four hex digits: digits are ordered in sensible
+             * (most to least significant) order within each group,
+             * but groups are ordered in silly (least to most)
+             * order within the bignum. The new format is two
+             * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
+             * A nonzero except in the special case 0x0, which
+             * doesn't appear anyway in RSA keys) separated by a
+             * comma. All hex digits are lowercase in both formats.
+             */
+            char *p = otherstr;
+            char *q = oldstyle;
+            int i, j;
+
+            for (i = 0; i < 2; i++) {
+                int ndigits, nwords;
+                *p++ = '0'; *p++ = 'x';
+                ndigits = strcspn(q, "/");   /* find / or end of string */
+                nwords = ndigits / 4;
+                /* now trim ndigits to remove leading zeros */
+                while (q[ (ndigits-1) ^ 3 ] == '0' && ndigits > 1)
+                    ndigits--;
+                /* now move digits over to new string */
+                for (j = 0; j < ndigits; j++)
+                    p[ndigits-1-j] = q[j^3];
+                p += ndigits;
+                q += nwords*4;
+                if (*q) {
+                    q++;               /* eat the slash */
+                    *p++ = ',';        /* add a comma */
+                }
+                *p = '\0';             /* terminate the string */
+            }
+
+            /*
+             * Now _if_ this key matches, we'll enter it in the new
+             * format. If not, we'll assume something odd went
+             * wrong, and hyper-cautiously do nothing.
+             */
+            if (!strcmp(otherstr, key))
+                RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,
+                              strlen(otherstr)+1);
+        }
+    }
+
+    RegCloseKey(rkey);
+
+    compare = strcmp(otherstr, key);
+
+    sfree(otherstr);
+    sfree(regname);
+
+    if (ret == ERROR_MORE_DATA ||
+        (ret == ERROR_SUCCESS && type == REG_SZ && compare))
+        return 2;                      /* key is different in registry */
+    else if (ret != ERROR_SUCCESS || type != REG_SZ)
+        return 1;                      /* key does not exist in registry */
+    else
+        return 0;                      /* key matched OK in registry */
+}
+
+void store_host_key(char *hostname, char *keytype, char *key) {
+    char *regname;
+    HKEY rkey;
+
+    regname = smalloc(3*(strlen(hostname)+strlen(keytype))+5);
+    if (!regname)
+       fatalbox("Out of memory");
+
+    hostkey_regname(regname, hostname, keytype);
+
+    if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
+                    &rkey) != ERROR_SUCCESS)
+        return;                        /* key does not exist in registry */
+    RegSetValueEx(rkey, regname, 0, REG_SZ, key,
+                  strlen(key)+1);
+    RegCloseKey(rkey);
+}
+
+/*
+ * Find the random seed file path and store it in `seedpath'.
+ */
+static void get_seedpath(void) {
+    HKEY rkey;
+    DWORD type, size;
+
+    size = sizeof(seedpath);
+
+    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey)==ERROR_SUCCESS) {
+       int ret = RegQueryValueEx(rkey, "RandSeedFile",
+                                 0, &type, seedpath, &size);
+       if (ret != ERROR_SUCCESS || type != REG_SZ)
+           seedpath[0] = '\0';
+       RegCloseKey(rkey);
+    } else
+       seedpath[0] = '\0';
+
+    if (!seedpath[0]) {
+       int len, ret;
+
+       len = GetEnvironmentVariable("HOMEDRIVE", seedpath, sizeof(seedpath));
+       ret = GetEnvironmentVariable("HOMEPATH", seedpath+len,
+                                     sizeof(seedpath)-len);
+       if (ret == 0) {                /* probably win95; store in \WINDOWS */
+           GetWindowsDirectory(seedpath, sizeof(seedpath));
+           len = strlen(seedpath);
+       } else
+           len += ret;
+       strcpy(seedpath+len, "\\PUTTY.RND");
+    }
+}
+
+void read_random_seed(noise_consumer_t consumer) {
+    HANDLE seedf;
+
+    if (!seedpath[0])
+       get_seedpath();
+
+    seedf = CreateFile(seedpath, GENERIC_READ,
+                      FILE_SHARE_READ | FILE_SHARE_WRITE,
+                      NULL, OPEN_EXISTING, 0, NULL);
+
+    if (seedf != INVALID_HANDLE_VALUE) {
+       while (1) {
+           char buf[1024];
+           DWORD len;
+
+           if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
+                consumer(buf, len);
+           else
+               break;
+       }
+       CloseHandle(seedf);
+    }
+}
+
+void write_random_seed(void *data, size_t len) {
+    HANDLE seedf;
+
+    if (!seedpath[0])
+       get_seedpath();
+
+    seedf = CreateFile(seedpath, GENERIC_WRITE, 0,
+                      NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+    if (seedf != INVALID_HANDLE_VALUE) {
+       DWORD lenwritten;
+
+       WriteFile(seedf, data, len, &lenwritten, NULL);
+       CloseHandle(seedf);
+    }
+}
+
+/*
+ * Recursively delete a registry key and everything under it.
+ */
+static void registry_recursive_remove(HKEY key) {
+    DWORD i;
+    char name[MAX_PATH+1];
+    HKEY subkey;
+
+    i = 0;
+    while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
+        if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
+            registry_recursive_remove(subkey);
+            RegCloseKey(subkey);
+        }
+        RegDeleteKey(key, name);
+    }
+}
+
+void cleanup_all(void) {
+    HKEY key;
+    int ret;
+    char name[MAX_PATH+1];
+
+    /* ------------------------------------------------------------
+     * Wipe out the random seed file.
+     */
+    if (!seedpath[0])
+       get_seedpath();
+    remove(seedpath);
+
+    /* ------------------------------------------------------------
+     * Destroy all registry information associated with PuTTY.
+     */
+
+    /*
+     * Open the main PuTTY registry key and remove everything in it.
+     */
+    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == ERROR_SUCCESS) {
+        registry_recursive_remove(key);
+        RegCloseKey(key);
+    }
+    /*
+     * Now open the parent key and remove the PuTTY main key. Once
+     * we've done that, see if the parent key has any other
+     * children.
+     */
+    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
+                   &key) == ERROR_SUCCESS) {
+        RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
+        ret = RegEnumKey(key, 0, name, sizeof(name));
+        RegCloseKey(key);
+        /*
+         * If the parent key had no other children, we must delete
+         * it in its turn. That means opening the _grandparent_
+         * key.
+         */
+        if (ret != ERROR_SUCCESS) {
+            if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
+                           &key) == ERROR_SUCCESS) {
+                RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
+                RegCloseKey(key);
+            }
+        }
+    }
+    /*
+     * Now we're done.
+     */
+}