arti_rpcserver/
globalid.rs1use tor_bytes::Reader;
9use tor_llcrypto::util::ct::CtByteArray;
10use tor_rpcbase::{LookupError, ObjectId};
11use zeroize::Zeroizing;
12
13use crate::{connection::ConnectionId, objmap::GenIdx};
14
15#[derive(Clone, Debug, Eq, PartialEq)]
22pub(crate) struct GlobalId {
23 pub(crate) connection: ConnectionId,
25 pub(crate) local_id: GenIdx,
27}
28
29const MAC_KEY_LEN: usize = 16;
34const MAC_LEN: usize = 16;
39
40#[derive(Clone)]
47pub(crate) struct MacKey {
48 key: Zeroizing<[u8; MAC_KEY_LEN]>,
50}
51
52type Mac = CtByteArray<MAC_LEN>;
54
55impl MacKey {
56 pub(crate) fn new<Rng: rand::Rng + rand::CryptoRng>(rng: &mut Rng) -> Self {
58 Self {
59 key: Zeroizing::new(rng.random()),
60 }
61 }
62
63 fn mac(&self, inp: &[u8], out: &mut [u8]) {
67 use tiny_keccak::{Hasher as _, Kmac};
68 let mut mac = Kmac::v128(&self.key[..], b"artirpc globalid");
69 mac.update(inp);
70 mac.finalize(out);
71 }
72}
73
74impl GlobalId {
75 const ENCODED_LEN: usize = MAC_LEN + ConnectionId::LEN + GenIdx::BYTE_LEN;
77
78 const TAG_CHAR: char = '$';
83
84 pub(crate) fn new(connection: ConnectionId, local_id: GenIdx) -> GlobalId {
86 GlobalId {
87 connection,
88 local_id,
89 }
90 }
91
92 pub(crate) fn encode(&self, key: &MacKey) -> ObjectId {
97 use base64ct::{Base64Unpadded as B64, Encoding};
98 let bytes = self.encode_as_bytes(key, &mut rand::rng());
99 let string = format!("{}{}", GlobalId::TAG_CHAR, B64::encode_string(&bytes[..]));
100 ObjectId::from(string)
101 }
102
103 fn encode_as_bytes<R: rand::RngCore>(&self, key: &MacKey, rng: &mut R) -> Vec<u8> {
105 let mut bytes = Vec::with_capacity(Self::ENCODED_LEN);
106 bytes.resize(MAC_LEN, 0);
107 bytes.extend_from_slice(self.connection.as_ref());
108 bytes.extend_from_slice(&self.local_id.to_bytes(rng));
109 {
110 let (mac, text) = bytes.split_at_mut(MAC_LEN);
112 key.mac(text, mac);
113 }
114 bytes
115 }
116
117 pub(crate) fn try_decode(key: &MacKey, s: &ObjectId) -> Result<Option<Self>, LookupError> {
121 use base64ct::{Base64Unpadded as B64, Encoding};
122 if !s.as_ref().starts_with(GlobalId::TAG_CHAR) {
123 return Ok(None);
124 }
125 let mut bytes = [0_u8; Self::ENCODED_LEN];
126 let byte_slice = B64::decode(&s.as_ref()[1..], &mut bytes[..])
127 .map_err(|_| LookupError::NoObject(s.clone()))?;
128 Self::try_decode_from_bytes(key, byte_slice)
129 .ok_or_else(|| LookupError::NoObject(s.clone()))
130 .map(Some)
131 }
132
133 fn try_decode_from_bytes(key: &MacKey, bytes: &[u8]) -> Option<Self> {
135 if bytes.len() != Self::ENCODED_LEN {
136 return None;
137 }
138
139 let mut found_mac = [0; MAC_LEN];
142 key.mac(&bytes[MAC_LEN..], &mut found_mac[..]);
143 let found_mac = Mac::from(found_mac);
144
145 let mut r: Reader = Reader::from_slice(bytes);
146 let declared_mac: Mac = r.extract().ok()?;
147 if found_mac != declared_mac {
148 return None;
149 }
150 let connection = r.extract::<[u8; ConnectionId::LEN]>().ok()?.into();
151 let rest = r.into_rest();
152 let local_id = GenIdx::from_bytes(rest)?;
153
154 Some(Self {
155 connection,
156 local_id,
157 })
158 }
159}
160
161#[cfg(test)]
162mod test {
163 #![allow(clippy::bool_assert_comparison)]
165 #![allow(clippy::clone_on_copy)]
166 #![allow(clippy::dbg_macro)]
167 #![allow(clippy::mixed_attributes_style)]
168 #![allow(clippy::print_stderr)]
169 #![allow(clippy::print_stdout)]
170 #![allow(clippy::single_char_pattern)]
171 #![allow(clippy::unwrap_used)]
172 #![allow(clippy::unchecked_time_subtraction)]
173 #![allow(clippy::useless_vec)]
174 #![allow(clippy::needless_pass_by_value)]
175 use super::*;
178
179 const GLOBAL_ID_B64_ENCODED_LEN: usize = (GlobalId::ENCODED_LEN * 8).div_ceil(6) + 1;
180
181 #[test]
182 fn roundtrip() {
183 use slotmap_careful::KeyData;
184 let mut rng = tor_basic_utils::test_rng::testing_rng();
185
186 let conn1 = ConnectionId::from(*b"example1-------!");
187 let genidx_s1 = GenIdx::from(KeyData::from_ffi(0x43_0000_0043));
188
189 let gid1 = GlobalId {
190 connection: conn1,
191 local_id: genidx_s1,
192 };
193 let mac_key = MacKey::new(&mut rng);
194 let enc1 = gid1.encode(&mac_key);
195 let gid1_decoded = GlobalId::try_decode(&mac_key, &enc1).unwrap().unwrap();
196 assert_eq!(gid1, gid1_decoded);
197 assert!(enc1.as_ref().starts_with(GlobalId::TAG_CHAR));
198
199 assert_eq!(enc1.as_ref().len(), GLOBAL_ID_B64_ENCODED_LEN);
200 }
201
202 #[test]
203 fn not_a_global_id() {
204 let mut rng = tor_basic_utils::test_rng::testing_rng();
205 let mac_key = MacKey::new(&mut rng);
206 let decoded = GlobalId::try_decode(&mac_key, &ObjectId::from("helloworld"));
207 assert!(matches!(decoded, Ok(None)));
208
209 let decoded = GlobalId::try_decode(&mac_key, &ObjectId::from("$helloworld"));
210 assert!(matches!(decoded, Err(LookupError::NoObject(_))));
211 }
212
213 #[test]
214 fn mac_works() {
215 use slotmap_careful::KeyData;
216 let mut rng = tor_basic_utils::test_rng::testing_rng();
217
218 let conn1 = ConnectionId::from(*b"example1-------!");
219 let conn2 = ConnectionId::from(*b"example2-------!");
220 let genidx_s1 = GenIdx::from(KeyData::from_ffi(0x43_0000_0043));
221 let genidx_s2 = GenIdx::from(KeyData::from_ffi(0x171_0000_0171));
222
223 let gid1 = GlobalId {
224 connection: conn1,
225 local_id: genidx_s1,
226 };
227 let gid2 = GlobalId {
228 connection: conn2,
229 local_id: genidx_s2,
230 };
231 let mac_key = MacKey::new(&mut rng);
232 let enc1 = gid1.encode_as_bytes(&mac_key, &mut rng);
233 let enc2 = gid2.encode_as_bytes(&mac_key, &mut rng);
234
235 let mut combined = Vec::from(&enc1[0..MAC_LEN]);
238 combined.extend_from_slice(&enc2[MAC_LEN..]);
239 let outcome = GlobalId::try_decode_from_bytes(&mac_key, &combined[..]);
240 assert!(outcome.is_none());
242 }
243}