From fae1a71b5a2fd1b2983be523350c3dae4b5e6903 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 30 Apr 2007 22:09:26 +0000 Subject: [PATCH] Add support for RFC 4432 RSA key exchange, the patch for which has been lying around in my home directory for _years_. git-svn-id: svn://svn.tartarus.org/sgt/putty@7496 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 10 ++-- config.c | 1 + doc/config.but | 4 ++ putty.h | 1 + settings.c | 5 +- ssh.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++---- ssh.h | 23 ++++++--- sshdh.c | 8 +-- sshrsa.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 314 insertions(+), 29 deletions(-) diff --git a/Recipe b/Recipe index efdfabab..8bb3d169 100644 --- a/Recipe +++ b/Recipe @@ -312,8 +312,8 @@ pageant : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234 puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc - + sshpubk sshaes sshsh512 import winutils puttygen.res tree234 - + notiming winhelp LIBS wintime + + sshpubk sshaes sshsh256 sshsh512 import winutils puttygen.res + + tree234 notiming winhelp LIBS wintime pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg @@ -328,8 +328,8 @@ plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc - + sshpubk sshaes sshsh512 import puttygen.res time tree234 uxgen - + notiming + + sshpubk sshaes sshsh256 sshsh512 import puttygen.res time tree234 + + uxgen notiming pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC @@ -342,7 +342,7 @@ PuTTYtel : [M] terminal wcwidth ldiscucs logging BE_NOSSH mac macdlg + CHARSET stricmp vsnprint dialog config macctrls minibidi PuTTYgen : [M] macpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand macnoise sshsha macstore misc sshrsa sshdss macmisc sshpubk - + sshaes sshsh512 import macpgen.rsrc macpgkey macabout + + sshaes sshsh256 sshsh512 import macpgen.rsrc macpgkey macabout PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH + ux_x11 uxpty uxsignal testback putty.icns info.plist diff --git a/config.c b/config.c index 477dc4d4..41bb4943 100644 --- a/config.c +++ b/config.c @@ -256,6 +256,7 @@ static void kexlist_handler(union control *ctrl, void *dlg, { "Diffie-Hellman group 1", KEX_DHGROUP1 }, { "Diffie-Hellman group 14", KEX_DHGROUP14 }, { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, { "-- warn below here --", KEX_WARN } }; diff --git a/doc/config.but b/doc/config.but index 3b744356..e5d42e9e 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2282,6 +2282,10 @@ exchange; the server can avoid groups known to be weak, and possibly invent new ones over time, without any changes required to PuTTY's configuration. We recommend use of this method, if possible. +In addition, PuTTY supports \i{RSA key exchange}, which requires much less +computational effort on the part of the client, and somewhat less on +the part of the server, than Diffie-Hellman key exchange. + If the first algorithm PuTTY finds is below the \q{warn below here} line, you will see a warning box when you make the connection, similar to that for cipher selection (see \k{config-ssh-encryption}). diff --git a/putty.h b/putty.h index b9f11ceb..3583ecf0 100644 --- a/putty.h +++ b/putty.h @@ -252,6 +252,7 @@ enum { KEX_DHGROUP1, KEX_DHGROUP14, KEX_DHGEX, + KEX_RSA, KEX_MAX }; diff --git a/settings.c b/settings.c index 10f3573f..c937d3c9 100644 --- a/settings.c +++ b/settings.c @@ -27,6 +27,7 @@ static const struct keyval kexnames[] = { { "dh-gex-sha1", KEX_DHGEX }, { "dh-group14-sha1", KEX_DHGROUP14 }, { "dh-group1-sha1", KEX_DHGROUP1 }, + { "rsa", KEX_RSA }, { "WARN", KEX_WARN } }; @@ -571,9 +572,9 @@ void load_open_settings(void *sesskey, Config *cfg) char *default_kexes; gppi(sesskey, "BugDHGEx2", 0, &i); i = 2-i; if (i == FORCE_ON) - default_kexes = "dh-group14-sha1,dh-group1-sha1,WARN,dh-gex-sha1"; + default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1"; else - default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,WARN"; + default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"; gprefs(sesskey, "KEX", default_kexes, kexnames, KEX_MAX, cfg->ssh_kexlist); } diff --git a/ssh.c b/ssh.c index bcc1dd1b..64a069ef 100644 --- a/ssh.c +++ b/ssh.c @@ -83,6 +83,9 @@ #define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ #define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ #define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ #define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ #define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ #define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ @@ -112,6 +115,7 @@ */ #define SSH2_PKTCTX_DHGROUP 0x0001 #define SSH2_PKTCTX_DHGEX 0x0002 +#define SSH2_PKTCTX_RSAKEX 0x0004 #define SSH2_PKTCTX_KEX_MASK 0x000F #define SSH2_PKTCTX_PUBLICKEY 0x0010 #define SSH2_PKTCTX_PASSWORD 0x0020 @@ -339,6 +343,9 @@ static char *ssh2_pkt_type(int pkt_ctx, int type) translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX); + translatec(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); + translatec(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); + translatec(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); translate(SSH2_MSG_USERAUTH_REQUEST); translate(SSH2_MSG_USERAUTH_FAILURE); translate(SSH2_MSG_USERAUTH_SUCCESS); @@ -5105,9 +5112,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, const struct ssh_mac *scmac_tobe; const struct ssh_compress *cscomp_tobe; const struct ssh_compress *sccomp_tobe; - char *hostkeydata, *sigdata, *keystr, *fingerprint; - int hostkeylen, siglen; + char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; + int hostkeylen, siglen, rsakeylen; void *hkey; /* actual host key */ + void *rsakey; /* for RSA kex */ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; int n_preferred_kex; const struct ssh_kexes *preferred_kex[KEX_MAX]; @@ -5161,6 +5169,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, s->preferred_kex[s->n_preferred_kex++] = &ssh_diffiehellman_group1; break; + case KEX_RSA: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_rsa_kex; + break; case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -5560,6 +5572,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, crWaitUntil(pktin); /* Ignore packet */ } + if (ssh->kex->main_type == KEXTYPE_DH) { + /* XXX The lines below should be reindented before this is committed.*/ /* * Work out the number of bits of key we will need from the key * exchange. We start with the maximum key length of either @@ -5635,6 +5649,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); if (!s->f) { bombout(("unable to parse key exchange reply packet")); @@ -5656,11 +5671,120 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } hash_mpint(ssh->kex->hash, ssh->exhash, s->e); hash_mpint(ssh->kex->hash, ssh->exhash, s->f); + + dh_cleanup(ssh->kex_ctx); + freebn(s->f); + if (!ssh->kex->pdata) { + freebn(s->g); + freebn(s->p); + } + /* XXX end incorrectly-indented section */ + } else { + logeventf(ssh, "Doing RSA key exchange with hash %s", + ssh->kex->hash->text_name); + ssh->pkt_ctx |= SSH2_PKTCTX_RSAKEX; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + bombout(("expected RSA public key packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + hash_string(ssh->kex->hash, ssh->exhash, + s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + + { + char *keydata; + ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); + s->rsakeydata = snewn(s->rsakeylen, char); + memcpy(s->rsakeydata, keydata, s->rsakeylen); + } + + s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); + if (!s->rsakey) { + sfree(s->rsakeydata); + bombout(("unable to parse RSA public key from server")); + crStop(0); + } + + hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); + + /* + * Next, set up a shared secret K, of precisely KLEN - + * 2*HLEN - 49 bits, where KLEN is the bit length of the + * RSA key modulus and HLEN is the bit length of the hash + * we're using. + */ + { + int klen = ssh_rsakex_klen(s->rsakey); + int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); + int i, byte = 0; + unsigned char *kstr1, *kstr2, *outstr; + int kstr1len, kstr2len, outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + kstr1 = ssh2_mpint_fmt(s->K, &kstr1len); + kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char); + PUT_32BIT(kstr2, kstr1len); + memcpy(kstr2 + 4, kstr1, kstr1len); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len, + outstr, outstrlen, s->rsakey); + + /* + * And send it off in a return packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, outstr, outstrlen); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen); + + sfree(kstr2); + sfree(kstr1); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsakey); + + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + sfree(s->rsakeydata); + bombout(("expected signature packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + + sfree(s->rsakeydata); + } + hash_mpint(ssh->kex->hash, ssh->exhash, s->K); assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); ssh->kex->hash->final(ssh->exhash, s->exchange_hash); - dh_cleanup(ssh->kex_ctx); ssh->kex_ctx = NULL; #if 0 @@ -5668,7 +5792,6 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, dmemdump(s->exchange_hash, ssh->kex->hash->hlen); #endif - s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); if (!s->hkey || !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, (char *)s->exchange_hash, @@ -5850,14 +5973,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh->sccomp->text_name); /* - * Free key exchange data. + * Free shared secret. */ - freebn(s->f); freebn(s->K); - if (!ssh->kex->pdata) { - freebn(s->g); - freebn(s->p); - } /* * Key exchange is over. Loop straight back round if we have a diff --git a/ssh.h b/ssh.h index fede9968..30311412 100644 --- a/ssh.h +++ b/ssh.h @@ -82,6 +82,17 @@ void crcda_free_context(void *handle); int detect_attack(void *handle, unsigned char *buf, uint32 len, unsigned char *IV); +/* + * SSH2 RSA key exchange functions + */ +struct ssh_hash; +void *ssh_rsakex_newkey(char *data, int len); +void ssh_rsakex_freekey(void *key); +int ssh_rsakex_klen(void *key); +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key); + typedef struct { uint32 h[4]; } MD5_Core_State; @@ -194,15 +205,10 @@ struct ssh_hash { }; struct ssh_kex { - /* - * Plugging in another KEX algorithm requires structural chaos, - * so it's hard to abstract them into nice little structures - * like this. Fortunately, all our KEXes are basically - * Diffie-Hellman at the moment, so in this structure I simply - * parametrise the DH exchange a bit. - */ char *name, *groupname; - const unsigned char *pdata, *gdata;/* NULL means use group exchange */ + enum { KEXTYPE_DH, KEXTYPE_RSA } main_type; + /* For DH */ + const unsigned char *pdata, *gdata; /* NULL means group exchange */ int plen, glen; const struct ssh_hash *hash; }; @@ -268,6 +274,7 @@ extern const struct ssh_hash ssh_sha256; extern const struct ssh_kexes ssh_diffiehellman_group1; extern const struct ssh_kexes ssh_diffiehellman_group14; extern const struct ssh_kexes ssh_diffiehellman_gex; +extern const struct ssh_kexes ssh_rsa_kex; extern const struct ssh_signkey ssh_dss; extern const struct ssh_signkey ssh_rsa; extern const struct ssh_mac ssh_hmac_md5; diff --git a/sshdh.c b/sshdh.c index f0abd3ec..c733b61f 100644 --- a/sshdh.c +++ b/sshdh.c @@ -52,7 +52,7 @@ static const unsigned char G[] = { 2 }; static const struct ssh_kex ssh_diffiehellman_group1_sha1 = { "diffie-hellman-group1-sha1", "group1", - P1, G, lenof(P1), lenof(G), &ssh_sha1 + KEXTYPE_DH, P1, G, lenof(P1), lenof(G), &ssh_sha1 }; static const struct ssh_kex *const group1_list[] = { @@ -66,7 +66,7 @@ const struct ssh_kexes ssh_diffiehellman_group1 = { static const struct ssh_kex ssh_diffiehellman_group14_sha1 = { "diffie-hellman-group14-sha1", "group14", - P14, G, lenof(P14), lenof(G), &ssh_sha1 + KEXTYPE_DH, P14, G, lenof(P14), lenof(G), &ssh_sha1 }; static const struct ssh_kex *const group14_list[] = { @@ -80,12 +80,12 @@ const struct ssh_kexes ssh_diffiehellman_group14 = { static const struct ssh_kex ssh_diffiehellman_gex_sha256 = { "diffie-hellman-group-exchange-sha256", NULL, - NULL, NULL, 0, 0, &ssh_sha256 + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha256 }; static const struct ssh_kex ssh_diffiehellman_gex_sha1 = { "diffie-hellman-group-exchange-sha1", NULL, - NULL, NULL, 0, 0, &ssh_sha1 + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha1 }; static const struct ssh_kex *const gex_list[] = { diff --git a/sshrsa.c b/sshrsa.c index b862d3f6..6db265ee 100644 --- a/sshrsa.c +++ b/sshrsa.c @@ -836,3 +836,156 @@ const struct ssh_signkey ssh_rsa = { "ssh-rsa", "rsa2" }; + +void *ssh_rsakex_newkey(char *data, int len) +{ + return rsa2_newkey(data, len); +} + +void ssh_rsakex_freekey(void *key) +{ + rsa2_freekey(key); +} + +int ssh_rsakex_klen(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + + return bignum_bitcount(rsa->modulus); +} + +static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, + void *vdata, int datalen) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned count = 0; + + while (datalen > 0) { + int i, max = (datalen > h->hlen ? h->hlen : datalen); + void *s; + unsigned char counter[4], hash[h->hlen]; + + PUT_32BIT(counter, count); + s = h->init(); + h->bytes(s, seed, seedlen); + h->bytes(s, counter, 4); + h->final(s, hash); + count++; + + for (i = 0; i < max; i++) + data[i] ^= hash[i]; + + data += max; + datalen -= max; + } +} + +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key) +{ + Bignum b1, b2; + struct RSAKey *rsa = (struct RSAKey *) key; + int k, i; + char *p; + const int HLEN = h->hlen; + + /* + * Here we encrypt using RSAES-OAEP. Essentially this means: + * + * - we have a SHA-based `mask generation function' which + * creates a pseudo-random stream of mask data + * deterministically from an input chunk of data. + * + * - we have a random chunk of data called a seed. + * + * - we use the seed to generate a mask which we XOR with our + * plaintext. + * + * - then we use _the masked plaintext_ to generate a mask + * which we XOR with the seed. + * + * - then we concatenate the masked seed and the masked + * plaintext, and RSA-encrypt that lot. + * + * The result is that the data input to the encryption function + * is random-looking and (hopefully) contains no exploitable + * structure such as PKCS1-v1_5 does. + * + * For a precise specification, see RFC 3447, section 7.1.1. + * Some of the variable names below are derived from that, so + * it'd probably help to read it anyway. + */ + + /* k denotes the length in octets of the RSA modulus. */ + k = (7 + bignum_bitcount(rsa->modulus)) / 8; + + /* The length of the input data must be at most k - 2hLen - 2. */ + assert(inlen > 0 && inlen <= k - 2*HLEN - 2); + + /* The length of the output data wants to be precisely k. */ + assert(outlen == k); + + /* + * Now perform EME-OAEP encoding. First set up all the unmasked + * output data. + */ + /* Leading byte zero. */ + out[0] = 0; + /* At position 1, the seed: HLEN bytes of random data. */ + for (i = 0; i < HLEN; i++) + out[i + 1] = random_byte(); + /* At position 1+HLEN, the data block DB, consisting of: */ + /* The hash of the label (we only support an empty label here) */ + h->final(h->init(), out + HLEN + 1); + /* A bunch of zero octets */ + memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1)); + /* A single 1 octet, followed by the input message data. */ + out[outlen - inlen - 1] = 1; + memcpy(out + outlen - inlen, in, inlen); + + /* + * Now use the seed data to mask the block DB. + */ + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* + * And now use the masked DB to mask the seed itself. + */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + + /* + * Now `out' contains precisely the data we want to + * RSA-encrypt. + */ + b1 = bignum_from_bytes(out, outlen); + b2 = modpow(b1, rsa->exponent, rsa->modulus); + p = out; + for (i = outlen; i--;) { + *p++ = bignum_byte(b2, i); + } + freebn(b1); + freebn(b2); + + /* + * And we're done. + */ +} + +static const struct ssh_kex ssh_rsa_kex_sha1 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1 +}; + +static const struct ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256 +}; + +static const struct ssh_kex *const rsa_kex_list[] = { + &ssh_rsa_kex_sha256, + &ssh_rsa_kex_sha1 +}; + +const struct ssh_kexes ssh_rsa_kex = { + sizeof(rsa_kex_list) / sizeof(*rsa_kex_list), + rsa_kex_list +}; -- 2.11.0