Skip to content

Commit 0f7b0c0

Browse files
authored
Fix NSS KeyLog cannot decrypt TLS1.3 traffic. (#4767)
* Fix NSS KeyLog cannot decrypt TLS1.3 traffic. * Adding TLS1.3 NSS Keylog Decryption Unit Tests.
1 parent 7fb32a1 commit 0f7b0c0

File tree

5 files changed

+128
-65
lines changed

5 files changed

+128
-65
lines changed

doc/notebooks/tls/notebook3_tls_compromised.ipynb

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,89 +12,89 @@
1212
]
1313
},
1414
{
15-
"cell_type": "code",
16-
"execution_count": null,
1715
"metadata": {},
18-
"outputs": [],
16+
"cell_type": "code",
1917
"source": [
2018
"from scapy.all import *\n",
2119
"load_layer('tls')"
22-
]
20+
],
21+
"outputs": [],
22+
"execution_count": null
2323
},
2424
{
25-
"cell_type": "code",
26-
"execution_count": null,
2725
"metadata": {},
28-
"outputs": [],
26+
"cell_type": "code",
2927
"source": [
3028
"record1_str = open('raw_data/tls_session_compromised/01_cli.raw', 'rb').read()\n",
3129
"record1 = TLS(record1_str)\n",
3230
"record1.msg[0].show()"
33-
]
31+
],
32+
"outputs": [],
33+
"execution_count": null
3434
},
3535
{
3636
"cell_type": "code",
37-
"execution_count": null,
3837
"metadata": {
3938
"scrolled": true
4039
},
41-
"outputs": [],
4240
"source": [
4341
"record2_str = open('raw_data/tls_session_compromised/02_srv.raw', 'rb').read()\n",
4442
"record2 = TLS(record2_str, tls_session=record1.tls_session.mirror())\n",
4543
"record2.msg[0].show()"
46-
]
44+
],
45+
"outputs": [],
46+
"execution_count": null
4747
},
4848
{
4949
"cell_type": "code",
50-
"execution_count": null,
5150
"metadata": {},
52-
"outputs": [],
5351
"source": [
5452
"# Supposing that the private key of the server was stolen,\n",
5553
"# the traffic can be decoded by registering it to the Scapy TLS session\n",
5654
"key = PrivKey('raw_data/pki/srv_key.pem')\n",
5755
"record2.tls_session.server_rsa_key = key"
58-
]
56+
],
57+
"outputs": [],
58+
"execution_count": null
5959
},
6060
{
6161
"cell_type": "code",
62-
"execution_count": null,
6362
"metadata": {},
64-
"outputs": [],
6563
"source": [
6664
"record3_str = open('raw_data/tls_session_compromised/03_cli.raw', 'rb').read()\n",
6765
"record3 = TLS(record3_str, tls_session=record2.tls_session.mirror())\n",
6866
"record3.show()"
69-
]
67+
],
68+
"outputs": [],
69+
"execution_count": null
7070
},
7171
{
7272
"cell_type": "code",
73-
"execution_count": null,
7473
"metadata": {},
75-
"outputs": [],
7674
"source": [
7775
"record4_str = open('raw_data/tls_session_compromised/04_srv.raw', 'rb').read()\n",
7876
"record4 = TLS(record4_str, tls_session=record3.tls_session.mirror())\n",
7977
"record4.show()"
80-
]
78+
],
79+
"outputs": [],
80+
"execution_count": null
8181
},
8282
{
8383
"cell_type": "code",
84-
"execution_count": null,
8584
"metadata": {},
86-
"outputs": [],
8785
"source": [
8886
"# This is the first TLS Record containing user data. If decryption works,\n",
8987
"# you should see the string \"To boldly go where no man has gone before...\" in plaintext.\n",
9088
"record5_str = open('raw_data/tls_session_compromised/05_cli.raw', 'rb').read()\n",
9189
"record5 = TLS(record5_str, tls_session=record4.tls_session.mirror())\n",
9290
"record5.show()"
93-
]
91+
],
92+
"outputs": [],
93+
"execution_count": null
9494
},
9595
{
96-
"cell_type": "markdown",
9796
"metadata": {},
97+
"cell_type": "markdown",
9898
"source": [
9999
"# Decrypting TLS Traffic Protected with PFS\n",
100100
"\n",
@@ -104,14 +104,15 @@
104104
"```\n",
105105
"cd doc/notebooks/tls/raw_data/\n",
106106
"\n",
107-
"# Start a TLS 1.12 Server using the s_server\n",
108-
"sudo openssl s_server -accept localhost:443 -cert pki/srv_cert.pem -key pki/srv_key.pem -WWW -tls1_2\n",
107+
"# Start a TLS Server using the s_server\n",
108+
"sudo openssl s_server -accept localhost:443 -cert pki/srv_cert.pem -key pki/srv_key.pem -WWW\n",
109109
"\n",
110110
"# Sniff the network and write packets to a file\n",
111111
"sudo tcpdump -i lo -w tls_nss_example.pcap port 443\n",
112112
"\n",
113-
"# Connect to the server using s_client and retrieve the secrets.txt file\n",
114-
"openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt\n",
113+
"# Connect to the server using TLS 1.2 and TLS 1.3, and write the keys to a file\n",
114+
"echo -e \"GET /pki/srv_key.pem HTTP/1.0\\r\\n\" | openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt -tls1_2 -ign_eof\n",
115+
"echo -e \"GET /pki/srv_key.pem HTTP/1.0\\r\\n\" | openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt -tls1_3 -ign_eof\n",
115116
"```\n",
116117
"\n",
117118
"## Decrypt a PCAP files\n",
@@ -120,38 +121,58 @@
120121
]
121122
},
122123
{
123-
"cell_type": "code",
124-
"execution_count": null,
125124
"metadata": {},
126-
"outputs": [],
125+
"cell_type": "code",
127126
"source": [
128127
"load_layer(\"tls\")\n",
129128
"\n",
130129
"conf.tls_session_enable = True\n",
131130
"conf.tls_nss_filename = \"raw_data/tls_nss_example.keys.txt\"\n",
132131
"\n",
133-
"packets = rdpcap(\"raw_data/tls_nss_example.pcap\")"
134-
]
132+
"packets = sniff(offline=\"raw_data/tls_nss_example.pcap\", session=TCPSession)"
133+
],
134+
"outputs": [],
135+
"execution_count": null
135136
},
136137
{
137-
"cell_type": "code",
138-
"execution_count": null,
139138
"metadata": {},
140-
"outputs": [],
139+
"cell_type": "code",
141140
"source": [
142-
"# Display the HTTP GET query\n",
143-
"packets[11][TLS].show()"
144-
]
141+
"# Display the TLS1.2 HTTP GET query\n",
142+
"packets[9][TLS].show()"
143+
],
144+
"outputs": [],
145+
"execution_count": null
145146
},
146147
{
148+
"metadata": {},
147149
"cell_type": "code",
148-
"execution_count": null,
150+
"source": [
151+
"# Display the answer containing the secret\n",
152+
"packets[10][TLS].show()"
153+
],
154+
"outputs": [],
155+
"execution_count": null
156+
},
157+
{
149158
"metadata": {},
159+
"cell_type": "code",
160+
"source": [
161+
"# Display the TLS1.3 HTTP GET query\n",
162+
"packets[27][TLS13].show()"
163+
],
150164
"outputs": [],
165+
"execution_count": null
166+
},
167+
{
168+
"metadata": {},
169+
"cell_type": "code",
151170
"source": [
152171
"# Display the answer containing the secret\n",
153-
"packets[13][TLS].show()"
154-
]
172+
"packets[28][TLS13].show()"
173+
],
174+
"outputs": [],
175+
"execution_count": null
155176
},
156177
{
157178
"cell_type": "markdown",
@@ -166,24 +187,23 @@
166187
},
167188
{
168189
"cell_type": "code",
169-
"execution_count": null,
170190
"metadata": {},
171-
"outputs": [],
172191
"source": [
173192
"# Read packets from a pcap\n",
174193
"load_layer(\"tls\")\n",
175194
"\n",
195+
"conf.tls_session_enable = False\n",
176196
"packets = rdpcap(\"raw_data/tls_nss_example.pcap\")\n",
177197
"\n",
178198
"# Load the keys from a NSS Key Log\n",
179199
"nss_keys = load_nss_keys(\"raw_data/tls_nss_example.keys.txt\")"
180-
]
200+
],
201+
"outputs": [],
202+
"execution_count": null
181203
},
182204
{
183205
"cell_type": "code",
184-
"execution_count": null,
185206
"metadata": {},
186-
"outputs": [],
187207
"source": [
188208
"# Parse the Client Hello message from its raw bytes. This configures a new tlsSession object\n",
189209
"client_hello = TLS(raw(packets[3][TLS]))\n",
@@ -192,34 +212,37 @@
192212
"server_hello = TLS(raw(packets[5][TLS]), tls_session=client_hello.tls_session.mirror())\n",
193213
"\n",
194214
"# Configure the TLS master secret retrieved from the NSS Key Log\n",
195-
"server_hello.tls_session.master_secret = nss_keys[\"CLIENT_RANDOM\"][\"Secret\"]\n",
215+
"server_hello.tls_session.master_secret = nss_keys[\"CLIENT_RANDOM\"][client_hello.tls_session.client_random]\n",
216+
"server_hello.tls_session.compute_ms_and_derive_keys()\n",
196217
"\n",
197218
"# Parse remaining TLS messages\n",
198219
"client_finished = TLS(raw(packets[7][TLS]), tls_session=server_hello.tls_session.mirror())\n",
199-
"server_finished = TLS(raw(packets[9][TLS]), tls_session=client_finished.tls_session.mirror())"
200-
]
220+
"server_finished = TLS(raw(packets[8][TLS]), tls_session=client_finished.tls_session.mirror())"
221+
],
222+
"outputs": [],
223+
"execution_count": null
201224
},
202225
{
203226
"cell_type": "code",
204-
"execution_count": null,
205227
"metadata": {},
206-
"outputs": [],
207228
"source": [
208229
"# Display the HTTP GET query\n",
209-
"http_query = TLS(raw(packets[11][TLS]), tls_session=server_finished.tls_session.mirror())\n",
230+
"http_query = TLS(raw(packets[9][TLS]), tls_session=server_finished.tls_session.mirror())\n",
210231
"http_query.show()"
211-
]
232+
],
233+
"outputs": [],
234+
"execution_count": null
212235
},
213236
{
214237
"cell_type": "code",
215-
"execution_count": null,
216238
"metadata": {},
217-
"outputs": [],
218239
"source": [
219240
"# Display the answer containing the secret\n",
220-
"http_response = TLS(raw(packets[13][TLS]), tls_session=http_query.tls_session.mirror())\n",
241+
"http_response = TLS(raw(packets[10][TLS]), tls_session=http_query.tls_session.mirror())\n",
221242
"http_response.show()"
222-
]
243+
],
244+
"outputs": [],
245+
"execution_count": null
223246
}
224247
],
225248
"metadata": {
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
# SSL/TLS secrets log file, generated by OpenSSL
2-
CLIENT_RANDOM c43c799f04ad31e397ee4fe14c8819a19bf5951bbc545cada407c6c7589e60ab b599798159244555ddd10d80b5552a37d327fd6e661f3520194c28ef6e8bb0af6e3fb4d4f9945a61e83a41f2345fa27a
2+
CLIENT_RANDOM 216e876ea1a480c60145c4c80eb8d05c85b6806043105c391236cd4e88f79a21 54a828bfc25edf47070cd48b8253e8137e88082face8d7e96960756653b57f41bc6df3f45a5746bc9c6305ccd9b35ab8
3+
SERVER_HANDSHAKE_TRAFFIC_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 5f2fd60aecc80ee54d17d48ec58fcfccf6fe229e08055dba1a6a09297bea98fd1268bdd6fe19e15c76d7c152d17f7237
4+
EXPORTER_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 02aa67e90b524002f7eb00fcda23365ca6bfea5ad179d965264b5c1f6ff93483465b3c147c5070a90e47a406bd431152
5+
SERVER_TRAFFIC_SECRET_0 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 c5f265aee5d17472c71fa889cfa351b12b9280bf74d16477161fd495c87432632908cae923e390d5d52a4719c2f896de
6+
CLIENT_HANDSHAKE_TRAFFIC_SECRET 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 bf58ee2a720cb26a594c0c7b714783a406f4daad18fbf7b7b3437bfe944d840cbc0e1843096e1c4ec92b68f230b22fa9
7+
CLIENT_TRAFFIC_SECRET_0 74ef95570af6a305910ee6cb0f98fc5bcec0c5d5dffe5f293ae9a4d7ba2110f2 7f3ac59f48dbe7f0fa66f92a0e691cf6ad4b84062e66b303f3149107c723ffb8424f8a3488072a8938d842b403e43229
6.7 KB
Binary file not shown.

scapy/layers/tls/session.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,15 @@ def compute_tls13_early_secrets(self, external=False):
731731
b"".join(self.handshake_messages))
732732
self.tls13_derived_secrets["early_exporter_secret"] = ees
733733

