sustenet_shared/
security.rs

1pub mod base64engine {
2    use base64::{ Engine, alphabet, engine::{ self, general_purpose } };
3
4    const CUSTOM_ENGINE: engine::GeneralPurpose = engine::GeneralPurpose::new(
5        &alphabet::URL_SAFE,
6        general_purpose::NO_PAD
7    );
8    pub fn base64_encode(input: &[u8]) -> String {
9        CUSTOM_ENGINE.encode(input)
10    }
11
12    pub fn base64_decode(input: &str) -> Vec<u8> {
13        CUSTOM_ENGINE.decode(input).unwrap()
14    }
15}
16
17pub mod aes {
18    use std::{ collections::HashMap, fs::File, io::{ Read, Write }, vec };
19
20    use aes_gcm::{
21        Aes256Gcm, // Or `Aes128Gcm`
22        Key,
23        Nonce,
24        aead::{ Aead, AeadCore, KeyInit, OsRng },
25    };
26
27    pub fn create_keys_dir() -> std::io::Result<()> {
28        if std::fs::DirBuilder::new().recursive(true).create("keys").is_err() {
29            return Err(
30                std::io::Error::new(
31                    std::io::ErrorKind::Other,
32                    "Failed to create the 'keys' directory."
33                )
34            );
35        }
36
37        Ok(())
38    }
39
40    pub fn generate_key() -> Key<Aes256Gcm> {
41        Aes256Gcm::generate_key(OsRng)
42    }
43
44    pub fn save_key(name: &str, key: Key<Aes256Gcm>) -> std::io::Result<()> {
45        let mut file = File::create(format!("keys/{name}"))?;
46        file.write_all(key.as_slice())?;
47        Ok(())
48    }
49
50    pub fn load_key(name: &str) -> std::io::Result<Key<Aes256Gcm>> {
51        let mut file = match File::open(format!("keys/{name}")) {
52            Ok(file) => file,
53            Err(_) => {
54                return Err(
55                    std::io::Error::new(std::io::ErrorKind::NotFound, "Failed to open file.")
56                );
57            }
58        };
59        let mut buf = vec![];
60        match file.read_to_end(&mut buf) {
61            Ok(_) => {
62                if buf.is_empty() {
63                    return Err(
64                        std::io::Error::new(std::io::ErrorKind::InvalidData, "Key is empty.")
65                    );
66                }
67
68                if buf.len() != 32 {
69                    return Err(
70                        std::io::Error::new(std::io::ErrorKind::InvalidData, "Key is not 32 bytes.")
71                    );
72                }
73            }
74            Err(_) => {
75                return Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed to read file."));
76            }
77        }
78        Ok(Key::<Aes256Gcm>::from_slice(buf.as_slice()).to_owned())
79    }
80
81    pub type KeyMap = HashMap<String, Key<Aes256Gcm>>;
82
83    pub fn load_all_keys() -> std::io::Result<HashMap<String, Key<Aes256Gcm>>> {
84        let mut keys = HashMap::new();
85
86        let entries = match std::fs::read_dir("keys") {
87            Ok(entries) => entries,
88            Err(_) => {
89                return Err(
90                    std::io::Error::new(std::io::ErrorKind::NotFound, "Directory 'keys' missing.")
91                );
92            }
93        };
94
95        for entry in entries {
96            let entry = match entry {
97                Ok(entry) => entry,
98                Err(_) => {
99                    continue;
100                }
101            };
102            let name = match entry.file_name().to_str() {
103                Some(name) => name.to_string(),
104                None => {
105                    continue;
106                }
107            };
108            let key = match load_key(name.as_str()) {
109                Ok(key) => key,
110                Err(_) => {
111                    continue;
112                }
113            };
114            keys.insert(name, key);
115        }
116        Ok(keys)
117    }
118
119    pub fn encrypt(data: &[u8], key: &Key<Aes256Gcm>) -> Vec<u8> {
120        let nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message
121        let cipher = Aes256Gcm::new(&key);
122
123        let ciphered_data = cipher.encrypt(&nonce, data).expect("Failed to encrypt data.");
124        [nonce.as_slice(), ciphered_data.as_slice()].concat()
125    }
126
127    pub fn decrypt(data: &[u8], key: &Key<Aes256Gcm>) -> Vec<u8> {
128        let (nonce, data) = data.split_at(12);
129        let nonce = Nonce::from_slice(nonce);
130        let cipher = Aes256Gcm::new(&key);
131        cipher
132            .decrypt(nonce, data)
133            .expect("Failed to decrypt data. Maybe the key doesn't match the name?")
134    }
135}
136
137#[cfg(test)]
138pub mod tests {
139    use super::{ aes::*, base64engine::* };
140
141    #[test]
142    pub fn test_create_keys_dir() {
143        match create_keys_dir() {
144            Ok(_) =>
145                assert!(std::path::Path::new("keys").exists(), "Keys directory does not exist."),
146            Err(e) => {
147                panic!("Failed to create keys directory: {:?}", e);
148            }
149        };
150    }
151
152    #[test]
153    pub fn test_key_gen_encode_and_decode() {
154        let key = generate_key();
155        let keyb64 = base64_encode(key.as_slice());
156        let key2 = base64_decode(&keyb64);
157        assert_ne!(key.as_slice(), keyb64.as_bytes());
158        assert_eq!(key.as_slice(), key2)
159    }
160
161    #[test]
162    pub fn test_encrypt_and_decrypt() {
163        let key = generate_key();
164        let data = b"Hello, World!";
165        let encrypted_data = encrypt(data, &key);
166        let decrypted_data = decrypt(encrypted_data.as_slice(), &key);
167        assert_ne!(data, encrypted_data.as_slice());
168        assert_eq!(data, decrypted_data.as_slice());
169    }
170
171    #[test]
172    pub fn test_save_key() {
173        match save_key("cluster_key_testrunner", generate_key()) {
174            Ok(_) => {}
175            Err(e) => {
176                println!("Failed to save key: {:?}", e);
177            }
178        };
179    }
180
181    #[test]
182    pub fn test_save_key_and_load_key() {
183        let key = generate_key();
184
185        match save_key("cluster_key_testrunner2", key) {
186            Ok(_) => {}
187            Err(e) => {
188                panic!("Failed to save key: {:?}", e);
189            }
190        }
191
192        let key2 = match load_key("cluster_key_testrunner2") {
193            Ok(key) => key,
194            Err(e) => {
195                panic!("Failed to load key: {:?}", e);
196            }
197        };
198
199        assert_eq!(key.as_slice(), key2.as_slice());
200    }
201
202    #[test]
203    pub fn test_load_key() {
204        if let Err(e) = create_keys_dir() {
205            panic!("Failed to create keys directory: {:?}", e);
206        }
207
208        match save_key("cluster_key_testrunner3", generate_key()) {
209            Ok(_) => {}
210            Err(e) => {
211                println!("Failed to save key: {:?}", e);
212            }
213        };
214
215        let key = match load_key("cluster_key_testrunner3") {
216            Ok(key) => key,
217            Err(e) => {
218                panic!("Failed to load key: {:?}", e);
219            }
220        };
221        assert_eq!(key.as_slice().len(), 32);
222    }
223
224    #[test]
225    pub fn test_load_all_keys() {
226        if let Err(e) = create_keys_dir() {
227            panic!("Failed to create keys directory: {:?}", e);
228        }
229
230        match save_key("cluster_key_testrunner4", generate_key()) {
231            Ok(_) => {}
232            Err(e) => {
233                println!("Failed to save key: {:?}", e);
234            }
235        };
236
237        match save_key("cluster_key_testrunner5", generate_key()) {
238            Ok(_) => {}
239            Err(e) => {
240                println!("Failed to save key: {:?}", e);
241            }
242        };
243
244        let keys = match load_all_keys() {
245            Ok(keys) => keys,
246            Err(e) => {
247                panic!("Failed to load all keys: {:?}", e);
248            }
249        };
250
251        assert!(keys.len() >= 2);
252    }
253
254    #[test]
255    pub fn test_path() {
256        println!("Path: {:?}", workspace_dir());
257    }
258
259    fn workspace_dir() -> std::path::PathBuf {
260        let output = std::process::Command
261            ::new(env!("CARGO"))
262            .arg("locate-project")
263            .arg("--workspace")
264            .arg("--message-format=plain")
265            .output()
266            .unwrap().stdout;
267        let cargo_path = std::path::Path::new(std::str::from_utf8(&output).unwrap().trim());
268        cargo_path.parent().unwrap().to_path_buf()
269    }
270}