Privacy improving suggestions for ObscureKeystrokeTiming

Damien Miller djm at mindrot.org
Thu Sep 7 14:47:29 AEST 2023


On Wed, 6 Sep 2023, procmem at riseup.net wrote:

> Hi, Whonix OS privacy dev here. I had a discussion concerning the new
> ObscureKeystrokeTiming feature with a prominent researcher and author of the
> mouse and keyboard biometrics obfuscation tool called Kloak. While it's
> exciting to see keystroke obfuscation measures [1] start to become more
> prevalent mainstream, the current implementation of using a 50Hz fixed packet
> timing has the potential to create fingerprinting risks for hosts. Reason
> being, not all computer clocks have the exact same precision. Some may
> oscillate slightly faster or slower because of the physical discrepancies of
> clock crystals. A network adversary monitoring connections on the clearnet
> could potentially link future ones of the same host even if routed through an
> anonymity network like Tor.
> 
> Advanced attacks where attackers run loads on onion services that influence
> CPU activity and clock skew in predictable ways [2] may be possibly used to
> deanonymize them.
> 
> We would suggest drawing the padding packet intervals from some other
> distribution instead of firing these off on a fixed timer. Basically, do what
> kloak does but at the network layer.

Yeah, making the intervals a bit uncertain seems like a reasonable idea.
This gives them 10% jitter.

diff --git a/clientloop.c b/clientloop.c
index b461917..73cdd09 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -109,6 +109,9 @@
 /* Permitted RSA signature algorithms for UpdateHostkeys proofs */
 #define HOSTKEY_PROOF_RSA_ALGS	"rsa-sha2-512,rsa-sha2-256"
 
+/* Uncertainty (in percent) of keystroke timing intervals */
+#define SSH_KEYSTROKE_TIMING_FUZZ 10
+
 /* import options */
 extern Options options;
 
@@ -519,6 +522,33 @@ send_chaff(struct ssh *ssh)
 	return 1;
 }
 
+/* Sets the next interval to send a keystroke or chaff packet */
+static void
+set_next_interval(const struct timespec *now, struct timespec *next_interval,
+    u_int interval_ms, u_int interval_fuzz_pct)
+{
+	struct timespec tmp;
+	long long interval_ns, fuzz_ns;
+
+	interval_ns = interval_ms * (1000LL * 1000);
+	fuzz_ns = (interval_ns * interval_fuzz_pct) / 100;
+	/* Center fuzz around requested interval */
+	if (fuzz_ns > INT_MAX)
+		fuzz_ns = INT_MAX;
+	if (fuzz_ns > interval_ns) {
+		/* Shouldn't happen */
+		fatal_f("internal error: fuzz %u%% %lldns > interval %lldns",
+		    interval_fuzz_pct, fuzz_ns, interval_ns);
+	}
+	interval_ns -= fuzz_ns / 2;
+	interval_ns += arc4random_uniform(fuzz_ns);
+
+	tmp.tv_sec = interval_ns / (1000 * 1000 * 1000);
+	tmp.tv_nsec = interval_ns % (1000 * 1000 * 1000);
+
+	timespecadd(now, &tmp, next_interval);
+}
+
 /*
  * Performs keystroke timing obfuscation. Returns non-zero if the
  * output fd should be polled.
@@ -586,8 +616,9 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
 		    options.obscure_keystroke_timing_interval);
 		just_started = had_keystroke = active = 1;
 		nchaff = 0;
-		ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval);
-		timespecadd(&now, &tmp, &next_interval);
+		set_next_interval(&now, &next_interval,
+		    options.obscure_keystroke_timing_interval,
+		    SSH_KEYSTROKE_TIMING_FUZZ);
 	}
 
 	/* Don't hold off if obfuscation inactive */
@@ -620,8 +651,9 @@ obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout,
 	n = (n < 0) ? 1 : n + 1;
 
 	/* Advance to the next interval */
-	ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval * n);
-	timespecadd(&now, &tmp, &next_interval);
+	set_next_interval(&now, &next_interval,
+	    options.obscure_keystroke_timing_interval * n,
+	    SSH_KEYSTROKE_TIMING_FUZZ);
 	return 1;
 }
 


More information about the openssh-unix-dev mailing list