Skip to content

Commit 7efc355

Browse files
committed
add section: buffer and pad msgs to protect against traffic analysis
1 parent 4a1ff00 commit 7efc355

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

docs/protocol-basics.rst

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ within strings, so no confusion is possible there. However it does permit newli
3232
extraneous whitespace between elements; client and server MUST NOT use newlines in such a
3333
way.
3434

35+
Messages SHOULD be :ref:`padded <padding_messages>` to bucketed lengths,
36+
and buffered (to introduce delays) to protect against traffic analysis.
37+
3538
If using JSON RPC 2.0's feature of parameter passing by name, the
3639
names shown in the description of the method or notification in
3740
question MUST be used.
@@ -258,3 +261,117 @@ and confirm the returned roots match.
258261
implementation would require hashing approximately 88MB of data to
259262
provide a single merkle proof. ElectrumX implements an optimization
260263
such that it hashes only approximately 180KB of data per proof.
264+
265+
266+
.. _padding_messages:
267+
268+
Traffic analysis
269+
----------------
270+
271+
The goal is to defend against a passive network Man-in-the-Middle, such as an ISP
272+
or a Tor exit node, observing the encrypted TLS stream, and making educated guesses
273+
of the message contents based on TCP packet flow: timing, direction, and sizes of TCP packets.
274+
275+
.. note:: When using raw cleartext TCP as transport for the JSON-RPC payloads, without encryption,
276+
a passive network observer can see all the plaintext messages.
277+
278+
As a generic mitigation, implementations (both client and server) SHOULD
279+
280+
- pad messages to bucketed lengths (e.g. powers of 2, with a min size), and
281+
282+
- introduce small timing delays, ideally by buffering messages.
283+
284+
We can fully backwards-compatibly add padding to the JSON-RPC messages by adding extra
285+
whitespaces inside the JSON objects in a way that parsers ignore.
286+
This can be done at any protocol version.
287+
288+
For example, instead of sending::
289+
290+
{"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"]}\n
291+
292+
293+
the client could send::
294+
295+
{"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"] }\n
296+
297+
298+
For better results, both the client and the server SHOULD implement logic to pad the messages
299+
that they send. So that requests and responses (and notifications) SHOULD all be padded.
300+
This does not have to be rolled out simultaneously: it is ok for only a client to pad what
301+
they send and not the server (or the other way around),
302+
that just limits the effectiveness of the defense against traffic analysis.
303+
304+
Note when the JSON-RPC messages are sent in the TLS stream, they are sometimes batched together.
305+
That is, a single TCP packet might contain multiple small JSON-RPC messages,
306+
e.g. if the client tries to send multiple messages in a short burst.
307+
Also, many protocol requests are <100 bytes, so it would be wasteful to pad all to e.g. 1 kbyte.
308+
309+
To save bandwidth, instead of padding individual JSON-RPC messages,
310+
participants (the client and the server) COULD implement an application-level buffer,
311+
write the messages into that buffer, periodically empty the buffer into the TLS stream
312+
and only add the padding into e.g. the last JSON-RPC message when emptying the buffer.
313+
314+
.. note:: Example implementation
315+
in the `Electrum client <https://github.com/spesmilo/electrum/pull/9875>`_
316+
and in the `electrumx server <https://github.com/spesmilo/electrumx/pull/301>`_:
317+
318+
Both the client and the server writes raw JSON-RPC protocol messages into a buffer,
319+
which is then occasionally flushed to the wire. When it is flushed, padding is added
320+
to round up the total length to 1 KB, or to the next power of 2.
321+
The buffer is flushed if it reaches 1 KB, plus there is extra logic that periodically polls
322+
if the oldest message in the buffer is older than 1 second, in which case it is also flushed.
323+
The worst-case 1 second delay is a performance hit that might in cases be felt by the user.
324+
This is a tradeoff to make the flow of packets harder to analyse for an observer.
325+
326+
Implementations COULD make the buffer size and the max time delay configurable.
327+
328+
.. note:: Many protocol requests are <100 bytes. Contrast that with broadcasting a transaction,
329+
which could potentially be several megabytes of data.
330+
(max consensus-valid tx is 4 MB, times 2 for hex-encoding)
331+
Hence padding to a constant size is not practical.
332+
Instead it is recommended to pad to bucketed lengths, e.g. to powers of 2.
333+
334+
The specific details of the size of the buffer, how often it is flushed, and how the padding
335+
is done is not specified by the protocol at the moment. Consequently, neither the client nor
336+
the server can enforce the other to pad and much less to delay messages.
337+
338+
.. note:: Some server implementations do not deal with TLS at all,
339+
they only implement the raw cleartext TCP protocol, and just recommend operators
340+
to put a reverse proxy in front that does TLS termination.
341+
Such protocol implementations (both client and server) nevertheless still
342+
SHOULD implement all mentioned traffic analysis protections.
343+
That way, if the operator tunnels the traffic over TLS externally,
344+
the resulting stream meaningfully receives the protections.
345+
346+
.. note:: An alternative approach to the buffer-based delaying and padding
347+
could be to more aggressively use JSON-RPC batching to batch messages, and to use the
348+
optional Record Padding of
349+
`TLS 1.3 <https://www.rfc-editor.org/rfc/rfc8446#section-5.4>`_.
350+
See `SSL_CONF_cmd RecordPadding` in openssl. However this only allows
351+
padding to multiples of a constant, while above the recommendation was to pad to powers of 2.
352+
The implementation might still want to delay messages a bit
353+
to accumulate them into a larger JSON-RPC batch. Also note if users
354+
(e.g. server operators) are expected to do their own TLS termination
355+
(e.g. using a reverse proxy), configuring TLS RecordPadding would become an externality
356+
for them to do, which they might forget.
357+
358+
.. note:: Buffering the messages to introduce timing delays
359+
and padding to ~bucketed sizes is a good baseline.
360+
However even approximate timing and direction of TCP packets
361+
can leak too much information in some scenarios.
362+
363+
To combat timing analysis, both the client and the server
364+
COULD send dummy RPCs with a random timer, but more importantly at strategically selected events.
365+
For example, when the client receives a new block header notification,
366+
it COULD probabilistically send a random number of "server.ping" messages
367+
with small random sleeps in-between.
368+
369+
Protocol version 1.7 extends "server.ping" so that either party can send it, and
370+
that it can be sent either as a JSON-RPC "Request" or as a JSON-RPC "Notification".
371+
If sent as a notification, the receiver is expected not to respond.
372+
373+
When the server sends a block header notification to the client,
374+
it COULD also probabilistically send noise ("server.ping") notifications to the client,
375+
perhaps conditioned on whether it will also send
376+
:func:`blockchain.scriptpubkey.subscribe` notifications.
377+
(so server could send noise if there are no status notifications to be sent)

0 commit comments

Comments
 (0)