1mod errors;
24
25pub use crate::errors::VaultError;
26use crate::errors::*;
27use aes_ctr::cipher::{NewStreamCipher, SyncStreamCipher};
28use aes_ctr::Aes256Ctr;
29use block_padding::{Padding, Pkcs7};
30use hmac::{Hmac, Mac, NewMac};
31use pbkdf2::pbkdf2;
32use rand::Rng;
33use sha2::Sha256;
34use std::fs::File;
35use std::io::{BufRead, Read};
36use std::path::Path;
37
38const VAULT_1_1_PREFIX: &str = "$ANSIBLE_VAULT;1.1;AES256";
39const AES_BLOCK_SIZE: usize = 16; const KEY_SIZE: usize = 32;
41
42type HmacSha256 = Hmac<Sha256>;
43
44fn verify_vault(key: &[u8], ciphertext: &[u8], crypted_hmac: &[u8]) -> Result<()> {
46 let mut hmac = HmacSha256::new_varkey(key)?;
47 hmac.update(&ciphertext);
48 Ok(hmac.verify(crypted_hmac)?)
49}
50
51fn generate_derived_key(
53 key: &str,
54 salt: &[u8],
55) -> ([u8; KEY_SIZE], [u8; KEY_SIZE], [u8; AES_BLOCK_SIZE]) {
56 let mut hmac_buffer = [0; 2 * KEY_SIZE + AES_BLOCK_SIZE];
57 pbkdf2::<HmacSha256>(key.as_bytes(), salt, 10000, &mut hmac_buffer);
58
59 let mut key1 = [0u8; KEY_SIZE];
60 let mut key2 = [0u8; KEY_SIZE];
61 let mut iv = [0u8; AES_BLOCK_SIZE];
62 key1.copy_from_slice(&hmac_buffer[0..KEY_SIZE]);
63 key2.copy_from_slice(&hmac_buffer[KEY_SIZE..2 * KEY_SIZE]);
64 iv.copy_from_slice(&hmac_buffer[2 * KEY_SIZE..2 * KEY_SIZE + AES_BLOCK_SIZE]);
65
66 (key1, key2, iv)
67}
68
69pub fn decrypt<T: Read>(mut input: T, key: &str) -> Result<Vec<u8>> {
88 let mut payload = String::new();
90 input.read_to_string(&mut payload)?;
91 let unhex_payload = String::from_utf8(hex::decode(&payload)?)?;
92
93 let mut lines = unhex_payload.lines();
95 let salt = hex::decode(
96 &lines
97 .next()
98 .ok_or_else(|| VaultError::from_kind(ErrorKind::InvalidFormat))?,
99 )?;
100 let hmac_verify = hex::decode(
101 &lines
102 .next()
103 .ok_or_else(|| VaultError::from_kind(ErrorKind::InvalidFormat))?,
104 )?;
105 let mut ciphertext = hex::decode(
106 &lines
107 .next()
108 .ok_or_else(|| VaultError::from_kind(ErrorKind::InvalidFormat))?,
109 )?;
110
111 let (key1, key2, iv) = &generate_derived_key(key, salt.as_slice());
113 verify_vault(key2, &ciphertext, &hmac_verify)?;
114
115 let mut cipher = Aes256Ctr::new_var(key1, iv)?;
117 cipher.apply_keystream(&mut ciphertext);
118 let n = Pkcs7::unpad(&ciphertext)?.len();
119 ciphertext.truncate(n);
120
121 Ok(ciphertext)
122}
123
124pub fn decrypt_vault<T: Read>(input: T, key: &str) -> Result<Vec<u8>> {
140 let mut lines = std::io::BufReader::new(input).lines();
141 let first: String = lines
142 .next()
143 .ok_or_else(|| VaultError::from_kind(ErrorKind::NotAVault))??;
144
145 if first != VAULT_1_1_PREFIX {
146 return Err(VaultError::from_kind(ErrorKind::NotAVault));
147 }
148
149 let payload = lines
150 .filter_map(|i| i.ok())
151 .map(|s| s.trim().to_owned())
152 .collect::<Vec<String>>()
153 .join("");
154
155 decrypt(payload.as_bytes(), key)
156}
157
158pub fn decrypt_vault_from_file<P: AsRef<Path>>(path: P, key: &str) -> Result<Vec<u8>> {
167 let f = File::open(path)?;
168 decrypt_vault(f, key)
169}
170
171pub fn encrypt_vault<T: Read>(input: T, key: &str) -> Result<String> {
187 let line_length = 80;
188 let ciphertext = encrypt(input, key)?;
189 let mut buffer = Vec::new();
190 for chunk in ciphertext.into_bytes().chunks(line_length) {
191 let mut line = [chunk, "\n".as_bytes()].concat();
192 buffer.append(&mut line);
193 }
194
195 let vault_text = format! {"{}\n{}", VAULT_1_1_PREFIX, String::from_utf8(buffer)?};
196
197 Ok(vault_text)
198}
199
200pub fn encrypt<T: Read>(mut input: T, key: &str) -> Result<String> {
213 let mut buffer = Vec::new();
215 input.read_to_end(&mut buffer)?;
216 let pos = buffer.len();
217 let pad_len = AES_BLOCK_SIZE - (pos % AES_BLOCK_SIZE);
218 buffer.resize(pos + pad_len, 0);
219 let mut block_buffer = Pkcs7::pad(buffer.as_mut_slice(), pos, AES_BLOCK_SIZE)?;
220
221 let salt = rand::thread_rng().gen::<[u8; 32]>();
223 let (key1, key2, iv) = &generate_derived_key(key, &salt);
224
225 let mut cipher = Aes256Ctr::new_var(key1, iv)?;
227 cipher.apply_keystream(&mut block_buffer);
228
229 let mut mac = HmacSha256::new_varkey(key2)?;
231 mac.update(block_buffer);
232 let result = mac.finalize();
233 let b_hmac = result.into_bytes();
234
235 let ciphertext = format!(
237 "{}\n{}\n{}",
238 hex::encode(salt),
239 hex::encode(b_hmac),
240 hex::encode(block_buffer)
241 );
242
243 Ok(hex::encode(ciphertext))
244}
245
246pub fn encrypt_vault_from_file<P: AsRef<Path>>(path: P, key: &str) -> Result<String> {
255 let f = File::open(path)?;
256 encrypt_vault(f, key)
257}
258
259#[cfg(test)]
260mod tests {
261 use crate::errors::{ErrorKind, VaultError};
262 use std::fs;
263
264 const LIPSUM_PATH: &str = "./test/lipsum.txt";
265 const LIPSUM_VAULT_PATH: &str = "./test/lipsum.vault";
266 const LIPSUM_SECRET: &str = "shibboleet";
267
268 #[test]
269 fn test_wrong_password() {
270 let result = crate::decrypt_vault_from_file(LIPSUM_VAULT_PATH, "p@$$w0rd").unwrap_err();
271 assert_eq!(result, VaultError::from_kind(ErrorKind::IncorrectSecret));
272 }
273
274 #[test]
275 fn test_decrypt() {
276 let buf = crate::decrypt_vault_from_file(LIPSUM_VAULT_PATH, LIPSUM_SECRET).unwrap();
277 let lipsum = String::from_utf8(buf).unwrap();
278 let reference = fs::read_to_string(LIPSUM_PATH).unwrap();
279 assert_eq!(lipsum, reference);
280 }
281
282 #[test]
283 fn test_encrypt() {
284 let lipsum = fs::read_to_string(LIPSUM_PATH).unwrap();
285 let encoded = crate::encrypt_vault_from_file(LIPSUM_PATH, LIPSUM_SECRET).unwrap();
286 let decoded = crate::decrypt_vault(encoded.as_bytes(), LIPSUM_SECRET).unwrap();
287 let decoded_str = String::from_utf8(decoded).unwrap();
288 assert_eq!(lipsum, decoded_str);
289 }
290
291}