tor_netdoc/doc/hsdesc/
desc_enc.rs1use tor_hscrypto::{pk::HsBlindId, RevisionCounter, Subcredential};
4use tor_llcrypto::cipher::aes::Aes256Ctr as Cipher;
5use tor_llcrypto::d::Sha3_256 as Hash;
6use tor_llcrypto::d::Shake256 as KDF;
7
8use cipher::{KeyIvInit, StreamCipher};
9use digest::{ExtendableOutput, FixedOutput, Update, XofReader};
10#[cfg(any(test, feature = "hs-service"))]
11use rand::{CryptoRng, Rng};
12use tor_llcrypto::pk::curve25519::PublicKey;
13use tor_llcrypto::pk::curve25519::StaticSecret;
14use tor_llcrypto::util::ct::CtByteArray;
15use zeroize::Zeroizing as Z;
16
17pub(super) struct HsDescEncryption<'a> {
22 pub(super) blinded_id: &'a HsBlindId,
26 pub(super) desc_enc_nonce: Option<&'a HsDescEncNonce>,
33 pub(super) subcredential: &'a Subcredential,
35 pub(super) revision: RevisionCounter,
37 pub(super) string_const: &'a [u8],
42}
43
44pub(crate) const HS_DESC_CLIENT_ID_LEN: usize = 8;
46
47pub(crate) const HS_DESC_IV_LEN: usize = 16;
49
50pub(crate) const HS_DESC_ENC_NONCE_LEN: usize = 16;
52
53#[derive(derive_more::AsRef, derive_more::From)]
59pub(super) struct HsDescEncNonce([u8; HS_DESC_ENC_NONCE_LEN]);
60
61const SALT_LEN: usize = 16;
63const MAC_LEN: usize = 32;
65
66impl<'a> HsDescEncryption<'a> {
67 const MAC_KEY_LEN: usize = 32;
69 const CIPHER_KEY_LEN: usize = 32;
71 const IV_LEN: usize = 16;
73
74 #[cfg(any(test, feature = "hs-service"))]
76 pub(super) fn encrypt<R: Rng + CryptoRng>(&self, rng: &mut R, data: &[u8]) -> Vec<u8> {
77 let output_len = data.len() + SALT_LEN + MAC_LEN;
78 let mut output = Vec::with_capacity(output_len);
79 let salt: [u8; SALT_LEN] = rng.random();
80
81 let (mut cipher, mut mac) = self.init(&salt);
82
83 output.extend_from_slice(&salt[..]);
84 output.extend_from_slice(data);
85 cipher.apply_keystream(&mut output[SALT_LEN..]);
86 mac.update(&output[SALT_LEN..]);
87 let mut mac_val = Default::default();
88 mac.finalize_into(&mut mac_val);
89 output.extend_from_slice(&mac_val);
90 debug_assert_eq!(output.len(), output_len);
91
92 output
93 }
94 pub(super) fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, DecryptionError> {
97 if data.len() < SALT_LEN + MAC_LEN {
98 return Err(DecryptionError::default());
99 }
100 let msg_len = data.len() - SALT_LEN - MAC_LEN;
101
102 let salt = data[0..SALT_LEN]
103 .try_into()
104 .expect("Failed try_into for 16-byte array.");
105 let ciphertext = &data[SALT_LEN..(SALT_LEN + msg_len)];
106
107 let expected_mac = CtByteArray::from(
108 <[u8; MAC_LEN]>::try_from(&data[SALT_LEN + msg_len..SALT_LEN + msg_len + MAC_LEN])
109 .expect("Failed try_into for 32-byte array."),
110 );
111 let (mut cipher, mut mac) = self.init(&salt);
112
113 mac.update(ciphertext);
115 let mut received_mac = CtByteArray::from([0_u8; MAC_LEN]);
116 mac.finalize_into(received_mac.as_mut().into());
117 if received_mac != expected_mac {
118 return Err(DecryptionError::default());
119 }
120
121 let mut decrypted = ciphertext.to_vec();
122 cipher.apply_keystream(&mut decrypted[..]);
123
124 Ok(decrypted)
125 }
126
127 fn init(&self, salt: &[u8; 16]) -> (Cipher, Hash) {
138 let mut key_stream = self.get_kdf(salt).finalize_xof();
139
140 let mut key = Z::new([0_u8; Self::CIPHER_KEY_LEN]);
141 let mut iv = Z::new([0_u8; Self::IV_LEN]);
142 let mut mac_key = Z::new([0_u8; Self::MAC_KEY_LEN]);
143 key_stream.read(&mut key[..]);
144 key_stream.read(&mut iv[..]);
145 key_stream.read(&mut mac_key[..]);
146
147 let cipher = Cipher::new(key.as_ref().into(), iv.as_ref().into());
148
149 let mut mac = Hash::default();
150 mac.update(&(Self::MAC_KEY_LEN as u64).to_be_bytes());
151 mac.update(&mac_key[..]);
152 mac.update(&(salt.len() as u64).to_be_bytes());
153 mac.update(&salt[..]);
154
155 (cipher, mac)
156 }
157
158 fn get_kdf(&self, salt: &[u8; 16]) -> KDF {
166 let mut kdf = KDF::default();
167
168 kdf.update(self.blinded_id.as_ref());
172 if let Some(cookie) = self.desc_enc_nonce {
173 kdf.update(cookie.as_ref());
174 }
175 kdf.update(self.subcredential.as_ref());
176 kdf.update(&u64::from(self.revision).to_be_bytes());
177
178 kdf.update(salt);
180 kdf.update(self.string_const);
181
182 kdf
183 }
184}
185
186#[non_exhaustive]
190#[derive(Clone, Debug, Default, thiserror::Error)]
191#[error("Unable to decrypt onion service descriptor.")]
192pub struct DecryptionError {}
193
194pub(crate) fn build_descriptor_cookie_key(
212 our_secret_key: &StaticSecret,
213 their_public_key: &PublicKey,
214 subcredential: &Subcredential,
215) -> (CtByteArray<8>, [u8; 32]) {
216 let secret_seed = our_secret_key.diffie_hellman(their_public_key);
217 let mut kdf = KDF::default();
218 kdf.update(subcredential.as_ref());
219 kdf.update(secret_seed.as_bytes());
220 let mut keys = kdf.finalize_xof();
221 let mut client_id = CtByteArray::from([0_u8; 8]);
222 let mut cookie_key = [0_u8; 32];
223 keys.read(client_id.as_mut());
224 keys.read(&mut cookie_key);
225
226 (client_id, cookie_key)
227}
228
229#[cfg(test)]
230mod test {
231 #![allow(clippy::bool_assert_comparison)]
233 #![allow(clippy::clone_on_copy)]
234 #![allow(clippy::dbg_macro)]
235 #![allow(clippy::mixed_attributes_style)]
236 #![allow(clippy::print_stderr)]
237 #![allow(clippy::print_stdout)]
238 #![allow(clippy::single_char_pattern)]
239 #![allow(clippy::unwrap_used)]
240 #![allow(clippy::unchecked_duration_subtraction)]
241 #![allow(clippy::useless_vec)]
242 #![allow(clippy::needless_pass_by_value)]
243 use super::*;
246 use tor_basic_utils::test_rng::testing_rng;
247
248 #[test]
249 fn roundtrip_basics() {
250 let blinded_id = [7; 32].into();
251 let subcredential = [11; 32].into();
252 let revision = 13.into();
253 let string_const = "greetings puny humans";
254 let params = HsDescEncryption {
255 blinded_id: &blinded_id,
256 desc_enc_nonce: None,
257 subcredential: &subcredential,
258 revision,
259 string_const: string_const.as_bytes(),
260 };
261
262 let mut rng = testing_rng();
263
264 let bigmsg: Vec<u8> = (1..123).cycle().take(1021).collect();
265 for message in [&b""[..], &b"hello world"[..], &bigmsg[..]] {
266 let mut encrypted = params.encrypt(&mut rng, message);
267 assert_eq!(encrypted.len(), message.len() + 48);
268 let decrypted = params.decrypt(&encrypted[..]).unwrap();
269 assert_eq!(message, &decrypted);
270
271 let decryption_err = params.decrypt(&encrypted[..encrypted.len() - 1]);
273 assert!(decryption_err.is_err());
274 encrypted[7] ^= 3;
276 let decryption_err = params.decrypt(&encrypted[..]);
277 assert!(decryption_err.is_err());
278 }
279 }
280
281 #[test]
282 fn too_short() {
283 let blinded_id = [7; 32].into();
284 let subcredential = [11; 32].into();
285 let revision = 13.into();
286 let string_const = "greetings puny humans";
287 let params = HsDescEncryption {
288 blinded_id: &blinded_id,
289 desc_enc_nonce: None,
290 subcredential: &subcredential,
291 revision,
292 string_const: string_const.as_bytes(),
293 };
294
295 assert!(params.decrypt(b"").is_err());
296 assert!(params.decrypt(&[0_u8; 47]).is_err());
297 }
298}