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