1use std::collections::BTreeMap;
6use std::fmt;
7use std::str::FromStr;
8
9use anyhow::{Context as _, Error, Result, bail};
10
11use crate::key::{DcKey, SignedPublicKey};
12
13#[derive(PartialEq, Eq, Debug, Default, Clone, Copy, FromPrimitive, ToPrimitive)]
15#[repr(u8)]
16pub enum EncryptPreference {
17 #[default]
18 NoPreference = 0,
19 Mutual = 1,
20}
21
22impl fmt::Display for EncryptPreference {
23 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
24 match *self {
25 EncryptPreference::Mutual => write!(fmt, "mutual"),
26 EncryptPreference::NoPreference => write!(fmt, "nopreference"),
27 }
28 }
29}
30
31impl FromStr for EncryptPreference {
32 type Err = Error;
33
34 fn from_str(s: &str) -> Result<Self> {
35 match s {
36 "mutual" => Ok(EncryptPreference::Mutual),
37 "nopreference" => Ok(EncryptPreference::NoPreference),
38 _ => bail!("Cannot parse encryption preference {}", s),
39 }
40 }
41}
42
43#[derive(Debug)]
45pub struct Aheader {
46 pub addr: String,
47 pub public_key: SignedPublicKey,
48 pub prefer_encrypt: EncryptPreference,
49
50 pub verified: bool,
56}
57
58impl fmt::Display for Aheader {
59 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
60 write!(fmt, "addr={};", self.addr.to_lowercase())?;
61 if self.prefer_encrypt == EncryptPreference::Mutual {
62 write!(fmt, " prefer-encrypt=mutual;")?;
63 }
64 if self.verified {
65 write!(fmt, " _verified=1;")?;
66 }
67
68 let keydata = self.public_key.to_base64().chars().enumerate().fold(
72 String::new(),
73 |mut res, (i, c)| {
74 if i % 78 == 78 - "keydata=".len() {
75 res.push(' ')
76 }
77 res.push(c);
78 res
79 },
80 );
81 write!(fmt, " keydata={keydata}")
82 }
83}
84
85impl FromStr for Aheader {
86 type Err = Error;
87
88 fn from_str(s: &str) -> Result<Self> {
89 let mut attributes: BTreeMap<String, String> = s
90 .split(';')
91 .filter_map(|a| {
92 let attribute: Vec<&str> = a.trim().splitn(2, '=').collect();
93 match &attribute[..] {
94 [key, value] => Some((key.trim().to_string(), value.trim().to_string())),
95 _ => None,
96 }
97 })
98 .collect();
99
100 let addr = match attributes.remove("addr") {
101 Some(addr) => addr,
102 None => bail!("Autocrypt header has no addr"),
103 };
104 let public_key: SignedPublicKey = attributes
105 .remove("keydata")
106 .context("keydata attribute is not found")
107 .and_then(|raw| {
108 SignedPublicKey::from_base64(&raw).context("autocrypt key cannot be decoded")
109 })
110 .and_then(|key| {
111 key.verify()
112 .and(Ok(key))
113 .context("autocrypt key cannot be verified")
114 })?;
115
116 let prefer_encrypt = attributes
117 .remove("prefer-encrypt")
118 .and_then(|raw| raw.parse().ok())
119 .unwrap_or_default();
120
121 let verified = attributes.remove("_verified").is_some();
122
123 if attributes.keys().any(|k| !k.starts_with('_')) {
126 bail!("Unknown Autocrypt attribute found");
127 }
128
129 Ok(Aheader {
130 addr,
131 public_key,
132 prefer_encrypt,
133 verified,
134 })
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 const RAWKEY: &str = "xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJgWL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKKbhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1KvVL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbGUuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VNHtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Dddfxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCvSJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vauf1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjmkRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09/JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHRTR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaKrc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivXurm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9MtrmZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb+F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNgwm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc=";
143
144 #[test]
145 fn test_from_str() -> Result<()> {
146 let h: Aheader =
147 format!("addr=me@mail.com; prefer-encrypt=mutual; keydata={RAWKEY}").parse()?;
148
149 assert_eq!(h.addr, "me@mail.com");
150 assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
151 assert_eq!(h.verified, false);
152 Ok(())
153 }
154
155 #[test]
157 fn test_from_str_reset() -> Result<()> {
158 let raw = format!("addr=reset@example.com; prefer-encrypt=reset; keydata={RAWKEY}");
159 let h: Aheader = raw.parse()?;
160
161 assert_eq!(h.addr, "reset@example.com");
162 assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
163 Ok(())
164 }
165
166 #[test]
167 fn test_from_str_non_critical() -> Result<()> {
168 let raw = format!("addr=me@mail.com; _foo=one; _bar=two; keydata={RAWKEY}");
169 let h: Aheader = raw.parse()?;
170
171 assert_eq!(h.addr, "me@mail.com");
172 assert_eq!(h.prefer_encrypt, EncryptPreference::NoPreference);
173 Ok(())
174 }
175
176 #[test]
177 fn test_from_str_superflous_critical() {
178 let raw = format!("addr=me@mail.com; _foo=one; _bar=two; other=me; keydata={RAWKEY}");
179 assert!(raw.parse::<Aheader>().is_err());
180 }
181
182 #[test]
183 fn test_good_headers() -> Result<()> {
184 let fixed_header = concat!(
185 "addr=a@b.example.org; prefer-encrypt=mutual; ",
186 "keydata=xsBNBFzG3j0BCAC6iNhT8zydvCXi8LI/gFnkadMbfmSE/rTJskRRra/utGbLyDta/yTrJg",
187 " WL7O3y/g4HdDW/dN2z26Y6W13IMzx9gLInn1KQZChtqWAcr/ReUucXcymwcfg1mdkBGk3TSLeLihN6",
188 " CJx8Wsv8ig+kgAzte4f5rqEEAJVQ9WZHuti7UiYs6oRzqTo06CRe9owVXxzdMf0VDQtf7ZFm9dpzKK",
189 " bhH7Lu8880iiotQ9/yRCkDGp9fNThsrLdZiK6OIAcIBAqi2rI89aS1dAmnRbktQieCx5izzyYkR1Kv",
190 " VL3gTTllHOzfKVEC2asmtWu2e4se/+O4WMIS1eGrn7GeWVb0Vwc5ABEBAAHNETxhQEBiLmV4YW1wbG",
191 " UuZGU+wsCJBBABCAAzAhkBBQJcxt5FAhsDBAsJCAcGFQgJCgsCAxYCARYhBI4xxYKBgH3ANh5cufaK",
192 " rc9mtiMLAAoJEPaKrc9mtiML938H/18F+3Wf9/JaAy/8hCO1v4S2PVBhxaKCokaNFtkfaMRne2l087",
193 " LscCFPiFNyb4mv6Z3YeK8Xpxlp2sI0ecvdiqLUOGfnxS6tQrj+83EjtIrZ/hXOk1h121QFWH9Zg2VN",
194 " HtODXjAgdLDC0NWUrclR0ZOqEDQHeo0ibTILdokVfXFN25wakPmGaYJP2y729cb1ve7RzvIvwn+Ddd",
195 " fxo3ao72rBfLi7l4NQ4S0KsY4cw+/6l5bRCKYCP77wZtvCwUvfVVosLdT43agtSiBI49+ayqvZ8OCv",
196 " SJa61i+v81brTiEy9GBod4eAp45Ibsuemkw+gon4ZOvUXHTjwFB+h63MrozOwE0EXMbePQEIAL/vau",
197 " f1zK8JgCu3V+G+SOX0iWw5xUlCPX+ERpBbWfwu3uAqn4wYXD3JDE/fVAF668xiV4eTPtlSUd5h0mn+",
198 " G7uXMMOtkb+20SoEt50f8zw8TrL9t+ZsV11GKZWJpCar5AhXWsn6EEi8I2hLL5vn55ZZmHuGgN4jjm",
199 " kRl3ToKCLhaXwTBjCJem7N5EH7F75wErEITa55v4Lb4Nfca7vnvtYrI1OA446xa8gHra0SINelTD09",
200 " /JM/Fw4sWVPBaRZmJK/Tnu79N23No9XBUubmFPv1pNexZsQclicnTpt/BEWhiun7d6lfGB63K1aoHR",
201 " TR1pcrWvBuALuuz0gqar2zlI0AEQEAAcLAdgQYAQgAIAUCXMbeRQIbDBYhBI4xxYKBgH3ANh5cufaK",
202 " rc9mtiMLAAoJEPaKrc9mtiMLKSEIAIyLCRO2OyZ0IYRvRPpMn4p7E+7Pfcz/0mSkOy+1hshgJnqivX",
203 " urm8zwGrwdMqeV4eslKR9H1RUdWGUQJNbtwmmjrt5DHpIhYHl5t3FpCBaGbV20Omo00Q38lBl9Mtrm",
204 " ZkZw+ktEk6X+0xCKssMF+2MADkSOIufbR5HrDVB89VZOHCO9DeXvCUUAw2hyJiL/LHmLzJ40zYoTmb",
205 " +F//f0k0j+tRdbkefyRoCmwG7YGiT+2hnCdgcezswnzah5J3ZKlrg7jOGo1LxtbvNUzxNBbC6S/aNg",
206 " wm6qxo7xegRhmEl5uZ16zwyj4qz+xkjGy25Of5mWfUDoNw7OT7sjUbHOOMc="
207 );
208
209 let ah = Aheader::from_str(fixed_header)?;
210 assert_eq!(ah.addr, "a@b.example.org");
211 assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
212 assert_eq!(format!("{ah}"), fixed_header);
213
214 let rendered = ah.to_string();
215 assert_eq!(rendered, fixed_header);
216
217 let ah = Aheader::from_str(&format!(
218 " _foo; __FOO=BAR ;;; addr = a@b.example.org ;\r\n prefer-encrypt = mutual ; keydata = {RAWKEY}"
219 ))?;
220 assert_eq!(ah.addr, "a@b.example.org");
221 assert_eq!(ah.prefer_encrypt, EncryptPreference::Mutual);
222
223 Aheader::from_str(&format!(
224 "addr=a@b.example.org; prefer-encrypt=ignoreUnknownValues; keydata={RAWKEY}"
225 ))?;
226
227 Aheader::from_str(&format!("addr=a@b.example.org; keydata={RAWKEY}"))?;
228 Ok(())
229 }
230
231 #[test]
232 fn test_bad_headers() {
233 assert!(Aheader::from_str("").is_err());
234 assert!(Aheader::from_str("foo").is_err());
235 assert!(Aheader::from_str("\n\n\n").is_err());
236 assert!(Aheader::from_str(" ;;").is_err());
237 assert!(Aheader::from_str("addr=a@t.de; unknown=1; keydata=jau").is_err());
238 }
239
240 #[test]
241 fn test_display_aheader() {
242 assert!(
243 format!(
244 "{}",
245 Aheader {
246 addr: "test@example.com".to_string(),
247 public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
248 prefer_encrypt: EncryptPreference::Mutual,
249 verified: false
250 }
251 )
252 .contains("prefer-encrypt=mutual;")
253 );
254
255 assert!(
259 !format!(
260 "{}",
261 Aheader {
262 addr: "test@example.com".to_string(),
263 public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
264 prefer_encrypt: EncryptPreference::NoPreference,
265 verified: false
266 }
267 )
268 .contains("prefer-encrypt")
269 );
270
271 assert!(
273 format!(
274 "{}",
275 Aheader {
276 addr: "TeSt@eXaMpLe.cOm".to_string(),
277 public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
278 prefer_encrypt: EncryptPreference::Mutual,
279 verified: false
280 }
281 )
282 .contains("test@example.com")
283 );
284
285 assert!(
286 format!(
287 "{}",
288 Aheader {
289 addr: "test@example.com".to_string(),
290 public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
291 prefer_encrypt: EncryptPreference::NoPreference,
292 verified: true
293 }
294 )
295 .contains("_verified")
296 );
297 }
298}