tor_keymgr/keystore/ctor/
service.rs1use crate::keystore::ctor::err::{CTorKeystoreError, MalformedServiceKeyError};
6use crate::keystore::ctor::CTorKeystore;
7use crate::keystore::fs_utils::{checked_op, FilesystemAction, FilesystemError};
8use crate::keystore::{EncodableItem, ErasedKey, KeySpecifier, Keystore, KeystoreId};
9use crate::{CTorPath, CTorServicePath, KeyPath, Result};
10
11use fs_mistrust::Mistrust;
12use tor_basic_utils::PathExt as _;
13use tor_error::internal;
14use tor_key_forge::{KeyType, KeystoreItemType};
15use tor_llcrypto::pk::ed25519;
16use tor_persist::hsnickname::HsNickname;
17
18use std::path::{Path, PathBuf};
19use std::result::Result as StdResult;
20use std::sync::Arc;
21
22pub struct CTorServiceKeystore {
47 keystore: CTorKeystore,
49 nickname: HsNickname,
51}
52
53impl CTorServiceKeystore {
54 pub fn from_path_and_mistrust(
60 keystore_dir: impl AsRef<Path>,
61 mistrust: &Mistrust,
62 id: KeystoreId,
63 nickname: HsNickname,
64 ) -> Result<Self> {
65 let keystore = CTorKeystore::from_path_and_mistrust(keystore_dir, mistrust, id)?;
66
67 Ok(Self { keystore, nickname })
68 }
69}
70
71macro_rules! rel_path_if_supported {
76 ($self:expr, $spec:expr, $ret:expr, $item_type:expr) => {{
77 use KeystoreItemType::*;
78
79 let Some(ctor_path) = $spec.ctor_path() else {
82 return $ret;
83 };
84
85 let CTorPath::Service { path, nickname } = ctor_path else {
87 return $ret;
88 };
89
90 if nickname != $self.nickname {
93 return $ret;
94 };
95
96 let relpath = $self.keystore.rel_path(PathBuf::from(path.to_string()));
97 match ($item_type, &path) {
98 (Key(KeyType::Ed25519ExpandedKeypair), CTorServicePath::PrivateKey)
99 | (Key(KeyType::Ed25519PublicKey), CTorServicePath::PublicKey) => Ok(()),
100 _ => Err(CTorKeystoreError::InvalidKeystoreItemType {
101 item_type: $item_type.clone(),
102 item: format!("key {}", relpath.rel_path_unchecked().display_lossy()),
103 }),
104 }?;
105
106 relpath
107 }};
108}
109
110impl Keystore for CTorServiceKeystore {
111 fn id(&self) -> &KeystoreId {
112 &self.keystore.id
113 }
114
115 fn contains(&self, key_spec: &dyn KeySpecifier, item_type: &KeystoreItemType) -> Result<bool> {
116 let path = rel_path_if_supported!(self, key_spec, Ok(false), item_type);
117
118 let meta = match checked_op!(metadata, path) {
119 Ok(meta) => meta,
120 Err(fs_mistrust::Error::NotFound(_)) => return Ok(false),
121 Err(e) => {
122 return Err(FilesystemError::FsMistrust {
123 action: FilesystemAction::Read,
124 path: path.rel_path_unchecked().into(),
125 err: e.into(),
126 })
127 .map_err(|e| CTorKeystoreError::Filesystem(e).into());
128 }
129 };
130
131 if meta.is_file() {
133 Ok(true)
134 } else {
135 Err(
136 CTorKeystoreError::Filesystem(FilesystemError::NotARegularFile(
137 path.rel_path_unchecked().into(),
138 ))
139 .into(),
140 )
141 }
142 }
143
144 fn get(
145 &self,
146 key_spec: &dyn KeySpecifier,
147 item_type: &KeystoreItemType,
148 ) -> Result<Option<ErasedKey>> {
149 use KeystoreItemType::*;
150
151 let path = rel_path_if_supported!(self, key_spec, Ok(None), item_type);
152
153 let key = match checked_op!(read, path) {
154 Err(fs_mistrust::Error::NotFound(_)) => return Ok(None),
155 res => res
156 .map_err(|err| FilesystemError::FsMistrust {
157 action: FilesystemAction::Read,
158 path: path.rel_path_unchecked().into(),
159 err: err.into(),
160 })
161 .map_err(CTorKeystoreError::Filesystem)?,
162 };
163
164 let parse_err = |err: MalformedServiceKeyError| CTorKeystoreError::MalformedKey {
165 path: path.rel_path_unchecked().into(),
166 err: err.into(),
167 };
168
169 let parsed_key: ErasedKey = match item_type {
170 Key(KeyType::Ed25519ExpandedKeypair) => parse_ed25519_keypair(&key)
171 .map_err(parse_err)
172 .map(Box::new)?,
173 Key(KeyType::Ed25519PublicKey) => parse_ed25519_public(&key)
174 .map_err(parse_err)
175 .map(Box::new)?,
176 _ => {
177 return Err(
178 internal!("item type was not validated by rel_path_if_supported?!").into(),
179 );
180 }
181 };
182
183 Ok(Some(parsed_key))
184 }
185
186 fn insert(
187 &self,
188 _key: &dyn EncodableItem,
189 _key_spec: &dyn KeySpecifier,
190 _item_type: &KeystoreItemType,
191 ) -> Result<()> {
192 Err(CTorKeystoreError::NotSupported { action: "insert" }.into())
193 }
194
195 fn remove(
196 &self,
197 _key_spec: &dyn KeySpecifier,
198 _item_type: &KeystoreItemType,
199 ) -> Result<Option<()>> {
200 Err(CTorKeystoreError::NotSupported { action: "remove" }.into())
201 }
202
203 fn list(&self) -> Result<Vec<(KeyPath, KeystoreItemType)>> {
204 use crate::CTorServicePath::*;
205 use itertools::Itertools;
206
207 let all_keys = [
210 (
211 CTorPath::Service {
212 nickname: self.nickname.clone(),
213 path: PublicKey,
214 },
215 KeyType::Ed25519PublicKey.into(),
216 ),
217 (
218 CTorPath::Service {
219 nickname: self.nickname.clone(),
220 path: PrivateKey,
221 },
222 KeyType::Ed25519ExpandedKeypair.into(),
223 ),
224 ];
225
226 all_keys
227 .into_iter()
228 .map(|(path, key_type)| {
229 self.contains(&path, &key_type)
230 .map(|res: bool| (path, key_type, res))
231 })
232 .filter_map_ok(|(path, key_type, res)| res.then_some((path.into(), key_type)))
233 .collect()
234 }
235}
236
237macro_rules! parse_ed25519 {
239 ($key:expr, $parse_fn:expr, $tag:expr, $key_len:expr) => {{
240 let expected_len = $tag.len() + $key_len;
241
242 if $key.len() != expected_len {
243 return Err(MalformedServiceKeyError::InvalidKeyLen {
244 len: $key.len(),
245 expected_len,
246 });
247 }
248
249 let (tag, key) = $key.split_at($tag.len());
250
251 if tag != $tag {
252 return Err(MalformedServiceKeyError::InvalidTag {
253 tag: tag.to_vec(),
254 expected_tag: $tag.into(),
255 });
256 }
257
258 ($parse_fn)(key)
259 }};
260}
261
262fn parse_ed25519_public(key: &[u8]) -> StdResult<ed25519::PublicKey, MalformedServiceKeyError> {
264 const PUBKEY_TAG: &[u8] = b"== ed25519v1-public: type0 ==\0\0\0";
266 const PUBKEY_LEN: usize = 32;
268
269 parse_ed25519!(
270 key,
271 |key| ed25519::PublicKey::try_from(key)
272 .map_err(|e| MalformedServiceKeyError::from(Arc::new(e))),
273 PUBKEY_TAG,
274 PUBKEY_LEN
275 )
276}
277
278fn parse_ed25519_keypair(
280 key: &[u8],
281) -> StdResult<ed25519::ExpandedKeypair, MalformedServiceKeyError> {
282 const KEYPAIR_TAG: &[u8] = b"== ed25519v1-secret: type0 ==\0\0\0";
284 const KEYPAIR_LEN: usize = 64;
286
287 parse_ed25519!(
288 key,
289 |key: &[u8]| {
290 let key: [u8; 64] = key
291 .try_into()
292 .map_err(|_| internal!("bad length on expanded ed25519 secret key "))?;
293 ed25519::ExpandedKeypair::from_secret_key_bytes(key)
294 .ok_or(MalformedServiceKeyError::Ed25519Keypair)
295 },
296 KEYPAIR_TAG,
297 KEYPAIR_LEN
298 )
299}
300
301#[cfg(test)]
302mod tests {
303 #![allow(clippy::bool_assert_comparison)]
305 #![allow(clippy::clone_on_copy)]
306 #![allow(clippy::dbg_macro)]
307 #![allow(clippy::mixed_attributes_style)]
308 #![allow(clippy::print_stderr)]
309 #![allow(clippy::print_stdout)]
310 #![allow(clippy::single_char_pattern)]
311 #![allow(clippy::unwrap_used)]
312 #![allow(clippy::unchecked_duration_subtraction)]
313 #![allow(clippy::useless_vec)]
314 #![allow(clippy::needless_pass_by_value)]
315 use super::*;
318 use std::fs;
319 use std::str::FromStr as _;
320 use tempfile::{tempdir, TempDir};
321
322 use crate::test_utils::{assert_found, DummyKey, TestCTorSpecifier};
323 use crate::CTorServicePath;
324
325 const PUBKEY: &[u8] = include_bytes!("../../../testdata/tor-service/hs_ed25519_public_key");
326 const PRIVKEY: &[u8] = include_bytes!("../../../testdata/tor-service/hs_ed25519_secret_key");
327
328 #[cfg(unix)]
329 use std::os::unix::fs::PermissionsExt;
330
331 fn init_keystore(id: &str, nickname: &str) -> (CTorServiceKeystore, TempDir) {
332 let keystore_dir = tempdir().unwrap();
333
334 #[cfg(unix)]
335 fs::set_permissions(&keystore_dir, fs::Permissions::from_mode(0o700)).unwrap();
336
337 let id = KeystoreId::from_str(id).unwrap();
338 let nickname = HsNickname::from_str(nickname).unwrap();
339 let keystore = CTorServiceKeystore::from_path_and_mistrust(
340 &keystore_dir,
341 &Mistrust::default(),
342 id,
343 nickname,
344 )
345 .unwrap();
346
347 const KEYS: &[(&str, &[u8])] = &[
348 ("hs_ed25519_public_key", PUBKEY),
349 ("hs_ed25519_secret_key", PRIVKEY),
350 ];
351
352 for (name, key) in KEYS {
353 fs::write(keystore_dir.path().join(name), key).unwrap();
354 }
355
356 (keystore, keystore_dir)
357 }
358
359 #[test]
360 fn get() {
361 let (keystore, _keystore_dir) = init_keystore("foo", "allium-cepa");
362
363 let unk_nickname = HsNickname::new("acutus-cepa".into()).unwrap();
364 let path = CTorPath::Service {
365 nickname: unk_nickname.clone(),
366 path: CTorServicePath::PublicKey,
367 };
368
369 assert_found!(
371 keystore,
372 &TestCTorSpecifier(path.clone()),
373 &KeyType::Ed25519PublicKey,
374 false
375 );
376
377 let path = CTorPath::Service {
380 nickname: keystore.nickname.clone(),
381 path: CTorServicePath::PublicKey,
382 };
383 assert_found!(
384 keystore,
385 &TestCTorSpecifier(path.clone()),
386 &KeyType::Ed25519PublicKey,
387 true
388 );
389
390 let path = CTorPath::Service {
391 nickname: keystore.nickname.clone(),
392 path: CTorServicePath::PrivateKey,
393 };
394 assert_found!(
395 keystore,
396 &TestCTorSpecifier(path.clone()),
397 &KeyType::Ed25519ExpandedKeypair,
398 true
399 );
400 }
401
402 #[test]
403 fn unsupported_operation() {
404 let (keystore, _keystore_dir) = init_keystore("foo", "allium-cepa");
405 let path = CTorPath::Service {
406 nickname: keystore.nickname.clone(),
407 path: CTorServicePath::PublicKey,
408 };
409
410 let err = keystore
411 .remove(
412 &TestCTorSpecifier(path.clone()),
413 &KeyType::Ed25519PublicKey.into(),
414 )
415 .unwrap_err();
416
417 assert_eq!(err.to_string(), "Operation not supported: remove");
418
419 let err = keystore
420 .insert(
421 &DummyKey,
422 &TestCTorSpecifier(path.clone()),
423 &KeyType::Ed25519PublicKey.into(),
424 )
425 .unwrap_err();
426
427 assert_eq!(err.to_string(), "Operation not supported: insert");
428 }
429
430 #[test]
431 fn wrong_keytype() {
432 let (keystore, _keystore_dir) = init_keystore("foo", "allium-cepa");
433
434 let path = CTorPath::Service {
435 nickname: keystore.nickname.clone(),
436 path: CTorServicePath::PublicKey,
437 };
438
439 let err = keystore
440 .get(
441 &TestCTorSpecifier(path.clone()),
442 &KeyType::X25519StaticKeypair.into(),
443 )
444 .map(|_| ())
445 .unwrap_err();
446
447 assert_eq!(
448 err.to_string(),
449 "Invalid item type X25519StaticKeypair for key hs_ed25519_public_key"
450 );
451 }
452
453 #[test]
454 fn list() {
455 let (keystore, _keystore_dir) = init_keystore("foo", "allium-cepa");
456 let keys: Vec<_> = keystore.list().unwrap();
457
458 assert_eq!(keys.len(), 2);
459
460 assert!(keys
461 .iter()
462 .any(|(_, key_type)| *key_type == KeyType::Ed25519ExpandedKeypair.into()));
463
464 assert!(keys
465 .iter()
466 .any(|(_, key_type)| *key_type == KeyType::Ed25519PublicKey.into()));
467 }
468}