2 # Copyright 2017-2018, The Tor Project, Inc
3 # See LICENSE for licensing information
8 This module is a reference implementation of the modified ntor protocol
9 proposed for Tor hidden services in proposal 224 (Next Generation Hidden
10 Services) in section [NTOR-WITH-EXTRA-DATA].
12 The modified ntor protocol is a single-round protocol, with three steps in total:
14 1: Client generates keys and sends them to service via INTRODUCE cell
16 2: Service computes key material based on client's keys, and sends its own
17 keys to client via RENDEZVOUS cell
19 3: Client computes key material as well.
21 It's meant to be used to validate Tor's HS ntor implementation by conducting
22 various integration tests. Specifically it conducts the following three tests:
24 - Tests our Python implementation by running the whole protocol in Python and
25 making sure that results are consistent.
27 - Tests little-t-tor ntor implementation. We use this Python code to instrument
28 little-t-tor and carry out the handshake by using little-t-tor code. The
29 small C wrapper at src/test/test-hs-ntor-cl is used for this Python module to
30 interface with little-t-tor.
32 - Cross-tests Python and little-t-tor implementation by running half of the
33 protocol in Python code and the other in little-t-tor. This is actually two
34 tests so that all parts of the protocol are run both by little-t-tor and
37 It requires the curve25519 python module from the curve25519-donna package.
39 The whole logic and concept for this test suite was taken from ntor_ref.py.
41 *** DO NOT USE THIS IN PRODUCTION. ***
51 curve25519mod
= curve25519
.keys
54 import slownacl_curve25519
55 curve25519mod
= slownacl_curve25519
61 # In python 3.6, the sha3 functions are in hashlib whether we
66 # Pull the sha3 functions in.
67 from hashlib
import sha3_256
, shake_256
68 shake_squeeze
= shake_256
.digest
70 if hasattr(sha3
, "SHA3256"):
71 # If this happens, then we have the old "sha3" module which
72 # hashlib and pysha3 superseded.
73 sha3_256
= sha3
.SHA3256
74 shake_256
= sha3
.SHAKE256
75 shake_squeeze
= shake_256
.squeeze
77 # error code 77 tells automake to skip this test
80 # Import Nick's ntor reference implementation in Python
81 # We are gonna use a few of its utilities.
82 from ntor_ref
import hash_nil
83 from ntor_ref
import PrivateKey
85 # String constants used in this protocol
86 PROTOID
= b
"tor-hs-ntor-curve25519-sha3-256-1"
87 T_HSENC
= PROTOID
+ b
":hs_key_extract"
88 T_HSVERIFY
= PROTOID
+ b
":hs_verify"
89 T_HSMAC
= PROTOID
+ b
":hs_mac"
90 M_HSEXPAND
= PROTOID
+ b
":hs_key_expand"
92 INTRO_SECRET_LEN
= 161
96 # Implements MAC(k,m) = H(htonll(len(k)) | k | m)
99 return struct
.pack('!q', num
)
102 s
.update(htonll(len(k
)))
107 ######################################################################
109 # Functions that implement the modified HS ntor protocol
111 """As client compute key material for INTRODUCE cell as follows:
113 intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
114 info = m_hsexpand | subcredential
115 hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
116 ENC_KEY = hs_keys[0:S_KEY_LEN]
117 MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
119 def intro2_ntor_client(intro_auth_pubkey_str
, intro_enc_pubkey
,
120 client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
, subcredential
):
122 dh_result
= client_ephemeral_enc_privkey
.get_shared_key(intro_enc_pubkey
, hash_nil
)
123 secret
= dh_result
+ intro_auth_pubkey_str
+ client_ephemeral_enc_pubkey
.serialize() + intro_enc_pubkey
.serialize() + PROTOID
124 assert(len(secret
) == INTRO_SECRET_LEN
)
125 info
= M_HSEXPAND
+ subcredential
128 kdf
.update(secret
+ T_HSENC
+ info
)
129 key_material
= shake_squeeze(kdf
, 64*8)
131 enc_key
= key_material
[0:32]
132 mac_key
= key_material
[32:64]
134 return enc_key
, mac_key
136 """Wrapper over intro2_ntor_client()"""
137 def client_part1(intro_auth_pubkey_str
, intro_enc_pubkey
,
138 client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
, subcredential
):
139 enc_key
, mac_key
= intro2_ntor_client(intro_auth_pubkey_str
, intro_enc_pubkey
, client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
, subcredential
)
143 return enc_key
, mac_key
145 """As service compute key material for INTRODUCE cell as follows:
147 intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID
148 info = m_hsexpand | subcredential
149 hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
150 HS_DEC_KEY = hs_keys[0:S_KEY_LEN]
151 HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
153 def intro2_ntor_service(intro_auth_pubkey_str
, client_enc_pubkey
, service_enc_privkey
, service_enc_pubkey
, subcredential
):
154 dh_result
= service_enc_privkey
.get_shared_key(client_enc_pubkey
, hash_nil
)
155 secret
= dh_result
+ intro_auth_pubkey_str
+ client_enc_pubkey
.serialize() + service_enc_pubkey
.serialize() + PROTOID
156 assert(len(secret
) == INTRO_SECRET_LEN
)
157 info
= M_HSEXPAND
+ subcredential
160 kdf
.update(secret
+ T_HSENC
+ info
)
161 key_material
= shake_squeeze(kdf
, 64*8)
163 enc_key
= key_material
[0:32]
164 mac_key
= key_material
[32:64]
166 return enc_key
, mac_key
168 """As service compute key material for INTRODUCE and REDNEZVOUS cells.
170 Use intro2_ntor_service() to calculate the INTRODUCE key material, and use
171 the following computations to do the RENDEZVOUS ones:
173 rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
174 NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
175 verify = MAC(rend_secret_hs_input, t_hsverify)
176 auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
177 AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
179 def service_part1(intro_auth_pubkey_str
, client_enc_pubkey
, intro_enc_privkey
, intro_enc_pubkey
, subcredential
):
180 intro_enc_key
, intro_mac_key
= intro2_ntor_service(intro_auth_pubkey_str
, client_enc_pubkey
, intro_enc_privkey
, intro_enc_pubkey
, subcredential
)
181 assert(intro_enc_key
)
182 assert(intro_mac_key
)
184 service_ephemeral_privkey
= PrivateKey()
185 service_ephemeral_pubkey
= service_ephemeral_privkey
.get_public()
187 dh_result1
= service_ephemeral_privkey
.get_shared_key(client_enc_pubkey
, hash_nil
)
188 dh_result2
= intro_enc_privkey
.get_shared_key(client_enc_pubkey
, hash_nil
)
189 rend_secret_hs_input
= dh_result1
+ dh_result2
+ intro_auth_pubkey_str
+ intro_enc_pubkey
.serialize() + client_enc_pubkey
.serialize() + service_ephemeral_pubkey
.serialize() + PROTOID
190 assert(len(rend_secret_hs_input
) == REND_SECRET_LEN
)
192 ntor_key_seed
= mac(rend_secret_hs_input
, T_HSENC
)
193 verify
= mac(rend_secret_hs_input
, T_HSVERIFY
)
194 auth_input
= verify
+ intro_auth_pubkey_str
+ intro_enc_pubkey
.serialize() + service_ephemeral_pubkey
.serialize() + client_enc_pubkey
.serialize() + PROTOID
+ b
"Server"
195 assert(len(auth_input
) == AUTH_INPUT_LEN
)
196 auth_input_mac
= mac(auth_input
, T_HSMAC
)
198 assert(ntor_key_seed
)
199 assert(auth_input_mac
)
200 assert(service_ephemeral_pubkey
)
202 return intro_enc_key
, intro_mac_key
, ntor_key_seed
, auth_input_mac
, service_ephemeral_pubkey
204 """As client compute key material for rendezvous cells as follows:
206 rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID
207 NTOR_KEY_SEED = MAC(ntor_secret_input, t_hsenc)
208 verify = MAC(ntor_secret_input, t_hsverify)
209 auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
210 AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
212 def client_part2(intro_auth_pubkey_str
, client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
,
213 intro_enc_pubkey
, service_ephemeral_rend_pubkey
):
214 dh_result1
= client_ephemeral_enc_privkey
.get_shared_key(service_ephemeral_rend_pubkey
, hash_nil
)
215 dh_result2
= client_ephemeral_enc_privkey
.get_shared_key(intro_enc_pubkey
, hash_nil
)
216 rend_secret_hs_input
= dh_result1
+ dh_result2
+ intro_auth_pubkey_str
+ intro_enc_pubkey
.serialize() + client_ephemeral_enc_pubkey
.serialize() + service_ephemeral_rend_pubkey
.serialize() + PROTOID
217 assert(len(rend_secret_hs_input
) == REND_SECRET_LEN
)
219 ntor_key_seed
= mac(rend_secret_hs_input
, T_HSENC
)
220 verify
= mac(rend_secret_hs_input
, T_HSVERIFY
)
221 auth_input
= verify
+ intro_auth_pubkey_str
+ intro_enc_pubkey
.serialize() + service_ephemeral_rend_pubkey
.serialize() + client_ephemeral_enc_pubkey
.serialize() + PROTOID
+ b
"Server"
222 assert(len(auth_input
) == AUTH_INPUT_LEN
)
223 auth_input_mac
= mac(auth_input
, T_HSMAC
)
225 assert(ntor_key_seed
)
226 assert(auth_input_mac
)
228 return ntor_key_seed
, auth_input_mac
230 #################################################################################
233 Utilities for communicating with the little-t-tor ntor wrapper to conduct the
237 PROG
= "./src/test/test-hs-ntor-cl"
238 if sys
.version_info
[0] >= 3:
239 enhex
=lambda s
: binascii
.b2a_hex(s
).decode("ascii")
241 enhex
=lambda s
: binascii
.b2a_hex(s
)
242 dehex
=lambda s
: binascii
.a2b_hex(s
.strip())
244 def tor_client1(intro_auth_pubkey_str
, intro_enc_pubkey
,
245 client_ephemeral_enc_privkey
, subcredential
):
246 p
= subprocess
.Popen([PROG
, "client1",
247 enhex(intro_auth_pubkey_str
),
248 enhex(intro_enc_pubkey
.serialize()),
249 enhex(client_ephemeral_enc_privkey
.serialize()),
250 enhex(subcredential
)],
251 stdout
=subprocess
.PIPE
)
252 return map(dehex
, p
.stdout
.readlines())
254 def tor_server1(intro_auth_pubkey_str
, intro_enc_privkey
,
255 client_ephemeral_enc_pubkey
, subcredential
):
256 p
= subprocess
.Popen([PROG
, "server1",
257 enhex(intro_auth_pubkey_str
),
258 enhex(intro_enc_privkey
.serialize()),
259 enhex(client_ephemeral_enc_pubkey
.serialize()),
260 enhex(subcredential
)],
261 stdout
=subprocess
.PIPE
)
262 return map(dehex
, p
.stdout
.readlines())
264 def tor_client2(intro_auth_pubkey_str
, client_ephemeral_enc_privkey
,
265 intro_enc_pubkey
, service_ephemeral_rend_pubkey
, subcredential
):
266 p
= subprocess
.Popen([PROG
, "client2",
267 enhex(intro_auth_pubkey_str
),
268 enhex(client_ephemeral_enc_privkey
.serialize()),
269 enhex(intro_enc_pubkey
.serialize()),
270 enhex(service_ephemeral_rend_pubkey
.serialize()),
271 enhex(subcredential
)],
272 stdout
=subprocess
.PIPE
)
273 return map(dehex
, p
.stdout
.readlines())
275 ##################################################################################
277 # Perform a pure python ntor test
278 def do_pure_python_ntor_test():
279 # Initialize all needed key material
280 client_ephemeral_enc_privkey
= PrivateKey()
281 client_ephemeral_enc_pubkey
= client_ephemeral_enc_privkey
.get_public()
282 intro_enc_privkey
= PrivateKey()
283 intro_enc_pubkey
= intro_enc_privkey
.get_public()
284 intro_auth_pubkey_str
= os
.urandom(32)
285 subcredential
= os
.urandom(32)
287 client_enc_key
, client_mac_key
= client_part1(intro_auth_pubkey_str
, intro_enc_pubkey
, client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
, subcredential
)
289 service_enc_key
, service_mac_key
, service_ntor_key_seed
, service_auth_input_mac
, service_ephemeral_pubkey
= service_part1(intro_auth_pubkey_str
, client_ephemeral_enc_pubkey
, intro_enc_privkey
, intro_enc_pubkey
, subcredential
)
291 assert(client_enc_key
== service_enc_key
)
292 assert(client_mac_key
== service_mac_key
)
294 client_ntor_key_seed
, client_auth_input_mac
= client_part2(intro_auth_pubkey_str
, client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
,
295 intro_enc_pubkey
, service_ephemeral_pubkey
)
297 assert(client_ntor_key_seed
== service_ntor_key_seed
)
298 assert(client_auth_input_mac
== service_auth_input_mac
)
300 print("DONE: python dance [%s]" % repr(client_auth_input_mac
))
302 # Perform a pure little-t-tor integration test.
303 def do_little_t_tor_ntor_test():
304 # Initialize all needed key material
305 subcredential
= os
.urandom(32)
306 client_ephemeral_enc_privkey
= PrivateKey()
307 client_ephemeral_enc_pubkey
= client_ephemeral_enc_privkey
.get_public()
308 intro_enc_privkey
= PrivateKey()
309 intro_enc_pubkey
= intro_enc_privkey
.get_public() # service-side enc key
310 intro_auth_pubkey_str
= os
.urandom(32)
312 client_enc_key
, client_mac_key
= tor_client1(intro_auth_pubkey_str
, intro_enc_pubkey
,
313 client_ephemeral_enc_privkey
, subcredential
)
314 assert(client_enc_key
)
315 assert(client_mac_key
)
317 service_enc_key
, service_mac_key
, service_ntor_auth_mac
, service_ntor_key_seed
, service_eph_pubkey
= tor_server1(intro_auth_pubkey_str
,
319 client_ephemeral_enc_pubkey
,
321 assert(service_enc_key
)
322 assert(service_mac_key
)
323 assert(service_ntor_auth_mac
)
324 assert(service_ntor_key_seed
)
326 assert(client_enc_key
== service_enc_key
)
327 assert(client_mac_key
== service_mac_key
)
329 # Turn from bytes to key
330 service_eph_pubkey
= curve25519mod
.Public(service_eph_pubkey
)
332 client_ntor_auth_mac
, client_ntor_key_seed
= tor_client2(intro_auth_pubkey_str
, client_ephemeral_enc_privkey
,
333 intro_enc_pubkey
, service_eph_pubkey
, subcredential
)
334 assert(client_ntor_auth_mac
)
335 assert(client_ntor_key_seed
)
337 assert(client_ntor_key_seed
== service_ntor_key_seed
)
338 assert(client_ntor_auth_mac
== service_ntor_auth_mac
)
340 print("DONE: tor dance [%s]" % repr(client_ntor_auth_mac
))
343 Do mixed test as follows:
344 1. C -> S (python mode)
346 3. Client computes keys (python mode)
348 def do_first_mixed_test():
349 subcredential
= os
.urandom(32)
351 client_ephemeral_enc_privkey
= PrivateKey()
352 client_ephemeral_enc_pubkey
= client_ephemeral_enc_privkey
.get_public()
353 intro_enc_privkey
= PrivateKey()
354 intro_enc_pubkey
= intro_enc_privkey
.get_public() # service-side enc key
356 intro_auth_pubkey_str
= os
.urandom(32)
359 client_enc_key
, client_mac_key
= client_part1(intro_auth_pubkey_str
, intro_enc_pubkey
,
360 client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
,
363 service_enc_key
, service_mac_key
, service_ntor_auth_mac
, service_ntor_key_seed
, service_eph_pubkey
= tor_server1(intro_auth_pubkey_str
,
365 client_ephemeral_enc_pubkey
,
367 assert(service_enc_key
)
368 assert(service_mac_key
)
369 assert(service_ntor_auth_mac
)
370 assert(service_ntor_key_seed
)
371 assert(service_eph_pubkey
)
373 assert(client_enc_key
== service_enc_key
)
374 assert(client_mac_key
== service_mac_key
)
376 # Turn from bytes to key
377 service_eph_pubkey
= curve25519mod
.Public(service_eph_pubkey
)
379 client_ntor_key_seed
, client_auth_input_mac
= client_part2(intro_auth_pubkey_str
, client_ephemeral_enc_pubkey
, client_ephemeral_enc_privkey
,
380 intro_enc_pubkey
, service_eph_pubkey
)
382 assert(client_auth_input_mac
== service_ntor_auth_mac
)
383 assert(client_ntor_key_seed
== service_ntor_key_seed
)
385 print("DONE: 1st mixed dance [%s]" % repr(client_auth_input_mac
))
388 Do mixed test as follows:
390 2. C <- S (python mode)
391 3. Client computes keys (tor mode)
393 def do_second_mixed_test():
394 subcredential
= os
.urandom(32)
396 client_ephemeral_enc_privkey
= PrivateKey()
397 client_ephemeral_enc_pubkey
= client_ephemeral_enc_privkey
.get_public()
398 intro_enc_privkey
= PrivateKey()
399 intro_enc_pubkey
= intro_enc_privkey
.get_public() # service-side enc key
401 intro_auth_pubkey_str
= os
.urandom(32)
404 client_enc_key
, client_mac_key
= tor_client1(intro_auth_pubkey_str
, intro_enc_pubkey
,
405 client_ephemeral_enc_privkey
, subcredential
)
406 assert(client_enc_key
)
407 assert(client_mac_key
)
409 service_enc_key
, service_mac_key
, service_ntor_key_seed
, service_ntor_auth_mac
, service_ephemeral_pubkey
= service_part1(intro_auth_pubkey_str
, client_ephemeral_enc_pubkey
, intro_enc_privkey
, intro_enc_pubkey
, subcredential
)
411 client_ntor_auth_mac
, client_ntor_key_seed
= tor_client2(intro_auth_pubkey_str
, client_ephemeral_enc_privkey
,
412 intro_enc_pubkey
, service_ephemeral_pubkey
, subcredential
)
413 assert(client_ntor_auth_mac
)
414 assert(client_ntor_key_seed
)
416 assert(client_ntor_key_seed
== service_ntor_key_seed
)
417 assert(client_ntor_auth_mac
== service_ntor_auth_mac
)
419 print("DONE: 2nd mixed dance [%s]" % repr(client_ntor_auth_mac
))
421 def do_mixed_tests():
422 do_first_mixed_test()
423 do_second_mixed_test()
425 if __name__
== '__main__':
426 do_pure_python_ntor_test()
427 do_little_t_tor_ntor_test()