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