sustenet_shared/
security.rs1pub 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, 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); 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}