From d5859615f641e5bbd853cd42aafd4fa577da17eb Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 27 Sep 2000 15:21:04 +0000 Subject: [PATCH] Rationalised host key storage. Also started code reorg: persistent-state 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 | 9 +- noise.c | 87 ++-------------- putty.h | 3 +- ssh.c | 12 ++- ssh.h | 2 + sshdss.c | 77 ++++++++++++--- sshrsa.c | 26 ++--- storage.h | 88 +++++++++++++++++ windlg.c | 177 +++++++++++---------------------- window.c | 4 +- winstore.c | 330 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 584 insertions(+), 231 deletions(-) create mode 100644 storage.h create mode 100644 winstore.c diff --git a/Makefile b/Makefile index 62e34ea4..7d927203 100644 --- 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 383e6c33..88b13a97 100644 --- 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 016c56c8..07990a63 100644 --- 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 cdb12bfc..9fbcf463 100644 --- 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 19a514cd..6a49863e 100644 --- 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 { diff --git a/sshdss.c b/sshdss.c index ce18f472..86a16176 100644 --- 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" }; diff --git a/sshrsa.c b/sshrsa.c index 5ea4cc72..d39f8462 100644 --- 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 index 00000000..b5ac3b51 --- /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 diff --git a/windlg.c b/windlg.c index 71cc634b..71f46bae 100644 --- 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. - */ } diff --git a/window.c b/window.c index 91b4dc53..4c83e9bd 100644 --- 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 index 00000000..bc3ab181 --- /dev/null +++ b/winstore.c @@ -0,0 +1,330 @@ +/* + * winstore.c: Windows-specific implementation of the interface + * defined in storage.h. + */ + +#include +#include +#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. + */ +} -- 2.11.0