Skip to content

Commit 605de65

Browse files
committed
add section: buffer and pad msgs to protect against traffic analysis
1 parent 662a4d6 commit 605de65

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

docs/protocol-basics.rst

Lines changed: 100 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.
@@ -211,3 +214,100 @@ and confirm the returned roots match.
211214
implementation would require hashing approximately 88MB of data to
212215
provide a single merkle proof. ElectrumX implements an optimization
213216
such that it hashes only approximately 180KB of data per proof.
217+
218+
219+
.. _padding_messages:
220+
221+
Traffic analysis
222+
----------------
223+
224+
The goal is to defend against a passive network Man-in-the-Middle, such as an ISP
225+
or a Tor exit node, observing the encrypted TLS stream, and making educated guesses
226+
of the message contents based on TCP packet flow: timing, direction, and sizes of TCP packets.
227+
228+
.. note:: Raw cleartext TCP as transport for the JSON-RPC payloads is clearly out-of-scope here.
229+
Without encryption, a passive network observer could just see all the plaintext messages anyway.
230+
231+
As a generic mitigation, implementations (both client and server) SHOULD
232+
233+
- pad messages to bucketed lengths (e.g. powers of 2, with a min size), and
234+
235+
- introduce small timing delays, ideally by buffering messages.
236+
237+
We can fully backwards-compatibly add padding to the JSON-RPC messages by adding extra
238+
whitespaces inside the JSON objects in a way that parsers ignore.
239+
This can be done at any protocol version.
240+
241+
For example, instead of sending::
242+
243+
{"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"]}\n
244+
245+
246+
the client could send::
247+
248+
{"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"] }\n
249+
250+
251+
For better results, both the client and the server SHOULD implement logic to pad the messages
252+
that they send. So that requests and responses (and notifications) SHOULD all be padded.
253+
This does not have to be rolled out simultaneously: it is ok for only a client to pad what
254+
they send and not the server (or the other way around),
255+
that just limits the effectiveness of the defense against traffic analysis.
256+
257+
Note when the JSON-RPC messages are sent in the TLS stream, they are sometimes batched together.
258+
That is, a single TCP packet might contain multiple small JSON-RPC messages,
259+
e.g. if the client tries to send multiple messages in a short burst.
260+
Also, many protocol requests are <100 bytes, so it would be wasteful to pad all to e.g. 1 kbyte.
261+
262+
To save bandwidth, instead of padding individual JSON-RPC messages,
263+
participants (the client and the server) COULD implement an application-level buffer,
264+
write the messages into that buffer, periodically empty the buffer into the TLS stream
265+
and only add the padding into e.g. the last JSON-RPC message when emptying the buffer.
266+
267+
.. note:: Example implementation
268+
in the `Electrum client <https://github.com/spesmilo/electrum/pull/9875>`_
269+
and in the `electrumx server <https://github.com/spesmilo/electrumx/pull/301>`_:
270+
271+
Both the client and the server writes raw JSON-RPC protocol messages into a buffer,
272+
which is then occasionally flushed to the wire. When it is flushed, padding is added
273+
to round up the total length to 1 KB, or to the next power of 2.
274+
The buffer is flushed if it reaches 1 KB, plus there is extra logic that periodically polls
275+
if the oldest message in the buffer is older than 1 second, in which case it is also flushed.
276+
277+
.. note:: Many protocol requests are <100 bytes. Contrast that with broadcasting a transaction,
278+
which could potentially be several megabytes of data.
279+
(max consensus-valid tx is 4 MB, times 2 for hex-encoding)
280+
Hence padding to a constant size is not practical.
281+
Instead it is recommended to pad to bucketed lengths, e.g. to powers of 2.
282+
283+
The specific details of the size of the buffer, how often it is flushed, and how the padding
284+
is done is not specified by the protocol at the moment.
285+
286+
.. note:: Some server implementations do not deal with TLS at all,
287+
they only implement the raw cleartext TCP protocol, and just recommend operators
288+
to put a reverse proxy in front that does TLS termination.
289+
Such protocol implementations (both client and server) are nevertheless still
290+
SHOULD implement all mentioned traffic analysis protections.
291+
That way, if the operator tunnels the traffic over TLS externally,
292+
the resulting stream meaningfully receives the protections.
293+
294+
.. note:: Buffering the messages to introduce timing delays
295+
and padding to ~bucketed sizes is a good baseline.
296+
However even approximate timing and direction of TCP packets
297+
can leak too much information in some scenarios.
298+
299+
To combat timing analysis, both the client and the server
300+
COULD send noise with a random timer, but more importantly at strategically selected events.
301+
For example, when the client receives a new block header notification,
302+
it COULD probabilistically send a random number of "server.ping" messages
303+
with small random sleeps in-between.
304+
305+
Protocol version 1.6 extends "server.ping" so that either party can send it, and
306+
that it can be sent either as a JSON-RPC "Request" or as a JSON-RPC "Notification".
307+
If sent as a notification, the receiver is expected not to respond.
308+
309+
When the server sends a block header notification to the client,
310+
it COULD also probabilistically send noise ("server.ping") notifications to the client,
311+
perhaps conditioned on whether it will also send
312+
:func:`blockchain.scriptpubkey.subscribe` notifications.
313+
(so server could send noise if there are no status notifications to be sent)

0 commit comments

Comments
 (0)