1use crate::traits::{PemStorableKey, PemStorableKeyPair};
5use pem::Pem;
6use std::fs::File;
7use std::io::{self, Read, Write};
8use std::ops::Deref;
9use std::path::{Path, PathBuf};
10use tracing::debug;
11use zeroize::{Zeroize, Zeroizing};
12
13pub mod traits;
14
15struct ZeroizingPem(Pem);
16
17impl Zeroize for ZeroizingPem {
18 fn zeroize(&mut self) {
19 self.0.tag.zeroize();
20 self.0.contents.zeroize();
21 }
22}
23impl Drop for ZeroizingPem {
24 fn drop(&mut self) {
25 self.zeroize();
26 }
27}
28
29impl Deref for ZeroizingPem {
30 type Target = Pem;
31 fn deref(&self) -> &Self::Target {
32 &self.0
33 }
34}
35
36#[derive(Debug, Clone, Default)]
37pub struct KeyPairPath {
38 pub private_key_path: PathBuf,
39 pub public_key_path: PathBuf,
40}
41
42impl KeyPairPath {
43 pub fn new<P: AsRef<Path>>(private_key_path: P, public_key_path: P) -> Self {
44 KeyPairPath {
45 private_key_path: private_key_path.as_ref().to_owned(),
46 public_key_path: public_key_path.as_ref().to_owned(),
47 }
48 }
49}
50
51pub fn load_keypair<T>(paths: &KeyPairPath) -> io::Result<T>
52where
53 T: PemStorableKeyPair,
54{
55 let private: T::PrivatePemKey = load_key(&paths.private_key_path)?;
56 let public: T::PublicPemKey = load_key(&paths.public_key_path)?;
57 Ok(T::from_keys(private, public))
58}
59
60pub fn store_keypair<T>(keypair: &T, paths: &KeyPairPath) -> io::Result<()>
61where
62 T: PemStorableKeyPair,
63{
64 store_key(keypair.public_key(), &paths.public_key_path)?;
65 store_key(keypair.private_key(), &paths.private_key_path)
66}
67
68pub fn load_key<T, P>(path: P) -> io::Result<T>
69where
70 T: PemStorableKey,
71 P: AsRef<Path>,
72{
73 debug!(
74 "attempting to load key with the following pem type: {}",
75 T::pem_type()
76 );
77 let key_pem = read_pem_file(path)?;
78
79 if T::pem_type() != key_pem.tag {
80 return Err(io::Error::other(format!(
81 "unexpected key pem tag. Got '{}', expected: '{}'",
82 key_pem.0.tag,
83 T::pem_type()
84 )));
85 }
86
87 let key = match T::from_bytes(&key_pem.contents) {
88 Ok(key) => key,
89 Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err.to_string())),
90 };
91
92 Ok(key)
93}
94
95pub fn store_key<T, P>(key: &T, path: P) -> io::Result<()>
96where
97 T: PemStorableKey,
98 P: AsRef<Path>,
99{
100 write_pem_file(path, key.to_bytes(), T::pem_type())
101}
102
103fn read_pem_file<P: AsRef<Path>>(filepath: P) -> io::Result<ZeroizingPem> {
104 let mut pem_bytes = File::open(filepath)?;
105 let mut buf = Zeroizing::new(Vec::new());
106 pem_bytes.read_to_end(&mut buf)?;
107 pem::parse(&buf).map(ZeroizingPem).map_err(io::Error::other)
108}
109
110fn write_pem_file<P: AsRef<Path>>(filepath: P, mut data: Vec<u8>, tag: &str) -> io::Result<()> {
111 #[allow(clippy::collapsible_if)]
114 if let Some(parent_dir) = filepath.as_ref().parent() {
115 if let Err(err) = std::fs::create_dir_all(parent_dir) {
116 data.zeroize();
119 return Err(err);
120 }
121 }
122
123 let mut file = File::create(filepath.as_ref())?;
124
125 let pem = ZeroizingPem(Pem {
126 tag: tag.to_string(),
127 contents: data,
128 });
129 let key = Zeroizing::new(pem::encode(&pem));
130 file.write_all(key.as_bytes())?;
131
132 #[cfg(target_family = "unix")]
138 {
139 use std::fs;
140 use std::os::unix::fs::PermissionsExt;
141
142 let mut permissions = file.metadata()?.permissions();
143 permissions.set_mode(0o600);
144 fs::set_permissions(filepath, permissions)?;
145 }
146
147 Ok(())
148}