ijima_server/
key_store.rs1use std::path::{Path, PathBuf};
14
15use ijima_core::{IjimaError, Result};
16
17const KEY_FILENAME: &str = "issuer.key";
18
19pub fn default_key_path() -> Result<PathBuf> {
27 let file = crate::config::load().ok();
30 if let Some(p) = crate::config::resolve_path("IJIMA_KEY", file.and_then(|c| c.issuer_key)) {
31 return Ok(p);
32 }
33 Ok(crate::config::resolve_data_dir()?.join(KEY_FILENAME))
34}
35
36pub fn load_or_create(path: &Path) -> Result<[u8; 32]> {
46 schubert::crypto::KeyStore::load_or_create(path).map_err(|e| IjimaError::Store {
47 detail: format!("issuer key {}: {e}", path.display()),
48 })
49}
50
51pub fn load(path: &Path) -> Result<[u8; 32]> {
59 schubert::crypto::KeyStore::load(path).map_err(|e| IjimaError::Store {
60 detail: format!("issuer key {}: {e}", path.display()),
61 })
62}
63
64pub fn public_key_hex(seed: &[u8; 32]) -> String {
66 use ed25519_dalek::SigningKey;
67 SigningKey::from_bytes(seed)
68 .verifying_key()
69 .to_bytes()
70 .iter()
71 .map(|b| format!("{b:02x}"))
72 .collect()
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn load_or_create_then_load_round_trips() {
81 let dir = tempfile_dir();
82 let path = dir.join(KEY_FILENAME);
83 let created = load_or_create(&path).expect("create");
84 let loaded = load_or_create(&path).expect("load");
86 assert_eq!(created, loaded);
87 let direct = load(&path).expect("direct load");
88 assert_eq!(created, direct);
89 }
90
91 #[test]
92 fn public_key_hex_is_64_lowercase_chars() {
93 let seed = [1u8; 32];
94 let hex = public_key_hex(&seed);
95 assert_eq!(hex.len(), 64);
96 assert!(
97 hex.chars()
98 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
99 );
100 }
101
102 #[test]
103 fn wrong_size_key_file_rejected() {
104 let dir = tempfile_dir();
105 let path = dir.join(KEY_FILENAME);
106 std::fs::write(&path, b"too short").unwrap();
107 assert!(load(&path).is_err());
108 }
109
110 fn tempfile_dir() -> PathBuf {
111 let dir = std::env::temp_dir().join(format!(
112 "ijima-keystore-test-{}",
113 std::time::SystemTime::now()
114 .duration_since(std::time::UNIX_EPOCH)
115 .unwrap()
116 .as_nanos()
117 ));
118 std::fs::create_dir_all(&dir).unwrap();
119 dir
120 }
121}