Skip to main content

tor_hsservice/publish/
descriptor.rs

1//! Helpers for building and representing hidden service descriptors.
2
3use super::*;
4use crate::config::OnionServiceConfigPublisherView;
5use tor_cell::chancell::msg::HandshakeType;
6use tor_llcrypto::rng::EntropicRng;
7use tor_netdir::params::NetParameters;
8
9/// Build the descriptor.
10///
11/// The `now` argument is used for computing the expiry of the `intro_{auth, enc}_key_cert`
12/// certificates included in the descriptor. The expiry will be set to 54 hours from `now`.
13///
14/// Note: `blind_id_kp` is the blinded hidden service signing keypair used to sign descriptor
15/// signing keys (KP_hs_blind_id, KS_hs_blind_id).
16#[allow(clippy::too_many_arguments)]
17pub(super) fn build_sign<
18    Rng: rand::Rng + CryptoRng,
19    KeyRng: rand::Rng + EntropicRng,
20    R: Runtime,
21>(
22    keymgr: &Arc<KeyMgr>,
23    pow_manager: &Arc<PowManager<R>>,
24    config: &Arc<OnionServiceConfigPublisherView>,
25    netparams: &NetParameters,
26    authorized_clients: Option<&RestrictedDiscoveryKeys>,
27    ipt_set: &IptSet,
28    period: TimePeriod,
29    revision_counter: RevisionCounter,
30    rng: &mut Rng,
31    key_rng: &mut KeyRng,
32    now: SystemTime,
33    max_hsdesc_len: usize,
34) -> Result<VersionedDescriptor, FatalError> {
35    // TODO: should this be configurable? If so, we should read it from the svc config.
36    //
37    /// The CREATE handshake type we support.
38    //
39    // NOTE: This list may be useless. See
40    // <https://gitlab.torproject.org/tpo/core/torspec/-/work_items/420>.
41    const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::NTOR];
42
43    /// Lifetime of the intro_{auth, enc}_key_cert certificates in the descriptor.
44    ///
45    /// From C-Tor src/feature/hs/hs_descriptor.h:
46    ///
47    /// "This defines the lifetime of the descriptor signing key and the cross certification cert of
48    /// that key. It is set to 54 hours because a descriptor can be around for 48 hours and because
49    /// consensuses are used after the hour, add an extra 6 hours to give some time for the service
50    /// to stop using it."
51    const HS_DESC_CERT_LIFETIME_SEC: Duration = Duration::from_secs(54 * 60 * 60);
52
53    let intro_points = ipt_set
54        .ipts
55        .iter()
56        .map(|ipt_in_set| ipt_in_set.ipt.clone())
57        .collect::<Vec<_>>();
58
59    let nickname = &config.nickname;
60
61    let svc_key_spec = HsIdPublicKeySpecifier::new(nickname.clone());
62    let hsid = keymgr
63        .get::<HsIdKey>(&svc_key_spec)?
64        .ok_or_else(|| FatalError::MissingHsIdKeypair(nickname.clone()))?;
65
66    // TODO: make the keystore selector configurable
67    let keystore_selector = Default::default();
68    let blind_id_kp = read_blind_id_keypair(keymgr, nickname, period)?
69        .ok_or_else(|| internal!("hidden service offline mode not supported"))?;
70
71    let blind_id_key = HsBlindIdKey::from(&blind_id_kp);
72    let subcredential = hsid.compute_subcredential(&blind_id_key, period);
73
74    let hs_desc_sign_key_spec = DescSigningKeypairSpecifier::new(nickname.clone(), period);
75    let hs_desc_sign = keymgr.get_or_generate::<HsDescSigningKeypair>(
76        &hs_desc_sign_key_spec,
77        keystore_selector,
78        key_rng,
79    )?;
80
81    // TODO #1028: support introduction-layer authentication.
82    let auth_required = None;
83
84    // TODO(#727): add support for single onion services
85    let is_single_onion_service = false;
86
87    // TODO (#955): perhaps the certificates should be read from the keystore, rather than created
88    // when building the descriptor. See #1048
89    let intro_auth_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
90    let intro_enc_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
91    let hs_desc_sign_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
92
93    cfg_if::cfg_if! {
94        if #[cfg(feature = "restricted-discovery")] {
95            let auth_clients: Option<Vec<curve25519::PublicKey>> = authorized_clients
96                .as_ref()
97                .map(|authorized_clients| {
98                    if authorized_clients.is_empty() {
99                        return Err(internal!("restricted discovery enabled, but no authorized clients?!"));
100                    }
101                    let auth_clients = authorized_clients
102                        .iter()
103                        .map(|(nickname, key)| {
104                            trace!("encrypting descriptor for client {nickname}");
105                            (*key).clone().into()
106                        })
107                        .collect_vec();
108                    Ok(auth_clients)
109                })
110                .transpose()?;
111        } else {
112            let auth_clients: Option<Vec<curve25519::PublicKey>> = None;
113        }
114    }
115
116    if let Some(ref auth_clients) = auth_clients {
117        debug!("Encrypting descriptor for {} clients", auth_clients.len());
118    }
119
120    let desc_signing_key_cert = create_desc_sign_key_cert(
121        &hs_desc_sign.as_ref().verifying_key(),
122        &blind_id_kp,
123        hs_desc_sign_cert_expiry,
124    )
125    .map_err(into_bad_api_usage!(
126        "failed to sign the descriptor signing key"
127    ))?;
128
129    let blind_id_kp = (&blind_id_kp).into();
130
131    let mut desc = HsDescBuilder::default()
132        .blinded_id(&blind_id_kp)
133        .hs_desc_sign(hs_desc_sign.as_ref())
134        .hs_desc_sign_cert(desc_signing_key_cert)
135        .create2_formats(CREATE2_FORMATS)
136        .auth_required(auth_required)
137        .is_single_onion_service(is_single_onion_service)
138        .intro_points(&intro_points[..])
139        .intro_auth_key_cert_expiry(intro_auth_key_cert_expiry)
140        .intro_enc_key_cert_expiry(intro_enc_key_cert_expiry)
141        .lifetime(((ipt_set.lifetime.as_secs() / 60) as u16).into())
142        .revision_counter(revision_counter)
143        .subcredential(subcredential)
144        .auth_clients(auth_clients.as_deref())
145        .max_generated_len(max_hsdesc_len);
146
147    #[cfg(feature = "negotiate-extensions")]
148    {
149        // We support negotiating protocol extensions, so we're going to advertise what we support.
150        use crate::caps;
151
152        // TODO #2594: In theory we should associate the sendme_inc with our intro points,
153        // and rotate them if the sendme_inc value changes.
154        //
155        // (In practice the lack of negotiation would cause temporary trouble in the future if
156        // the value changes between what we publish and what we use, but we are pretty sure
157        // that we won't actually change the value any time soon.
158        // See <https://gitlab.torproject.org/tpo/core/torspec/-/work_items/421> for
159        // discussion and possible solutions.)
160        desc = desc.flow_control(caps::declared_flowctrl(netparams));
161
162        desc = desc.supported_protocols(caps::declared_protocols());
163    }
164
165    cfg_if::cfg_if! {
166        if #[cfg(feature = "hs-pow-full")] {
167            let pow_params = pow_manager.get_pow_params(period, &mut rand::rng());
168            match pow_params {
169                Ok(ref pow_params) => {
170                    if config.enable_pow {
171                        desc = desc.pow_params(Some(pow_params));
172                    }
173                },
174                Err(err) => {
175                    warn!(?err, "Couldn't get PoW params");
176                }
177            }
178        }
179    }
180
181    let desc = desc.build_sign(rng).map_err(|e| match e {
182        tor_bytes::EncodeError::BadLengthValue => FatalError::HsDescTooLong,
183        e => into_internal!("failed to build descriptor")(e).into(),
184    })?;
185
186    Ok(VersionedDescriptor {
187        desc,
188        revision_counter,
189    })
190}
191
192/// The freshness status of a descriptor at a particular HsDir.
193#[derive(Copy, Clone, Debug, Default, PartialEq)]
194pub(super) enum DescriptorStatus {
195    #[default]
196    /// Dirty, needs to be (re)uploaded.
197    Dirty,
198    /// Clean, does not need to be reuploaded.
199    Clean,
200}
201
202/// A descriptor and its revision.
203#[derive(Clone)]
204pub(super) struct VersionedDescriptor {
205    /// The serialized descriptor.
206    pub(super) desc: String,
207    /// The revision counter.
208    pub(super) revision_counter: RevisionCounter,
209}