734+
if self.nss_keys:
735+
cets_dict = self.nss_keys.get('CLIENT_EARLY_TRAFFIC_SECRET', {})
736+
cets = cets_dict.get(self.client_random, cets)
737+
self.tls13_derived_secrets["client_early_traffic_secret"] = cets
738+
739+
ees_dict = self.nss_keys.get('EARLY_EXPORTER_SECRET', {})
740+
ees = ees_dict.get(self.client_random, ees)
741+
self.tls13_derived_secrets["early_exporter_secret"] = ees
742+
734743
if self.connection_end == "server":
735744
if self.prcs:
736745
self.prcs.tls13_derive_keys(cets)
@@ -768,6 +777,15 @@ def compute_tls13_handshake_secrets(self):
768777
b"".join(self.handshake_messages))
769778
self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts
770779

780+
if self.nss_keys:
781+
chts_dict = self.nss_keys.get('CLIENT_HANDSHAKE_TRAFFIC_SECRET', {})
782+
chts = chts_dict.get(self.client_random, chts)
783+
self.tls13_derived_secrets["client_handshake_traffic_secret"] = chts
784+
785+
shts_dict = self.nss_keys.get('SERVER_HANDSHAKE_TRAFFIC_SECRET', {})
786+
shts = shts_dict.get(self.client_random, shts)
787+
self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts
788+
771789
def compute_tls13_traffic_secrets(self):
772790
"""
773791
Ciphers key and IV are updated accordingly for Application data.
@@ -801,6 +819,19 @@ def compute_tls13_traffic_secrets(self):
801819
b"".join(self.handshake_messages))
802820
self.tls13_derived_secrets["exporter_secret"] = es
803821

822+
if self.nss_keys:
823+
cts0_dict = self.nss_keys.get('CLIENT_TRAFFIC_SECRET_0', {})
824+
cts0 = cts0_dict.get(self.client_random, cts0)
825+
self.tls13_derived_secrets["client_traffic_secrets"] = [cts0]
826+
827+
sts0_dict = self.nss_keys.get('SERVER_TRAFFIC_SECRET_0', {})
828+
sts0 = sts0_dict.get(self.client_random, sts0)
829+
self.tls13_derived_secrets["server_traffic_secrets"] = [sts0]
830+
831+
es_dict = self.nss_keys.get('EXPORTER_SECRET', {})
832+
es = es_dict.get(self.client_random, es)
833+
self.tls13_derived_secrets["exporter_secret"] = es
834+
804835
if self.connection_end == "server":
805836
# self.prcs.tls13_derive_keys(cts0)
806837
self.pwcs.tls13_derive_keys(sts0)

test/scapy/layers/tls/tls.uts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,9 +1585,11 @@ bck_conf = conf
15851585
conf.tls_session_enable = True
15861586
conf.tls_nss_filename = scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.keys.txt")
15871587

1588-
packets = rdpcap(scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.pcap"))
1589-
assert b"GET /secret.txt HTTP/1.0\n" in packets[11].msg[0].data
1590-
assert b"z2|gxarIKOxt,G1d>.Q2MzGY[k@" in packets[13].msg[0].data
1588+
packets = sniff(offline=scapy_path("doc/notebooks/tls/raw_data/tls_nss_example.pcap"), session=TCPSession)
1589+
assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[9].msg[0].data
1590+
assert b"BEGIN PRIVATE KEY" in packets[10].msg[0].data
1591+
assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[27].inner.msg[0].data
1592+
assert b"BEGIN PRIVATE KEY" in packets[28].inner.msg[0].data
15911593

15921594
conf = bck_conf
15931595

@@ -1602,9 +1604,11 @@ if shutil.which("editcap"):
16021604
pcapng_path = get_temp_file()
16031605
exit_status = os.system("editcap --inject-secrets tls,%s %s %s" % (key_log_path, pcap_path, pcapng_path))
16041606
assert exit_status == 0
1605-
packets = rdpcap(pcapng_path)
1606-
assert b"GET /secret.txt HTTP/1.0\n" in packets[11].msg[0].data
1607-
assert b"z2|gxarIKOxt,G1d>.Q2MzGY[k@" in packets[13].msg[0].data
1607+
packets = sniff(offline=pcapng_path, session=TCPSession)
1608+
assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[9].msg[0].data
1609+
assert b"BEGIN PRIVATE KEY" in packets[10].msg[0].data
1610+
assert b"GET /pki/srv_key.pem HTTP/1.0\r\n" in packets[27].inner.msg[0].data
1611+
assert b"BEGIN PRIVATE KEY" in packets[28].inner.msg[0].data
16081612
conf = bck_conf
16091613

16101614
= pcapng file with a non-UTF-8 Decryption Secrets Block

0 commit comments

Comments
 (0)