#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */
+/*
+ * Various remote-bug flags.
+ */
+#define BUG_CHOKES_ON_SSH1_IGNORE 1
+#define BUG_SSH2_HMAC 2
+
#define GET_32BIT(cp) \
(((unsigned long)(unsigned char)(cp)[0] << 24) | \
((unsigned long)(unsigned char)(cp)[1] << 16) | \
unsigned char **outblock, int *outlen) {
return 0;
}
+static int ssh_comp_none_disable(void) { return 0; }
const static struct ssh_compress ssh_comp_none = {
"none",
ssh_comp_none_init, ssh_comp_none_block,
- ssh_comp_none_init, ssh_comp_none_block
+ ssh_comp_none_init, ssh_comp_none_block,
+ ssh_comp_none_disable
};
extern const struct ssh_compress ssh_zlib;
const static struct ssh_compress *compressions[] = {
static int ssh1_compressing;
static int ssh_agentfwd_enabled;
static int ssh_X11_fwd_enabled;
+static int ssh_remote_bugs;
static const struct ssh_cipher *cipher = NULL;
static const struct ssh2_cipher *cscipher = NULL;
static const struct ssh2_cipher *sccipher = NULL;
pktout.body[-1] = pktout.type;
+#if 0
+ debug(("Packet payload pre-compression:\n"));
+ for (i = -1; i < pktout.length; i++)
+ debug((" %02x", (unsigned char)pktout.body[i]));
+ debug(("\r\n"));
+#endif
+
if (ssh1_compressing) {
unsigned char *compblk;
int complen;
-#if 0
- debug(("Packet payload pre-compression:\n"));
- for (i = -1; i < pktout.length; i++)
- debug((" %02x", (unsigned char)pktout.body[i]));
- debug(("\r\n"));
-#endif
zlib_compress_block(pktout.body-1, pktout.length+1,
&compblk, &complen);
ssh1_pktout_size(complen-1);
return b;
}
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(char *vstring) {
+ char *imp; /* pointer to implementation part */
+ imp = vstring;
+ imp += strcspn(imp, "-");
+ imp += strcspn(imp, "-");
+
+ ssh_remote_bugs = 0;
+
+ if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+ !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+ !strcmp(imp, "1.2.22")) {
+ /*
+ * These versions don't support SSH1_MSG_IGNORE, so we have
+ * to use a different defence against password length
+ * sniffing.
+ */
+ ssh_remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+ logevent("We believe remote version has SSH1 ignore bug");
+ }
+
+ if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
+ !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
+ !strncmp(imp, "2.1 ", 4)) {
+ /*
+ * These versions have the HMAC bug.
+ */
+ ssh_remote_bugs |= BUG_SSH2_HMAC;
+ logevent("We believe remote version has SSH2 HMAC bug");
+ }
+}
+
static int do_ssh_init(unsigned char c) {
static char *vsp;
static char version[10];
*vsp = 0;
sprintf(vlog, "Server version: %s", vstring);
+ ssh_detect_bugs(vstring);
vlog[strcspn(vlog, "\r\n")] = '\0';
logevent(vlog);
if (response) {
p = response + 5;
nkeys = GET_32BIT(p); p += 4;
- { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+ { char buf[64]; sprintf(buf, "Pageant has %d SSH1 keys", nkeys);
logevent(buf); }
for (i = 0; i < nkeys; i++) {
static struct RSAKey key;
* N+7. This won't obscure the order of
* magnitude of the password length, but it will
* introduce a bit of extra uncertainty.
+ *
+ * A few servers (the old 1.2.18 through 1.2.22)
+ * can't deal with SSH1_MSG_IGNORE. For these
+ * servers, we need an alternative defence. We make
+ * use of the fact that the password is interpreted
+ * as a C string: so we can append a NUL, then some
+ * random data.
*/
- int bottom, top, pwlen, i;
- char *randomstr;
-
- pwlen = strlen(password);
- if (pwlen < 16) {
- bottom = 1;
- top = 15;
+ if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
+ char string[64];
+ char *s;
+ int len;
+
+ len = strlen(password);
+ if (len < sizeof(string)) {
+ s = string;
+ strcpy(string, password);
+ len++; /* cover the zero byte */
+ while (len < sizeof(string)) {
+ string[len++] = (char)random_byte();
+ }
+ } else {
+ s = password;
+ }
+ send_packet(pwpkt_type, PKT_INT, len,
+ PKT_DATA, s, len, PKT_END);
} else {
- bottom = pwlen &~ 7;
- top = bottom + 7;
- }
+ int bottom, top, pwlen, i;
+ char *randomstr;
- assert(pwlen >= bottom && pwlen <= top);
+ pwlen = strlen(password);
+ if (pwlen < 16) {
+ bottom = 1;
+ top = 15;
+ } else {
+ bottom = pwlen &~ 7;
+ top = bottom + 7;
+ }
- randomstr = smalloc(top+1);
+ assert(pwlen >= bottom && pwlen <= top);
- for (i = bottom; i <= top; i++) {
- if (i == pwlen)
- defer_packet(pwpkt_type, PKT_STR, password, PKT_END);
- else {
- for (j = 0; j < i; j++) {
- do {
- randomstr[j] = random_byte();
- } while (randomstr[j] == '\0');
+ randomstr = smalloc(top+1);
+
+ for (i = bottom; i <= top; i++) {
+ if (i == pwlen)
+ defer_packet(pwpkt_type, PKT_STR, password, PKT_END);
+ else {
+ for (j = 0; j < i; j++) {
+ do {
+ randomstr[j] = random_byte();
+ } while (randomstr[j] == '\0');
+ }
+ randomstr[i] = '\0';
+ defer_packet(SSH1_MSG_IGNORE,
+ PKT_STR, randomstr, PKT_END);
}
- randomstr[i] = '\0';
- defer_packet(SSH1_MSG_IGNORE,
- PKT_STR, randomstr, PKT_END);
}
+ ssh_pkt_defersend();
}
- ssh_pkt_defersend();
} else {
send_packet(pwpkt_type, PKT_STR, password, PKT_END);
}
/*
* Be prepared to work around the buggy MAC problem.
*/
- if (cfg.buggymac)
+ if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
maclist = buggymacs, nmacs = lenof(buggymacs);
else
maclist = macs, nmacs = lenof(macs);
if (response) {
p = response + 5;
nkeys = GET_32BIT(p); p += 4;
- { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+ { char buf[64]; sprintf(buf, "Pageant has %d SSH2 keys", nkeys);
logevent(buf); }
for (i = 0; i < nkeys; i++) {
static char *pkblob, *alg, *commentp;
* reason, we don't do this trick at all because we gain
* nothing by it.
*/
- if (cscipher) {
- int i, j;
+ if (cscipher) {
+ int stringlen, i;
+
+ stringlen = (256 - deferred_len);
+ stringlen += cscipher->blksize - 1;
+ stringlen -= (stringlen % cscipher->blksize);
+ if (cscomp) {
+ /*
+ * Temporarily disable actual compression,
+ * so we can guarantee to get this string
+ * exactly the length we want it. The
+ * compression-disabling routine should
+ * return an integer indicating how many
+ * bytes we should adjust our string length
+ * by.
+ */
+ stringlen -= cscomp->disable_compression();
+ }
ssh2_pkt_init(SSH2_MSG_IGNORE);
ssh2_pkt_addstring_start();
- for (i = deferred_len; i <= 256; i += cscipher->blksize) {
- for (j = 0; j < cscipher->blksize; j++) {
- char c = (char)random_byte();
- ssh2_pkt_addstring_data(&c, 1);
- }
+ for (i = 0; i < stringlen; i++) {
+ char c = (char)random_byte();
+ ssh2_pkt_addstring_data(&c, 1);
}
ssh2_pkt_defer();
}