1//! Helpers for building and representing hidden service descriptors.
23use super::*;
4use crate::config::OnionServiceConfigPublisherView;
5use tor_cell::chancell::msg::HandshakeType;
6use tor_llcrypto::rng::EntropicRng;
78/// 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, R: Runtime>(
17 keymgr: &Arc<KeyMgr>,
18 pow_manager: &Arc<PowManager<R>>,
19 config: &Arc<OnionServiceConfigPublisherView>,
20 authorized_clients: Option<&RestrictedDiscoveryKeys>,
21 ipt_set: &IptSet,
22 period: TimePeriod,
23 revision_counter: RevisionCounter,
24 rng: &mut Rng,
25 key_rng: &mut KeyRng,
26 now: SystemTime,
27) -> Result<VersionedDescriptor, FatalError> {
28// TODO: should this be configurable? If so, we should read it from the svc config.
29 //
30/// The CREATE handshake type we support.
31const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::NTOR];
3233/// Lifetime of the intro_{auth, enc}_key_cert certificates in the descriptor.
34 ///
35 /// From C-Tor src/feature/hs/hs_descriptor.h:
36 ///
37 /// "This defines the lifetime of the descriptor signing key and the cross certification cert of
38 /// that key. It is set to 54 hours because a descriptor can be around for 48 hours and because
39 /// consensuses are used after the hour, add an extra 6 hours to give some time for the service
40 /// to stop using it."
41const HS_DESC_CERT_LIFETIME_SEC: Duration = Duration::from_secs(54 * 60 * 60);
4243let intro_points = ipt_set
44 .ipts
45 .iter()
46 .map(|ipt_in_set| ipt_in_set.ipt.clone())
47 .collect::<Vec<_>>();
4849let nickname = &config.nickname;
5051let svc_key_spec = HsIdPublicKeySpecifier::new(nickname.clone());
52let hsid = keymgr
53 .get::<HsIdKey>(&svc_key_spec)?
54.ok_or_else(|| FatalError::MissingHsIdKeypair(nickname.clone()))?;
5556// TODO: make the keystore selector configurable
57let keystore_selector = Default::default();
58let blind_id_kp = read_blind_id_keypair(keymgr, nickname, period)?
59.ok_or_else(|| internal!("hidden service offline mode not supported"))?;
6061let blind_id_key = HsBlindIdKey::from(&blind_id_kp);
62let subcredential = hsid.compute_subcredential(&blind_id_key, period);
6364let hs_desc_sign_key_spec = DescSigningKeypairSpecifier::new(nickname.clone(), period);
65let hs_desc_sign = keymgr.get_or_generate::<HsDescSigningKeypair>(
66&hs_desc_sign_key_spec,
67 keystore_selector,
68 key_rng,
69 )?;
7071// TODO #1028: support introduction-layer authentication.
72let auth_required = None;
7374// TODO(#727): add support for single onion services
75let is_single_onion_service = false;
7677// TODO (#955): perhaps the certificates should be read from the keystore, rather than created
78 // when building the descriptor. See #1048
79let intro_auth_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
80let intro_enc_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
81let hs_desc_sign_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC;
8283cfg_if::cfg_if! {
84if #[cfg(feature = "restricted-discovery")] {
85let auth_clients: Option<Vec<curve25519::PublicKey>> = authorized_clients
86 .as_ref()
87 .map(|authorized_clients| {
88if authorized_clients.is_empty() {
89return Err(internal!("restricted discovery enabled, but no authorized clients?!"));
90 }
91let auth_clients = authorized_clients
92 .iter()
93 .map(|(nickname, key)| {
94trace!("encrypting descriptor for client {nickname}");
95 (*key).clone().into()
96 })
97 .collect_vec();
98Ok(auth_clients)
99 })
100 .transpose()?;
101 } else {
102let auth_clients: Option<Vec<curve25519::PublicKey>> = None;
103 }
104 }
105106if let Some(ref auth_clients) = auth_clients {
107debug!("Encrypting descriptor for {} clients", auth_clients.len());
108 }
109110let desc_signing_key_cert = create_desc_sign_key_cert(
111&hs_desc_sign.as_ref().verifying_key(),
112&blind_id_kp,
113 hs_desc_sign_cert_expiry,
114 )
115 .map_err(into_bad_api_usage!(
116"failed to sign the descriptor signing key"
117))?;
118119let blind_id_kp = (&blind_id_kp).into();
120121let mut desc = HsDescBuilder::default()
122 .blinded_id(&blind_id_kp)
123 .hs_desc_sign(hs_desc_sign.as_ref())
124 .hs_desc_sign_cert(desc_signing_key_cert)
125 .create2_formats(CREATE2_FORMATS)
126 .auth_required(auth_required)
127 .is_single_onion_service(is_single_onion_service)
128 .intro_points(&intro_points[..])
129 .intro_auth_key_cert_expiry(intro_auth_key_cert_expiry)
130 .intro_enc_key_cert_expiry(intro_enc_key_cert_expiry)
131 .lifetime(((ipt_set.lifetime.as_secs() / 60) as u16).into())
132 .revision_counter(revision_counter)
133 .subcredential(subcredential)
134 .auth_clients(auth_clients.as_deref());
135136cfg_if::cfg_if! {
137if #[cfg(feature = "hs-pow-full")] {
138let pow_params = pow_manager.get_pow_params(period);
139match pow_params {
140Ok(ref pow_params) => {
141 desc = desc.pow_params(Some(pow_params));
142 },
143Err(err) => {
144warn!(?err, "Couldn't get PoW params");
145 }
146 }
147 }
148 }
149150let desc = desc
151 .build_sign(rng)
152 .map_err(|e| into_internal!("failed to build descriptor")(e))?;
153154Ok(VersionedDescriptor {
155 desc,
156 revision_counter,
157 })
158}
159160/// The freshness status of a descriptor at a particular HsDir.
161#[derive(Copy, Clone, Debug, Default, PartialEq)]
162pub(super) enum DescriptorStatus {
163#[default]
164/// Dirty, needs to be (re)uploaded.
165Dirty,
166/// Clean, does not need to be reuploaded.
167Clean,
168}
169170/// A descriptor and its revision.
171#[derive(Clone)]
172pub(super) struct VersionedDescriptor {
173/// The serialized descriptor.
174pub(super) desc: String,
175/// The revision counter.
176pub(super) revision_counter: RevisionCounter,
177}