From 7cca0d811c4d1b5bb346cd60fdfa461a558aefec Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 7 Sep 2000 16:33:49 +0000 Subject: [PATCH] RSA key authentication in ssh1 works; SSH2 is nearly there git-svn-id: svn://svn.tartarus.org/sgt/putty@572 cda61777-01e9-0310-a592-d414129be87e --- Makefile | 3 +- putty.h | 1 + ssh.c | 916 ++++++++++++++++++++++++++++++++++++++++++++----------------- ssh.h | 47 +++- sshbn.c | 171 +++++++++--- sshdes.c | 13 + sshdss.c | 184 +++++++++++++ sshrsa.c | 57 ++-- sshsha.c | 29 -- win_res.h | 3 + win_res.rc | 6 + windlg.c | 36 +++ 12 files changed, 1101 insertions(+), 365 deletions(-) diff --git a/Makefile b/Makefile index dc671ffa..91493569 100644 --- 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 3b2257a0..a8135117 100644 --- 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 d327471c..7511e085 100644 --- 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 @@ -54,6 +57,32 @@ #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<= 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 84e3c205..c3233fe9 100644 --- a/ssh.h +++ b/ssh.h @@ -18,6 +18,13 @@ #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 a9eae859..97ae3575 100644 --- a/sshbn.c +++ b/sshbn.c @@ -6,36 +6,17 @@ #include #include +#include /* FIXME */ +#include /* FIXME */ +#include /* 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= 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; +} diff --git a/sshdes.c b/sshdes.c index 1c82193c..bc1fe59e 100644 --- a/sshdes.c +++ b/sshdes.c @@ -1,4 +1,5 @@ #include +#include /* FIXME */ #include /* FIXME */ #include /* 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, diff --git a/sshdss.c b/sshdss.c index c651140e..ce18f472 100644 --- a/sshdss.c +++ b/sshdss.c @@ -1,5 +1,189 @@ +#include +#include + #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" }; diff --git a/sshrsa.c b/sshrsa.c index bf54e409..bd92c7bd 100644 --- 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; diff --git a/sshsha.c b/sshsha.c index 7c10a9a7..d47b1be9 100644 --- a/sshsha.c +++ b/sshsha.c @@ -1,7 +1,3 @@ -#include /* FIXME */ -#include /* 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