ssh_vault/vault/
parse.rs

1use anyhow::{Result, anyhow};
2use base64ct::{Base64, Encoding};
3
4// check if it's a valid SSH-VAULT file and return the data
5pub fn parse(data: &str) -> Result<(&str, String, Vec<u8>, Vec<u8>)> {
6    let tokens: Vec<_> = data.split(';').collect();
7
8    // Validate tokens length before accessing indices
9    if tokens.len() < 2 {
10        return Err(anyhow!("Not a valid SSH-VAULT file"));
11    }
12
13    if tokens[0] != "SSH-VAULT" || (tokens[1] != "AES256" && tokens[1] != "CHACHA20-POLY1305") {
14        return Err(anyhow!("Not a valid SSH-VAULT file"));
15    }
16
17    if tokens[1] == "AES256" {
18        if tokens.len() != 4 {
19            return Err(anyhow!("Not a valid SSH-VAULT file"));
20        }
21
22        let mut lines = tokens[2].lines();
23
24        let fingerprint = lines
25            .next()
26            .ok_or_else(|| anyhow!("Not a valid SSH-VAULT file"))?;
27
28        let password = lines.collect::<Vec<&str>>().join("");
29        let password = Base64::decode_vec(&password)?;
30
31        lines = tokens[3].lines();
32
33        let data = lines.collect::<Vec<&str>>().join("");
34        let data = Base64::decode_vec(&data)?;
35
36        return Ok((tokens[1], fingerprint.to_string(), password, data));
37    } else if tokens[1] == "CHACHA20-POLY1305" {
38        if tokens.len() != 6 {
39            return Err(anyhow!("Not a valid SSH-VAULT file"));
40        }
41
42        let fingerprint = tokens[2].lines().collect::<Vec<&str>>().join("");
43
44        let epk = tokens[3].lines().collect::<Vec<&str>>().join("");
45        let epk = Base64::decode_vec(&epk)?;
46
47        let password = tokens[4].lines().collect::<Vec<&str>>().join("");
48        let password = Base64::decode_vec(&password)?;
49
50        let mut epk_and_password = Vec::new();
51        epk_and_password.extend_from_slice(&epk);
52        epk_and_password.extend_from_slice(&password);
53
54        let data = tokens[5].lines().collect::<Vec<&str>>().join("");
55        let data = Base64::decode_vec(&data)?;
56
57        return Ok((tokens[1], fingerprint, epk_and_password, data));
58    }
59
60    Err(anyhow!("Not a valid SSH-VAULT file"))
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_parse_invalid_headers() {
69        let data = r"SSH-VAULT:CHACHA20-POLY1305;0;0;0;0";
70        assert!(parse(data).is_err());
71    }
72
73    #[test]
74    fn test_parse_invalid_vault() {
75        let data = r"SSH-VAULTCHACHA20-POLY1305SHA256:ZnlGYSmE8yBioOm+jhTxPAk4JagMu
76mruoD1rf+WcpFY;EExFHBkGr4L2e0SS0y2Yw9lglLBGVmcho7r3EWSSZHU=;p3kQ
77AVM09aZlRhfTZ4Gpp3WJ6AfurNqLo2Y8aDtQVj9uVx8FTJ+pVOTzphZMbCgzbSiU
78pqwAZIHYhzss";
79        assert!(parse(data).is_err());
80    }
81
82    #[test]
83    fn test_parse_missing_data() {
84        let data = r"SSH-VAULT;CHACHA20-POLY1305;SHA256:ZnlGYSmE8yBioOm+jhTxPAk4JagMu
85mruoD1rf+WcpFY;EExFHBkGr4L2e0SS0y2Yw9lglLBGVmcho7r3EWSSZHU=;p3kQ
86AVM09aZlRhfTZ4Gpp3WJ6AfurNqLo2Y8aDtQVj9uVx8FTJ+pVOTzphZMbCgzbSiU
87pqwAZIHYhzss";
88        assert!(parse(data).is_err());
89    }
90
91    #[test]
92    fn test_parse_invalid_rsa_vault() {
93        let data = r"SSH-VAULT;AES256;SHA256:ZnlGYSmE8yBioOm+jhTxPAk4JagMu";
94        assert!(parse(data).is_err());
95    }
96
97    #[test]
98    fn test_parse_no_fingerprint() {
99        let data = r"SSH-VAULT;AES256";
100        assert!(parse(data).is_err());
101    }
102
103    #[test]
104    fn test_parse_no_payload() {
105        let data = r"SSH-VAULT;AES256;;0";
106        assert!(parse(data).is_err());
107    }
108
109    #[test]
110    fn test_parse_empty_string() {
111        let data = "";
112        let result = parse(data);
113        assert!(result.is_err());
114        assert!(
115            result
116                .unwrap_err()
117                .to_string()
118                .contains("Not a valid SSH-VAULT file")
119        );
120    }
121
122    #[test]
123    fn test_parse_single_token() {
124        let data = "SSH-VAULT";
125        let result = parse(data);
126        assert!(result.is_err());
127        assert!(
128            result
129                .unwrap_err()
130                .to_string()
131                .contains("Not a valid SSH-VAULT file")
132        );
133    }
134
135    #[test]
136    fn test_parse_no_tokens() {
137        let data = ";";
138        let result = parse(data);
139        assert!(result.is_err());
140    }
141
142    #[test]
143    fn test_parse_malformed_header() {
144        let data = "INVALID";
145        let result = parse(data);
146        assert!(result.is_err());
147    }
148
149    #[test]
150    fn test_parse_wrong_crypto_type() {
151        let data = "SSH-VAULT;INVALID_CRYPTO";
152        let result = parse(data);
153        assert!(result.is_err());
154        assert!(
155            result
156                .unwrap_err()
157                .to_string()
158                .contains("Not a valid SSH-VAULT file")
159        );
160    }
161}