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 #[serde(try_from = "StaticKeyProvider", into = "StaticKeyProvider")]
43}
44
45impl TryFrom<StaticKeyProvider> for StaticKeyProviderBuilder {
46 type Error = ConfigBuildError;
47
48 fn try_from(value: StaticKeyProvider) -> Result<Self, Self::Error> {
49 let mut list_builder = StaticKeyProviderBuilder::default();
50 for (nickname, key) in value.0 {
51 list_builder.access().push((nickname, key));
52 }
53 Ok(list_builder)
54 }
55}
56
57impl From<StaticKeyProviderBuilder> for StaticKeyProvider {
58 fn from(value: StaticKeyProviderBuilder) -> Self {
64 let mut map = BTreeMap::new();
65 for (nickname, key) in value.keys.into_iter().flatten() {
66 map.insert(nickname, key);
67 }
68 Self(map)
69 }
70}
71
72fn build_static(
76 keys: Vec<(HsClientNickname, HsClientDescEncKey)>,
77) -> Result<StaticKeyProvider, ConfigBuildError> {
78 let mut key_map = BTreeMap::new();
79
80 for (nickname, key) in keys.into_iter() {
81 if key_map.insert(nickname.clone(), key).is_some() {
82 return Err(ConfigBuildError::Invalid {
83 field: "keys".into(),
84 problem: format!("Multiple client keys for nickname {nickname}"),
85 });
86 };
87 }
88
89 Ok(StaticKeyProvider(key_map))
90}
91
92#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters)]
98#[derive_deftly(TorConfig)]
99#[deftly(tor_config(no_default_trait))]
100pub struct DirectoryKeyProvider {
101 #[deftly(tor_config(no_default))]
103 path: CfgPath,
104
105 #[deftly(tor_config(
107 sub_builder(build_fn = "build_for_arti"),
108 extend_with = "extend_with_replace"
109 ))]
110 permissions: Mistrust,
111}
112
113pub type DirectoryKeyProviderList = Vec<DirectoryKeyProvider>;
115
116define_list_builder_helper! {
117 pub struct DirectoryKeyProviderListBuilder {
118 key_dirs: [DirectoryKeyProviderBuilder],
119 }
120 built: DirectoryKeyProviderList = key_dirs;
121 default = vec![];
122}
123
124impl DirectoryKeyProvider {
125 pub(super) fn read_keys(
127 &self,
128 path_resolver: &CfgPathResolver,
129 ) -> Result<Vec<(HsClientNickname, HsClientDescEncKey)>, DirectoryKeyProviderError> {
130 let dir_path = self.path.path(path_resolver).map_err(|err| {
131 DirectoryKeyProviderError::PathExpansionFailed {
132 path: self.path.clone(),
133 err,
134 }
135 })?;
136
137 let checked_dir = self
138 .permissions
139 .verifier()
140 .secure_dir(&dir_path)
141 .map_err(|err| DirectoryKeyProviderError::FsMistrust {
142 path: dir_path.clone(),
143 err,
144 })?;
145
146 Ok(fs::read_dir(checked_dir.as_path())
148 .map_err(|e| DirectoryKeyProviderError::IoError(Arc::new(e)))?
149 .flat_map(|entry| match read_key_file(&checked_dir, entry) {
150 Ok((client_nickname, key)) => Some((client_nickname, key)),
151 Err(e) => {
152 warn_report!(e, "Failed to read client discovery key",);
153 None
154 }
155 })
156 .collect_vec())
157 }
158}
159
160fn read_key_file(
162 checked_dir: &CheckedDir,
163 entry: io::Result<DirEntry>,
164) -> Result<(HsClientNickname, HsClientDescEncKey), DirectoryKeyProviderError> {
165 const KEY_EXTENSION: &str = "auth";
167
168 let entry = entry.map_err(|e| DirectoryKeyProviderError::IoError(Arc::new(e)))?;
169
170 if entry.path().is_dir() {
171 return Err(DirectoryKeyProviderError::InvalidKeyDirectoryEntry {
172 path: entry.path(),
173 problem: "entry is a directory".into(),
174 });
175 }
176
177 let file_name = entry.file_name();
178 let file_name: &Path = file_name.as_ref();
179 let extension = file_name.extension().and_then(|e| e.to_str());
180 if extension != Some(KEY_EXTENSION) {
181 return Err(DirectoryKeyProviderError::InvalidKeyDirectoryEntry {
182 path: file_name.into(),
183 problem: "invalid extension (file must end in .auth)".into(),
184 });
185 }
186
187 let client_nickname = file_name
190 .file_stem()
191 .and_then(|e| e.to_str())
192 .unwrap_or_default();
193 let client_nickname = HsClientNickname::from_str(client_nickname)?;
194
195 let key = checked_dir.read_to_string(file_name).map_err(|err| {
196 DirectoryKeyProviderError::FsMistrust {
197 path: entry.path(),
198 err,
199 }
200 })?;
201
202 let parsed_key = HsClientDescEncKey::from_str(key.trim()).map_err(|err| {
203 DirectoryKeyProviderError::KeyParse {
204 path: entry.path(),
205 err,
206 }
207 })?;
208
209 Ok((client_nickname, parsed_key))
210}
211
212#[derive(Debug, Clone, thiserror::Error)]
214pub(super) enum DirectoryKeyProviderError {
215 #[error("Inaccessible path or bad permissions on {path}")]
217 FsMistrust {
218 path: PathBuf,
220 #[source]
222 err: fs_mistrust::Error,
223 },
224
225 #[error("IO error while reading discovery keys")]
227 IoError(#[source] Arc<io::Error>),
228
229 #[error("Failed to expand path {path}")]
231 PathExpansionFailed {
232 path: CfgPath,
234 #[source]
236 err: CfgPathError,
237 },
238
239 #[error("{path} is not a valid key entry: {problem}")]
241 InvalidKeyDirectoryEntry {
242 path: PathBuf,
244 problem: String,
246 },
247
248 #[error("Invalid client nickname")]
250 ClientNicknameParse(#[from] BadSlug),
251
252 #[error("Failed to parse key at {path}")]
254 KeyParse {
255 path: PathBuf,
257 #[source]
259 err: HsClientDescEncKeyParseError,
260 },
261}