1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
//! 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<KeyMgr>,
pow_manager: &Arc<PowManager<R>>,
config: &Arc<OnionServiceConfigPublisherView>,
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<VersionedDescriptor, FatalError> {
// 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
// <https://gitlab.torproject.org/tpo/core/torspec/-/work_items/420>.
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::<Vec<_>>();
let nickname = &config.nickname;
let svc_key_spec = HsIdPublicKeySpecifier::new(nickname.clone());
let hsid = keymgr
.get::<HsIdKey>(&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::<HsDescSigningKeypair>(
&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<Vec<curve25519::PublicKey>> = 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<Vec<curve25519::PublicKey>> = 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 <https://gitlab.torproject.org/tpo/core/torspec/-/work_items/421> 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,
}
|