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