Reviewing Ledger Live's network traffic
There are a lot of rumors about hardware wallet clients tracing their customers, collecting their data through official clients. One of the most common hardware wallets, which has also been much discussed for possible privacy issues, is undoubtedly the Ledger.
Let's try to analyze the Ledger Live client's network traffic to see what data it collects.
One of the most common rumors is that the Ledger's servers collect the XPUB keys, being able to know all the past, present and future activities of each customer. Another rumor, spread when the company launched the ability to recover users' seeds, is that the devices are sending them to the servers, putting user's funds at risk.
Myth busting rumors
Let's try to verify these rumors, by analyzing the behavior of the client. To do this, we could capture the network traffic using Wireshark, which is included in AnuBitux with the Ledger Live client and the udev rules to handle Ledger devices.
Likely the network traffic between the Ledger Live client and Ledger's servers is encrypted. So we can set up the environment as we explained in this post, start the traffic capture by clicking on the proper button in Wireshark and launch the Ledger Live client from our command line (unless doing so, it will not write the TLS data in the SSLKeyLog file and encrypted traffic will not be readable).
Now we can regularly connect our Ledger, install the Bitcoin app on the device if needed, set up a Bitcoin account in the client, wait for the sync to complete and then we can stop Wireshark and unplug our Ledger.
Now we can analyze all the network traffic from Wireshark and, since we have set it up to decode encrypted traffic and since the Ledger Live client is based on a web browser, we are also able to inspect the content of the transmitted network traffic.
First of all, ledger Live sends information about the client and the device we are using, like any web browser commonly does. Here you can find the whole content in plain text of a packet sending this kind of data.
Transport Layer Security
TLSv1.3 Record Layer: Application Data Protocol: http2
Opaque Type: Application Data (23)
Version: TLS 1.2 (0x0303)
Length: 256
[Content Type: Application Data (23)]
Encrypted Application Data: 72dd5dc080844e1149f02f93017803ecddd6334b9f86f3abfead7a05aa517a6ef3bfbb2e…
[Application Data Protocol: http2]
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 230, GET /edgedl/chrome/dict/en-us-10-1.bdic
Length: 230
Type: HEADERS (1)
Flags: 0x25, Priority, End Headers, End Stream
00.0 ..0. = Unused: 0x00
..1. .... = Priority: True
.... 0... = Padded: False
.... .1.. = End Headers: True
.... ...1 = End Stream: True
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
1... .... .... .... .... .... .... .... = Exclusive: True
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Dependency: 0
Weight: 109
[Weight real: 110]
Header Block Fragment: 82418eb0b21ac29127b179bba42b90f4ff87049960b24c59286093d87a4ac48622582d4b…
[Header Length: 431]
[Header Count: 10]
Header: :method: GET
Name Length: 7
Name: :method
Value Length: 3
Value: GET
:method: GET
[Unescaped: GET]
Representation: Indexed Header Field
Index: 2
Header: :authority: redirector.gvt1.com
Name Length: 10
Name: :authority
Value Length: 19
Value: redirector.gvt1.com
:authority: redirector.gvt1.com
[Unescaped: redirector.gvt1.com]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 1
Header: :scheme: https
Name Length: 7
Name: :scheme
Value Length: 5
Value: https
:scheme: https
[Unescaped: https]
Representation: Indexed Header Field
Index: 7
Header: :path: /edgedl/chrome/dict/en-us-10-1.bdic
Name Length: 5
Name: :path
Value Length: 35
Value: /edgedl/chrome/dict/en-us-10-1.bdic
:path: /edgedl/chrome/dict/en-us-10-1.bdic
[Unescaped: /edgedl/chrome/dict/en-us-10-1.bdic]
Representation: Literal Header Field without Indexing - Indexed Name
Index: 4
Header: sec-fetch-site: none
Name Length: 14
Name: sec-fetch-site
Value Length: 4
Value: none
[Unescaped: none]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: sec-fetch-mode: no-cors
Name Length: 14
Name: sec-fetch-mode
Value Length: 7
Value: no-cors
[Unescaped: no-cors]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: sec-fetch-dest: empty
Name Length: 14
Name: sec-fetch-dest
Value Length: 5
Value: empty
[Unescaped: empty]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) LedgerLive/2.55.0 Chrome/110.0.5481.100 Electron/23.1.0 Safari/537.36
Name Length: 10
Name: user-agent
Value Length: 140
Value: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) LedgerLive/2.55.0 Chrome/110.0.5481.100 Electron/23.1.0 Safari/537.36
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) LedgerLive/2.55.0 Chrome/110.0.5481.100 Electron/23.1.0 Safari/537.36
[Unescaped: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) LedgerLive/2.55.0 Chrome/110.0.5481.100 Electron/23.1.0 Safari/537.36]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 58
Header: accept-encoding: gzip, deflate, br
Name Length: 15
Name: accept-encoding
Value Length: 17
Value: gzip, deflate, br
accept-encoding: gzip, deflate, br
[Unescaped: gzip, deflate, br]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 16
Header: accept-language: en-US
Name Length: 15
Name: accept-language
Value Length: 5
Value: en-US
accept-language: en-US
[Unescaped: en-US]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 17
To verify if the client is sending our XPUB key, we can simply try to search the "xpub" string using the proper Wireshark function.
As we can see, we are also looking into the detail of the transmitted packets, but there seems to be no trace of any XPUB key, even encrypted with TLS.
Analyzing the transmitted packages, we can see that the client is sending many Bitcoin addresses in plain text, as shown above.
This is a normal behavior, since it happens with most of the SPV wallets that are not connecting to users' private nodes. We can see that the client is checking each of our addresses but it is not sending the whole XPUB key or a lot of unnecessary addresses. If it seems to send more addresses than we have ever used, it depends on the default gap limit. This means that Ledger Live continues deriving addresses until it finds 20 unused addresses, so that it should not miss any balance. We can change this value from the "Experimental features" tab, setting a "Custom gap limit".
The second rumor we talked about regards the possibility that Ledger Live is sending our seed to the Ledger's servers. To verify this myth, first of all, we exported the packets in a plain text file.
Then we used our seedsearch tool to verify if there is any mnemonic seed among all the transmitted data. As expected, we didn't find anyone.
We could notice that the tool didn't access the pcapng file, but this does not matter since it examined the plain text file we have exported, which contains all the content of any captured packet.
Final thoughts
To analyze the rumors around the privacy issues related to Ledger devices, we tried to examine the network traffic of their official client but we could also refer to their website, even if one of the main rules of the crypto users is "don't trust, verify".
This analysis seems to confirm that Ledger is doing what is claimed on their website and that they are not "stealing" users' data. Of course, if Ledger was/became a malicious actor, xpub keys or other sensitive data could be sent with additional encryption so that it couldn't be read even decrypting the TLS traffic, but there is no evidence to support this rumor and we never have to forget that another main rule of the crypto users is "be your own bank" and only we can choose what is best for our needs.