1use super::{Canonicalization, Signature};
8use crate::common::headers::{HeaderStream, Writable, Writer};
9
10pub struct CanonicalBody<'a> {
11 canonicalization: Canonicalization,
12 body: &'a [u8],
13}
14
15impl Writable for CanonicalBody<'_> {
16 fn write(self, hasher: &mut impl Writer) {
17 let mut crlf_seq = 0;
18
19 match self.canonicalization {
20 Canonicalization::Relaxed => {
21 let mut last_ch = 0;
22 let mut is_empty = true;
23
24 for &ch in self.body {
25 match ch {
26 b' ' | b'\t' => {
27 while crlf_seq > 0 {
28 hasher.write(b"\r\n");
29 crlf_seq -= 1;
30 }
31 is_empty = false;
32 }
33 b'\n' => {
34 crlf_seq += 1;
35 }
36 b'\r' => {}
37 _ => {
38 while crlf_seq > 0 {
39 hasher.write(b"\r\n");
40 crlf_seq -= 1;
41 }
42
43 if last_ch == b' ' || last_ch == b'\t' {
44 hasher.write(b" ");
45 }
46
47 hasher.write(&[ch]);
48 is_empty = false;
49 }
50 }
51
52 last_ch = ch;
53 }
54
55 if !is_empty {
56 hasher.write(b"\r\n");
57 }
58 }
59 Canonicalization::Simple => {
60 for &ch in self.body {
61 match ch {
62 b'\n' => {
63 crlf_seq += 1;
64 }
65 b'\r' => {}
66 _ => {
67 while crlf_seq > 0 {
68 hasher.write(b"\r\n");
69 crlf_seq -= 1;
70 }
71 hasher.write(&[ch]);
72 }
73 }
74 }
75
76 hasher.write(b"\r\n");
77 }
78 }
79 }
80}
81
82impl Canonicalization {
83 pub fn canonicalize_headers<'a>(
84 &self,
85 headers: impl Iterator<Item = (&'a [u8], &'a [u8])>,
86 hasher: &mut impl Writer,
87 ) {
88 match self {
89 Canonicalization::Relaxed => {
90 for (name, value) in headers {
91 for &ch in name {
92 if !ch.is_ascii_whitespace() {
93 hasher.write(&[ch.to_ascii_lowercase()]);
94 }
95 }
96
97 hasher.write(b":");
98 let mut bw = 0;
99 let mut last_ch = 0;
100
101 for &ch in value {
102 if !ch.is_ascii_whitespace() {
103 if [b' ', b'\t'].contains(&last_ch) && bw > 0 {
104 hasher.write_len(b" ", &mut bw);
105 }
106 hasher.write_len(&[ch], &mut bw);
107 }
108 last_ch = ch;
109 }
110
111 if last_ch == b'\n' {
112 hasher.write(b"\r\n");
113 }
114 }
115 }
116 Canonicalization::Simple => {
117 for (name, value) in headers {
118 hasher.write(name);
119 hasher.write(b":");
120 hasher.write(value);
121 }
122 }
123 }
124 }
125
126 pub fn canonical_headers<'a>(
127 &self,
128 headers: Vec<(&'a [u8], &'a [u8])>,
129 ) -> CanonicalHeaders<'a> {
130 CanonicalHeaders {
131 canonicalization: *self,
132 headers,
133 }
134 }
135
136 pub fn canonical_body<'a>(&self, body: &'a [u8], l: u64) -> CanonicalBody<'a> {
137 CanonicalBody {
138 canonicalization: *self,
139 body: if l == 0 || body.is_empty() {
140 body
141 } else {
142 &body[..std::cmp::min(l as usize, body.len())]
143 },
144 }
145 }
146
147 pub fn serialize_name(&self, writer: &mut impl Writer) {
148 writer.write(match self {
149 Canonicalization::Relaxed => b"relaxed",
150 Canonicalization::Simple => b"simple",
151 });
152 }
153}
154
155impl Signature {
156 pub fn canonicalize<'x>(
157 &self,
158 mut message: impl HeaderStream<'x>,
159 ) -> (usize, CanonicalHeaders<'x>, Vec<String>, CanonicalBody<'x>) {
160 let mut headers = Vec::with_capacity(self.h.len());
161 let mut found_headers = vec![false; self.h.len()];
162 let mut signed_headers = Vec::with_capacity(self.h.len());
163
164 while let Some((name, value)) = message.next_header() {
165 if let Some(pos) = self
166 .h
167 .iter()
168 .position(|header| name.eq_ignore_ascii_case(header.as_bytes()))
169 {
170 headers.push((name, value));
171 found_headers[pos] = true;
172 signed_headers.push(std::str::from_utf8(name).unwrap().into());
173 }
174 }
175
176 let body = message.body();
177 let body_len = body.len();
178 let canonical_headers = self.ch.canonical_headers(headers);
179 let canonical_body = self.ch.canonical_body(body, u64::MAX);
180
181 signed_headers.reverse();
183 for (header, found) in self.h.iter().zip(found_headers) {
184 if !found {
185 signed_headers.push(header.to_string());
186 }
187 }
188
189 (body_len, canonical_headers, signed_headers, canonical_body)
190 }
191}
192
193pub struct CanonicalHeaders<'a> {
194 canonicalization: Canonicalization,
195 headers: Vec<(&'a [u8], &'a [u8])>,
196}
197
198impl Writable for CanonicalHeaders<'_> {
199 fn write(self, writer: &mut impl Writer) {
200 self.canonicalization
201 .canonicalize_headers(self.headers.into_iter().rev(), writer)
202 }
203}
204
205#[cfg(test)]
206mod test {
207 use mail_builder::encoders::base64::base64_encode;
208
209 use super::{CanonicalBody, CanonicalHeaders};
210 use crate::{
211 common::{
212 crypto::{HashImpl, Sha256},
213 headers::{HeaderIterator, Writable},
214 },
215 dkim::Canonicalization,
216 };
217
218 #[test]
219 #[allow(clippy::needless_collect)]
220 fn dkim_canonicalize() {
221 for (message, (relaxed_headers, relaxed_body), (simple_headers, simple_body)) in [
222 (
223 concat!(
224 "A: X\r\n",
225 "B : Y\t\r\n",
226 "\tZ \r\n",
227 "\r\n",
228 " C \r\n",
229 "D \t E\r\n"
230 ),
231 (
232 concat!("a:X\r\n", "b:Y Z\r\n",),
233 concat!(" C\r\n", "D E\r\n"),
234 ),
235 ("A: X\r\nB : Y\t\r\n\tZ \r\n", " C \r\nD \t E\r\n"),
236 ),
237 (
238 concat!(
239 " From : John\tdoe <jdoe@domain.com>\t\r\n",
240 "SUB JECT:\ttest \t \r\n\r\n",
241 " body \t \r\n",
242 "\r\n",
243 "\r\n",
244 ),
245 (
246 concat!("from:John doe <jdoe@domain.com>\r\n", "subject:test\r\n"),
247 " body\r\n",
248 ),
249 (
250 concat!(
251 " From : John\tdoe <jdoe@domain.com>\t\r\n",
252 "SUB JECT:\ttest \t \r\n"
253 ),
254 " body \t \r\n",
255 ),
256 ),
257 (
258 "H: value\t\r\n\r\n",
259 ("h:value\r\n", ""),
260 ("H: value\t\r\n", "\r\n"),
261 ),
262 (
263 "\tx\t: \t\t\tz\r\n\r\nabc",
264 ("x:z\r\n", "abc\r\n"),
265 ("\tx\t: \t\t\tz\r\n", "abc\r\n"),
266 ),
267 (
268 "Subject: hello\r\n\r\n\r\n",
269 ("subject:hello\r\n", ""),
270 ("Subject: hello\r\n", "\r\n"),
271 ),
272 ] {
273 let mut header_iterator = HeaderIterator::new(message.as_bytes());
274 let parsed_headers = (&mut header_iterator).collect::<Vec<_>>();
275 let raw_body = header_iterator
276 .body_offset()
277 .map(|pos| &message.as_bytes()[pos..])
278 .unwrap_or_default();
279
280 for (canonicalization, expected_headers, expected_body) in [
281 (Canonicalization::Relaxed, relaxed_headers, relaxed_body),
282 (Canonicalization::Simple, simple_headers, simple_body),
283 ] {
284 let mut headers = Vec::new();
285 CanonicalHeaders {
286 canonicalization,
287 headers: parsed_headers.iter().cloned().rev().collect(),
288 }
289 .write(&mut headers);
290 assert_eq!(expected_headers, String::from_utf8(headers).unwrap());
291
292 let mut body = Vec::new();
293 CanonicalBody {
294 canonicalization,
295 body: raw_body,
296 }
297 .write(&mut body);
298 assert_eq!(expected_body, String::from_utf8(body).unwrap());
299 }
300 }
301
302 for (canonicalization, hash) in [
304 (
305 Canonicalization::Relaxed,
306 "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
307 ),
308 (
309 Canonicalization::Simple,
310 "frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=",
311 ),
312 ] {
313 for body in ["\r\n", ""] {
314 let mut hasher = Sha256::hasher();
315 CanonicalBody {
316 canonicalization,
317 body: body.as_bytes(),
318 }
319 .write(&mut hasher);
320
321 #[cfg(feature = "sha1")]
322 {
323 use sha1::Digest;
324 assert_eq!(
325 String::from_utf8(base64_encode(hasher.finalize().as_ref()).unwrap())
326 .unwrap(),
327 hash,
328 );
329 }
330
331 #[cfg(all(feature = "ring", not(feature = "sha1")))]
332 assert_eq!(
333 String::from_utf8(base64_encode(hasher.finish().as_ref()).unwrap()).unwrap(),
334 hash,
335 );
336 }
337 }
338 }
339}