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 regular 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 available only when the `hs-common` feature is enabled.  The
16
//! specific handshakes are enabled by `hs-client` and `hs-service`.
17

            
18
// We want to use the exact variable names from the rend-spec-v3.txt proposal.
19
// This means that we allow variables to be named x (privkey) and X (pubkey).
20
// FIXME: this particular allow is rather hazardous since it can stop warnings
21
//        about match statements containing bindings rather than variant names.
22
#![allow(non_snake_case)]
23

            
24
use crate::crypto::handshake::KeyGenerator;
25
use crate::crypto::ll::kdf::{Kdf, ShakeKdf};
26
use crate::{Error, Result};
27
use tor_bytes::{Reader, SecretBuf, Writer};
28
use tor_hscrypto::{
29
    Subcredential,
30
    ops::{HS_MAC_LEN, hs_mac},
31
    pk::{HsIntroPtSessionIdKey, HsSvcNtorKey},
32
};
33
use tor_llcrypto::pk::{curve25519, ed25519};
34
use tor_llcrypto::util::ct::CtByteArray;
35

            
36
use cipher::{KeyIvInit, StreamCipher};
37

            
38
use tor_error::into_internal;
39
use tor_llcrypto::cipher::aes::Aes256Ctr;
40
use zeroize::{Zeroize as _, Zeroizing};
41

            
42
#[cfg(any(test, feature = "hs-service"))]
43
use tor_hscrypto::pk::HsSvcNtorKeypair;
44

            
45
/// The ENC_KEY from the HS Ntor protocol
46
//
47
// TODO (nickm): Any move operations applied to this key could subvert the zeroizing.
48
type EncKey = Zeroizing<[u8; 32]>;
49
/// The MAC_KEY from the HS Ntor protocol
50
type MacKey = [u8; 32];
51
/// A generic 256-bit MAC tag
52
type MacTag = CtByteArray<HS_MAC_LEN>;
53
/// The AUTH_INPUT_MAC from the HS Ntor protocol
54
type AuthInputMac = MacTag;
55

            
56
/// The key generator used by the HS ntor handshake.  Implements the simple key
57
/// expansion protocol specified in section "Key expansion" of rend-spec-v3.txt .
58
pub struct HsNtorHkdfKeyGenerator {
59
    /// Secret data derived from the handshake, used as input to HKDF
60
    seed: SecretBuf,
61
}
62

            
63
impl HsNtorHkdfKeyGenerator {
64
    /// Create a new key generator to expand a given seed
65
5
    pub fn new(seed: SecretBuf) -> Self {
66
5
        HsNtorHkdfKeyGenerator { seed }
67
5
    }
68
}
69

            
70
impl KeyGenerator for HsNtorHkdfKeyGenerator {
71
    /// Expand the seed into a keystream of 'keylen' size
72
5
    fn expand(self, keylen: usize) -> Result<SecretBuf> {
73
5
        ShakeKdf::new().derive(&self.seed[..], keylen)
74
5
    }
75
}
76

            
77
/*********************** Client Side Code ************************************/
78

            
79
/// Information about an onion service that is needed for a client to perform an
80
/// hs_ntor handshake with it.
81
#[derive(Clone)]
82
#[cfg(any(test, feature = "hs-client"))]
83
pub struct HsNtorServiceInfo {
84
    /// Introduction point encryption key (aka `B`, aka `KP_hss_ntor`)
85
    /// (found in the HS descriptor)
86
    B: HsSvcNtorKey,
87

            
88
    /// Introduction point authentication key (aka `AUTH_KEY`, aka `KP_hs_ipt_sid`)
89
    /// (found in the HS descriptor)
90
    ///
91
    /// TODO: This is needed to begin _and end_ the handshake, which makes
92
    /// things a little trickier if someday we want to have several of these
93
    /// handshakes in operation at once, so that we can make
94
    /// multiple introduction attempts simultaneously
95
    /// using the same renedezvous point.
96
    /// That's not something that C Tor supports, though, so neither do we (yet).
97
    auth_key: HsIntroPtSessionIdKey,
98

            
99
    /// Service subcredential
100
    subcredential: Subcredential,
101
}
102

            
103
#[cfg(any(test, feature = "hs-client"))]
104
impl HsNtorServiceInfo {
105
    /// Create a new `HsNtorServiceInfo`
106
4251
    pub fn new(
107
4251
        B: HsSvcNtorKey,
108
4251
        auth_key: HsIntroPtSessionIdKey,
109
4251
        subcredential: Subcredential,
110
4251
    ) -> Self {
111
4251
        HsNtorServiceInfo {
112
4251
            B,
113
4251
            auth_key,
114
4251
            subcredential,
115
4251
        }
116
4251
    }
117
}
118

            
119
/// Client state for an ntor handshake.
120
#[cfg(any(test, feature = "hs-client"))]
121
pub struct HsNtorClientState {
122
    /// Information about the service we are connecting to.
123
    service_info: HsNtorServiceInfo,
124

            
125
    /// The temporary curve25519 secret that we generated for this handshake.
126
    x: curve25519::StaticSecret,
127
    /// The corresponding private key
128
    X: curve25519::PublicKey,
129

            
130
    /// A shared secret constructed from our secret key `x` and the service's
131
    /// public ntor key `B`.  (The service has a separate public ntor key
132
    /// associated with each intro point.)
133
    Bx: curve25519::SharedSecret,
134

            
135
    /// Target length for the generated Introduce1 messages.
136
    intro1_target_len: usize,
137
}
138

            
139
/// Default target length for our generated Introduce1 messages.
140
///
141
/// This value is chosen to be the maximum size of a single-cell message
142
/// after proposal 340 is implemented.  (509 bytes for cell data, minus 16 bytes
143
/// for relay encryption overhead, minus 1 byte for relay command, minus 2 bytes
144
/// for relay message length.)
145
#[cfg(any(test, feature = "hs-client"))]
146
const INTRO1_TARGET_LEN: usize = 490;
147

            
148
#[cfg(any(test, feature = "hs-client"))]
149
impl HsNtorClientState {
150
    /// Construct a new `HsNtorClientState` for connecting to a given onion
151
    /// service described in `service_info`.
152
    ///
153
    /// Once constructed, this `HsNtorClientState` can be used to construct an
154
    /// INTRODUCE1 bodies that can be sent to an introduction point.
155
86
    pub fn new<R>(rng: &mut R, service_info: HsNtorServiceInfo) -> Self
156
86
    where
157
86
        R: rand::Rng + rand::CryptoRng,
158
    {
159
86
        let x = curve25519::StaticSecret::random_from_rng(rng);
160
86
        Self::new_no_keygen(service_info, x)
161
86
    }
162

            
163
    /// Override the default target length for the resulting introduce1 message.
164
    #[cfg(test)]
165
1
    fn set_intro1_target_len(&mut self, len: usize) {
166
1
        self.intro1_target_len = len;
167
1
    }
168

            
169
    /// As `new()`, but do not use an RNG to generate our ephemeral secret key x.
170
4252
    fn new_no_keygen(service_info: HsNtorServiceInfo, x: curve25519::StaticSecret) -> Self {
171
4252
        let X = curve25519::PublicKey::from(&x);
172
4252
        let Bx = x.diffie_hellman(&service_info.B);
173
4252
        Self {
174
4252
            service_info,
175
4252
            x,
176
4252
            X,
177
4252
            Bx,
178
4252
            intro1_target_len: INTRO1_TARGET_LEN,
179
4252
        }
180
4252
    }
181

            
182
    /// Return the data that should be written as the encrypted part of and
183
    /// the data that should be written as the encrypted part of the INTRODUCE1
184
    /// message. The data that is
185
    /// written is:
186
    ///
187
    /// ```text
188
    ///  CLIENT_PK                [PK_PUBKEY_LEN bytes]
189
    ///  ENCRYPTED_DATA           [Padded to length of plaintext]
190
    ///  MAC                      [MAC_LEN bytes]
191
    /// ```
192
4252
    pub fn client_send_intro(&self, intro_header: &[u8], plaintext_body: &[u8]) -> Result<Vec<u8>> {
193
        /// The number of bytes added by this encryption.
194
        const ENC_OVERHEAD: usize = 32 + 32;
195

            
196
4252
        let state = self;
197
4252
        let service = &state.service_info;
198

            
199
        // Compute keys required to finish this part of the handshake
200
4252
        let (enc_key, mac_key) = get_introduce_key_material(
201
4252
            &state.Bx,
202
4252
            &service.auth_key,
203
4252
            &state.X,
204
4252
            &service.B,
205
4252
            &service.subcredential,
206
        )?;
207

            
208
4252
        let padded_body_target_len = self
209
4252
            .intro1_target_len
210
4252
            .saturating_sub(intro_header.len() + ENC_OVERHEAD);
211
4252
        let mut padded_body = plaintext_body.to_vec();
212
4252
        if padded_body.len() < padded_body_target_len {
213
4251
            padded_body.resize(padded_body_target_len, 0);
214
4251
        }
215
4252
        debug_assert!(padded_body.len() >= padded_body_target_len);
216

            
217
4252
        let (ciphertext, mac_tag) =
218
4252
            encrypt_and_mac(&padded_body, intro_header, &state.X, &enc_key, mac_key);
219
4252
        padded_body.zeroize();
220

            
221
        // Create the relevant parts of INTRO1
222
4252
        let mut response: Vec<u8> = Vec::new();
223
4252
        response
224
4252
            .write(&state.X)
225
4252
            .and_then(|_| response.write(&ciphertext))
226
4252
            .and_then(|_| response.write(&mac_tag))
227
4252
            .map_err(into_internal!("Can't encode hs-ntor client handshake."))?;
228

            
229
4252
        Ok(response)
230
4252
    }
231

            
232
    /// The introduction has been completed and the service has replied with a
233
    /// RENDEZVOUS1 message, whose body is in `msg`.
234
    ///
235
    /// Handle it by computing and verifying the MAC, and if it's legit return a
236
    /// key generator based on the result of the key exchange.
237
2
    pub fn client_receive_rend(&self, msg: &[u8]) -> Result<HsNtorHkdfKeyGenerator> {
238
2
        let state = self;
239

            
240
        // Extract the public key of the service from the message
241
2
        let mut cur = Reader::from_slice(msg);
242
2
        let Y: curve25519::PublicKey = cur
243
2
            .extract()
244
2
            .map_err(|e| Error::from_bytes_err(e, "hs_ntor handshake"))?;
245
2
        let mac_tag: MacTag = cur
246
2
            .extract()
247
2
            .map_err(|e| Error::from_bytes_err(e, "hs_ntor handshake"))?;
248

            
249
        // Get EXP(Y,x) and EXP(B,x)
250
2
        let xy = state.x.diffie_hellman(&Y);
251
2
        let xb = state.x.diffie_hellman(&state.service_info.B);
252

            
253
2
        let (keygen, my_mac_tag) = get_rendezvous_key_material(
254
2
            &xy,
255
2
            &xb,
256
2
            &state.service_info.auth_key,
257
2
            &state.service_info.B,
258
2
            &state.X,
259
2
            &Y,
260
        )?;
261

            
262
        // Validate the MAC!
263
2
        if my_mac_tag != mac_tag {
264
            return Err(Error::BadCircHandshakeAuth);
265
2
        }
266

            
267
2
        Ok(keygen)
268
2
    }
269
}
270

            
271
/// Encrypt the 'plaintext' using 'enc_key'. Then compute the intro cell MAC
272
/// using 'mac_key' over the text `(other_text, public_key, plaintext)`
273
/// and return (ciphertext, mac_tag).
274
#[cfg(any(test, feature = "hs-client"))]
275
4252
fn encrypt_and_mac(
276
4252
    plaintext: &[u8],
277
4252
    other_data: &[u8],
278
4252
    public_key: &curve25519::PublicKey,
279
4252
    enc_key: &EncKey,
280
4252
    mac_key: MacKey,
281
4252
) -> (Vec<u8>, MacTag) {
282
4252
    let mut ciphertext = plaintext.to_vec();
283
    // Encrypt the introduction data using 'enc_key'
284
4252
    let zero_iv = Default::default();
285
4252
    let mut cipher = Aes256Ctr::new((&**enc_key).into(), &zero_iv);
286
4252
    cipher.apply_keystream(&mut ciphertext);
287

            
288
    // Now staple the other INTRODUCE1 data right before the ciphertext to
289
    // create the body of the MAC tag
290
4252
    let mut mac_body: Vec<u8> = Vec::new();
291
4252
    mac_body.extend(other_data);
292
4252
    mac_body.extend(public_key.as_bytes());
293
4252
    mac_body.extend(&ciphertext);
294
4252
    let mac_tag = hs_mac(&mac_key, &mac_body);
295

            
296
4252
    (ciphertext, mac_tag)
297
4252
}
298

            
299
/*********************** Server Side Code ************************************/
300

            
301
/// Conduct the HS Ntor handshake as the service.
302
///
303
/// Return a key generator which is the result of the key exchange, the
304
/// RENDEZVOUS1 response to send to the client, and the introduction plaintext that we decrypted.
305
///
306
/// The response to the client is:
307
/// ```text
308
///    SERVER_PK   Y                         [PK_PUBKEY_LEN bytes]
309
///    AUTH        AUTH_INPUT_MAC            [MAC_LEN bytes]
310
/// ```
311
#[cfg(any(test, feature = "hs-service"))]
312
1
pub fn server_receive_intro<R>(
313
1
    rng: &mut R,
314
1
    k_hss_ntor: &HsSvcNtorKeypair,
315
1
    auth_key: &HsIntroPtSessionIdKey,
316
1
    subcredential: &[Subcredential],
317
1
    intro_header: &[u8],
318
1
    msg: &[u8],
319
1
) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)>
320
1
where
321
1
    R: rand::Rng + rand::CryptoRng,
