1 //! Implements the HS ntor key exchange, as used in v3 onion services.
2 //!
3 //! The Ntor protocol of this section is specified in section
4 //! [NTOR-WITH-EXTRA-DATA] of rend-spec-v3.txt.
5 //!
6 //! The main difference between this HS Ntor handshake and the standard Ntor
7 //! handshake in ./ntor.rs is that this one allows each party to encrypt data
8 //! (without forward secrecy) after it sends the first message. This
9 //! opportunistic encryption property is used by clients in the onion service
10 //! protocol to encrypt introduction data in the INTRODUCE1 cell, and by
11 //! services to encrypt data in the RENDEZVOUS1 cell.
12 //!
13 //! # Status
14 //!
15 //! This module is a work in progress, and is not actually used anywhere yet
16 //! or tested: please expect the API to change.
17 //!
18 //! This module is available only when the `hs` feature is enabled.
19 
20 // We want to use the exact variable names from the rend-spec-v3.txt proposal.
21 // This means that we allow variables to be named x (privkey) and X (pubkey).
22 #![allow(non_snake_case)]
23 // This module is still unused: so allow some dead code for now.
24 #![allow(dead_code)]
25 #![allow(unreachable_pub)]
26 
27 use crate::crypto::handshake::KeyGenerator;
28 use crate::crypto::ll::kdf::{Kdf, ShakeKdf};
29 use crate::{Error, Result, SecretBytes};
30 use tor_bytes::{Reader, Writer};
31 use tor_llcrypto::d::Sha3_256;
32 use tor_llcrypto::pk::{curve25519, ed25519};
33 use tor_llcrypto::util::rand_compat::RngCompatExt;
34 
35 use cipher::{NewCipher, StreamCipher};
36 
37 use digest::Digest;
38 use generic_array::GenericArray;
39 use rand_core::{CryptoRng, RngCore};
40 use std::convert::TryInto;
41 use tor_llcrypto::cipher::aes::Aes256Ctr;
42 use zeroize::Zeroizing;
43 
44 /// The ENC_KEY from the HS Ntor protocol
45 type EncKey = [u8; 32];
46 /// The MAC_KEY from the HS Ntor protocol
47 type MacKey = [u8; 32];
48 /// A generic 256-bit MAC tag
49 type MacTag = [u8; 32];
50 /// The AUTH_INPUT_MAC from the HS Ntor protocol
51 type AuthInputMac = MacTag;
52 /// The Service's subcredential
53 pub type Subcredential = [u8; 32];
54 
55 /// The key generator used by the HS ntor handshake.  Implements the simple key
56 /// expansion protocol specified in section "Key expansion" of rend-spec-v3.txt .
57 pub struct HsNtorHkdfKeyGenerator {
58     /// Secret data derived from the handshake, used as input to HKDF
59     seed: SecretBytes,
60 }
61 
62 impl HsNtorHkdfKeyGenerator {
63     /// Create a new key generator to expand a given seed
new(seed: SecretBytes) -> Self64     pub fn new(seed: SecretBytes) -> Self {
65         HsNtorHkdfKeyGenerator { seed }
66     }
67 }
68 
69 impl KeyGenerator for HsNtorHkdfKeyGenerator {
70     /// Expand the seed into a keystream of 'keylen' size
expand(self, keylen: usize) -> Result<SecretBytes>71     fn expand(self, keylen: usize) -> Result<SecretBytes> {
72         ShakeKdf::new().derive(&self.seed[..], keylen)
73     }
74 }
75 
76 /*********************** Client Side Code ************************************/
77 
78 /// The input to enter the HS Ntor protocol as a client
79 /// XXX Make a constructor
80 #[derive(Clone)]
81 pub struct HsNtorClientInput {
82     /// Introduction point encryption key (aka B)
83     /// (found in the HS descriptor)
84     pub B: curve25519::PublicKey,
85 
86     /// Introduction point authentication key (aka AUTH_KEY)
87     /// (found in the HS descriptor)
88     pub auth_key: ed25519::PublicKey,
89 
90     /// Service subcredential
91     pub subcredential: Subcredential,
92 
93     /// The plaintext that should be encrypted into ENCRYPTED_DATA It's
94     /// structure is irrelevant for this crate, but can be found in section
95     /// \[PROCESS_INTRO2\] of the spec
96     pub plaintext: Vec<u8>,
97 
98     /// The data of the INTRODUCE1 cell from the beginning and up to the start
99     /// of the ENCRYPTED_DATA. It's used to compute the MAC at the end of the
100     /// INTRODUCE1 cell.
101     pub intro_cell_data: Vec<u8>,
102 }
103 
104 /// Client state for an ntor handshake.
105 pub struct HsNtorClientState {
106     /// Keys received from our caller when we started the protocol. The rest of
107     /// the keys in this state structure have been created during the protocol.
108     proto_input: HsNtorClientInput,
109 
110     /// The temporary curve25519 secret that we generated for this handshake.
111     x: curve25519::StaticSecret,
112     /// The corresponding private key
113     X: curve25519::PublicKey,
114 }
115 
116 /// Encrypt the 'plaintext' using 'enc_key'. Then compute the intro cell MAC
117 /// using 'mac_key' and return (ciphertext, mac_tag).
encrypt_and_mac( mut plaintext: Vec<u8>, other_data: &[u8], enc_key: EncKey, mac_key: MacKey, ) -> Result<(Vec<u8>, MacTag)>118 fn encrypt_and_mac(
119     mut plaintext: Vec<u8>,
120     other_data: &[u8],
121     enc_key: EncKey,
122     mac_key: MacKey,
123 ) -> Result<(Vec<u8>, MacTag)> {
124     // Encrypt the introduction data using 'enc_key'
125     let zero_iv = GenericArray::default();
126     let mut cipher = Aes256Ctr::new(&enc_key.into(), &zero_iv);
127     cipher.apply_keystream(&mut plaintext);
128     let ciphertext = plaintext; // it's now encrypted
129 
130     // Now staple the other INTRODUCE1 data right before the ciphertext to
131     // create the body of the MAC tag
132     let mut mac_body: Vec<u8> = Vec::new();
133     mac_body.extend(other_data);
134     mac_body.extend(&ciphertext);
135     let mac_tag = hs_ntor_mac(&mac_body, &mac_key)?;
136 
137     Ok((ciphertext, mac_tag))
138 }
139 
140 /// The client is about to make an INTRODUCE1 cell. Perform the first part of
141 /// the client handshake.
142 ///
143 /// Return a state object containing the current progress of the handshake, and
144 /// the data that should be written in the INTRODUCE1 cell. The data that is
145 /// written is:
146 ///
147 ///  CLIENT_PK                [PK_PUBKEY_LEN bytes]
148 ///  ENCRYPTED_DATA           [Padded to length of plaintext]
149 ///  MAC                      [MAC_LEN bytes]
client_send_intro<R>( rng: &mut R, proto_input: &HsNtorClientInput, ) -> Result<(HsNtorClientState, Vec<u8>)> where R: RngCore + CryptoRng,150 pub fn client_send_intro<R>(
151     rng: &mut R,
152     proto_input: &HsNtorClientInput,
153 ) -> Result<(HsNtorClientState, Vec<u8>)>
154 where
155     R: RngCore + CryptoRng,
156 {
157     // Create client's ephemeral keys to be used for this handshake
158     let x = curve25519::StaticSecret::new(rng.rng_compat());
159     let X = curve25519::PublicKey::from(&x);
160 
161     // Get EXP(B,x)
162     let bx = x.diffie_hellman(&proto_input.B);
163 
164     // Compile our state structure
165     let state = HsNtorClientState {
166         proto_input: proto_input.clone(),
167         x,
168         X,
169     };
170 
171     // Compute keys required to finish this part of the handshake
172     let (enc_key, mac_key) = get_introduce1_key_material(
173         &bx,
174         &proto_input.auth_key,
175         &X,
176         &proto_input.B,
177         &proto_input.subcredential,
178     )?;
179 
180     let (ciphertext, mac_tag) = encrypt_and_mac(
181         proto_input.plaintext.clone(),
182         &proto_input.intro_cell_data,
183         enc_key,
184         mac_key,
185     )?;
186 
187     // Create the relevant parts of INTRO1
188     let mut response: Vec<u8> = Vec::new();
189     response.write(&X);
190     response.write(&ciphertext);
191     response.write(&mac_tag);
192 
193     Ok((state, response))
194 }
195 
196 /// The introduction has been completed and the service has replied with a
197 /// RENDEZVOUS1.
198 ///
199 /// Handle it by computing and verifying the MAC, and if it's legit return a
200 /// key generator based on the result of the key exchange.
client_receive_rend<T>(state: &HsNtorClientState, msg: T) -> Result<HsNtorHkdfKeyGenerator> where T: AsRef<[u8]>,201 pub fn client_receive_rend<T>(state: &HsNtorClientState, msg: T) -> Result<HsNtorHkdfKeyGenerator>
202 where
203     T: AsRef<[u8]>,
204 {
205     // Extract the public key of the service from the message
206     let mut cur = Reader::from_slice(msg.as_ref());
207     let Y: curve25519::PublicKey = cur.extract()?;
208     let mac_tag: MacTag = cur.extract()?;
209 
210     // Get EXP(Y,x) and EXP(B,x)
211     let xy = state.x.diffie_hellman(&Y);
212     let xb = state.x.diffie_hellman(&state.proto_input.B);
213 
214     let (keygen, my_mac_tag) = get_rendezvous1_key_material(
215         &xy,
216         &xb,
217         &state.proto_input.auth_key,
218         &state.proto_input.B,
219         &state.X,
220         &Y,
221     )?;
222 
223     // Validate the MAC!
224     if my_mac_tag != mac_tag {
225         return Err(Error::BadHandshake);
226     }
227 
228     Ok(keygen)
229 }
230 
231 /*********************** Server Side Code ************************************/
232 
233 /// The input required to enter the HS Ntor protocol as a service
234 /// XXX Make a constructor
235 pub struct HsNtorServiceInput {
236     /// Introduction point encryption privkey
237     pub b: curve25519::StaticSecret,
238     /// Introduction point encryption pubkey
239     pub B: curve25519::PublicKey,
240 
241     /// Introduction point authentication key (aka AUTH_KEY)
242     pub auth_key: ed25519::PublicKey,
243 
244     /// Our subcredential
245     pub subcredential: Subcredential,
246 
247     /// The data of the INTRODUCE1 cell from the beginning and up to the start
248     /// of the ENCRYPTED_DATA. Will be used to verify the MAC at the end of the
249     /// INTRODUCE1 cell.
250     pub intro_cell_data: Vec<u8>,
251 }
252 
253 /// Conduct the HS Ntor handshake as the service.
254 ///
255 /// Return a key generator which is the result of the key exchange, the
256 /// RENDEZVOUS1 response to the client, and the introduction plaintext that we decrypted.
257 ///
258 /// The response to the client is:
259 ///    SERVER_PK   Y                         [PK_PUBKEY_LEN bytes]
260 ///    AUTH        AUTH_INPUT_MAC            [MAC_LEN bytes]
server_receive_intro<R, T>( rng: &mut R, proto_input: &HsNtorServiceInput, msg: T, ) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)> where R: RngCore + CryptoRng, T: AsRef<[u8]>,261 pub fn server_receive_intro<R, T>(
262     rng: &mut R,
263     proto_input: &HsNtorServiceInput,
264     msg: T,
265 ) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)>
266 where
267     R: RngCore + CryptoRng,
268     T: AsRef<[u8]>,
269 {
270     // Extract all the useful pieces from the message
271     let mut cur = Reader::from_slice(msg.as_ref());
272     let X: curve25519::PublicKey = cur.extract()?;
273     let remaining_bytes = cur.remaining();
274     let ciphertext = &mut cur.take(remaining_bytes - 32)?.to_vec();
275     let mac_tag: MacTag = cur.extract()?;
276 
277     // Now derive keys needed for handling the INTRO1 cell
278     let bx = proto_input.b.diffie_hellman(&X);
279     let (enc_key, mac_key) = get_introduce1_key_material(
280         &bx,
281         &proto_input.auth_key,
282         &X,
283         &proto_input.B,
284         &proto_input.subcredential,
285     )?;
286 
287     // Now validate the MAC: Staple the previous INTRODUCE1 data along with the
288     // ciphertext to create the body of the MAC tag
289     let mut mac_body: Vec<u8> = Vec::new();
290     mac_body.extend(proto_input.intro_cell_data.clone());
291     mac_body.extend(ciphertext.clone());
292     let my_mac_tag = hs_ntor_mac(&mac_body, &mac_key)?;
293 
294     if my_mac_tag != mac_tag {
295         return Err(Error::BadHandshake);
296     }
297 
298     // Decrypt the ENCRYPTED_DATA from the intro cell
299     let zero_iv = GenericArray::default();
300     let mut cipher = Aes256Ctr::new(&enc_key.into(), &zero_iv);
301     cipher.apply_keystream(ciphertext);
302     let plaintext = ciphertext; // it's now decrypted
303 
304     // Generate ephemeral keys for this handshake
305     let y = curve25519::EphemeralSecret::new(rng.rng_compat());
306     let Y = curve25519::PublicKey::from(&y);
307 
308     // Compute EXP(X,y) and EXP(X,b)
309     let xy = y.diffie_hellman(&X);
310     let xb = proto_input.b.diffie_hellman(&X);
311 
312     let (keygen, auth_input_mac) =
313         get_rendezvous1_key_material(&xy, &xb, &proto_input.auth_key, &proto_input.B, &X, &Y)?;
314 
315     // Set up RENDEZVOUS1 reply to the client
316     let mut reply: Vec<u8> = Vec::new();
317     reply.write(&Y);
318     reply.write(&auth_input_mac);
319 
320     Ok((keygen, reply, plaintext.clone()))
321 }
322 
323 /*********************** Helper functions ************************************/
324 
325 /// Implement the MAC function used as part of the HS ntor handshake:
326 /// MAC(k, m) is H(k_len | k | m) where k_len is htonll(len(k)).
hs_ntor_mac(key: &[u8], message: &[u8]) -> Result<MacTag>327 fn hs_ntor_mac(key: &[u8], message: &[u8]) -> Result<MacTag> {
328     let k_len = key.len();
329 
330     let mut d = Sha3_256::new();
331     d.update((k_len as u64).to_be_bytes());
332     d.update(key);
333     d.update(message);
334 
335     let result = d.finalize();
336     result
337         .try_into()
338         .map_err(|_| Error::InternalError("failed MAC computation".into()))
339 }
340 
341 /// Helper function: Compute the part of the HS ntor handshake that generates
342 /// key material for creating and handling INTRODUCE1 cells. Function used
343 /// by both client and service. Specifically, calculate the following:
344 ///
345 /// ```pseudocode
346 ///  intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
347 ///  info = m_hsexpand | subcredential
348 ///  hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
349 ///  ENC_KEY = hs_keys[0:S_KEY_LEN]
350 ///  MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
351 /// ```
352 ///
353 /// Return (ENC_KEY, MAC_KEY).
get_introduce1_key_material( bx: &curve25519::SharedSecret, auth_key: &ed25519::PublicKey, X: &curve25519::PublicKey, B: &curve25519::PublicKey, subcredential: &Subcredential, ) -> Result<(EncKey, MacKey)>354 fn get_introduce1_key_material(
355     bx: &curve25519::SharedSecret,
356     auth_key: &ed25519::PublicKey,
357     X: &curve25519::PublicKey,
358     B: &curve25519::PublicKey,
359     subcredential: &Subcredential,
360 ) -> Result<(EncKey, MacKey)> {
361     let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
362     let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
363     let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
364 
365     // Construct hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
366     // Start by getting 'intro_secret_hs_input'
367     let mut secret_input = Zeroizing::new(Vec::new());
368     secret_input.write(bx); // EXP(B,x)
369     secret_input.write(auth_key); // AUTH_KEY
370     secret_input.write(X); // X
371     secret_input.write(B); // B
372     secret_input.write(hs_ntor_protoid_constant); // PROTOID
373 
374     // Now fold in the t_hsenc
375     secret_input.write(hs_ntor_key_constant);
376 
377     // and fold in the 'info'
378     secret_input.write(hs_ntor_expand_constant);
379     secret_input.write(subcredential);
380 
381     let hs_keys = ShakeKdf::new().derive(&secret_input[..], 32 + 32)?;
382     // Extract the keys into arrays
383     let enc_key = hs_keys[0..32]
384         .try_into()
385         .map_err(|_| Error::InternalError("converting enc_key".into()))?;
386     let mac_key = hs_keys[32..64]
387         .try_into()
388         .map_err(|_| Error::InternalError("converting mac_key".into()))?;
389 
390     Ok((enc_key, mac_key))
391 }
392 
393 /// Helper function: Compute the last part of the HS ntor handshake which
394 /// derives key material necessary to create and handle RENDEZVOUS1
395 /// cells. Function used by both client and service. The actual calculations is
396 /// as follows:
397 ///
398 ///  rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
399 ///  NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
400 ///  verify = MAC(rend_secret_hs_input, t_hsverify)
401 ///  auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
402 ///  AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
403 ///
404 /// Return (keygen, AUTH_INPUT_MAC), where keygen is a key generator based on
405 /// NTOR_KEY_SEED.
get_rendezvous1_key_material( xy: &curve25519::SharedSecret, xb: &curve25519::SharedSecret, auth_key: &ed25519::PublicKey, B: &curve25519::PublicKey, X: &curve25519::PublicKey, Y: &curve25519::PublicKey, ) -> Result<(HsNtorHkdfKeyGenerator, AuthInputMac)>406 fn get_rendezvous1_key_material(
407     xy: &curve25519::SharedSecret,
408     xb: &curve25519::SharedSecret,
409     auth_key: &ed25519::PublicKey,
410     B: &curve25519::PublicKey,
411     X: &curve25519::PublicKey,
412     Y: &curve25519::PublicKey,
413 ) -> Result<(HsNtorHkdfKeyGenerator, AuthInputMac)> {
414     let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
415     let hs_ntor_mac_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_mac"[..];
416     let hs_ntor_verify_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_verify"[..];
417     let server_string_constant = &b"Server"[..];
418     let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
419     let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
420 
421     // Start with rend_secret_hs_input
422     let mut secret_input = Zeroizing::new(Vec::new());
423     secret_input.write(xy); // EXP(X,y)
424     secret_input.write(xb); // EXP(X,b)
425     secret_input.write(auth_key); // AUTH_KEY
426     secret_input.write(B); // B
427     secret_input.write(X); // X
428     secret_input.write(Y); // Y
429     secret_input.write(hs_ntor_protoid_constant); // PROTOID
430 
431     // Build NTOR_KEY_SEED and verify
432     let ntor_key_seed = hs_ntor_mac(&secret_input, hs_ntor_key_constant)?;
433     let verify = hs_ntor_mac(&secret_input, hs_ntor_verify_constant)?;
434 
435     // Start building 'auth_input'
436     let mut auth_input = Zeroizing::new(Vec::new());
437     auth_input.write(&verify);
438     auth_input.write(auth_key); // AUTH_KEY
439     auth_input.write(B); // B
440     auth_input.write(Y); // Y
441     auth_input.write(X); // X
442     auth_input.write(hs_ntor_protoid_constant); // PROTOID
443     auth_input.write(server_string_constant); // "Server"
444 
445     // Get AUTH_INPUT_MAC
446     let auth_input_mac = hs_ntor_mac(&auth_input, hs_ntor_mac_constant)?;
447 
448     // Now finish up with the KDF construction
449     let mut kdf_seed = Zeroizing::new(Vec::new());
450     kdf_seed.write(&ntor_key_seed);
451     kdf_seed.write(hs_ntor_expand_constant);
452     let keygen = HsNtorHkdfKeyGenerator::new(Zeroizing::new(kdf_seed.to_vec()));
453 
454     Ok((keygen, auth_input_mac))
455 }
456 
457 /*********************** Unit Tests ******************************************/
458 
459 #[cfg(test)]
460 mod test {
461     use super::*;
462     use hex_literal::hex;
463 
464     #[test]
465     /// Basic HS Ntor test that does the handshake between client and service
466     /// and makes sure that the resulting keys and KDF is legit.
hs_ntor() -> Result<()>467     fn hs_ntor() -> Result<()> {
468         let mut rng = rand::thread_rng().rng_compat();
469 
470         // Let's initialize keys for the client (and the intro point)
471         let intro_b_privkey = curve25519::StaticSecret::new(&mut rng);
472         let intro_b_pubkey = curve25519::PublicKey::from(&intro_b_privkey);
473         let intro_auth_key_privkey = ed25519::SecretKey::generate(&mut rng);
474         let intro_auth_key_pubkey = ed25519::PublicKey::from(&intro_auth_key_privkey);
475 
476         // Create keys for client and service
477         let client_keys = HsNtorClientInput {
478             B: intro_b_pubkey,
479             auth_key: intro_auth_key_pubkey,
480             subcredential: [5; 32],
481             plaintext: vec![66; 10],
482             intro_cell_data: vec![42; 60],
483         };
484 
485         let service_keys = HsNtorServiceInput {
486             b: intro_b_privkey,
487             B: intro_b_pubkey,
488             auth_key: intro_auth_key_pubkey,
489             subcredential: [5; 32],
490             intro_cell_data: vec![42; 60],
491         };
492 
493         // Client: Sends an encrypted INTRODUCE1 cell
494         let (state, cmsg) = client_send_intro(&mut rng, &client_keys)?;
495 
496         // Service: Decrypt INTRODUCE1 cell, and reply with RENDEZVOUS1 cell
497         let (skeygen, smsg, s_plaintext) = server_receive_intro(&mut rng, &service_keys, cmsg)?;
498 
499         // Check that the plaintext received by the service is the one that the
500         // client sent
501         assert_eq!(s_plaintext, vec![66; 10]);
502 
503         // Client: Receive RENDEZVOUS1 and create key material
504         let ckeygen = client_receive_rend(&state, smsg)?;
505 
506         // Test that RENDEZVOUS1 key material match
507         let skeys = skeygen.expand(128)?;
508         let ckeys = ckeygen.expand(128)?;
509         assert_eq!(skeys, ckeys);
510 
511         Ok(())
512     }
513 
514     #[test]
515     /// Test vectors generated with hs_ntor_ref.py from little-t-tor.
ntor_mac() -> Result<()>516     fn ntor_mac() -> Result<()> {
517         let result = hs_ntor_mac("who".as_bytes(), b"knows?")?;
518         assert_eq!(
519             &result,
520             &hex!("5e7da329630fdaa3eab7498bb1dc625bbb9ca968f10392b6af92d51d5db17473")
521         );
522 
523         let result = hs_ntor_mac("gone".as_bytes(), b"by")?;
524         assert_eq!(
525             &result,
526             &hex!("90071aabb06d3f7c777db41542f4790c7dd9e2e7b2b842f54c9c42bbdb37e9a0")
527         );
528 
529         Ok(())
530     }
531 }
532