Add support for RFC 4432 RSA key exchange, the patch for which has been
authorben <ben@cda61777-01e9-0310-a592-d414129be87e>
Mon, 30 Apr 2007 22:09:26 +0000 (22:09 +0000)
committerben <ben@cda61777-01e9-0310-a592-d414129be87e>
Mon, 30 Apr 2007 22:09:26 +0000 (22:09 +0000)
lying around in my home directory for _years_.

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

Recipe
config.c
doc/config.but
putty.h
settings.c
ssh.c
ssh.h
sshdh.c
sshrsa.c

diff --git a/Recipe b/Recipe
index efdfaba..8bb3d16 100644 (file)
--- 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
index 477dc4d..41bb494 100644 (file)
--- 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 }
        };
 
index 3b74435..e5d42e9 100644 (file)
@@ -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 b9f11ce..3583ecf 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -252,6 +252,7 @@ enum {
     KEX_DHGROUP1,
     KEX_DHGROUP14,
     KEX_DHGEX,
+    KEX_RSA,
     KEX_MAX
 };
 
index 10f3573..c937d3c 100644 (file)
@@ -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 bcc1dd1..64a069e 100644 (file)
--- 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 */
  */
 #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 fede996..3031141 100644 (file)
--- 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 f0abd3e..c733b61 100644 (file)
--- 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[] = {
index b862d3f..6db265e 100644 (file)
--- 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
+};