diff -ruN openssh-5.1p1/channels.c openssh-5.1p1.const-delay//channels.c --- openssh-5.1p1/channels.c 2008-07-16 14:42:06.000000000 +0200 +++ openssh-5.1p1.const-delay//channels.c 2008-12-07 02:19:52.000000000 +0100 @@ -1487,7 +1487,7 @@ static int channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset) { - char buf[CHAN_RBUF]; + char buf[CONSTDELAY_MAX_PACKET/2]; int len, force; force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; @@ -1626,7 +1626,7 @@ static int channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset) { - char buf[CHAN_RBUF]; + char buf[CONSTDELAY_MAX_PACKET/2]; int len; /** XXX handle drain efd, too */ diff -ruN openssh-5.1p1/clientloop.c openssh-5.1p1.const-delay//clientloop.c --- openssh-5.1p1/clientloop.c 2008-07-16 14:40:52.000000000 +0200 +++ openssh-5.1p1.const-delay//clientloop.c 2008-12-07 02:20:31.000000000 +0100 @@ -567,8 +567,11 @@ * some selected descriptor can be read, written, or has some other * event pending. */ - - if (options.server_alive_interval == 0 || !compat20) + if (const_time_delay) { + tv.tv_sec = 0; + tv.tv_usec = const_time_delay; + tvp = &tv; + } else if (options.server_alive_interval == 0 || !compat20) tvp = NULL; else { tv.tv_sec = options.server_alive_interval; @@ -593,8 +596,14 @@ snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; - } else if (ret == 0) - server_alive_check(); + } else if (ret == 0) { + if (const_time_delay) { + packet_send_ignore(1); + packet_send(); + packet_write_poll(); + } else + server_alive_check(); + } } static void @@ -1400,6 +1409,7 @@ if (packet_not_very_much_data_to_write()) channel_output_poll(); + packet_write_poll(); /* * Check if the window size has changed, and buffer a * message about it to the server if so. diff -ruN openssh-5.1p1/packet.c openssh-5.1p1.const-delay//packet.c --- openssh-5.1p1/packet.c 2008-07-11 09:36:48.000000000 +0200 +++ openssh-5.1p1.const-delay//packet.c 2008-12-07 01:55:38.000000000 +0100 @@ -93,6 +93,8 @@ static int connection_in = -1; static int connection_out = -1; +int const_time_delay = 0; + /* Protocol flags for the remote side. */ static u_int remote_protocol_flags = 0; @@ -791,6 +793,11 @@ padlen = block_size - (len % block_size); if (padlen < 4) padlen += block_size; + + if (const_time_delay) { + if (len + padlen < CONSTDELAY_MAX_PACKET) + padlen = CONSTDELAY_MAX_PACKET - len; + } if (extra_pad) { /* will wrap if extra_pad+padlen > 255 */ extra_pad = roundup(extra_pad, block_size); @@ -1487,9 +1494,26 @@ packet_write_poll(void) { int len = buffer_len(&output); + struct timeval tdiff, tnow; + static struct timeval told = {0, 0}; + + if (told.tv_sec == 0) + gettimeofday(&told, NULL); + + if (const_time_delay && len > 0 && packet_is_interactive()) { + gettimeofday(&tnow, NULL); + tdiff.tv_sec = 0; + tdiff.tv_usec = (tnow.tv_sec*1000000 + tnow.tv_usec - (told.tv_sec*1000000 + told.tv_usec)) % 1000000; + if (tdiff.tv_usec < const_time_delay) { + tdiff.tv_usec = const_time_delay - tdiff.tv_usec; + select(1, NULL, NULL, NULL, &tdiff); + } + } if (len > 0) { len = write(connection_out, buffer_ptr(&output), len); + gettimeofday(&told, NULL); + if (len == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) diff -ruN openssh-5.1p1/packet.h openssh-5.1p1.const-delay//packet.h --- openssh-5.1p1/packet.h 2008-07-11 09:36:48.000000000 +0200 +++ openssh-5.1p1.const-delay//packet.h 2008-12-07 02:22:13.000000000 +0100 @@ -89,6 +89,7 @@ extern u_int max_packet_size; extern int keep_alive_timeouts; +extern int const_time_delay; int packet_set_maxsize(u_int); #define packet_get_maxsize() max_packet_size @@ -106,4 +107,6 @@ int packet_need_rekeying(void); void packet_set_rekey_limit(u_int32_t); +#define CONSTDELAY_MAX_PACKET 240 /* must be multiple of 16 and less 256 */ + #endif /* PACKET_H */ diff -ruN openssh-5.1p1/readconf.c openssh-5.1p1.const-delay//readconf.c --- openssh-5.1p1/readconf.c 2008-06-29 16:04:03.000000000 +0200 +++ openssh-5.1p1.const-delay//readconf.c 2008-11-30 04:46:05.000000000 +0100 @@ -42,6 +42,7 @@ #include "buffer.h" #include "kex.h" #include "mac.h" +#include "packet.h" /* Format of the configuration file: @@ -130,7 +131,7 @@ oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, - oVisualHostKey, + oVisualHostKey, oConstTimeDelay, oDeprecated, oUnsupported } OpCodes; @@ -199,6 +200,7 @@ { "compressionlevel", oCompressionLevel }, { "tcpkeepalive", oTCPKeepAlive }, { "keepalive", oTCPKeepAlive }, /* obsolete */ + { "consttimedelay", oConstTimeDelay }, { "numberofpasswordprompts", oNumberOfPasswordPrompts }, { "loglevel", oLogLevel }, { "dynamicforward", oDynamicForward }, @@ -371,6 +373,14 @@ *intptr = value; break; + case oConstTimeDelay: + intptr = &const_time_delay; + arg = strdelim(&s); + if (!arg || *arg == '\0') + fatal("%s line %d: missing time value.", + filename, linenum); + *intptr = atoi(arg); + break; case oForwardAgent: intptr = &options->forward_agent; parse_flag: diff -ruN openssh-5.1p1/README.constdelay openssh-5.1p1.const-delay//README.constdelay --- openssh-5.1p1/README.constdelay 1970-01-01 01:00:00.000000000 +0100 +++ openssh-5.1p1.const-delay//README.constdelay 2008-12-07 02:50:06.000000000 +0100 @@ -0,0 +1,42 @@ +Potsdam, 22.12.08 + +OpenSSH 5.1p1 const time and packet size patch by +Sebastian Krahmer + + +SSH protocols are 'vulnerable' to connection-tracking in the meaning of +that a 'global observer' is able to match packet tracks (sizes, order or +time-delay) of SSH connections from one point of INET to another point. +For example even a connection that ends on "foo.bar.com" and which is done +via 12 hops from an yet unknown place could potentially matched to a series +of packets appearing from "secret7.place" by observing the links of +"foo.bar.com" and a couple of ISPs such as "secret1.place", "secret2.place", +... , "secretN.place". +Unfortunally it is probably even enough to just monitor the packet sizes +and their order (which is very easy) to find with high probability +where the connection is originated from. There is no need to know +via which hops the connection is done, but it is easy as well +to find out the in-between-hops. openssh-5.1p1.constdelay.diff prevents this. + +This patch could also be named 'openssh-5.1p1.notrack.diff' because it adds +constant packet delays into *interactive* SSH2 connections. If no typing is +done in this time-frame, an SSH_MSG_IGNORE packet is sent, using the same size +as if someting would have been typed. Any packet sent on such connection has a +constant size and is sent within a constant schedule-frame so an outside +observer can't distinguish between connections that carry data and silent ones. +If more than one connection to a hop is broken down this way, there is no way +to tell which outgoing hop-connection belongs to which incoming connection. + +In order to profit from the patch, both, client and server, need +to enable the ConstTimeDelay option. The value is the number +of msecs that is added as a delay. 10000 is a good value that still +makes the connection appear interactive. Using the option only on client +side is possible, as the server is still ignoring the none-packets +but doesnt make much sense since tracking is still possible. + +scp-style downloads wont profit from this patch as it only +works in interactive mode. The throughput and latency +will break down of course. SSHv2 was not really designed +for such patch, especially the padding restricts this +patch to send only at most some 240 bytes per packet. + diff -ruN openssh-5.1p1/servconf.c openssh-5.1p1.const-delay//servconf.c --- openssh-5.1p1/servconf.c 2008-07-04 05:51:12.000000000 +0200 +++ openssh-5.1p1.const-delay//servconf.c 2008-11-30 04:44:56.000000000 +0100 @@ -41,6 +41,7 @@ #include "match.h" #include "channels.h" #include "groupaccess.h" +#include "packet.h" static void add_listen_addr(ServerOptions *, char *, u_short); static void add_one_listen_addr(ServerOptions *, char *, u_short); @@ -302,6 +303,7 @@ sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, sMatch, sPermitOpen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, + sConstTimeDelay, sDeprecated, sUnsupported } ServerOpCodes; @@ -386,6 +388,7 @@ { "compression", sCompression, SSHCFG_GLOBAL }, { "tcpkeepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, { "keepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, /* obsolete alias */ + { "consttimedelay", sConstTimeDelay, SSHCFG_GLOBAL}, { "allowtcpforwarding", sAllowTcpForwarding, SSHCFG_ALL }, { "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL }, { "allowusers", sAllowUsers, SSHCFG_GLOBAL }, @@ -941,7 +944,14 @@ case sUseLogin: intptr = &options->use_login; goto parse_flag; - + case sConstTimeDelay: + intptr = &const_time_delay; + arg = strdelim(&cp); + if (!arg || *arg == '\0') + fatal("%s line %d: missing time value.", + filename, linenum); + *intptr = atoi(arg); + break; case sCompression: intptr = &options->compression; arg = strdelim(&cp); diff -ruN openssh-5.1p1/serverloop.c openssh-5.1p1.const-delay//serverloop.c --- openssh-5.1p1/serverloop.c 2008-07-04 15:10:49.000000000 +0200 +++ openssh-5.1p1.const-delay//serverloop.c 2008-12-04 23:33:21.000000000 +0100 @@ -78,6 +78,7 @@ #include "auth-options.h" #include "serverloop.h" #include "misc.h" +#include "packet.h" extern ServerOptions options; @@ -349,7 +350,11 @@ if (max_time_milliseconds == 0 || client_alive_scheduled) max_time_milliseconds = 100; - if (max_time_milliseconds == 0) + if (const_time_delay) { + tv.tv_sec = 0; + tv.tv_usec = const_time_delay; + tvp = &tv; + } else if (max_time_milliseconds == 0) tvp = NULL; else { tv.tv_sec = max_time_milliseconds / 1000; @@ -365,6 +370,10 @@ memset(*writesetp, 0, *nallocp); if (errno != EINTR) error("select: %.100s", strerror(errno)); + } else if (const_time_delay && ret == 0) { + packet_send_ignore(1); + packet_send(); + packet_write_wait(); } else { if (ret == 0 && client_alive_scheduled) client_alive_check(); @@ -842,8 +851,11 @@ rekeying = (xxx_kex != NULL && !xxx_kex->done); - if (!rekeying && packet_not_very_much_data_to_write()) + if (!rekeying && packet_not_very_much_data_to_write()) { channel_output_poll(); + drain_output(); + } + wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, 0); @@ -856,6 +868,7 @@ collect_children(); if (!rekeying) { channel_after_select(readset, writeset); + drain_output(); if (packet_need_rekeying()) { debug("need rekeying"); xxx_kex->done = 0;