//! Helpers for building and representing hidden service descriptors. use super::*; use crate::config::OnionServiceConfigPublisherView; use tor_cell::chancell::msg::HandshakeType; use tor_llcrypto::rng::EntropicRng; use tor_netdir::params::NetParameters; /// Build the descriptor. /// /// The `now` argument is used for computing the expiry of the `intro_{auth, enc}_key_cert` /// certificates included in the descriptor. The expiry will be set to 54 hours from `now`. /// /// Note: `blind_id_kp` is the blinded hidden service signing keypair used to sign descriptor /// signing keys (KP_hs_blind_id, KS_hs_blind_id). #[allow(clippy::too_many_arguments)] pub(super) fn build_sign< Rng: rand::Rng + CryptoRng, KeyRng: rand::Rng + EntropicRng, R: Runtime, >( keymgr: &Arc, pow_manager: &Arc>, config: &Arc, netparams: &NetParameters, authorized_clients: Option<&RestrictedDiscoveryKeys>, ipt_set: &IptSet, period: TimePeriod, revision_counter: RevisionCounter, rng: &mut Rng, key_rng: &mut KeyRng, now: SystemTime, max_hsdesc_len: usize, ) -> Result { // TODO: should this be configurable? If so, we should read it from the svc config. // /// The CREATE handshake type we support. // // NOTE: This list may be useless. See // . const CREATE2_FORMATS: &[HandshakeType] = &[HandshakeType::NTOR]; /// Lifetime of the intro_{auth, enc}_key_cert certificates in the descriptor. /// /// From C-Tor src/feature/hs/hs_descriptor.h: /// /// "This defines the lifetime of the descriptor signing key and the cross certification cert of /// that key. It is set to 54 hours because a descriptor can be around for 48 hours and because /// consensuses are used after the hour, add an extra 6 hours to give some time for the service /// to stop using it." const HS_DESC_CERT_LIFETIME_SEC: Duration = Duration::from_secs(54 * 60 * 60); let intro_points = ipt_set .ipts .iter() .map(|ipt_in_set| ipt_in_set.ipt.clone()) .collect::>(); let nickname = &config.nickname; let svc_key_spec = HsIdPublicKeySpecifier::new(nickname.clone()); let hsid = keymgr .get::(&svc_key_spec)? .ok_or_else(|| FatalError::MissingHsIdKeypair(nickname.clone()))?; // TODO: make the keystore selector configurable let keystore_selector = Default::default(); let blind_id_kp = read_blind_id_keypair(keymgr, nickname, period)? .ok_or_else(|| internal!("hidden service offline mode not supported"))?; let blind_id_key = HsBlindIdKey::from(&blind_id_kp); let subcredential = hsid.compute_subcredential(&blind_id_key, period); let hs_desc_sign_key_spec = DescSigningKeypairSpecifier::new(nickname.clone(), period); let hs_desc_sign = keymgr.get_or_generate::( &hs_desc_sign_key_spec, keystore_selector, key_rng, )?; // TODO #1028: support introduction-layer authentication. let auth_required = None; // TODO(#727): add support for single onion services let is_single_onion_service = false; // TODO (#955): perhaps the certificates should be read from the keystore, rather than created // when building the descriptor. See #1048 let intro_auth_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC; let intro_enc_key_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC; let hs_desc_sign_cert_expiry = now + HS_DESC_CERT_LIFETIME_SEC; cfg_if::cfg_if! { if #[cfg(feature = "restricted-discovery")] { let auth_clients: Option> = authorized_clients .as_ref() .map(|authorized_clients| { if authorized_clients.is_empty() { return Err(internal!("restricted discovery enabled, but no authorized clients?!")); } let auth_clients = authorized_clients .iter() .map(|(nickname, key)| { trace!("encrypting descriptor for client {nickname}"); (*key).clone().into() }) .collect_vec(); Ok(auth_clients) }) .transpose()?; } else { let auth_clients: Option> = None; } } if let Some(ref auth_clients) = auth_clients { debug!("Encrypting descriptor for {} clients", auth_clients.len()); } let desc_signing_key_cert = create_desc_sign_key_cert( &hs_desc_sign.as_ref().verifying_key(), &blind_id_kp, hs_desc_sign_cert_expiry, ) .map_err(into_bad_api_usage!( "failed to sign the descriptor signing key" ))?; let blind_id_kp = (&blind_id_kp).into(); let mut desc = HsDescBuilder::default() .blinded_id(&blind_id_kp) .hs_desc_sign(hs_desc_sign.as_ref()) .hs_desc_sign_cert(desc_signing_key_cert) .create2_formats(CREATE2_FORMATS) .auth_required(auth_required) .is_single_onion_service(is_single_onion_service) .intro_points(&intro_points[..]) .intro_auth_key_cert_expiry(intro_auth_key_cert_expiry) .intro_enc_key_cert_expiry(intro_enc_key_cert_expiry) .lifetime(((ipt_set.lifetime.as_secs() / 60) as u16).into()) .revision_counter(revision_counter) .subcredential(subcredential) .auth_clients(auth_clients.as_deref()) .max_generated_len(max_hsdesc_len); #[cfg(feature = "negotiate-extensions")] { // We support negotiating protocol extensions, so we're going to advertise what we support. use crate::caps; // TODO #2594: In theory we should associate the sendme_inc with our intro points, // and rotate them if the sendme_inc value changes. // // (In practice the lack of negotiation would cause temporary trouble in the future if // the value changes between what we publish and what we use, but we are pretty sure // that we won't actually change the value any time soon. // See for // discussion and possible solutions.) desc = desc.flow_control(caps::declared_flowctrl(netparams)); desc = desc.supported_protocols(caps::declared_protocols()); } cfg_if::cfg_if! { if #[cfg(feature = "hs-pow-full")] { let pow_params = pow_manager.get_pow_params(period, &mut rand::rng()); match pow_params { Ok(ref pow_params) => { if config.enable_pow { desc = desc.pow_params(Some(pow_params)); } }, Err(err) => { warn!(?err, "Couldn't get PoW params"); } } } } let desc = desc.build_sign(rng).map_err(|e| match e { tor_bytes::EncodeError::BadLengthValue => FatalError::HsDescTooLong, e => into_internal!("failed to build descriptor")(e).into(), })?; Ok(VersionedDescriptor { desc, revision_counter, }) } /// The freshness status of a descriptor at a particular HsDir. #[derive(Copy, Clone, Debug, Default, PartialEq)] pub(super) enum DescriptorStatus { #[default] /// Dirty, needs to be (re)uploaded. Dirty, /// Clean, does not need to be reuploaded. Clean, } /// A descriptor and its revision. #[derive(Clone)] pub(super) struct VersionedDescriptor { /// The serialized descriptor. pub(super) desc: String, /// The revision counter. pub(super) revision_counter: RevisionCounter, }