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 if let Ok(dir) = std::env::var("IJIMA_DIR") {
28 return Ok(PathBuf::from(dir).join(KEY_FILENAME));
29 }
30 let home = home_dir().ok_or_else(|| {
31 IjimaError::invalid_input("cannot resolve Ijima data dir: set IJIMA_DIR or HOME")
32 })?;
33 Ok(home.join(".ijima").join(KEY_FILENAME))
34}
35
36fn home_dir() -> Option<PathBuf> {
37 std::env::var_os("HOME")
38 .or_else(|| std::env::var_os("USERPROFILE"))
39 .map(PathBuf::from)
40}
41
42pub fn load_or_create(path: &Path) -> Result<[u8; 32]> {
52 schubert::crypto::KeyStore::load_or_create(path).map_err(|e| IjimaError::Store {
53 detail: format!("issuer key {}: {e}", path.display()),
54 })
55}
56
57pub fn load(path: &Path) -> Result<[u8; 32]> {
65 schubert::crypto::KeyStore::load(path).map_err(|e| IjimaError::Store {
66 detail: format!("issuer key {}: {e}", path.display()),
67 })
68}
69
70pub fn public_key_hex(seed: &[u8; 32]) -> String {
72 use ed25519_dalek::SigningKey;
73 SigningKey::from_bytes(seed)
74 .verifying_key()
75 .to_bytes()
76 .iter()
77 .map(|b| format!("{b:02x}"))
78 .collect()
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn load_or_create_then_load_round_trips() {
87 let dir = tempfile_dir();
88 let path = dir.join(KEY_FILENAME);
89 let created = load_or_create(&path).expect("create");
90 let loaded = load_or_create(&path).expect("load");
92 assert_eq!(created, loaded);
93 let direct = load(&path).expect("direct load");
94 assert_eq!(created, direct);
95 }
96
97 #[test]
98 fn public_key_hex_is_64_lowercase_chars() {
99 let seed = [1u8; 32];
100 let hex = public_key_hex(&seed);
101 assert_eq!(hex.len(), 64);
102 assert!(
103 hex.chars()
104 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
105 );
106 }
107
108 #[test]
109 fn wrong_size_key_file_rejected() {
110 let dir = tempfile_dir();
111 let path = dir.join(KEY_FILENAME);
112 std::fs::write(&path, b"too short").unwrap();
113 assert!(load(&path).is_err());
114 }
115
116 fn tempfile_dir() -> PathBuf {
117 let dir = std::env::temp_dir().join(format!(
118 "ijima-keystore-test-{}",
119 std::time::SystemTime::now()
120 .duration_since(std::time::UNIX_EPOCH)
121 .unwrap()
122 .as_nanos()
123 ));
124 std::fs::create_dir_all(&dir).unwrap();
125 dir
126 }
127}