tor_hsservice/config/restricted_discovery/
key_provider.rs1use crate::config::restricted_discovery::HsClientNickname;
4use crate::internal_prelude::*;
5use derive_more::From;
6
7use std::collections::BTreeMap;
8use std::fs::DirEntry;
9
10use derive_more::{AsRef, Into};
11use fs_mistrust::{CheckedDir, Mistrust, MistrustBuilder};
12
13use amplify::Getters;
14use serde_with::DisplayFromStr;
15
16use tor_config::define_list_builder_helper;
17use tor_config::derive::prelude::*;
18use tor_config::extend_builder::extend_with_replace;
19use tor_config::mistrust::BuilderExt as _;
20use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
21use tor_error::warn_report;
22use tor_hscrypto::pk::HsClientDescEncKeyParseError;
23use tor_persist::slug::BadSlug;
24
25#[serde_with::serde_as]
27#[derive(Default, Debug, Clone, Eq, PartialEq)] #[derive(Into, From, AsRef, Serialize, Deserialize)]
29pub struct StaticKeyProvider(
30 #[serde_as(as = "BTreeMap<DisplayFromStr, DisplayFromStr>")]
31 BTreeMap<HsClientNickname, HsClientDescEncKey>,
32);
33
34define_list_builder_helper! {
35 #[derive(Eq, PartialEq)]
36 pub struct StaticKeyProviderBuilder {
37 keys : [(HsClientNickname, HsClientDescEncKey)],
38 }
39 built: StaticKeyProvider = build_static(keys)?;
40 default = vec![];
41 item_build: |value| Ok(value.clone());
42 item_apply_defaults: |_| Ok::<_, tor_config::ConfigBuildError>(());
43 #[serde(try_from = "StaticKeyProvider", into = "StaticKeyProvider")]
44}
45
46impl TryFrom<StaticKeyProvider> for StaticKeyProviderBuilder {
47 type Error = ConfigBuildError;
48
49 fn try_from(value: StaticKeyProvider) -> Result<Self, Self::Error> {
50 let mut list_builder = StaticKeyProviderBuilder::default();
51 for (nickname, key) in value.0 {
52 list_builder.access().push((nickname, key));
53 }
54 Ok(list_builder)
55 }
56}
57
58impl From<StaticKeyProviderBuilder> for StaticKeyProvider {
59 fn from(value: StaticKeyProviderBuilder) -> Self {
65 let mut map = BTreeMap::new();
66 for (nickname, key) in value.keys.into_iter().flatten() {
67 map.insert(nickname, key);
68 }
69 Self(map)
70 }
71}
72
73fn build_static(
77 keys: Vec<(HsClientNickname, HsClientDescEncKey)>,
78) -> Result<StaticKeyProvider, ConfigBuildError> {
79 let mut key_map = BTreeMap::new();
80
81 for (nickname, key) in keys.into_iter() {
82 if key_map.insert(nickname.clone(), key).is_some() {
83 return Err(ConfigBuildError::Invalid {
84 field: "keys".into(),
85 problem: format!("Multiple client keys for nickname {nickname}"),
86 });
87 };
88 }
89
90 Ok(StaticKeyProvider(key_map))
91}
92
93#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters)]
99#[derive_deftly(TorConfig)]
100#[deftly(tor_config(no_default_trait))]
101pub struct DirectoryKeyProvider {
102 #[deftly(tor_config(no_default))]
104 path: CfgPath,
105
106 #[deftly(tor_config(
108 sub_builder(build_fn = "build_for_arti"),
109 extend_with = "extend_with_replace"
110 ))]
111 permissions: Mistrust,
112}
113
114pub type DirectoryKeyProviderList = Vec<DirectoryKeyProvider>;
116
117define_list_builder_helper! {
118 pub struct DirectoryKeyProviderListBuilder {
119 key_dirs: [DirectoryKeyProviderBuilder],
120 }
121 built: DirectoryKeyProviderList = key_dirs;
122 default = vec![];
123}
124
125impl DirectoryKeyProvider {
126 pub(super) fn read_keys(
128 &self,
129 path_resolver: &CfgPathResolver,
130 ) -> Result<Vec<(HsClientNickname, HsClientDescEncKey)>, DirectoryKeyProviderError> {
131 let dir_path = self.path.path(path_resolver).map_err(|err| {
132 DirectoryKeyProviderError::PathExpansionFailed {
133 path: self.path.clone(),
134 err,
135 }
136 })?;
137
138 let checked_dir = self
139 .permissions
140 .verifier()
141 .secure_dir(&dir_path)
142 .map_err(|err| DirectoryKeyProviderError::FsMistrust {
143 path: dir_path.clone(),
144 err,
145 })?;
146
147 Ok(fs::read_dir(checked_dir.as_path())
149 .map_err(|e| DirectoryKeyProviderError::IoError(Arc::new(e)))?
150 .flat_map(|entry| match read_key_file(&checked_dir, entry) {
151 Ok((client_nickname, key)) => Some((client_nickname, key)),
152 Err(e) => {
153 warn_report!(e, "Failed to read client discovery key",);
154 None
155 }
156 })
157 .collect_vec())
158 }
159}
160
161fn read_key_file(
163 checked_dir: &CheckedDir,
164 entry: io::Result<DirEntry>,
165) -> Result<(HsClientNickname, HsClientDescEncKey), DirectoryKeyProviderError> {
166 const KEY_EXTENSION: &str = "auth";
168
169 let entry = entry.map_err(|e| DirectoryKeyProviderError::IoError(Arc::new(e)))?;
170
171 if entry.path().is_dir() {
172 return Err(DirectoryKeyProviderError::InvalidKeyDirectoryEntry {
173 path: entry.path(),
174 problem: "entry is a directory".into(),
175 });
176 }
177
178 let file_name = entry.file_name();
179 let file_name: &Path = file_name.as_ref();
180 let extension = file_name.extension().and_then(|e| e.to_str());
181 if extension != Some(KEY_EXTENSION) {
182 return Err(DirectoryKeyProviderError::InvalidKeyDirectoryEntry {
183 path: file_name.into(),
184 problem: "invalid extension (file must end in .auth)".into(),
185 });
186 }
187
188 let client_nickname = file_name
191 .file_stem()
192 .and_then(|e| e.to_str())
193 .unwrap_or_default();
194 let client_nickname = HsClientNickname::from_str(client_nickname)?;
195
196 let key = checked_dir.read_to_string(file_name).map_err(|err| {
197 DirectoryKeyProviderError::FsMistrust {
198 path: entry.path(),
199 err,
200 }
201 })?;
202
203 let parsed_key = HsClientDescEncKey::from_str(key.trim()).map_err(|err| {
204 DirectoryKeyProviderError::KeyParse {
205 path: entry.path(),
206 err,
207 }
208 })?;
209
210 Ok((client_nickname, parsed_key))
211}
212
213#[derive(Debug, Clone, thiserror::Error)]
215pub(super) enum DirectoryKeyProviderError {
216 #[error("Inaccessible path or bad permissions on {path}")]
218 FsMistrust {
219 path: PathBuf,
221 #[source]
223 err: fs_mistrust::Error,
224 },
225
226 #[error("IO error while reading discovery keys")]
228 IoError(#[source] Arc<io::Error>),
229
230 #[error("Failed to expand path {path}")]
232 PathExpansionFailed {
233 path: CfgPath,
235 #[source]
237 err: CfgPathError,
238 },
239
240 #[error("{path} is not a valid key entry: {problem}")]
242 InvalidKeyDirectoryEntry {
243 path: PathBuf,
245 problem: String,
247 },
248
249 #[error("Invalid client nickname")]
251 ClientNicknameParse(#[from] BadSlug),
252
253 #[error("Failed to parse key at {path}")]
255 KeyParse {
256 path: PathBuf,
258 #[source]
260 err: HsClientDescEncKeyParseError,
261 },
262}