X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/4a8fc3c43e71f1500b1f1203431ce944a832110e..efa4a6f2829acde3b9213b4f8f77bd9617321e2d:/ssh.c diff --git a/ssh.c b/ssh.c index 033a0d27..5996fd39 100644 --- a/ssh.c +++ b/ssh.c @@ -87,6 +87,10 @@ #define SSH2_MSG_NEWKEYS 21 /* 0x15 */ #define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ #define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ +#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_USERAUTH_REQUEST 50 /* 0x32 */ #define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ #define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ @@ -161,10 +165,11 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM }; #define crWaitUntilV(c) do { crReturnV; } while (!(c)) extern const struct ssh_cipher ssh_3des; -extern const struct ssh_cipher ssh_3des_ssh2; +extern const struct ssh2_ciphers ssh2_3des; extern const struct ssh_cipher ssh_des; +extern const struct ssh2_ciphers ssh2_aes; extern const struct ssh_cipher ssh_blowfish_ssh1; -extern const struct ssh_cipher ssh_blowfish_ssh2; +extern const struct ssh2_ciphers ssh2_blowfish; extern char *x11_init (Socket *, char *, void *); extern void x11_close (Socket); @@ -177,10 +182,19 @@ extern void x11_invent_auth(char *, int, char *, int); * SSH1. (3DES uses outer chaining; Blowfish has the opposite * endianness and different-sized keys.) */ -const static struct ssh_cipher *ciphers[] = { &ssh_blowfish_ssh2, &ssh_3des_ssh2 }; +const static struct ssh2_ciphers *ciphers[] = { + &ssh2_aes, + &ssh2_blowfish, + &ssh2_3des, +}; extern const struct ssh_kex ssh_diffiehellman; -const static struct ssh_kex *kex_algs[] = { &ssh_diffiehellman }; +extern const struct ssh_kex ssh_diffiehellman_gex; +const static struct ssh_kex *kex_algs[] = { +#ifdef DO_DIFFIE_HELLMAN_GEX + &ssh_diffiehellman_gex, +#endif + &ssh_diffiehellman }; extern const struct ssh_signkey ssh_dss; const static struct ssh_signkey *hostkey_algs[] = { &ssh_dss }; @@ -260,8 +274,8 @@ static int ssh1_compressing; static int ssh_agentfwd_enabled; static int ssh_X11_fwd_enabled; static const struct ssh_cipher *cipher = NULL; -static const struct ssh_cipher *cscipher = NULL; -static const struct ssh_cipher *sccipher = NULL; +static const struct ssh2_cipher *cscipher = NULL; +static const struct ssh2_cipher *sccipher = NULL; static const struct ssh_mac *csmac = NULL; static const struct ssh_mac *scmac = NULL; static const struct ssh_compress *cscomp = NULL; @@ -806,8 +820,8 @@ static int ssh_versioncmp(char *a, char *b) { /* - * Utility routine for putting an SSH-protocol `string' into a SHA - * state. + * Utility routines for putting an SSH-protocol `string' and + * `uint32' into a SHA state. */ #include static void sha_string(SHA_State *s, void *str, int len) { @@ -817,6 +831,12 @@ static void sha_string(SHA_State *s, void *str, int len) { SHA_Bytes(s, str, len); } +static void sha_uint32(SHA_State *s, unsigned i) { + unsigned char intblk[4]; + PUT_32BIT(intblk, i); + SHA_Bytes(s, intblk, 4); +} + /* * SSH2 packet construction functions. */ @@ -869,20 +889,18 @@ static void ssh2_pkt_addstring(char *data) { } static char *ssh2_mpint_fmt(Bignum b, int *len) { unsigned char *p; - int i, n = b[0]; - p = smalloc(n * 2 + 1); + int i, n = (ssh1_bignum_bitcount(b)+7)/8; + p = smalloc(n + 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; - } + for (i = 1; i <= n; i++) + p[i] = bignum_byte(b, n-i); i = 0; - while (p[i] == 0 && (p[i+1] & 0x80) == 0) + while (i <= n && p[i] == 0 && (p[i+1] & 0x80) == 0) i++; - memmove(p, p+i, n*2+1-i); - *len = n*2+1-i; + memmove(p, p+i, n+1-i); + *len = n+1-i; return p; } static void ssh2_pkt_addmp(Bignum b) { @@ -927,7 +945,7 @@ static int ssh2_pkt_construct(void) { * 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 = cscipher ? cscipher->blksize : 8; /* block size */ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ padding = 4; padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk; @@ -1041,7 +1059,7 @@ static void ssh2_pkt_getstring(char **p, int *length) { } static Bignum ssh2_pkt_getmp(void) { char *p; - int i, j, length; + int length; Bignum b; ssh2_pkt_getstring(&p, &length); @@ -1051,15 +1069,7 @@ static Bignum ssh2_pkt_getmp(void) { bombout(("internal error: Can't handle negative mpints")); return NULL; } - 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]); - } - while (b[0] > 1 && b[b[0]] == 0) b[0]--; + b = bignum_from_bytes(p, length); return b; } @@ -1382,9 +1392,15 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) logevent("Encrypted session key"); - cipher_type = cfg.cipher == CIPHER_BLOWFISH ? SSH_CIPHER_BLOWFISH : - cfg.cipher == CIPHER_DES ? SSH_CIPHER_DES : - SSH_CIPHER_3DES; + switch (cfg.cipher) { + case CIPHER_BLOWFISH: cipher_type = SSH_CIPHER_BLOWFISH; break; + case CIPHER_DES: cipher_type = SSH_CIPHER_DES; break; + case CIPHER_3DES: cipher_type = SSH_CIPHER_3DES; break; + case CIPHER_AES: + c_write("AES not supported in SSH1, falling back to 3DES\r\n", 49); + cipher_type = SSH_CIPHER_3DES; + break; + } if ((supported_ciphers_mask & (1 << cipher_type)) == 0) { c_write("Selected cipher not supported, falling back to 3DES\r\n", 53); cipher_type = SSH_CIPHER_3DES; @@ -1753,9 +1769,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) 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; + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31-i); } MD5Init(&md5c); @@ -2168,13 +2183,14 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, char *keyspace */ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) { - static int i, len; + static int i, j, len, nbits; static char *str; - static Bignum e, f, K; + static Bignum p, g, e, f, K; + static int kex_init_value, kex_reply_value; static const struct ssh_mac **maclist; static int nmacs; - static const struct ssh_cipher *cscipher_tobe = NULL; - static const struct ssh_cipher *sccipher_tobe = NULL; + static const struct ssh2_cipher *cscipher_tobe = NULL; + static const struct ssh2_cipher *sccipher_tobe = NULL; static const struct ssh_mac *csmac_tobe = NULL; static const struct ssh_mac *scmac_tobe = NULL; static const struct ssh_compress *cscomp_tobe = NULL; @@ -2185,7 +2201,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) static unsigned char exchange_hash[20]; static unsigned char first_exchange_hash[20]; static unsigned char keyspace[40]; - static const struct ssh_cipher *preferred_cipher; + static const struct ssh2_ciphers *preferred_cipher; static const struct ssh_compress *preferred_comp; static int first_kex; @@ -2197,15 +2213,17 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) * Set up the preferred cipher and compression. */ if (cfg.cipher == CIPHER_BLOWFISH) { - preferred_cipher = &ssh_blowfish_ssh2; + preferred_cipher = &ssh2_blowfish; } else if (cfg.cipher == CIPHER_DES) { logevent("Single DES not supported in SSH2; using 3DES"); - preferred_cipher = &ssh_3des_ssh2; + preferred_cipher = &ssh2_3des; } else if (cfg.cipher == CIPHER_3DES) { - preferred_cipher = &ssh_3des_ssh2; + preferred_cipher = &ssh2_3des; + } else if (cfg.cipher == CIPHER_AES) { + preferred_cipher = &ssh2_aes; } else { /* Shouldn't happen, but we do want to initialise to _something_. */ - preferred_cipher = &ssh_3des_ssh2; + preferred_cipher = &ssh2_3des; } if (cfg.compression) preferred_comp = &ssh_zlib; @@ -2244,18 +2262,22 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) /* List client->server encryption algorithms. */ ssh2_pkt_addstring_start(); for (i = 0; i < lenof(ciphers)+1; i++) { - const struct ssh_cipher *c = i==0 ? preferred_cipher : ciphers[i-1]; - ssh2_pkt_addstring_str(c->name); - if (i < lenof(ciphers)) - ssh2_pkt_addstring_str(","); + const struct ssh2_ciphers *c = i==0 ? preferred_cipher : ciphers[i-1]; + for (j = 0; j < c->nciphers; j++) { + ssh2_pkt_addstring_str(c->list[j]->name); + if (i < lenof(ciphers) || j < c->nciphers-1) + ssh2_pkt_addstring_str(","); + } } /* List server->client encryption algorithms. */ ssh2_pkt_addstring_start(); for (i = 0; i < lenof(ciphers)+1; i++) { - const struct ssh_cipher *c = i==0 ? preferred_cipher : ciphers[i-1]; - ssh2_pkt_addstring_str(c->name); - if (i < lenof(ciphers)) - ssh2_pkt_addstring_str(","); + const struct ssh2_ciphers *c = i==0 ? preferred_cipher : ciphers[i-1]; + for (j = 0; j < c->nciphers; j++) { + ssh2_pkt_addstring_str(c->list[j]->name); + if (i < lenof(ciphers) || j < c->nciphers-1) + ssh2_pkt_addstring_str(","); + } } /* List client->server MAC algorithms. */ ssh2_pkt_addstring_start(); @@ -2331,19 +2353,27 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) } ssh2_pkt_getstring(&str, &len); /* client->server cipher */ for (i = 0; i < lenof(ciphers)+1; i++) { - const struct ssh_cipher *c = i==0 ? preferred_cipher : ciphers[i-1]; - if (in_commasep_string(c->name, str, len)) { - cscipher_tobe = c; - break; + const struct ssh2_ciphers *c = i==0 ? preferred_cipher : ciphers[i-1]; + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + cscipher_tobe = c->list[j]; + break; + } } + if (cscipher_tobe) + break; } ssh2_pkt_getstring(&str, &len); /* server->client cipher */ for (i = 0; i < lenof(ciphers)+1; i++) { - const struct ssh_cipher *c = i==0 ? preferred_cipher : ciphers[i-1]; - if (in_commasep_string(c->name, str, len)) { - sccipher_tobe = c; - break; + const struct ssh2_ciphers *c = i==0 ? preferred_cipher : ciphers[i-1]; + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + sccipher_tobe = c->list[j]; + break; + } } + if (sccipher_tobe) + break; } ssh2_pkt_getstring(&str, &len); /* client->server mac */ for (i = 0; i < nmacs; i++) { @@ -2377,25 +2407,60 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) } /* - * Currently we only support Diffie-Hellman and DSS, so let's - * bomb out if those aren't selected. + * If we're doing Diffie-Hellman group exchange, start by + * requesting a group. */ - if (kex != &ssh_diffiehellman || hostkey != &ssh_dss) { - bombout(("internal fault: chaos in SSH 2 transport layer")); - crReturn(0); + if (kex == &ssh_diffiehellman_gex) { + int csbits, scbits; + + logevent("Doing Diffie-Hellman group exchange"); + /* + * Work out number of bits. We start with the maximum key + * length of either cipher... + */ + csbits = cscipher_tobe->keylen; + scbits = sccipher_tobe->keylen; + nbits = (csbits > scbits ? csbits : scbits); + /* The keys only have 160-bit entropy, since they're based on + * a SHA-1 hash. So cap the key size at 160 bits. */ + if (nbits > 160) nbits = 160; + /* + * ... and then work out how big a DH group we will need to + * allow that much data. + */ + nbits = 512 << ((nbits-1) / 64); + ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); + ssh2_pkt_adduint32(nbits); + ssh2_pkt_send(); + + crWaitUntil(ispkt); + if (pktin.type != SSH2_MSG_KEX_DH_GEX_GROUP) { + bombout(("expected key exchange group packet from server")); + crReturn(0); + } + p = ssh2_pkt_getmp(); + g = ssh2_pkt_getmp(); + dh_setup_group(p, g); + kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; + kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; + } else { + dh_setup_group1(); + kex_init_value = SSH2_MSG_KEXDH_INIT; + kex_reply_value = SSH2_MSG_KEXDH_REPLY; } + logevent("Doing Diffie-Hellman key exchange"); /* - * Now we begin the fun. Generate and send e for Diffie-Hellman. + * Now generate and send e for Diffie-Hellman. */ e = dh_create_e(); - ssh2_pkt_init(SSH2_MSG_KEXDH_INIT); + ssh2_pkt_init(kex_init_value); ssh2_pkt_addmp(e); ssh2_pkt_send(); crWaitUntil(ispkt); - if (pktin.type != SSH2_MSG_KEXDH_REPLY) { - bombout(("expected key exchange packet from server")); + if (pktin.type != kex_reply_value) { + bombout(("expected key exchange reply packet from server")); crReturn(0); } ssh2_pkt_getstring(&hostkeydata, &hostkeylen); @@ -2405,11 +2470,18 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) K = dh_find_K(f); sha_string(&exhash, hostkeydata, hostkeylen); + if (kex == &ssh_diffiehellman_gex) { + sha_uint32(&exhash, nbits); + sha_mpint(&exhash, p); + sha_mpint(&exhash, g); + } sha_mpint(&exhash, e); sha_mpint(&exhash, f); sha_mpint(&exhash, K); SHA_Final(&exhash, exchange_hash); + dh_cleanup(); + #if 0 debug(("Exchange hash is:\r\n")); for (i = 0; i < 20; i++) @@ -2440,7 +2512,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) fingerprint = hostkey->fingerprint(hkey); verify_ssh_host_key(savedhost, savedport, hostkey->keytype, keystr, fingerprint); - if (first_kex) { /* don't bother logging this in rekeys */ + if (first_kex) { /* don't bother logging this in rekeys */ logevent("Host key fingerprint is:"); logevent(fingerprint); }