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