tor_hsservice/replay/ipt.rs
1//! Code for a replay log for [`Introduce2`] messages.
2
3use super::{ReplayLogType, MAGIC_LEN, OUTPUT_LEN, REPLAY_LOG_SUFFIX};
4use crate::internal_prelude::*;
5use hash::hash;
6use tor_cell::relaycell::msg::Introduce2;
7
8/// A [`ReplayLogType`] to indicate using [`Introduce2`] messages with [`IptLocalId`] names.
9pub(crate) struct IptReplayLogType;
10
11impl ReplayLogType for IptReplayLogType {
12 type Name = IptLocalId;
13 type Message = Introduce2;
14
15 // It would be better to specifically say that this is a IPT replay log here, but for backwards
16 // compatability we should keep this as-is.
17 const MAGIC: &'static [u8; MAGIC_LEN] = b"<tor hss replay Kangaroo12>\n\0\0\0\0";
18
19 fn format_filename(name: &IptLocalId) -> String {
20 format!("{name}{REPLAY_LOG_SUFFIX}")
21 }
22
23 fn transform_message(message: &Introduce2) -> [u8; OUTPUT_LEN] {
24 // This line here is really subtle! The decision of _what object_
25 // to check for replays is critical to making sure that the
26 // introduction point cannot do replays by modifying small parts of
27 // the replayed object. So we don't check the header; instead, we
28 // check the encrypted body. This in turn works only because the
29 // encryption format is non-malleable: modifying the encrypted
30 // message has negligible probability of making a message that can
31 // be decrypted.
32 //
33 // (Ancient versions of onion services used a malleable encryption
34 // format here, which made replay detection even harder.
35 // Fortunately, we don't have that problem in the current protocol)
36 hash(message.encrypted_body())
37 }
38
39 fn parse_log_leafname(leaf: &OsStr) -> Result<IptLocalId, Cow<'static, str>> {
40 let leaf = leaf.to_str().ok_or("not proper unicode")?;
41 let lid = leaf.strip_suffix(REPLAY_LOG_SUFFIX).ok_or("not *.bin")?;
42 let lid: IptLocalId = lid
43 .parse()
44 .map_err(|e: crate::InvalidIptLocalId| e.to_string())?;
45 Ok(lid)
46 }
47}
48
49/// Implementation code for pre-hashing our inputs.
50///
51/// We do this because we don't actually want to record the entirety of each
52/// encrypted introduction request.
53///
54/// We aren't terribly concerned about collision resistance: accidental
55/// collision don't matter, since we are okay with a false-positive rate.
56/// Intentional collisions are also okay, since the only impact of generating
57/// one would be that you could make an introduce2 message _of your own_ get
58/// rejected.
59///
60/// The impact of preimages is also not so bad. If somebody can reconstruct the
61/// original message, they still get an encrypted object, and need the
62/// `KP_hss_ntor` key to do anything with it. A second preimage attack just
63/// gives another message we won't accept.
64mod hash {
65 use super::OUTPUT_LEN;
66
67 /// Compute a hash from a given bytestring.
68 ///
69 /// We only keep 128 bits; see note above in the module documentation about why
70 /// this is okay.
71 pub(super) fn hash(s: &[u8]) -> [u8; OUTPUT_LEN] {
72 /// If we change OUTPUT_LEN, this function will need to change.
73 const _: () = assert!(OUTPUT_LEN == 16);
74
75 // I'm choosing kangaroo-twelve for its speed. This doesn't affect
76 // compatibility, so it's okay to use something a bit odd, since we can
77 // change it later if we want.
78 use digest::{ExtendableOutput, Update};
79 use k12::KangarooTwelve;
80 let mut d = KangarooTwelve::default();
81 let mut output = [0; OUTPUT_LEN];
82 d.update(s);
83 d.finalize_xof_into(&mut output);
84 output
85 }
86
87 #[cfg(test)]
88 mod test {
89 use super::*;
90
91 #[test]
92 fn hash_basics() {
93 let a = hash(b"123");
94 let b = hash(b"123");
95 let c = hash(b"1234");
96 assert_eq!(a, b);
97 assert_ne!(a, c);
98 }
99 }
100}