vault_core/
lib.rs

1extern crate core;
2
3use std::error::Error;
4use std::fs::File;
5use std::io::{Read, Write};
6
7use chacha20::XChaCha20;
8use chacha20poly1305::aead::stream;
9use chacha20poly1305::aead::stream::{Decryptor, Encryptor, StreamBE32};
10use chacha20poly1305::consts::U24;
11use chacha20poly1305::{ChaChaPoly1305, KeyInit, XChaCha20Poly1305};
12
13use err::EncryptionError;
14
15use crate::err::DecryptionError;
16
17mod err;
18mod keygen;
19
20type Cypher = ChaChaPoly1305<XChaCha20, U24>;
21
22const VERSION: u8 = 1;
23const BUFFER_SIZE: usize = 1024;
24const AUTHENTICATION_TAG_SIZE: usize = 16;
25
26pub fn encrypt_file(
27    source_path: &str,
28    target_path: &str,
29    password: &str,
30) -> Result<(), Box<dyn Error>> {
31    let mut generated_key = keygen::generate_key(password)?;
32    let nonce = generated_key.nonce.as_slice();
33    let salt = generated_key.salt.as_slice();
34
35    let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
36    let encryptor: Encryptor<Cypher, StreamBE32<Cypher>> =
37        stream::EncryptorBE32::from_aead(aead, nonce.into());
38
39    let mut source_file = File::open(source_path)?;
40    let mut destination_file = File::create(target_path)?;
41
42    write_header(&mut destination_file, salt, nonce)?;
43
44    write_encrypted_file(&mut source_file, &mut destination_file, encryptor)?;
45
46    generated_key.cleanup();
47
48    Ok(())
49}
50
51pub fn decrypt_file(
52    source_path: &str,
53    target_path: &str,
54    password: &str,
55) -> Result<(), Box<dyn Error>> {
56    let mut version = [0u8; 1];
57    let mut salt = [0u8; 32];
58    let mut nonce = [0u8; 19];
59
60    let mut source_file = File::open(source_path)?;
61    let mut target_file = File::create(target_path)?;
62
63    let mut bytes_read = source_file.read(&mut version)?;
64    if bytes_read != version.len() {
65        return Err(Box::new(DecryptionError::new(
66            "Invalid file format".to_string(),
67        )));
68    }
69
70    bytes_read = source_file.read(&mut salt)?;
71    if bytes_read != salt.len() {
72        return Err(Box::new(DecryptionError::new(
73            "Invalid file format".to_string(),
74        )));
75    }
76
77    bytes_read = source_file.read(&mut nonce)?;
78    if bytes_read != nonce.len() {
79        return Err(Box::new(DecryptionError::new(
80            "Invalid file format".to_string(),
81        )));
82    }
83
84    let mut generated_key = keygen::derive_key(password, salt.to_vec(), nonce.to_vec())?;
85
86    let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
87    let decryptor: Decryptor<Cypher, StreamBE32<Cypher>> =
88        stream::DecryptorBE32::from_aead(aead, generated_key.nonce.as_slice().into());
89
90    write_decrypted_file(&mut source_file, &mut target_file, decryptor)?;
91
92    generated_key.cleanup();
93
94    Ok(())
95}
96
97fn write_decrypted_file(
98    src: &mut File,
99    target: &mut File,
100    mut decryptor: Decryptor<Cypher, StreamBE32<Cypher>>,
101) -> Result<(), Box<dyn Error>> {
102    const DECRYPTION_BUFFER_SIZE: usize = BUFFER_SIZE + AUTHENTICATION_TAG_SIZE;
103    let mut buffer = [0u8; DECRYPTION_BUFFER_SIZE];
104
105    loop {
106        let bytes_read = src.read(&mut buffer)?;
107        let all_bytes_read = bytes_read < DECRYPTION_BUFFER_SIZE;
108        let buffer_slice = &buffer[..bytes_read];
109
110        if all_bytes_read {
111            let plain_text = decryptor
112                .decrypt_last(buffer_slice)
113                .map_err(|e| DecryptionError::new(e.to_string()))?;
114            target.write(&plain_text)?;
115            return Ok(());
116        } else {
117            let plain_text = decryptor
118                .decrypt_next(buffer_slice)
119                .map_err(|e| DecryptionError::new(e.to_string()))?;
120            target.write(&plain_text)?;
121        }
122    }
123}
124
125fn write_encrypted_file(
126    src: &mut File,
127    target: &mut File,
128    mut encryptor: Encryptor<Cypher, StreamBE32<Cypher>>,
129) -> Result<(), Box<dyn Error>> {
130    let mut buffer = [0u8; BUFFER_SIZE];
131
132    loop {
133        let bytes_read = src.read(&mut buffer)?;
134        let all_bytes_read = bytes_read < BUFFER_SIZE;
135        let buffer_slice = &buffer[..bytes_read];
136
137        if all_bytes_read {
138            let cipher_text = encryptor
139                .encrypt_last(buffer_slice)
140                .map_err(|e| EncryptionError::new(e.to_string()))?;
141            target.write(&cipher_text)?;
142            return Ok(());
143        } else {
144            let cipher_text = encryptor
145                .encrypt_next(buffer_slice)
146                .map_err(|e| EncryptionError::new(e.to_string()))?;
147            target.write(&cipher_text)?;
148        }
149    }
150}
151
152fn write_header(file: &mut File, salt: &[u8], nonce: &[u8]) -> Result<(), Box<dyn Error>> {
153    let version_buffer: [u8; 1] = [VERSION];
154    file.write(&version_buffer)?;
155
156    file.write(salt)?;
157    file.write(nonce)?;
158
159    Ok(())
160}