1use anyhow::{Result, anyhow};
2use base64ct::{Base64, Encoding};
3
4pub fn parse(data: &str) -> Result<(&str, String, Vec<u8>, Vec<u8>)> {
6 let tokens: Vec<_> = data.split(';').collect();
7
8 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}