tor_netdoc/doc/hsdesc/build/
middle.rs1use crate::build::NetdocEncoder;
8use crate::doc::hsdesc::build::ClientAuth;
9use crate::doc::hsdesc::desc_enc::{
10 build_descriptor_cookie_key, HS_DESC_CLIENT_ID_LEN, HS_DESC_ENC_NONCE_LEN, HS_DESC_IV_LEN,
11};
12use crate::doc::hsdesc::middle::{AuthClient, HsMiddleKwd, HS_DESC_AUTH_TYPE};
13use crate::NetdocBuilder;
14
15use tor_bytes::EncodeError;
16use tor_hscrypto::Subcredential;
17use tor_llcrypto::pk::curve25519::{EphemeralSecret, PublicKey};
18use tor_llcrypto::util::ct::CtByteArray;
19
20use base64ct::{Base64, Encoding};
21use rand::{CryptoRng, Rng, RngCore};
22
23#[derive(Debug)]
27pub(super) struct HsDescMiddle<'a> {
28 pub(super) client_auth: Option<&'a ClientAuth<'a>>,
31 pub(super) subcredential: Subcredential,
33 pub(super) encrypted: Vec<u8>,
40}
41
42impl<'a> NetdocBuilder for HsDescMiddle<'a> {
43 fn build_sign<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<String, EncodeError> {
44 use cipher::{KeyIvInit, StreamCipher};
45 use tor_llcrypto::cipher::aes::Aes256Ctr as Cipher;
46 use HsMiddleKwd::*;
47
48 let HsDescMiddle {
49 client_auth,
50 subcredential,
51 encrypted,
52 } = self;
53
54 let mut encoder = NetdocEncoder::new();
55
56 let (ephemeral_key, auth_clients): (_, Box<dyn std::iter::Iterator<Item = AuthClient>>) =
57 match client_auth {
58 Some(client_auth) if client_auth.auth_clients.is_empty() => {
59 return Err(tor_error::bad_api_usage!(
60 "restricted discovery is enabled, but there are no authorized clients"
61 )
62 .into());
63 }
64 Some(client_auth) => {
65 let auth_clients = client_auth.auth_clients.iter().map(|client| {
67 let (client_id, cookie_key) = build_descriptor_cookie_key(
68 client_auth.ephemeral_key.secret.as_ref(),
69 client,
70 &subcredential,
71 );
72
73 let mut encrypted_cookie = client_auth.descriptor_cookie;
75 let iv = rng.random::<[u8; HS_DESC_IV_LEN]>();
76 let mut cipher = Cipher::new(&cookie_key.into(), &iv.into());
77 cipher.apply_keystream(&mut encrypted_cookie);
78
79 AuthClient {
80 client_id,
81 iv,
82 encrypted_cookie,
83 }
84 });
85
86 (*client_auth.ephemeral_key.public, Box::new(auth_clients))
87 }
88 None => {
89 let dummy_auth_client = AuthClient {
92 client_id: CtByteArray::from(rng.random::<[u8; HS_DESC_CLIENT_ID_LEN]>()),
93 iv: rng.random::<[u8; HS_DESC_IV_LEN]>(),
94 encrypted_cookie: rng.random::<[u8; HS_DESC_ENC_NONCE_LEN]>(),
95 };
96
97 let secret = EphemeralSecret::random_from_rng(rng);
101 let dummy_ephemeral_key = PublicKey::from(&secret);
102
103 (
104 dummy_ephemeral_key,
105 Box::new(std::iter::once(dummy_auth_client)),
106 )
107 }
108 };
109
110 encoder.item(DESC_AUTH_TYPE).arg(&HS_DESC_AUTH_TYPE);
111 encoder
112 .item(DESC_AUTH_EPHEMERAL_KEY)
113 .arg(&Base64::encode_string(ephemeral_key.as_bytes()));
114
115 for auth_client in auth_clients {
116 encoder
117 .item(AUTH_CLIENT)
118 .arg(&Base64::encode_string(&*auth_client.client_id))
119 .arg(&Base64::encode_string(&auth_client.iv))
120 .arg(&Base64::encode_string(&auth_client.encrypted_cookie));
121 }
122
123 encoder.item(ENCRYPTED).object("MESSAGE", encrypted);
124 encoder.finish().map_err(|e| e.into())
125 }
126}
127
128#[cfg(test)]
129mod test {
130 #![allow(clippy::bool_assert_comparison)]
132 #![allow(clippy::clone_on_copy)]
133 #![allow(clippy::dbg_macro)]
134 #![allow(clippy::mixed_attributes_style)]
135 #![allow(clippy::print_stderr)]
136 #![allow(clippy::print_stdout)]
137 #![allow(clippy::single_char_pattern)]
138 #![allow(clippy::unwrap_used)]
139 #![allow(clippy::unchecked_duration_subtraction)]
140 #![allow(clippy::useless_vec)]
141 #![allow(clippy::needless_pass_by_value)]
142 use super::*;
145 use crate::doc::hsdesc::build::test::{create_curve25519_pk, expect_bug};
146 use crate::doc::hsdesc::build::ClientAuth;
147 use crate::doc::hsdesc::test_data::TEST_SUBCREDENTIAL;
148 use tor_basic_utils::test_rng::Config;
149 use tor_hscrypto::pk::HsSvcDescEncKeypair;
150 use tor_llcrypto::pk::curve25519;
151
152 const TEST_ENCRYPTED_VALUE: &[u8] = &[1, 2, 3, 4];
154
155 #[test]
156 fn middle_hsdesc_encoding_no_client_auth() {
157 let hs_desc = HsDescMiddle {
158 client_auth: None,
159 subcredential: TEST_SUBCREDENTIAL.into(),
160 encrypted: TEST_ENCRYPTED_VALUE.into(),
161 }
162 .build_sign(&mut Config::Deterministic.into_rng())
163 .unwrap();
164
165 assert_eq!(
166 hs_desc,
167 r#"desc-auth-type x25519
168desc-auth-ephemeral-key XI/a9NGh/7ClaFcKqtdI9DoP8da5ovwPDdgCHUr3xX0=
169auth-client F+Z6EDfG7oc= 7EIXRtlSozVtGAs6+mNujQ== pNtSIyiCahSvUVg+7s71Ow==
170encrypted
171-----BEGIN MESSAGE-----
172AQIDBA==
173-----END MESSAGE-----
174"#
175 );
176 }
177
178 #[test]
179 fn middle_hsdesc_encoding_with_bad_client_auth() {
180 let mut rng = Config::Deterministic.into_rng();
181 let secret = curve25519::StaticSecret::random_from_rng(&mut rng);
182 let public = curve25519::PublicKey::from(&secret).into();
183
184 let client_auth = ClientAuth {
185 ephemeral_key: HsSvcDescEncKeypair {
186 public,
187 secret: secret.into(),
188 },
189 auth_clients: &[],
190 descriptor_cookie: rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(&mut rng),
191 };
192
193 let err = HsDescMiddle {
194 client_auth: Some(&client_auth),
195 subcredential: TEST_SUBCREDENTIAL.into(),
196 encrypted: TEST_ENCRYPTED_VALUE.into(),
197 }
198 .build_sign(&mut rng)
199 .unwrap_err();
200
201 assert!(expect_bug(err)
202 .contains("restricted discovery is enabled, but there are no authorized clients"));
203 }
204
205 #[test]
206 fn middle_hsdesc_encoding_client_auth() {
207 let mut rng = Config::Deterministic.into_rng();
208 let auth_clients = vec![
210 create_curve25519_pk(&mut rng),
211 create_curve25519_pk(&mut rng),
212 ];
213
214 let secret = curve25519::StaticSecret::random_from_rng(&mut rng);
215 let public = curve25519::PublicKey::from(&secret).into();
216
217 let client_auth = ClientAuth {
218 ephemeral_key: HsSvcDescEncKeypair {
219 public,
220 secret: secret.into(),
221 },
222 auth_clients: &auth_clients,
223 descriptor_cookie: rand::Rng::random::<[u8; HS_DESC_ENC_NONCE_LEN]>(&mut rng),
224 };
225
226 let hs_desc = HsDescMiddle {
227 client_auth: Some(&client_auth),
228 subcredential: TEST_SUBCREDENTIAL.into(),
229 encrypted: TEST_ENCRYPTED_VALUE.into(),
230 }
231 .build_sign(&mut Config::Deterministic.into_rng())
232 .unwrap();
233
234 assert_eq!(
235 hs_desc,
236 r#"desc-auth-type x25519
237desc-auth-ephemeral-key 9Upi9XNWyqx3ZwHeQ5r3+Dh116k+C4yHeE9BcM68HDc=
238auth-client pxfSbhBMPw0= F+Z6EDfG7ofsQhdG2VKjNQ== fEursUD9Bj5Q9mFP8sIddA==
239auth-client DV7nt+CDOno= bRgLOvpjbo2k21IjKIJqFA== 2yVT+Lpm/WL4JAU64zlGpQ==
240encrypted
241-----BEGIN MESSAGE-----
242AQIDBA==
243-----END MESSAGE-----
244"#
245 );
246 }
247}