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

            
12
use tor_keymgr::{CTorPath, CTorServicePath};
13

            
14
use 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.
22
trait HsTimePeriodKeySpecifier: Debug {
23
    /// Inspect the nickname
24
    fn nickname(&self) -> &HsNickname;
25
    /// Inspect the period
26
    fn period(&self) -> &TimePeriod;
27
}
28

            
29
define_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.
51
pub struct HsIdPublicKeySpecifier {
52
    /// The nickname of the  hidden service.
53
    nickname: HsNickname,
54
}
55

            
56
/// The `CTorPath` of HsIdPublicKeySpecifier
57
fn 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.
71
pub struct HsIdKeypairSpecifier {
72
    /// The nickname of the  hidden service.
73
    pub(crate) nickname: HsNickname,
74
}
75

            
76
impl From<&HsIdPublicKeySpecifier> for HsIdKeypairSpecifier {
77
80
    fn from(hs_id_public_key_specifier: &HsIdPublicKeySpecifier) -> HsIdKeypairSpecifier {
78
80
        HsIdKeypairSpecifier::new(hs_id_public_key_specifier.nickname.clone())
79
80
    }
80
}
81

            
82
/// The `CTorPath` of HsIdKeypairKeySpecifier
83
fn 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.
96
pub 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.
111
pub 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

            
119
impl 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.
136
pub 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")]
147
pub(crate) enum IptKeyRole {
148
    /// `k_hss_ntor`
149
    KHssNtor,
150
    /// `k_hss_ntor`
151
    KSid,
152
}
153

            
154
impl 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")]
161
pub(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
174
pub(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)]
225
mod 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
}