#![allow(non_snake_case)]
use crate::crypto::handshake::KeyGenerator;
use crate::crypto::ll::kdf::{Kdf, ShakeKdf};
use crate::{Error, Result};
use tor_bytes::{Reader, SecretBuf, Writer};
use tor_hscrypto::{
Subcredential,
ops::{HS_MAC_LEN, hs_mac},
pk::{HsIntroPtSessionIdKey, HsSvcNtorKey},
};
use tor_llcrypto::pk::{curve25519, ed25519};
use tor_llcrypto::util::ct::CtByteArray;
use cipher::{KeyIvInit, StreamCipher};
use tor_error::into_internal;
use tor_llcrypto::cipher::aes::Aes256Ctr;
use zeroize::{Zeroize as _, Zeroizing};
#[cfg(any(test, feature = "hs-service"))]
use tor_hscrypto::pk::HsSvcNtorKeypair;
type EncKey = Zeroizing<[u8; 32]>;
type MacKey = [u8; 32];
type MacTag = CtByteArray<HS_MAC_LEN>;
type AuthInputMac = MacTag;
pub struct HsNtorHkdfKeyGenerator {
seed: SecretBuf,
}
impl HsNtorHkdfKeyGenerator {
pub fn new(seed: SecretBuf) -> Self {
HsNtorHkdfKeyGenerator { seed }
}
}
impl KeyGenerator for HsNtorHkdfKeyGenerator {
fn expand(self, keylen: usize) -> Result<SecretBuf> {
ShakeKdf::new().derive(&self.seed[..], keylen)
}
}
#[derive(Clone)]
#[cfg(any(test, feature = "hs-client"))]
pub struct HsNtorServiceInfo {
B: HsSvcNtorKey,
auth_key: HsIntroPtSessionIdKey,
subcredential: Subcredential,
}
#[cfg(any(test, feature = "hs-client"))]
impl HsNtorServiceInfo {
pub fn new(
B: HsSvcNtorKey,
auth_key: HsIntroPtSessionIdKey,
subcredential: Subcredential,
) -> Self {
HsNtorServiceInfo {
B,
auth_key,
subcredential,
}
}
}
#[cfg(any(test, feature = "hs-client"))]
pub struct HsNtorClientState {
service_info: HsNtorServiceInfo,
x: curve25519::StaticSecret,
X: curve25519::PublicKey,
Bx: curve25519::SharedSecret,
intro1_target_len: usize,
}
#[cfg(any(test, feature = "hs-client"))]
const INTRO1_TARGET_LEN: usize = 490;
#[cfg(any(test, feature = "hs-client"))]
impl HsNtorClientState {
pub fn new<R>(rng: &mut R, service_info: HsNtorServiceInfo) -> Self
where
R: rand::RngCore + rand::CryptoRng,
{
let x = curve25519::StaticSecret::random_from_rng(rng);
Self::new_no_keygen(service_info, x)
}
#[cfg(test)]
fn set_intro1_target_len(&mut self, len: usize) {
self.intro1_target_len = len;
}
fn new_no_keygen(service_info: HsNtorServiceInfo, x: curve25519::StaticSecret) -> Self {
let X = curve25519::PublicKey::from(&x);
let Bx = x.diffie_hellman(&service_info.B);
Self {
service_info,
x,
X,
Bx,
intro1_target_len: INTRO1_TARGET_LEN,
}
}
pub fn client_send_intro(&self, intro_header: &[u8], plaintext_body: &[u8]) -> Result<Vec<u8>> {
const ENC_OVERHEAD: usize = 32 + 32;
let state = self;
let service = &state.service_info;
let (enc_key, mac_key) = get_introduce_key_material(
&state.Bx,
&service.auth_key,
&state.X,
&service.B,
&service.subcredential,
)?;
let padded_body_target_len = self
.intro1_target_len
.saturating_sub(intro_header.len() + ENC_OVERHEAD);
let mut padded_body = plaintext_body.to_vec();
if padded_body.len() < padded_body_target_len {
padded_body.resize(padded_body_target_len, 0);
}
debug_assert!(padded_body.len() >= padded_body_target_len);
let (ciphertext, mac_tag) =
encrypt_and_mac(&padded_body, intro_header, &state.X, &enc_key, mac_key);
padded_body.zeroize();
let mut response: Vec<u8> = Vec::new();
response
.write(&state.X)
.and_then(|_| response.write(&ciphertext))
.and_then(|_| response.write(&mac_tag))
.map_err(into_internal!("Can't encode hs-ntor client handshake."))?;
Ok(response)
}
pub fn client_receive_rend(&self, msg: &[u8]) -> Result<HsNtorHkdfKeyGenerator> {
let state = self;
let mut cur = Reader::from_slice(msg);
let Y: curve25519::PublicKey = cur
.extract()
.map_err(|e| Error::from_bytes_err(e, "hs_ntor handshake"))?;
let mac_tag: MacTag = cur
.extract()
.map_err(|e| Error::from_bytes_err(e, "hs_ntor handshake"))?;
let xy = state.x.diffie_hellman(&Y);
let xb = state.x.diffie_hellman(&state.service_info.B);
let (keygen, my_mac_tag) = get_rendezvous_key_material(
&xy,
&xb,
&state.service_info.auth_key,
&state.service_info.B,
&state.X,
&Y,
)?;
if my_mac_tag != mac_tag {
return Err(Error::BadCircHandshakeAuth);
}
Ok(keygen)
}
}
#[cfg(any(test, feature = "hs-client"))]
fn encrypt_and_mac(
plaintext: &[u8],
other_data: &[u8],
public_key: &curve25519::PublicKey,
enc_key: &EncKey,
mac_key: MacKey,
) -> (Vec<u8>, MacTag) {
let mut ciphertext = plaintext.to_vec();
let zero_iv = Default::default();
let mut cipher = Aes256Ctr::new(enc_key.as_ref().into(), &zero_iv);
cipher.apply_keystream(&mut ciphertext);
let mut mac_body: Vec<u8> = Vec::new();
mac_body.extend(other_data);
mac_body.extend(public_key.as_bytes());
mac_body.extend(&ciphertext);
let mac_tag = hs_mac(&mac_key, &mac_body);
(ciphertext, mac_tag)
}
#[cfg(any(test, feature = "hs-service"))]
pub fn server_receive_intro<R>(
rng: &mut R,
k_hss_ntor: &HsSvcNtorKeypair,
auth_key: &HsIntroPtSessionIdKey,
subcredential: &[Subcredential],
intro_header: &[u8],
msg: &[u8],
) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)>
where
R: rand::RngCore + rand::CryptoRng,
{
let y = curve25519::StaticSecret::random_from_rng(rng);
server_receive_intro_no_keygen(&y, k_hss_ntor, auth_key, subcredential, intro_header, msg)
}
#[cfg(any(test, feature = "hs-service"))]
fn server_receive_intro_no_keygen(
y: &curve25519::StaticSecret,
k_hss_ntor: &HsSvcNtorKeypair,
auth_key: &HsIntroPtSessionIdKey,
subcredential: &[Subcredential],
intro_header: &[u8],
msg: &[u8],
) -> Result<(HsNtorHkdfKeyGenerator, Vec<u8>, Vec<u8>)> {
let mut cur = Reader::from_slice(msg);
let X: curve25519::PublicKey = cur
.extract()
.map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?;
let remaining_bytes = cur.remaining();
let ciphertext = &mut cur
.take(remaining_bytes - HS_MAC_LEN)
.map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?
.to_vec();
let mac_tag: MacTag = cur
.extract()
.map_err(|e| Error::from_bytes_err(e, "hs ntor handshake"))?;
let bx = k_hss_ntor.secret().as_ref().diffie_hellman(&X);
let mut found_dec_key = None;
for subcredential in subcredential {
let (dec_key, mac_key) =
get_introduce_key_material(&bx, auth_key, &X, k_hss_ntor.public(), subcredential)?;
let mut mac_body: Vec<u8> = Vec::new();
mac_body.extend(intro_header);
mac_body.extend(X.as_bytes());
mac_body.extend(&ciphertext[..]);
let my_mac_tag = hs_mac(&mac_key, &mac_body);
if my_mac_tag == mac_tag {
found_dec_key = Some(dec_key);
}
}
let Some(dec_key) = found_dec_key else {
return Err(Error::BadCircHandshakeAuth);
};
let zero_iv = Default::default();
let mut cipher = Aes256Ctr::new(dec_key.as_ref().into(), &zero_iv);
cipher.apply_keystream(ciphertext);
let plaintext = ciphertext;
let Y = curve25519::PublicKey::from(y);
let xy = y.diffie_hellman(&X);
let xb = k_hss_ntor.secret().as_ref().diffie_hellman(&X);
let (keygen, auth_input_mac) =
get_rendezvous_key_material(&xy, &xb, auth_key, k_hss_ntor.public(), &X, &Y)?;
let mut reply: Vec<u8> = Vec::new();
reply
.write(&Y)
.and_then(|_| reply.write(&auth_input_mac))
.map_err(into_internal!("Can't encode hs-ntor server handshake."))?;
Ok((keygen, reply, plaintext.clone()))
}
fn get_introduce_key_material(
bx: &curve25519::SharedSecret,
auth_key: &ed25519::PublicKey,
X: &curve25519::PublicKey,
B: &curve25519::PublicKey,
subcredential: &Subcredential,
) -> std::result::Result<(EncKey, MacKey), tor_error::Bug> {
let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
let mut secret_input = SecretBuf::new();
secret_input
.write(bx) .and_then(|_| secret_input.write(auth_key)) .and_then(|_| secret_input.write(X)) .and_then(|_| secret_input.write(B)) .and_then(|_| secret_input.write(hs_ntor_protoid_constant)) .and_then(|_| secret_input.write(hs_ntor_key_constant))
.and_then(|_| secret_input.write(hs_ntor_expand_constant))
.and_then(|_| secret_input.write(subcredential))
.map_err(into_internal!("Can't generate hs-ntor kdf input."))?;
let hs_keys = ShakeKdf::new()
.derive(&secret_input[..], 32 + 32)
.map_err(into_internal!("Can't compute SHAKE"))?;
let enc_key = Zeroizing::new(
hs_keys[0..32]
.try_into()
.map_err(into_internal!("converting enc_key"))?,
);
let mac_key = hs_keys[32..64]
.try_into()
.map_err(into_internal!("converting mac_key"))?;
Ok((enc_key, mac_key))
}
fn get_rendezvous_key_material(
xy: &curve25519::SharedSecret,
xb: &curve25519::SharedSecret,
auth_key: &ed25519::PublicKey,
B: &curve25519::PublicKey,
X: &curve25519::PublicKey,
Y: &curve25519::PublicKey,
) -> Result<(HsNtorHkdfKeyGenerator, AuthInputMac)> {
let hs_ntor_protoid_constant = &b"tor-hs-ntor-curve25519-sha3-256-1"[..];
let hs_ntor_mac_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_mac"[..];
let hs_ntor_verify_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_verify"[..];
let server_string_constant = &b"Server"[..];
let hs_ntor_expand_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand"[..];
let hs_ntor_key_constant = &b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_extract"[..];
let mut secret_input = SecretBuf::new();
secret_input
.write(xy) .and_then(|_| secret_input.write(xb)) .and_then(|_| secret_input.write(auth_key)) .and_then(|_| secret_input.write(B)) .and_then(|_| secret_input.write(X)) .and_then(|_| secret_input.write(Y)) .and_then(|_| secret_input.write(hs_ntor_protoid_constant)) .map_err(into_internal!(
"Can't encode input to hs-ntor key derivation."
))?;
let ntor_key_seed = hs_mac(&secret_input, hs_ntor_key_constant);
let verify = hs_mac(&secret_input, hs_ntor_verify_constant);
let mut auth_input = Vec::new();
auth_input
.write(&verify)
.and_then(|_| auth_input.write(auth_key)) .and_then(|_| auth_input.write(B)) .and_then(|_| auth_input.write(Y)) .and_then(|_| auth_input.write(X)) .and_then(|_| auth_input.write(hs_ntor_protoid_constant)) .and_then(|_| auth_input.write(server_string_constant)) .map_err(into_internal!("Can't encode auth-input for hs-ntor."))?;
let auth_input_mac = hs_mac(&auth_input, hs_ntor_mac_constant);
let mut kdf_seed = SecretBuf::new();
kdf_seed
.write(&ntor_key_seed)
.and_then(|_| kdf_seed.write(hs_ntor_expand_constant))
.map_err(into_internal!("Can't encode kdf-input for hs-ntor."))?;
let keygen = HsNtorHkdfKeyGenerator::new(kdf_seed);
Ok((keygen, auth_input_mac))
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use hex_literal::hex;
use tor_basic_utils::test_rng::testing_rng;
#[test]
fn hs_ntor() -> Result<()> {
let mut rng = testing_rng();
let intro_b_privkey = curve25519::StaticSecret::random_from_rng(&mut rng);
let intro_b_pubkey = curve25519::PublicKey::from(&intro_b_privkey);
let intro_auth_key_privkey = ed25519::Keypair::generate(&mut rng);
let intro_auth_key_pubkey = ed25519::PublicKey::from(&intro_auth_key_privkey);
drop(intro_auth_key_privkey);
let client_keys = HsNtorServiceInfo::new(
intro_b_pubkey.into(),
intro_auth_key_pubkey.into(),
[5; 32].into(),
);
let k_hss_ntor = HsSvcNtorKeypair::from_secret_key(intro_b_privkey.into());
let auth_key = intro_auth_key_pubkey.into();
let subcredentials = vec![[5; 32].into()];
let state = HsNtorClientState::new(&mut rng, client_keys);
let cmsg = state.client_send_intro(&[66; 10], &[42; 60])?;
assert_eq!(cmsg.len() + 10, INTRO1_TARGET_LEN);
let (skeygen, smsg, s_plaintext) = server_receive_intro(
&mut rng,
&k_hss_ntor,
&auth_key,
&subcredentials[..],
&[66; 10],
&cmsg,
)?;
assert_eq!(s_plaintext[0..60], vec![42; 60]);
let ckeygen = state.client_receive_rend(&smsg)?;
let skeys = skeygen.expand(128)?;
let ckeys = ckeygen.expand(128)?;
assert_eq!(skeys, ckeys);
Ok(())
}
#[test]
fn ntor_mac() {
let result = hs_mac("who".as_bytes(), b"knows?");
assert_eq!(
&result,
&hex!("5e7da329630fdaa3eab7498bb1dc625bbb9ca968f10392b6af92d51d5db17473").into()
);
let result = hs_mac("gone".as_bytes(), b"by");
assert_eq!(
&result,
&hex!("90071aabb06d3f7c777db41542f4790c7dd9e2e7b2b842f54c9c42bbdb37e9a0").into()
);
}
#[test]
fn testvec() {
let kp_hs_ipt_sid =
hex!("34E171E4358E501BFF21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F");
let subcredential =
hex!("0085D26A9DEBA252263BF0231AEAC59B17CA11BAD8A218238AD6487CBAD68B57");
let kp_hss_ntor = hex!("8E5127A40E83AABF6493E41F142B6EE3604B85A3961CD7E38D247239AFF71979");
let ks_hss_ntor = hex!("A0ED5DBF94EEB2EDB3B514E4CF6ABFF6022051CC5F103391F1970A3FCD15296A");
let key_x = hex!("60B4D6BF5234DCF87A4E9D7487BDF3F4A69B6729835E825CA29089CFDDA1E341");
let key_y = hex!("68CB5188CA0CD7924250404FAB54EE1392D3D2B9C049A2E446513875952F8F55");
let kp_hs_ipt_sid: HsIntroPtSessionIdKey = ed25519::PublicKey::from_bytes(&kp_hs_ipt_sid)
.unwrap()
.into();
let subcredential: Subcredential = subcredential.into();
let kp_hss_ntor: HsSvcNtorKey = curve25519::PublicKey::from(kp_hss_ntor).into();
let service_info = HsNtorServiceInfo {
B: kp_hss_ntor,
auth_key: kp_hs_ipt_sid.clone(),
subcredential: subcredential.clone(),
};
let key_x: curve25519::StaticSecret = curve25519::StaticSecret::from(key_x);
let intro_header = hex!(
"000000000000000000000000000000000000000002002034E171E4358E501BFF
21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F00"
);
let intro_body = hex!(
"6BD364C12638DD5C3BE23D76ACA05B04E6CE932C0101000100200DE6130E4FCA
C4EDDA24E21220CC3EADAE403EF6B7D11C8273AC71908DE565450300067F0000
0113890214F823C4F8CC085C792E0AEE0283FE00AD7520B37D0320728D5DF39B
7B7077A0118A900FF4456C382F0041300ACF9C58E51C392795EF870000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000"
);
let mut client_state = HsNtorClientState::new_no_keygen(service_info, key_x);
client_state.set_intro1_target_len(0);
let encrypted_body = client_state
.client_send_intro(&intro_header, &intro_body)
.unwrap();
let mut cell_out = intro_header.to_vec();
cell_out.extend(&encrypted_body);
let expected = &hex!(
"000000000000000000000000000000000000000002002034E171E4358E501BFF
21ED907E96AC6BFEF697C779D040BBAF49ACC30FC5D21F00BF04348B46D09AED
726F1D66C618FDEA1DE58E8CB8B89738D7356A0C59111D5DADBECCCB38E37830
4DCC179D3D9E437B452AF5702CED2CCFEC085BC02C4C175FA446525C1B9D5530
563C362FDFFB802DAB8CD9EBC7A5EE17DA62E37DEEB0EB187FBB48C63298B0E8
3F391B7566F42ADC97C46BA7588278273A44CE96BC68FFDAE31EF5F0913B9A9C
7E0F173DBC0BDDCD4ACB4C4600980A7DDD9EAEC6E7F3FA3FC37CD95E5B8BFB3E
35717012B78B4930569F895CB349A07538E42309C993223AEA77EF8AEA64F25D
DEE97DA623F1AEC0A47F150002150455845C385E5606E41A9A199E7111D54EF2
D1A51B7554D8B3692D85AC587FB9E69DF990EFB776D8"
);
assert_eq!(&cell_out, &expected);
let ks_hss_ntor = curve25519::StaticSecret::from(ks_hss_ntor).into();
let k_hss_ntor = HsSvcNtorKeypair::from_secret_key(ks_hss_ntor);
let key_y = curve25519::StaticSecret::from(key_y);
let subcredentials = vec![subcredential];
let (service_keygen, service_reply, service_plaintext) = server_receive_intro_no_keygen(
&key_y,
&k_hss_ntor,
&kp_hs_ipt_sid,
&subcredentials[..],
&intro_header,
&encrypted_body,
)
.unwrap();
assert_eq!(&service_plaintext, &intro_body);
let expected_reply = hex!(
"8fbe0db4d4a9c7ff46701e3e0ee7fd05cd28be4f302460addeec9e93354ee700
4A92E8437B8424D5E5EC279245D5C72B25A0327ACF6DAF902079FCB643D8B208"
);
assert_eq!(&service_reply, &expected_reply);
let client_keygen = client_state.client_receive_rend(&service_reply).unwrap();
let bytes_client = client_keygen.expand(128).unwrap();
let bytes_service = service_keygen.expand(128).unwrap();
let mut key_seed =
hex!("4D0C72FE8AFF35559D95ECC18EB5A36883402B28CDFD48C8A530A5A3D7D578DB").to_vec();
key_seed.extend(b"tor-hs-ntor-curve25519-sha3-256-1:hs_key_expand");
let bytes_expected = HsNtorHkdfKeyGenerator::new(key_seed.into())
.expand(128)
.unwrap();
assert_eq!(&bytes_client, &bytes_service);
assert_eq!(&bytes_client, &bytes_expected);
}
}