1
//! Manager-global identifiers, for things that need to be identified outside
2
//! the scope of a single RPC connection.
3
//!
4
//! We expect to use this code to identify `TorClient`s and similar objects that
5
//! can be passed as the target of a SOCKS request.  Since the SOCKS request is
6
//! not part of the RPC session, we need a way for it to refer to these objects.
7

            
8
use tor_bytes::Reader;
9
use tor_llcrypto::util::ct::CtByteArray;
10
use tor_rpcbase::{LookupError, ObjectId};
11
use zeroize::Zeroizing;
12

            
13
use crate::{connection::ConnectionId, objmap::GenIdx};
14

            
15
/// A [RpcMgr](crate::RpcMgr)-scoped identifier for an RPC object.
16
///
17
/// A `GlobalId` identifies an RPC object uniquely among all the objects visible
18
/// to any active session on an RpcMgr.
19
///
20
/// Its encoding is unforgeable.
21
#[derive(Clone, Debug, Eq, PartialEq)]
22
pub(crate) struct GlobalId {
23
    /// The RPC connection within whose object map `local_id` is visible.
24
    pub(crate) connection: ConnectionId,
25
    /// The identifier of the object within `connection`'s object map.
26
    pub(crate) local_id: GenIdx,
27
}
28

            
29
/// The number of bytes in our [`MacKey`].
30
///
31
/// (Our choice of algorithm allows any key length we want; 128 bits should be
32
/// secure enough.)
33
const MAC_KEY_LEN: usize = 16;
34
/// The number of bytes in a [`Mac`].
35
///
36
/// (Our choice of algorithm allows any MAC length we want; 128 bits should be
37
/// enough to make the results unforgeable.)
38
const MAC_LEN: usize = 16;
39

            
40
/// An key that we use to compute message authentication codes (MACs) for our
41
/// [`GlobalId`]s
42
///
43
/// We do not guarantee any particular MAC algorithm; we should be able to
44
/// change MAC algorithms without breaking any user code. Right now, we choose a
45
/// Kangaroo12-based construction in order to be reasonably fast.
46
#[derive(Clone)]
47
pub(crate) struct MacKey {
48
    /// The key itself.
49
    key: Zeroizing<[u8; MAC_KEY_LEN]>,
50
}
51

            
52
/// A message authentication code produced by [`MacKey::mac`].
53
type Mac = CtByteArray<MAC_LEN>;
54

            
55
impl MacKey {
56
    /// Construct a new random `MacKey`.
57
4
    pub(crate) fn new<Rng: rand::Rng + rand::CryptoRng>(rng: &mut Rng) -> Self {
58
4
        Self {
59
4
            key: Zeroizing::new(rng.gen()),
60
4
        }
61
4
    }
62

            
63
    /// Compute the AMC of a given input `inp`, and store the result into `out`.
64
    ///
65
    /// The current construction allows `out` to be any length.
66
14
    fn mac(&self, inp: &[u8], out: &mut [u8]) {
67
14
        use tiny_keccak::{Hasher as _, Kmac};
68
14
        let mut mac = Kmac::v128(&self.key[..], b"artirpc globalid");
69
14
        mac.update(inp);
70
14
        mac.finalize(out);
71
14
    }
72
}
73

            
74
impl GlobalId {
75
    /// The number of bytes used to encode a `GlobalId` in binary form.
76
    const ENCODED_LEN: usize = MAC_LEN + ConnectionId::LEN + GenIdx::BYTE_LEN;
77
    /// The number of bytes used to encode a `GlobalId` in base-64 form.
78
    // TODO: use div_ceil once it's stable.
79
    pub(crate) const B64_ENCODED_LEN: usize = (Self::ENCODED_LEN * 8 + 5) / 6;
80

            
81
    /// Create a new GlobalId from its parts.
82
    pub(crate) fn new(connection: ConnectionId, local_id: GenIdx) -> GlobalId {
83
        GlobalId {
84
            connection,
85
            local_id,
86
        }
87
    }
88

            
89
    /// Encode this ID in an unforgeable string that we can later use to
90
    /// uniquely identify an RPC object.
91
    ///
92
    /// As with local IDs, this encoding is nondeterministic.
93
4
    pub(crate) fn encode(&self, key: &MacKey) -> ObjectId {
94
4
        use base64ct::{Base64Unpadded as B64, Encoding};
95
4
        let bytes = self.encode_as_bytes(key, &mut rand::thread_rng());
96
4
        B64::encode_string(&bytes[..]).into()
97
4
    }
98

            
99
    /// As `encode`, but do not base64-encode the result.
100
8
    fn encode_as_bytes<R: rand::RngCore>(&self, key: &MacKey, rng: &mut R) -> Vec<u8> {
101
8
        let mut bytes = Vec::with_capacity(Self::ENCODED_LEN);
102
8
        bytes.resize(MAC_LEN, 0);
103
8
        bytes.extend_from_slice(self.connection.as_ref());
104
8
        bytes.extend_from_slice(&self.local_id.to_bytes(rng));
105
8
        {
106
8
            // TODO RPC: Maybe we should stick the MAC at the end to make everything simpler.
107
8
            let (mac, text) = bytes.split_at_mut(MAC_LEN);
108
8
            key.mac(text, mac);
109
8
        }
110
8
        bytes
111
8
    }
112

            
113
    /// Try to decode and validate `s` as a [`GlobalId`].
114
4
    pub(crate) fn try_decode(key: &MacKey, s: &ObjectId) -> Result<Self, LookupError> {
115
4
        use base64ct::{Base64Unpadded as B64, Encoding};
116
4
        let mut bytes = [0_u8; Self::ENCODED_LEN];
117
4
        let byte_slice = B64::decode(s.as_ref(), &mut bytes[..])
118
4
            .map_err(|_| LookupError::NoObject(s.clone()))?;
119
4
        Self::try_decode_from_bytes(key, byte_slice).ok_or_else(|| LookupError::NoObject(s.clone()))
120
4
    }
121

            
122
    /// As `try_decode`, but expect a byte slice rather than a base64-encoded string.
123
6
    fn try_decode_from_bytes(key: &MacKey, bytes: &[u8]) -> Option<Self> {
124
6
        if bytes.len() != Self::ENCODED_LEN {
125
            return None;
126
6
        }
127
6

            
128
6
        // TODO RPC: Just use Reader here?
129
6

            
130
6
        let mut found_mac = [0; MAC_LEN];
131
6
        key.mac(&bytes[MAC_LEN..], &mut found_mac[..]);
132
6
        let found_mac = Mac::from(found_mac);
133
6

            
134
6
        let mut r: Reader = Reader::from_slice(bytes);
135
6
        let declared_mac: Mac = r.extract().ok()?;
136
6
        if found_mac != declared_mac {
137
2
            return None;
138
4
        }
139
4
        let connection = r.extract::<[u8; ConnectionId::LEN]>().ok()?.into();
140
4
        let rest = r.into_rest();
141
4
        let local_id = GenIdx::from_bytes(rest)?;
142

            
143
4
        Some(Self {
144
4
            connection,
145
4
            local_id,
146
4
        })
147
6
    }
148
}
149

            
150
#[cfg(test)]
151
mod test {
152
    // @@ begin test lint list maintained by maint/add_warning @@
153
    #![allow(clippy::bool_assert_comparison)]
154
    #![allow(clippy::clone_on_copy)]
155
    #![allow(clippy::dbg_macro)]
156
    #![allow(clippy::mixed_attributes_style)]
157
    #![allow(clippy::print_stderr)]
158
    #![allow(clippy::print_stdout)]
159
    #![allow(clippy::single_char_pattern)]
160
    #![allow(clippy::unwrap_used)]
161
    #![allow(clippy::unchecked_duration_subtraction)]
162
    #![allow(clippy::useless_vec)]
163
    #![allow(clippy::needless_pass_by_value)]
164
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
165

            
166
    use super::*;
167

            
168
    #[test]
169
    fn roundtrip() {
170
        use crate::objmap::{StrongIdx, WeakIdx};
171
        use slotmap_careful::KeyData;
172
        let mut rng = tor_basic_utils::test_rng::testing_rng();
173

            
174
        let conn1 = ConnectionId::from(*b"example1-------!");
175
        let conn2 = ConnectionId::from(*b"example2!!!!!!!!");
176
        let genidx_s1 = GenIdx::Strong(StrongIdx::from(KeyData::from_ffi(0x43_0000_0043)));
177
        let genidx_w2 = GenIdx::Weak(WeakIdx::from(KeyData::from_ffi(0x171_0000_0171)));
178

            
179
        let gid1 = GlobalId {
180
            connection: conn1,
181
            local_id: genidx_s1,
182
        };
183
        let gid2 = GlobalId {
184
            connection: conn2,
185
            local_id: genidx_w2,
186
        };
187

            
188
        let mac_key = MacKey::new(&mut rng);
189
        let enc1 = gid1.encode(&mac_key);
190
        let gid1_decoded = GlobalId::try_decode(&mac_key, &enc1).unwrap();
191
        assert_eq!(gid1, gid1_decoded);
192

            
193
        let enc2 = gid2.encode(&mac_key);
194
        let gid2_decoded = GlobalId::try_decode(&mac_key, &enc2).unwrap();
195
        assert_eq!(gid2, gid2_decoded);
196
        assert_ne!(gid1_decoded, gid2_decoded);
197

            
198
        assert_eq!(enc1.as_ref().len(), GlobalId::B64_ENCODED_LEN);
199
        assert_eq!(enc2.as_ref().len(), GlobalId::B64_ENCODED_LEN);
200
    }
201

            
202
    #[test]
203
    fn mac_works() {
204
        use crate::objmap::{StrongIdx, WeakIdx};
205
        use slotmap_careful::KeyData;
206
        let mut rng = tor_basic_utils::test_rng::testing_rng();
207

            
208
        let conn1 = ConnectionId::from(*b"example1-------!");
209
        let conn2 = ConnectionId::from(*b"example2!!!!!!!!");
210
        let genidx_s1 = GenIdx::Strong(StrongIdx::from(KeyData::from_ffi(0x43_0000_0043)));
211
        let genidx_w1 = GenIdx::Weak(WeakIdx::from(KeyData::from_ffi(0x171_0000_0171)));
212

            
213
        let gid1 = GlobalId {
214
            connection: conn1,
215
            local_id: genidx_s1,
216
        };
217
        let gid2 = GlobalId {
218
            connection: conn2,
219
            local_id: genidx_w1,
220
        };
221
        let mac_key = MacKey::new(&mut rng);
222
        let enc1 = gid1.encode_as_bytes(&mac_key, &mut rng);
223
        let enc2 = gid2.encode_as_bytes(&mac_key, &mut rng);
224

            
225
        // Make a 'combined' encoded gid with the mac from one and the info from
226
        // the other.
227
        let mut combined = Vec::from(&enc1[0..MAC_LEN]);
228
        combined.extend_from_slice(&enc2[MAC_LEN..]);
229
        let outcome = GlobalId::try_decode_from_bytes(&mac_key, &combined[..]);
230
        // Can't decode, because MAC was wrong.
231
        assert!(outcome.is_none());
232
    }
233
}