tor_hsservice/
keys.rs

1//! [`KeySpecifier`] implementations for hidden service keys.
2//!
3//! Some of these `KeySpecifier`s represent time-bound keys (that are only valid
4//! as long as their time period is relevant). Time-bound keys are expired (removed)
5//! by [`expire_publisher_keys`].
6//!
7//! If you add a new key that is not a per-service singleton, you also need to
8//! make arrangements to delete old ones.
9//! For TP-based keys, that involves deriving [`HsTimePeriodKeySpecifier`]
10//! and adding a call to `remove_if_expired!` in [`expire_publisher_keys`].
11
12use tor_keymgr::{CTorPath, CTorServicePath};
13
14use crate::internal_prelude::*;
15
16/// Keys that are used by publisher, which relate to our HS and a TP
17///
18/// Derived using
19/// the derive-deftly macro of the same name.
20// We'd like to link to crate::derive_deftly_template_HsTimePeriodKeySpecifier
21// but linking to a module-local macro doesn't work with rustdoc.
22trait HsTimePeriodKeySpecifier: Debug {
23    /// Inspect the nickname
24    fn nickname(&self) -> &HsNickname;
25    /// Inspect the period
26    fn period(&self) -> &TimePeriod;
27}
28
29define_derive_deftly! {
30    /// Implement `HsTimePeriodKeySpecifier` for a struct with `nickname` and `period`
31    HsTimePeriodKeySpecifier:
32
33    impl HsTimePeriodKeySpecifier for $ttype {
34      $(
35        ${when any(approx_equal($fname, nickname), approx_equal($fname, period))}
36        fn $fname(&self) -> &$ftype {
37            &self.$fname
38        }
39      )
40    }
41}
42
43#[derive(Deftly, PartialEq, Debug, Constructor)]
44#[derive_deftly(KeySpecifier)]
45#[deftly(prefix = "hss")]
46#[deftly(role = "KP_hs_id")]
47#[deftly(summary = "Public part of the identity key")]
48#[deftly(keypair_specifier = "HsIdKeypairSpecifier")]
49#[deftly(ctor_path = "hsid_public_key_specifier_ctor_path")]
50/// The public part of the identity key of the service.
51pub struct HsIdPublicKeySpecifier {
52    /// The nickname of the  hidden service.
53    nickname: HsNickname,
54}
55
56/// The `CTorPath` of HsIdPublicKeySpecifier
57fn hsid_public_key_specifier_ctor_path(spec: &HsIdPublicKeySpecifier) -> CTorPath {
58    CTorPath::Service {
59        nickname: spec.nickname.clone(),
60        path: CTorServicePath::PublicKey,
61    }
62}
63
64#[derive(Deftly, PartialEq, Debug, Constructor)]
65#[derive_deftly(KeySpecifier)]
66#[deftly(prefix = "hss")]
67#[deftly(role = "KS_hs_id")]
68#[deftly(summary = "Long-term identity keypair")]
69#[deftly(ctor_path = "hsid_keypair_key_specifier_ctor_path")]
70/// The long-term identity keypair of the service.
71pub struct HsIdKeypairSpecifier {
72    /// The nickname of the  hidden service.
73    pub(crate) nickname: HsNickname,
74}
75
76impl From<&HsIdPublicKeySpecifier> for HsIdKeypairSpecifier {
77    fn from(hs_id_public_key_specifier: &HsIdPublicKeySpecifier) -> HsIdKeypairSpecifier {
78        HsIdKeypairSpecifier::new(hs_id_public_key_specifier.nickname.clone())
79    }
80}
81
82/// The `CTorPath` of HsIdKeypairKeySpecifier
83fn hsid_keypair_key_specifier_ctor_path(spec: &HsIdKeypairSpecifier) -> CTorPath {
84    CTorPath::Service {
85        nickname: spec.nickname.clone(),
86        path: CTorServicePath::PrivateKey,
87    }
88}
89
90#[derive(Deftly, PartialEq, Debug, Constructor)]
91#[derive_deftly(KeySpecifier, HsTimePeriodKeySpecifier)]
92#[deftly(prefix = "hss")]
93#[deftly(role = "KS_hs_blind_id")]
94#[deftly(summary = "Blinded signing keypair")]
95/// The blinded signing keypair.
96pub struct BlindIdKeypairSpecifier {
97    /// The nickname of the  hidden service.
98    pub(crate) nickname: HsNickname,
99    #[deftly(denotator)]
100    /// The time period associated with this key.
101    pub(crate) period: TimePeriod,
102}
103
104#[derive(Deftly, PartialEq, Debug, Constructor)]
105#[derive_deftly(KeySpecifier, HsTimePeriodKeySpecifier)]
106#[deftly(prefix = "hss")]
107#[deftly(role = "KP_hs_blind_id")]
108#[deftly(keypair_specifier = "BlindIdKeypairSpecifier")]
109#[deftly(summary = "Blinded public key")]
110/// The blinded public key.
111pub struct BlindIdPublicKeySpecifier {
112    /// The nickname of the  hidden service.
113    pub(crate) nickname: HsNickname,
114    #[deftly(denotator)]
115    /// The time period associated with this key.
116    pub(crate) period: TimePeriod,
117}
118
119impl From<&BlindIdPublicKeySpecifier> for BlindIdKeypairSpecifier {
120    fn from(
121        hs_blind_id_public_key_specifier: &BlindIdPublicKeySpecifier,
122    ) -> BlindIdKeypairSpecifier {
123        BlindIdKeypairSpecifier::new(
124            hs_blind_id_public_key_specifier.nickname.clone(),
125            hs_blind_id_public_key_specifier.period,
126        )
127    }
128}
129
130#[derive(Deftly, PartialEq, Debug, Constructor)]
131#[derive_deftly(KeySpecifier, HsTimePeriodKeySpecifier)]
132#[deftly(prefix = "hss")]
133#[deftly(role = "KS_hs_desc_sign")]
134#[deftly(summary = "Descriptor signing key")]
135/// The descriptor signing key.
136pub struct DescSigningKeypairSpecifier {
137    /// The nickname of the  hidden service.
138    pub(crate) nickname: HsNickname,
139    #[deftly(denotator)]
140    /// The time period associated with this key.
141    pub(crate) period: TimePeriod,
142}
143
144/// Denotates one of the keys, in the context of a particular HS and intro point
145#[derive(Debug, Deftly, Eq, PartialEq, strum::Display, strum::EnumString)]
146#[strum(serialize_all = "snake_case")]
147pub(crate) enum IptKeyRole {
148    /// `k_hss_ntor`
149    KHssNtor,
150    /// `k_hss_ntor`
151    KSid,
152}
153
154impl KeySpecifierComponentViaDisplayFromStr for IptKeyRole {}
155
156/// Specifies an intro point key
157#[derive(Debug, Deftly, Eq, PartialEq)]
158#[derive_deftly(KeySpecifier)]
159#[deftly(prefix = "hss")]
160#[deftly(summary = "introduction point key")]
161pub(crate) struct IptKeySpecifier {
162    /// nick
163    pub(crate) nick: HsNickname,
164    /// which key
165    #[deftly(fixed_path_component = "ipts")]
166    #[deftly(role)]
167    pub(crate) role: IptKeyRole,
168    /// lid
169    #[deftly(denotator)]
170    pub(crate) lid: IptLocalId,
171}
172
173/// Expire publisher keys for no-longer relevant TPs
174pub(crate) fn expire_publisher_keys(
175    keymgr: &KeyMgr,
176    nickname: &HsNickname,
177    relevant_periods: &[HsDirParams],
178) -> tor_keymgr::Result<()> {
179    // Only remove the keys of the hidden service
180    // that concerns us
181    let arti_pat = tor_keymgr::KeyPathPattern::Arti(format!("hss/{}/*", &nickname));
182    let possibly_relevant_keys = keymgr.list_matching(&arti_pat)?;
183
184    for entry in possibly_relevant_keys {
185        let key_path = entry.key_path();
186        // Remove the key identified by `spec` if it's no longer relevant
187        let remove_if_expired = |spec: &dyn HsTimePeriodKeySpecifier| {
188            if spec.nickname() != nickname {
189                return Err(internal!(
190                    "keymgr gave us key {spec:?} that doesn't match our pattern {arti_pat:?}"
191                )
192                .into());
193            }
194            let is_expired = relevant_periods
195                .iter()
196                .all(|p| &p.time_period() != spec.period());
197
198            if is_expired {
199                keymgr.remove_entry(&entry)?;
200            }
201
202            tor_keymgr::Result::Ok(())
203        };
204
205        /// Remove the specified key, if it's no longer relevant.
206        macro_rules! remove_if_expired {
207            ($K:ty) => {{
208                if let Ok(spec) = <$K>::try_from(key_path) {
209                    remove_if_expired(&spec)?;
210                }
211            }};
212        }
213
214        // TODO: any invalid/malformed keys are ignored (rather than
215        // removed).
216        remove_if_expired!(BlindIdPublicKeySpecifier);
217        remove_if_expired!(BlindIdKeypairSpecifier);
218        remove_if_expired!(DescSigningKeypairSpecifier);
219    }
220
221    Ok(())
222}
223
224#[cfg(test)]
225mod test {
226    // @@ begin test lint list maintained by maint/add_warning @@
227    #![allow(clippy::bool_assert_comparison)]
228    #![allow(clippy::clone_on_copy)]
229    #![allow(clippy::dbg_macro)]
230    #![allow(clippy::mixed_attributes_style)]
231    #![allow(clippy::print_stderr)]
232    #![allow(clippy::print_stdout)]
233    #![allow(clippy::single_char_pattern)]
234    #![allow(clippy::unwrap_used)]
235    #![allow(clippy::unchecked_duration_subtraction)]
236    #![allow(clippy::useless_vec)]
237    #![allow(clippy::needless_pass_by_value)]
238    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
239    use super::*;
240    use tor_keymgr::test_utils::check_key_specifier;
241    use tor_keymgr::KeySpecifier;
242
243    #[test]
244    fn hsid_key_specifiers() {
245        let nickname = HsNickname::try_from("shallot".to_string()).unwrap();
246        let key_spec = HsIdPublicKeySpecifier::new(nickname.clone());
247        assert_eq!(
248            key_spec.arti_path().unwrap().as_str(),
249            "hss/shallot/kp_hs_id"
250        );
251
252        let key_spec = HsIdKeypairSpecifier::new(nickname);
253        check_key_specifier(&key_spec, "hss/shallot/ks_hs_id");
254    }
255
256    #[test]
257    fn blind_id_key_specifiers() {
258        let nickname = HsNickname::try_from("shallot".to_string()).unwrap();
259        let period = TimePeriod::from_parts(1, 2, 3);
260        let key_spec = BlindIdPublicKeySpecifier::new(nickname.clone(), period);
261        assert_eq!(
262            key_spec.arti_path().unwrap().as_str(),
263            "hss/shallot/kp_hs_blind_id+2_1_3"
264        );
265
266        let key_spec = BlindIdKeypairSpecifier::new(nickname, period);
267        check_key_specifier(&key_spec, "hss/shallot/ks_hs_blind_id+2_1_3");
268    }
269
270    #[test]
271    fn desc_signing_key_specifiers() {
272        let nickname = HsNickname::try_from("shallot".to_string()).unwrap();
273        let period = TimePeriod::from_parts(1, 2, 3);
274        let key_spec = DescSigningKeypairSpecifier::new(nickname, period);
275        check_key_specifier(&key_spec, "hss/shallot/ks_hs_desc_sign+2_1_3");
276    }
277
278    #[test]
279    fn ipt_key_specifiers() {
280        let nick = HsNickname::try_from("shallot".to_string()).unwrap();
281        let lid = IptLocalId::dummy(1);
282        let spec = |role| IptKeySpecifier {
283            nick: nick.clone(),
284            lid,
285            role,
286        };
287        let lid_s = "0101010101010101010101010101010101010101010101010101010101010101";
288        check_key_specifier(
289            &spec(IptKeyRole::KHssNtor),
290            &format!("hss/shallot/ipts/k_hss_ntor+{lid_s}"),
291        );
292        check_key_specifier(
293            &spec(IptKeyRole::KSid),
294            &format!("hss/shallot/ipts/k_sid+{lid_s}"),
295        );
296    }
297}