signatory/key/store/
fs.rs1use crate::{Error, KeyHandle, KeyInfo, KeyName, KeyRing, LoadPkcs8, Result};
4use pkcs8::der::pem::PemLabel;
5use std::{
6 fs,
7 path::{Path, PathBuf},
8};
9use zeroize::Zeroizing;
10
11#[cfg(unix)]
12use std::{fs::Permissions, os::unix::fs::PermissionsExt};
13
14#[cfg(unix)]
16const REQUIRED_DIR_MODE: u32 = 0o700;
17
18const PRIVATE_KEY_BOUNDARY: &str = "-----BEGIN PRIVATE KEY-----";
20
21const ENCRYPTED_PRIVATE_KEY_BOUNDARY: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
23
24pub struct FsKeyStore {
26 path: PathBuf,
27}
28
29impl FsKeyStore {
30 pub fn create_or_open(dir_path: &Path) -> Result<Self> {
33 Self::open(dir_path).or_else(|_| Self::create(dir_path))
34 }
35
36 pub fn create(dir_path: &Path) -> Result<Self> {
39 fs::create_dir_all(dir_path)?;
40
41 #[cfg(unix)]
42 fs::set_permissions(dir_path, Permissions::from_mode(REQUIRED_DIR_MODE))?;
43
44 Self::open(dir_path)
45 }
46
47 pub fn open(dir_path: &Path) -> Result<Self> {
51 let path = dir_path.canonicalize()?;
52 let st = path.metadata()?;
53
54 if !st.is_dir() {
55 return Err(Error::NotADirectory);
56 }
57
58 #[cfg(unix)]
59 if st.permissions().mode() & 0o777 != REQUIRED_DIR_MODE {
60 return Err(Error::Permissions);
61 }
62
63 Ok(Self { path })
64 }
65
66 pub fn info(&self, name: &KeyName) -> Result<KeyInfo> {
68 let pem_data = Zeroizing::new(fs::read_to_string(self.key_path(name))?);
69
70 let encrypted = if pem_data.starts_with(ENCRYPTED_PRIVATE_KEY_BOUNDARY) {
71 true
72 } else if pem_data.starts_with(PRIVATE_KEY_BOUNDARY) {
73 false
74 } else {
75 return Err(pkcs8::Error::KeyMalformed.into());
76 };
77
78 let algorithm = if encrypted {
79 None
80 } else {
81 let (label, der) = pkcs8::SecretDocument::from_pem(&pem_data)?;
82 pkcs8::PrivateKeyInfo::validate_pem_label(label)?;
83 der.decode_msg::<pkcs8::PrivateKeyInfo<'_>>()?
84 .algorithm
85 .try_into()
86 .ok()
87 };
88
89 Ok(KeyInfo {
90 name: name.clone(),
91 algorithm,
92 encrypted,
93 })
94 }
95
96 pub fn import(&self, name: &KeyName, key_ring: &mut KeyRing) -> Result<KeyHandle> {
98 key_ring.load_pkcs8(self.load(name)?.decode_msg()?)
99 }
100
101 pub fn load(&self, name: &KeyName) -> Result<pkcs8::SecretDocument> {
103 let (label, doc) = pkcs8::SecretDocument::read_pem_file(self.key_path(name))?;
104 pkcs8::PrivateKeyInfo::validate_pem_label(&label)?;
105 Ok(doc)
106 }
107
108 pub fn store(&self, name: &KeyName, der: &pkcs8::SecretDocument) -> Result<()> {
110 der.write_pem_file(
111 self.key_path(name),
112 pkcs8::PrivateKeyInfo::PEM_LABEL,
113 Default::default(),
114 )?;
115 Ok(())
116 }
117
118 pub fn delete(&self, name: &KeyName) -> Result<()> {
120 fs::remove_file(self.key_path(name))?;
121
122 Ok(())
123 }
124
125 fn key_path(&self, name: &KeyName) -> PathBuf {
127 let mut path = self.path.join(name);
128 path.set_extension("pem");
129 path
130 }
131}
132
133#[cfg(test)]
134#[allow(unused_imports)] mod tests {
136 use super::FsKeyStore;
137 use crate::{Algorithm, GeneratePkcs8};
138
139 #[cfg(feature = "secp256k1")]
140 use crate::ecdsa::secp256k1;
141
142 pub const EXAMPLE_KEY: &str = "example-key";
143
144 pub struct FsStoreHandle {
145 pub keystore: FsKeyStore,
146 pub dir: tempfile::TempDir,
147 }
148
149 #[allow(dead_code)]
151 fn create_example_keystore(example_key: &pkcs8::SecretDocument) -> FsStoreHandle {
152 let dir = tempfile::tempdir().unwrap();
153 let keystore = FsKeyStore::create_or_open(&dir.path().join("keys")).unwrap();
154
155 keystore
156 .store(&EXAMPLE_KEY.parse().unwrap(), example_key)
157 .unwrap();
158
159 FsStoreHandle { keystore, dir }
160 }
161
162 #[cfg(feature = "secp256k1")]
163 #[test]
164 fn import_and_delete_key() {
165 let key_name = EXAMPLE_KEY.parse().unwrap();
166 let example_key = secp256k1::SigningKey::generate_pkcs8();
167 let ks = create_example_keystore(&example_key);
168
169 let example_key2 = ks.keystore.load(&key_name).unwrap();
170 assert_eq!(example_key.as_bytes(), example_key2.as_bytes());
171
172 ks.keystore.delete(&key_name).unwrap();
173 }
174
175 #[cfg(feature = "secp256k1")]
176 #[test]
177 fn get_key_info() {
178 let key_name = EXAMPLE_KEY.parse().unwrap();
179 let example_key = secp256k1::SigningKey::generate_pkcs8();
180 let ks = create_example_keystore(&example_key);
181
182 let key_info = ks.keystore.info(&key_name).unwrap();
183 assert_eq!(key_info.name, key_name);
184 assert_eq!(key_info.algorithm, Some(Algorithm::EcdsaSecp256k1));
185 assert!(!key_info.encrypted);
186 }
187}