322
{
323
1
    let y = curve25519::StaticSecret::random_from_rng(rng);
324
1
    server_receive_intro_no_keygen(&y, k_hss_ntor, auth_key, subcredential, intro_header, msg)
325
1
}
326

            
327
/// Helper: Like server_receive_intro, but take an ephemeral key rather than a RNG.
328
#[cfg(any(test, feature = "hs-service"))]
329
2
fn server_receive_intro_no_keygen(
330
2
    // This should be an EphemeralSecret, but using a StaticSecret is necessary
331
2
    // so that we can make one from raw bytes in our test.
332
2
    y: &curve25519::StaticSecret,
333
2
    k_hss_ntor: &HsSvcNtorKeypair,
334
2
    auth_key: &HsIntroPtSessionIdKey,
335
2
    subcredential: &[Subcredential],
336
2
    intro_header: &[u8],
337
2
    msg: &[u8],
338
2
) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)> {
339
    // Extract all the useful pieces from the message
340
2
    let mut cur = Reader::from_slice(msg);
341
2
    let X: curve25519::PublicKey = cur
342
2
        .extract()
343
2
        .map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?;
344
2
    let ciphertext = &mut cur
345
2
        .take_all_but(HS_MAC_LEN)
346
2
        .map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?
347
2
        .to_vec();
348
2
    let mac_tag: MacTag = cur
349
2
        .extract()
350
2
        .map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?;
351

            
352
    // Now derive keys needed for handling the INTRO1 cell
353
2
    let bx = k_hss_ntor.secret().as_ref().diffie_hellman(&X);
354

            
355
    // We have to do this for every possible subcredential to find out which
356
    // one, if any, gives a valid encryption key.  We don't break on our first
357
    // success, to avoid a timing sidechannel.
358
    //
359
    // TODO SPEC: Document this behavior and explain why it's necessary and okay.
360
2
    let mut found_dec_key = None;
361

            
362
2
    for subcredential in subcredential {
363
2
        let (dec_key, mac_key) =
364
2
            get_introduce_key_material(&bx, auth_key, &X, k_hss_ntor.public(), subcredential)?; // This can only fail with a Bug, so it's okay to bail early.
365

            
366
        // Now validate the MAC: Staple the previous parts of the INTRODUCE2
367
        // message, along with the ciphertext, to create the body of the MAC tag.
368
2
        let mut mac_body: Vec<u8> = Vec::new();
369
2
        mac_body.extend(intro_header);
370
2
        mac_body.extend(X.as_bytes());
371
2
        mac_body.extend(&ciphertext[..]);
372
2
        let my_mac_tag = hs_mac(&mac_key, &mac_body);
373

            
374
2
        if my_mac_tag == mac_tag {
375
2
            // TODO: We could use CtOption here.
376
2
            found_dec_key = Some(dec_key);
377
2
        }
378
    }
379

            
380
2
    let Some(dec_key) = found_dec_key else {
381
        return Err(Error::BadCircHandshakeAuth);
382
    };
383

            
384
    // Decrypt the ENCRYPTED_DATA from the intro cell
385
2
    let zero_iv = Default::default();
386
2
    let mut cipher = Aes256Ctr::new((&*dec_key).into(), &zero_iv);
387
2
    cipher.apply_keystream(ciphertext);
388
2
    let plaintext = ciphertext; // it's now decrypted
389

            
390
    // Generate ephemeral keys for this handshake
391
2
    let Y = curve25519::PublicKey::from(y);
392

            
393
    // Compute EXP(X,y) and EXP(X,b)
394
2
    let xy = y.diffie_hellman(&X);
395
2
    let xb = k_hss_ntor.secret().as_ref().diffie_hellman(&X);
396

            
397
2
    let (keygen, auth_input_mac) =
398
2
        get_rendezvous_key_material(&xy, &xb, auth_key, k_hss_ntor.public(), &X, &Y)?;
399

            
400
    // Set up RENDEZVOUS1 reply to the client
401
2
    let mut reply: Vec<u8> = Vec::new();
402
2
    reply
403
2
        .write(&Y)
404
2
        .and_then(|_| reply.write(&auth_input_mac))
405
2
        .map_err(into_internal!("Can't encode hs-ntor server handshake."))?;
406

            
407
2
    Ok((keygen, reply, plaintext.clone()))
408
2
}
409

            
410
/*********************** Helper functions ************************************/
411

            
412
/// Helper function: Compute the part of the HS ntor handshake that generates
413
/// key material for creating and handling INTRODUCE1 cells. Function used
414
/// by both client and service. Specifically, calculate the following:
415
///
416
/// ```pseudocode
417
///  intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
418
///  info = m_hsexpand | subcredential
419
///  hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
420
///  ENC_KEY = hs_keys[0:S_KEY_LEN]
421
///  MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
422
/// ```
423
///
424
/// Return (ENC_KEY, MAC_KEY).
425
4254
fn get_introduce_key_material(
426
4254
    bx: &curve25519::SharedSecret,
427
4254
    auth_key: &ed25519::PublicKey,
428
4254
    X: &curve25519::PublicKey,
429
4254
    B: &curve25519::PublicKey,
430
4254
    subcredential: &Subcredential,
431
4254
) -> std::result::Result<(EncKey, MacKey), tor_error::Bug> {
432
4254
    let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
433
4254
    let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
434
4254
    let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
435

            
436
    // Construct hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
437
    // Start by getting 'intro_secret_hs_input'
438
4254
    let mut secret_input = SecretBuf::new();
439
4254
    secret_input
440
4254
        .write(bx) // EXP(B,x)
441
4254
        .and_then(|_| secret_input.write(auth_key)) // AUTH_KEY
442
4254
        .and_then(|_| secret_input.write(X)) // X
443
4254
        .and_then(|_| secret_input.write(B)) // B
444
4254
        .and_then(|_| secret_input.write(hs_ntor_protoid_constant)) // PROTOID
445
        // Now fold in the t_hsenc
446
4254
        .and_then(|_| secret_input.write(hs_ntor_key_constant))
447
        // and fold in the 'info'
448
4254
        .and_then(|_| secret_input.write(hs_ntor_expand_constant))
449
4254
        .and_then(|_| secret_input.write(subcredential))
450
4254
        .map_err(into_internal!("Can't generate hs-ntor kdf input."))?;
451

            
452
4254
    let hs_keys = ShakeKdf::new()
453
4254
        .derive(&secret_input[..], 32 + 32)
454
4254
        .map_err(into_internal!("Can't compute SHAKE"))?;
455
    // Extract the keys into arrays
456
4254
    let enc_key = Zeroizing::new(
457
4254
        hs_keys[0..32]
458
4254
            .try_into()
459
4254
            .map_err(into_internal!("converting enc_key"))?,
460
    );
461
4254
    let mac_key = hs_keys[32..64]
462
4254
        .try_into()
463
4254
        .map_err(into_internal!("converting mac_key"))?;
464

            
465
4254
    Ok((enc_key, mac_key))
466
4254
}
467

            
468
/// Helper function: Compute the last part of the HS ntor handshake which
469
/// derives key material necessary to create and handle RENDEZVOUS1
470
/// cells. Function used by both client and service. The actual calculations is
471
/// as follows:
472
///
473
///  rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
474
///  NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
475
///  verify = MAC(rend_secret_hs_input, t_hsverify)
476
///  auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
477
///  AUTH_INPUT_MAC = MAC(auth_input, t_hsmac)
478
///
479
/// Return (keygen, AUTH_INPUT_MAC), where keygen is a key generator based on
480
/// NTOR_KEY_SEED.
481
4
fn get_rendezvous_key_material(
482
4
    xy: &curve25519::SharedSecret,
483
4
    xb: &curve25519::SharedSecret,
484
4
    auth_key: &ed25519::PublicKey,
485
4
    B: &curve25519::PublicKey,
486
4
    X: &curve25519::PublicKey,
487
4
    Y: &curve25519::PublicKey,
488
4
) -> Result<(HsNtorHkdfKeyGenerator, AuthInputMac)> {
489
4
    let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
490
4
    let hs_ntor_mac_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_mac"[..];
491
4
    let hs_ntor_verify_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_verify"[..];
492
4
    let server_string_constant = &b"Server"[..];
493
4
    let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
494
4
    let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
495

            
496
    // Start with rend_secret_hs_input
497
4
    let mut secret_input = SecretBuf::new();
498
4
    secret_input
499
4
        .write(xy) // EXP(X,y)
500
4
        .and_then(|_| secret_input.write(xb)) // EXP(X,b)
501
4
        .and_then(|_| secret_input.write(auth_key)) // AUTH_KEY
502
4
        .and_then(|_| secret_input.write(B)) // B
503
4
        .and_then(|_| secret_input.write(X)) // X
504
4
        .and_then(|_| secret_input.write(Y)) // Y
505
4
        .and_then(|_| secret_input.write(hs_ntor_protoid_constant)) // PROTOID
506
4
        .map_err(into_internal!(
507
            "Can't encode input to hs-ntor key derivation."
508
        ))?;
509

            
510
    // Build NTOR_KEY_SEED and verify
511
4
    let ntor_key_seed = hs_mac(&secret_input, hs_ntor_key_constant);
512
4
    let verify = hs_mac(&secret_input, hs_ntor_verify_constant);
513

            
514
    // Start building 'auth_input'
515
4
    let mut auth_input = Vec::new();
516
4
    auth_input
517
4
        .write(&verify)
518
4
        .and_then(|_| auth_input.write(auth_key)) // AUTH_KEY
519
4
        .and_then(|_| auth_input.write(B)) // B
520
4
        .and_then(|_| auth_input.write(Y)) // Y
521
4
        .and_then(|_| auth_input.write(X)) // X
522
4
        .and_then(|_| auth_input.write(hs_ntor_protoid_constant)) // PROTOID
523
4
        .and_then(|_| auth_input.write(server_string_constant)) // "Server"
524
4
        .map_err(into_internal!("Can't encode auth-input for hs-ntor."))?;
525

            
526
    // Get AUTH_INPUT_MAC
527
4
    let auth_input_mac = hs_mac(&auth_input, hs_ntor_mac_constant);
528

            
529
    // Now finish up with the KDF construction
530
4
    let mut kdf_seed = SecretBuf::new();
531
4
    kdf_seed
532
4
        .write(&ntor_key_seed)
533
4
        .and_then(|_| kdf_seed.write(hs_ntor_expand_constant))
534
4
        .map_err(into_internal!("Can't encode kdf-input for hs-ntor."))?;
535
4
    let keygen = HsNtorHkdfKeyGenerator::new(kdf_seed);
536

            
537
4
    Ok((keygen, auth_input_mac))
538
4
}
539

            
540
/*********************** Unit Tests ******************************************/
541

            
542
#[cfg(test)]
543
mod test {
544
    // @@ begin test lint list maintained by maint/add_warning @@
545
    #![allow(clippy::bool_assert_comparison)]
546
    #![allow(clippy::clone_on_copy)]
547
    #![allow(clippy::dbg_macro)]
548
    #![allow(clippy::mixed_attributes_style)]
549
    #![allow(clippy::print_stderr)]
550
    #![allow(clippy::print_stdout)]
551
    #![allow(clippy::single_char_pattern)]
552
    #![allow(clippy::unwrap_used)]
553
    #![allow(clippy::unchecked_time_subtraction)]
554
    #![allow(clippy::useless_vec)]
555
    #![allow(clippy::needless_pass_by_value)]
556
    #![allow(clippy::string_slice)] // See arti#2571
557
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
558
    use super::*;
559
    use hex_literal::hex;
560
    use tor_basic_utils::test_rng::testing_rng;
561

            
562
    #[test]
563
    /// Basic HS Ntor test that does the handshake between client and service
564
    /// and makes sure that the resulting keys and KDF is legit.
565
    fn hs_ntor() -> Result<()> {
566
        let mut rng = testing_rng();
567

            
568
        // Let's initialize keys for the client (and the intro point)
569
        let intro_b_privkey = curve25519::StaticSecret::random_from_rng(&mut rng);
570
        let intro_b_pubkey = curve25519::PublicKey::from(&intro_b_privkey);
571
        let intro_auth_key_privkey = ed25519::Keypair::generate(&mut rng);
572
        let intro_auth_key_pubkey = ed25519::PublicKey::from(&intro_auth_key_privkey);
573
        drop(intro_auth_key_privkey); // not actually used in this part of the protocol.
574

            
575
        // Create keys for client and service
576
        let client_keys = HsNtorServiceInfo::new(
577
            intro_b_pubkey.into(),
578
            intro_auth_key_pubkey.into(),
579
            [5; 32].into(),
580
        );
581

            
582
        let k_hss_ntor = HsSvcNtorKeypair::from_secret_key(intro_b_privkey.into());
583
        let auth_key = intro_auth_key_pubkey.into();
584
        let subcredentials = vec![[5; 32].into()];
585

            
586
        // Client: Sends an encrypted INTRODUCE1 cell
587
        let state = HsNtorClientState::new(&mut rng, client_keys);
588
        let cmsg = state.client_send_intro(&[66; 10], &[42; 60])?;
589
        assert_eq!(cmsg.len() + 10, INTRO1_TARGET_LEN);
590

            
591
        // Service: Decrypt INTRODUCE1 cell, and reply with RENDEZVOUS1 cell
592
        let (skeygen, smsg, s_plaintext) = server_receive_intro(
593
            &mut rng,
594
            &k_hss_ntor,
595
            &auth_key,
596
            &subcredentials[..],
597
            &[66; 10],
598
            &cmsg,
599
        )?;
600

            
601
        // Check that the plaintext received by the service is the one that the
602
        // client sent
603
        assert_eq!(s_plaintext[0..60], vec![42; 60]);
604

            
605
        // Client: Receive RENDEZVOUS1 and create key material
606
        let ckeygen = state.client_receive_rend(&smsg)?;
607

            
608
        // Test that RENDEZVOUS1 key material match
609
        let skeys = skeygen.expand(128)?;
610
        let ckeys = ckeygen.expand(128)?;
611
        assert_eq!(skeys, ckeys);
612

            
613
        Ok(())
614
    }
615

            
616
    #[test]
617
    /// Test vectors generated with hs_ntor_ref.py from little-t-tor.
618
    fn ntor_mac() {
619
        let result = hs_mac("who".as_bytes(), b"knows?");
620
        assert_eq!(
621
            &result,
622
            &hex!("5e7da329630fdaa3eab7498bb1dc625bbb9ca968f10392b6af92d51d5db17473").into()
623
        );
624

            
625
        let result = hs_mac("gone".as_bytes(), b"by");
626
        assert_eq!(
627
            &result,
628
            &hex!("90071aabb06d3f7c777db41542f4790c7dd9e2e7b2b842f54c9c42bbdb37e9a0").into()
629
        );
630
    }
631

            
632
    /// A set of test vectors generated with C tor and chutney.
633
    #[test]
634
    fn testvec() {
635
        let kp_hs_ipt_sid =
636
            hex!("34E171E4358E501BFF21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F");
637
        let subcredential =
638
            hex!("0085D26A9DEBA252263BF0231AEAC59B17CA11BAD8A218238AD6487CBAD68B57");
639
        let kp_hss_ntor = hex!("8E5127A40E83AABF6493E41F142B6EE3604B85A3961CD7E38D247239AFF71979");
640
        let ks_hss_ntor = hex!("A0ED5DBF94EEB2EDB3B514E4CF6ABFF6022051CC5F103391F1970A3FCD15296A");
641
        let key_x = hex!("60B4D6BF5234DCF87A4E9D7487BDF3F4A69B6729835E825CA29089CFDDA1E341");
642
        let key_y = hex!("68CB5188CA0CD7924250404FAB54EE1392D3D2B9C049A2E446513875952F8F55");
643

            
644
        // Information about the service.
645
        let kp_hs_ipt_sid: HsIntroPtSessionIdKey = ed25519::PublicKey::from_bytes(&kp_hs_ipt_sid)
646
            .unwrap()
647
            .into();
648
        let subcredential: Subcredential = subcredential.into();
649
        let kp_hss_ntor: HsSvcNtorKey = curve25519::PublicKey::from(kp_hss_ntor).into();
650

            
651
        let service_info = HsNtorServiceInfo {
652
            B: kp_hss_ntor,
653
            auth_key: kp_hs_ipt_sid.clone(),
654
            subcredential: subcredential.clone(),
655
        };
656

            
657
        // The client has to generate an ephemeral keypair.
658
        let key_x: curve25519::StaticSecret = curve25519::StaticSecret::from(key_x);
659

            
660
        // Information about the message to be sent to the service in the
661
        // INTRODUCE1 cell.
662
        let intro_header = hex!(
663
            "000000000000000000000000000000000000000002002034E171E4358E501BFF
664
            21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F00"
665
        );
666
        let intro_body = hex!(
667
            "6BD364C12638DD5C3BE23D76ACA05B04E6CE932C0101000100200DE6130E4FCA
668
             C4EDDA24E21220CC3EADAE403EF6B7D11C8273AC71908DE565450300067F0000
669
             0113890214F823C4F8CC085C792E0AEE0283FE00AD7520B37D0320728D5DF39B
670
             7B7077A0118A900FF4456C382F0041300ACF9C58E51C392795EF870000000000
671
             0000000000000000000000000000000000000000000000000000000000000000
672
             000000000000000000000000000000000000000000000000000000000000"
673
        );
674
        // Now try to do the handshake...
675
        let mut client_state = HsNtorClientState::new_no_keygen(service_info, key_x);
676
        // (Do not pad, since C tor's padding algorithm didn't match ours when
677
        // these test vectors were generated.)
678
        client_state.set_intro1_target_len(0);
679
        let encrypted_body = client_state
680
            .client_send_intro(&intro_header, &intro_body)
681
            .unwrap();
682

            
683
        let mut cell_out = intro_header.to_vec();
684
        cell_out.extend(&encrypted_body);
685
        let expected = &hex!(
686
            "000000000000000000000000000000000000000002002034E171E4358E501BFF
687
             21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F00BF04348B46D09AED
688
             726F1D66C618FDEA1DE58E8CB8B89738D7356A0C59111D5DADBECCCB38E37830
689
             4DCC179D3D9E437B452AF5702CED2CCFEC085BC02C4C175FA446525C1B9D5530
690
             563C362FDFFB802DAB8CD9EBC7A5EE17DA62E37DEEB0EB187FBB48C63298B0E8
691
             3F391B7566F42ADC97C46BA7588278273A44CE96BC68FFDAE31EF5F0913B9A9C
692
             7E0F173DBC0BDDCD4ACB4C4600980A7DDD9EAEC6E7F3FA3FC37CD95E5B8BFB3E
693
             35717012B78B4930569F895CB349A07538E42309C993223AEA77EF8AEA64F25D
694
             DEE97DA623F1AEC0A47F150002150455845C385E5606E41A9A199E7111D54EF2
695
             D1A51B7554D8B3692D85AC587FB9E69DF990EFB776D8"
696
        );
697
        assert_eq!(&cell_out, &expected);
698

            
699
        // ===
700
        // Okay, we have the message to send to the onion service.
701
        // ===
702

            
703
        // This corresponds to the public key above...
704
        let ks_hss_ntor = curve25519::StaticSecret::from(ks_hss_ntor).into();
705
        let k_hss_ntor = HsSvcNtorKeypair::from_secret_key(ks_hss_ntor);
706
        let key_y = curve25519::StaticSecret::from(key_y);
707
        let subcredentials = vec![subcredential];
708

            
709
        let (service_keygen, service_reply, service_plaintext) = server_receive_intro_no_keygen(
710
            &key_y,
711
            &k_hss_ntor,
712
            &kp_hs_ipt_sid,
713
            &subcredentials[..],
714
            &intro_header,
715
            &encrypted_body,
716
        )
717
        .unwrap();
718

            
719
        // Did we recover the plaintext correctly?
720
        assert_eq!(&service_plaintext, &intro_body);
721

            
722
        let expected_reply = hex!(
723
            "8fbe0db4d4a9c7ff46701e3e0ee7fd05cd28be4f302460addeec9e93354ee700
724
             4A92E8437B8424D5E5EC279245D5C72B25A0327ACF6DAF902079FCB643D8B208"
725
        );
726
        assert_eq!(&service_reply, &expected_reply);
727

            
728
        // Let's see if the client handles this reply!
729
        let client_keygen = client_state.client_receive_rend(&service_reply).unwrap();
730
        let bytes_client = client_keygen.expand(128).unwrap();
731
        let bytes_service = service_keygen.expand(128).unwrap();
732
        let mut key_seed =
733
            hex!("4D0C72FE8AFF35559D95ECC18EB5A36883402B28CDFD48C8A530A5A3D7D578DB").to_vec();
734
        key_seed.extend(b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand");
735
        let bytes_expected = HsNtorHkdfKeyGenerator::new(key_seed.into())
736
            .expand(128)
737
            .unwrap();
738
        assert_eq!(&bytes_client, &bytes_service);
739
        assert_eq!(&bytes_client, &bytes_expected);
740
    }
741
}