Skip to main content

rvaultlib/decrypt/
mod.rs

1use crate::{
2    keys::is_encrypted,
3    typedetect::{FileFormat, detect_format},
4    types::{ProcessHandling, TypedValue, ValueType},
5    vaultfunc,
6};
7
8/// Decrypt all (or a filtered subset of) vault-encrypted values in a JSON or YAML file.
9///
10/// - `input`:    path to the input file
11/// - `output`:   path to write the result, or `"stdout"` to print to stdout
12/// - `password`: vault password
13/// - `keys`:     dot-notation key paths to decrypt; empty means all encrypted values
14pub fn decrypt(
15    input: &str,
16    output: &str,
17    password: &str,
18    keys: &[String],
19) -> Result<(), Box<dyn std::error::Error>> {
20    let mut processor = make_decrypt_processor(password);
21    match detect_format(input)? {
22        FileFormat::Json => crate::json::process_file(input, output, keys, &mut processor),
23        FileFormat::Yaml => crate::yaml::process_file(input, output, keys, &mut processor),
24    }
25}
26
27pub(crate) fn make_decrypt_processor(
28    password: &str,
29) -> impl FnMut(
30    TypedValue,
31    ValueType,
32    &str,
33) -> Result<(TypedValue, ValueType, ProcessHandling), Box<dyn std::error::Error>>
34+ '_ {
35    move |typed, vt, key_path| {
36        let (encrypted, vault_str) = is_encrypted(&typed);
37        if !encrypted {
38            return Ok((typed, vt, ProcessHandling::Skip));
39        }
40        let vault_str = vault_str.unwrap();
41        match vaultfunc::decrypt(&vault_str, password) {
42            Ok((decrypted, new_vt)) => Ok((decrypted, new_vt, ProcessHandling::Process)),
43            Err(e) => {
44                eprintln!("error decrypting key '{}': {}", key_path, e);
45                Ok((typed, vt, ProcessHandling::Skip))
46            }
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    fn resources(filename: &str) -> String {
56        format!(
57            "{}/resources/tests/{}",
58            env!("CARGO_MANIFEST_DIR"),
59            filename
60        )
61    }
62
63    // --- YAML decrypt tests ---
64
65    struct YamlDecryptCase {
66        input: &'static str,
67        reference: &'static str,
68        keys: &'static [&'static str],
69    }
70
71    fn run_yaml_decrypt(case: &YamlDecryptCase) {
72        let input = resources(case.input);
73        let reference = resources(case.reference);
74        let keys: Vec<String> = case.keys.iter().map(|s| s.to_string()).collect();
75
76        let output = tempfile::NamedTempFile::new().unwrap();
77        let output_path = output.path().to_str().unwrap().to_string();
78
79        decrypt(&input, &output_path, "test999", &keys).unwrap();
80
81        let got: serde_yaml::Value =
82            serde_yaml::from_str(&std::fs::read_to_string(&output_path).unwrap()).unwrap();
83        let expected: serde_yaml::Value =
84            serde_yaml::from_str(&std::fs::read_to_string(&reference).unwrap()).unwrap();
85        assert_eq!(got, expected, "YAML decrypt '{}' failed", case.input);
86    }
87
88    #[test]
89    fn test_decrypt_yaml_all_keys() {
90        run_yaml_decrypt(&YamlDecryptCase {
91            input: "partial_encrypted_example.yaml",
92            reference: "partial_encrypted_example_decrypted_01.yaml",
93            keys: &[],
94        });
95    }
96
97    #[test]
98    fn test_decrypt_yaml_filtered_key() {
99        run_yaml_decrypt(&YamlDecryptCase {
100            input: "partial_encrypted_example.yaml",
101            reference: "partial_encrypted_example_decrypted_03.yaml",
102            keys: &["third.carrot"],
103        });
104    }
105
106    #[test]
107    fn test_decrypt_yaml_multiple_keys() {
108        run_yaml_decrypt(&YamlDecryptCase {
109            input: "partial_encrypted_example.yaml",
110            reference: "partial_encrypted_example_decrypted_04.yaml",
111            keys: &["first.a", "first.z", "second.b.2", "fourth.list"],
112        });
113    }
114
115    #[test]
116    fn test_decrypt_yaml_02() {
117        run_yaml_decrypt(&YamlDecryptCase {
118            input: "partial_encrypted_example_02.yaml",
119            reference: "partial_encrypted_example_decrypted_01.yaml",
120            keys: &[],
121        });
122    }
123
124    #[test]
125    fn test_decrypt_yaml_03() {
126        run_yaml_decrypt(&YamlDecryptCase {
127            input: "partial_encrypted_example_03.yaml",
128            reference: "partial_encrypted_example_decrypted_01.yaml",
129            keys: &[],
130        });
131    }
132
133    // --- JSON decrypt tests ---
134
135    struct JsonDecryptCase {
136        input: &'static str,
137        reference: &'static str,
138        keys: &'static [&'static str],
139    }
140
141    fn run_json_decrypt(case: &JsonDecryptCase) {
142        let input = resources(case.input);
143        let reference = resources(case.reference);
144        let keys: Vec<String> = case.keys.iter().map(|s| s.to_string()).collect();
145
146        let output = tempfile::NamedTempFile::new().unwrap();
147        let output_path = output.path().to_str().unwrap().to_string();
148
149        decrypt(&input, &output_path, "test999", &keys).unwrap();
150
151        let got: serde_json::Value =
152            serde_json::from_str(&std::fs::read_to_string(&output_path).unwrap()).unwrap();
153        let expected: serde_json::Value =
154            serde_json::from_str(&std::fs::read_to_string(&reference).unwrap()).unwrap();
155        assert_eq!(got, expected, "JSON decrypt '{}' failed", case.input);
156    }
157
158    #[test]
159    fn test_decrypt_json_all_keys() {
160        run_json_decrypt(&JsonDecryptCase {
161            input: "partial_encrypted_example.json",
162            reference: "partial_encrypted_example_decrypted_01.json",
163            keys: &[],
164        });
165    }
166
167    #[test]
168    fn test_decrypt_json_filtered_key() {
169        run_json_decrypt(&JsonDecryptCase {
170            input: "partial_encrypted_example.json",
171            reference: "partial_encrypted_example_decrypted_03.json",
172            keys: &["third.carrot"],
173        });
174    }
175
176    #[test]
177    fn test_decrypt_json_multiple_keys() {
178        run_json_decrypt(&JsonDecryptCase {
179            input: "partial_encrypted_example.json",
180            reference: "partial_encrypted_example_decrypted_04.json",
181            keys: &["first.a", "first.z", "second.b.2", "fourth.list"],
182        });
183    }
184
185    #[test]
186    fn test_decrypt_json_02() {
187        run_json_decrypt(&JsonDecryptCase {
188            input: "partial_encrypted_example_02.json",
189            reference: "partial_encrypted_example_decrypted_01.json",
190            keys: &[],
191        });
192    }
193
194    #[test]
195    fn test_decrypt_json_03() {
196        run_json_decrypt(&JsonDecryptCase {
197            input: "partial_encrypted_example_03.json",
198            reference: "partial_encrypted_example_decrypted_01.json",
199            keys: &[],
200        });
201    }
202}