Commit
+199 -120 +/-7 browse
1 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
2 | index 0ba3101..0c24d6b 100644 |
3 | --- a/src/arc/seal.rs |
4 | +++ b/src/arc/seal.rs |
5 | @@ -14,10 +14,10 @@ use mail_builder::encoders::base64::base64_encode; |
6 | |
7 | use crate::{ |
8 | common::{ |
9 | - crypto::{HashAlgorithm, HashContext, Sha256, SigningKey}, |
10 | - headers::Writer, |
11 | + crypto::{HashAlgorithm, Sha256, SigningKey}, |
12 | + headers::{Writable, Writer}, |
13 | }, |
14 | - dkim::{Canonicalization, Done}, |
15 | + dkim::{canonicalize::CanonicalHeaders, Canonicalization, Done}, |
16 | ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, |
17 | }; |
18 | |
19 | @@ -55,12 +55,9 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
20 | _ => ChainValidation::Fail, |
21 | }; |
22 | } |
23 | - // Canonicalize headers |
24 | - let mut header_hasher = self.key.hasher(); |
25 | - let signed_headers = set |
26 | - .signature |
27 | - .canonicalize_headers(message, &mut header_hasher)?; |
28 | |
29 | + // Canonicalize headers |
30 | + let (canonical_headers, signed_headers) = set.signature.canonicalize_headers(message)?; |
31 | if signed_headers.is_empty() { |
32 | return Err(Error::NoHeadersFound); |
33 | } |
34 | @@ -78,15 +75,16 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
35 | // Use cached hash |
36 | set.signature.bh = base64_encode(bh)?; |
37 | } else { |
38 | - let mut body_hasher = self.key.hasher(); |
39 | - set.signature.cb.canonicalize_body( |
40 | - message |
41 | - .raw_message |
42 | - .get(message.body_offset..) |
43 | - .unwrap_or_default(), |
44 | - &mut body_hasher, |
45 | + let hash = self.key.hash( |
46 | + set.signature.cb.canonical_body( |
47 | + message |
48 | + .raw_message |
49 | + .get(message.body_offset..) |
50 | + .unwrap_or_default(), |
51 | + u64::MAX, |
52 | + ), |
53 | ); |
54 | - set.signature.bh = base64_encode(body_hasher.complete().as_ref())?; |
55 | + set.signature.bh = base64_encode(hash.as_ref())?; |
56 | } |
57 | |
58 | // Create Signature |
59 | @@ -103,39 +101,60 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
60 | }; |
61 | set.signature.h = signed_headers; |
62 | |
63 | - // Add signature to hash |
64 | - set.signature.write(&mut header_hasher, false); |
65 | - |
66 | // Sign |
67 | - let b = self.key.sign(header_hasher.complete())?; |
68 | + let b = self.key.sign(SignableSet { |
69 | + set: &set, |
70 | + headers: canonical_headers, |
71 | + })?; |
72 | set.signature.b = base64_encode(&b)?; |
73 | |
74 | - // Hash ARC chain |
75 | - let mut header_hasher = self.key.hasher(); |
76 | - if !arc_output.set.is_empty() { |
77 | + // Seal |
78 | + let b = self.key.sign(SignableChain { |
79 | + arc_output, |
80 | + set: &set, |
81 | + })?; |
82 | + set.seal.b = base64_encode(&b)?; |
83 | + |
84 | + Ok(set) |
85 | + } |
86 | + } |
87 | + |
88 | + struct SignableSet<'a> { |
89 | + set: &'a ArcSet<'a>, |
90 | + headers: CanonicalHeaders<'a>, |
91 | + } |
92 | + |
93 | + impl<'a> Writable for SignableSet<'a> { |
94 | + fn write(self, writer: &mut impl Writer) { |
95 | + self.headers.write(writer); |
96 | + self.set.signature.write(writer, false); |
97 | + } |
98 | + } |
99 | + |
100 | + struct SignableChain<'a> { |
101 | + arc_output: &'a ArcOutput<'a>, |
102 | + set: &'a ArcSet<'a>, |
103 | + } |
104 | + |
105 | + impl<'a> Writable for SignableChain<'a> { |
106 | + fn write(self, writer: &mut impl Writer) { |
107 | + if !self.arc_output.set.is_empty() { |
108 | Canonicalization::Relaxed.canonicalize_headers( |
109 | - &mut arc_output.set.iter().flat_map(|set| { |
110 | + self.arc_output.set.iter().flat_map(|set| { |
111 | [ |
112 | (set.results.name, set.results.value), |
113 | (set.signature.name, set.signature.value), |
114 | (set.seal.name, set.seal.value), |
115 | ] |
116 | }), |
117 | - &mut header_hasher, |
118 | + writer, |
119 | ); |
120 | } |
121 | |
122 | - // Hash ARC headers for the current instance |
123 | - set.results.write(&mut header_hasher, set.seal.i, false); |
124 | - set.signature.write(&mut header_hasher, false); |
125 | - header_hasher.write(b"\r\n"); |
126 | - set.seal.write(&mut header_hasher, false); |
127 | - |
128 | - // Seal |
129 | - let b = self.key.sign(header_hasher.complete())?; |
130 | - set.seal.b = base64_encode(&b)?; |
131 | - |
132 | - Ok(set) |
133 | + self.set.results.write(writer, self.set.seal.i, false); |
134 | + self.set.signature.write(writer, false); |
135 | + writer.write(b"\r\n"); |
136 | + self.set.seal.write(writer, false); |
137 | } |
138 | } |
139 | |
140 | @@ -143,8 +162,7 @@ impl Signature { |
141 | pub(crate) fn canonicalize_headers<'x>( |
142 | &self, |
143 | message: &'x AuthenticatedMessage<'x>, |
144 | - header_hasher: &mut impl Writer, |
145 | - ) -> crate::Result<Vec<String>> { |
146 | + ) -> crate::Result<(CanonicalHeaders<'x>, Vec<String>)> { |
147 | let mut headers = Vec::with_capacity(self.h.len()); |
148 | let mut found_headers = vec![false; self.h.len()]; |
149 | let mut signed_headers = Vec::with_capacity(self.h.len()); |
150 | @@ -161,8 +179,7 @@ impl Signature { |
151 | } |
152 | } |
153 | |
154 | - self.ch |
155 | - .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher); |
156 | + let canonical_headers = self.ch.canonical_headers(headers); |
157 | |
158 | // Add any missing headers |
159 | signed_headers.reverse(); |
160 | @@ -172,7 +189,7 @@ impl Signature { |
161 | } |
162 | } |
163 | |
164 | - Ok(signed_headers) |
165 | + Ok((canonical_headers, signed_headers)) |
166 | } |
167 | } |
168 | |
169 | diff --git a/src/common/crypto/mod.rs b/src/common/crypto/mod.rs |
170 | index 6c8693d..1ee3b41 100644 |
171 | --- a/src/common/crypto/mod.rs |
172 | +++ b/src/common/crypto/mod.rs |
173 | @@ -2,7 +2,7 @@ use sha1::{digest::Output, Digest}; |
174 | |
175 | use crate::{dkim::Canonicalization, Result}; |
176 | |
177 | - use super::headers::Writer; |
178 | + use super::headers::{Writable, Writer}; |
179 | |
180 | mod rust_crypto; |
181 | pub use rust_crypto::{Ed25519Key, RsaKey}; |
182 | @@ -11,10 +11,12 @@ pub(crate) use rust_crypto::{Ed25519PublicKey, RsaPublicKey}; |
183 | pub trait SigningKey { |
184 | type Hasher: HashImpl; |
185 | |
186 | - fn sign(&self, data: HashOutput) -> Result<Vec<u8>>; |
187 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>>; |
188 | |
189 | - fn hasher(&self) -> <Self::Hasher as HashImpl>::Context { |
190 | - <Self::Hasher as HashImpl>::hasher() |
191 | + fn hash(&self, data: impl Writable) -> HashOutput { |
192 | + let mut hasher = <Self::Hasher as HashImpl>::hasher(); |
193 | + data.write(&mut hasher); |
194 | + hasher.complete() |
195 | } |
196 | |
197 | fn algorithm(&self) -> Algorithm; |
198 | @@ -71,10 +73,18 @@ pub enum HashAlgorithm { |
199 | } |
200 | |
201 | impl HashAlgorithm { |
202 | - pub fn hash(&self, data: &[u8]) -> HashOutput { |
203 | + pub fn hash(&self, data: impl Writable) -> HashOutput { |
204 | match self { |
205 | - Self::Sha1 => HashOutput::Sha1(sha1::Sha1::digest(data)), |
206 | - Self::Sha256 => HashOutput::Sha256(sha2::Sha256::digest(data)), |
207 | + Self::Sha1 => { |
208 | + let mut hasher = sha1::Sha1::new(); |
209 | + data.write(&mut hasher); |
210 | + HashOutput::Sha1(hasher.finalize()) |
211 | + } |
212 | + Self::Sha256 => { |
213 | + let mut hasher = sha2::Sha256::new(); |
214 | + data.write(&mut hasher); |
215 | + HashOutput::Sha256(hasher.finalize()) |
216 | + } |
217 | } |
218 | } |
219 | } |
220 | diff --git a/src/common/crypto/rust_crypto.rs b/src/common/crypto/rust_crypto.rs |
221 | index 2d6c34d..a1af70d 100644 |
222 | --- a/src/common/crypto/rust_crypto.rs |
223 | +++ b/src/common/crypto/rust_crypto.rs |
224 | @@ -4,7 +4,11 @@ use ed25519_dalek::Signer; |
225 | use rsa::{pkcs1::DecodeRsaPrivateKey, PaddingScheme, PublicKey as _, RsaPrivateKey}; |
226 | use sha2::digest::Digest; |
227 | |
228 | - use crate::{common::headers::Writer, dkim::Canonicalization, Error, Result}; |
229 | + use crate::{ |
230 | + common::headers::{Writable, Writer}, |
231 | + dkim::Canonicalization, |
232 | + Error, Result, |
233 | + }; |
234 | |
235 | use super::{Algorithm, HashContext, HashImpl, HashOutput, Sha1, Sha256, SigningKey, VerifyingKey}; |
236 | |
237 | @@ -41,11 +45,12 @@ impl<T: HashImpl> RsaKey<T> { |
238 | impl SigningKey for RsaKey<Sha1> { |
239 | type Hasher = Sha1; |
240 | |
241 | - fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
242 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>> { |
243 | + let hash = self.hash(input); |
244 | self.inner |
245 | .sign( |
246 | PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(), |
247 | - data.as_ref(), |
248 | + hash.as_ref(), |
249 | ) |
250 | .map_err(|err| Error::CryptoError(err.to_string())) |
251 | } |
252 | @@ -58,11 +63,12 @@ impl SigningKey for RsaKey<Sha1> { |
253 | impl SigningKey for RsaKey<Sha256> { |
254 | type Hasher = Sha256; |
255 | |
256 | - fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
257 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>> { |
258 | + let hash = self.hash(input); |
259 | self.inner |
260 | .sign( |
261 | PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(), |
262 | - data.as_ref(), |
263 | + hash.as_ref(), |
264 | ) |
265 | .map_err(|err| Error::CryptoError(err.to_string())) |
266 | } |
267 | @@ -93,8 +99,9 @@ impl Ed25519Key { |
268 | impl SigningKey for Ed25519Key { |
269 | type Hasher = Sha256; |
270 | |
271 | - fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
272 | - Ok(self.inner.sign(data.as_ref()).to_bytes().to_vec()) |
273 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>> { |
274 | + let hash = self.hash(input); |
275 | + Ok(self.inner.sign(hash.as_ref()).to_bytes().to_vec()) |
276 | } |
277 | |
278 | fn algorithm(&self) -> Algorithm { |
279 | @@ -128,7 +135,10 @@ impl VerifyingKey for RsaPublicKey { |
280 | ) -> Result<()> { |
281 | match algorithm { |
282 | Algorithm::RsaSha256 => { |
283 | - let hash = canonicalization.hash_headers::<Sha256>(headers); |
284 | + let mut hasher = sha2::Sha256::new(); |
285 | + canonicalization.canonicalize_headers(headers, &mut hasher); |
286 | + let hash = hasher.finalize(); |
287 | + |
288 | self.inner |
289 | .verify( |
290 | PaddingScheme::new_pkcs1v15_sign::<sha2::Sha256>(), |
291 | @@ -138,7 +148,10 @@ impl VerifyingKey for RsaPublicKey { |
292 | .map_err(|_| Error::FailedVerification) |
293 | } |
294 | Algorithm::RsaSha1 => { |
295 | - let hash = canonicalization.hash_headers::<Sha1>(headers); |
296 | + let mut hasher = sha1::Sha1::new(); |
297 | + canonicalization.canonicalize_headers(headers, &mut hasher); |
298 | + let hash = hasher.finalize(); |
299 | + |
300 | self.inner |
301 | .verify( |
302 | PaddingScheme::new_pkcs1v15_sign::<sha1::Sha1>(), |
303 | @@ -179,7 +192,10 @@ impl VerifyingKey for Ed25519PublicKey { |
304 | return Err(Error::IncompatibleAlgorithms); |
305 | } |
306 | |
307 | - let hash = canonicalization.hash_headers::<Sha256>(headers); |
308 | + let mut hasher = sha2::Sha256::new(); |
309 | + canonicalization.canonicalize_headers(headers, &mut hasher); |
310 | + let hash = hasher.finalize(); |
311 | + |
312 | self.inner |
313 | .verify_strict( |
314 | hash.as_ref(), |
315 | diff --git a/src/common/headers.rs b/src/common/headers.rs |
316 | index 4b24ea2..51cdb07 100644 |
317 | --- a/src/common/headers.rs |
318 | +++ b/src/common/headers.rs |
319 | @@ -343,6 +343,16 @@ pub trait HeaderWriter: Sized { |
320 | } |
321 | } |
322 | |
323 | + pub trait Writable { |
324 | + fn write(self, writer: &mut impl Writer); |
325 | + } |
326 | + |
327 | + impl Writable for &[u8] { |
328 | + fn write(self, writer: &mut impl Writer) { |
329 | + writer.write(self); |
330 | + } |
331 | + } |
332 | + |
333 | pub trait Writer { |
334 | fn write(&mut self, buf: &[u8]); |
335 | |
336 | diff --git a/src/common/message.rs b/src/common/message.rs |
337 | index c6ed497..b5a33c1 100644 |
338 | --- a/src/common/message.rs |
339 | +++ b/src/common/message.rs |
340 | @@ -10,11 +10,7 @@ |
341 | |
342 | use mail_parser::{parsers::MessageStream, HeaderValue}; |
343 | |
344 | - use crate::{ |
345 | - arc, |
346 | - common::crypto::{HashAlgorithm, Sha1, Sha256}, |
347 | - dkim, AuthenticatedMessage, |
348 | - }; |
349 | + use crate::{arc, common::crypto::HashAlgorithm, dkim, AuthenticatedMessage}; |
350 | |
351 | use super::headers::{AuthenticatedHeader, Header, HeaderParser}; |
352 | |
353 | @@ -159,10 +155,7 @@ impl<'x> AuthenticatedMessage<'x> { |
354 | |
355 | // Calculate body hashes |
356 | for (cb, ha, l, bh) in &mut message.body_hashes { |
357 | - *bh = match ha { |
358 | - HashAlgorithm::Sha256 => cb.hash_body::<Sha256>(body, *l).as_ref().to_vec(), |
359 | - HashAlgorithm::Sha1 => cb.hash_body::<Sha1>(body, *l).as_ref().to_vec(), |
360 | - }; |
361 | + *bh = ha.hash(cb.canonical_body(body, *l)).as_ref().to_vec(); |
362 | } |
363 | |
364 | // Sort ARC headers |
365 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
366 | index a0a8bd1..4184675 100644 |
367 | --- a/src/dkim/canonicalize.rs |
368 | +++ b/src/dkim/canonicalize.rs |
369 | @@ -8,22 +8,24 @@ |
370 | * except according to those terms. |
371 | */ |
372 | |
373 | - use crate::common::{ |
374 | - crypto::{HashContext, HashImpl}, |
375 | - headers::{HeaderStream, Writer}, |
376 | - }; |
377 | + use crate::common::headers::{HeaderStream, Writable, Writer}; |
378 | |
379 | use super::{Canonicalization, Signature}; |
380 | |
381 | - impl Canonicalization { |
382 | - pub fn canonicalize_body(&self, body: &[u8], hasher: &mut impl Writer) { |
383 | + pub struct CanonicalBody<'a> { |
384 | + canonicalization: Canonicalization, |
385 | + body: &'a [u8], |
386 | + } |
387 | + |
388 | + impl Writable for CanonicalBody<'_> { |
389 | + fn write(self, hasher: &mut impl Writer) { |
390 | let mut crlf_seq = 0; |
391 | |
392 | - match self { |
393 | + match self.canonicalization { |
394 | Canonicalization::Relaxed => { |
395 | let mut last_ch = 0; |
396 | |
397 | - for &ch in body { |
398 | + for &ch in self.body { |
399 | match ch { |
400 | b' ' | b'\t' => { |
401 | while crlf_seq > 0 { |
402 | @@ -53,7 +55,7 @@ impl Canonicalization { |
403 | } |
404 | } |
405 | Canonicalization::Simple => { |
406 | - for &ch in body { |
407 | + for &ch in self.body { |
408 | match ch { |
409 | b'\n' => { |
410 | crlf_seq += 1; |
411 | @@ -73,10 +75,12 @@ impl Canonicalization { |
412 | |
413 | hasher.write(b"\r\n"); |
414 | } |
415 | + } |
416 | |
417 | - pub fn canonicalize_headers<'x>( |
418 | + impl Canonicalization { |
419 | + pub fn canonicalize_headers<'a>( |
420 | &self, |
421 | - headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
422 | + headers: impl Iterator<Item = (&'a [u8], &'a [u8])>, |
423 | hasher: &mut impl Writer, |
424 | ) { |
425 | match self { |
426 | @@ -87,6 +91,7 @@ impl Canonicalization { |
427 | hasher.write(&[ch.to_ascii_lowercase()]); |
428 | } |
429 | } |
430 | + |
431 | hasher.write(b":"); |
432 | let mut bw = 0; |
433 | let mut last_ch = 0; |
434 | @@ -100,6 +105,7 @@ impl Canonicalization { |
435 | } |
436 | last_ch = ch; |
437 | } |
438 | + |
439 | if last_ch == b'\n' { |
440 | hasher.write(b"\r\n"); |
441 | } |
442 | @@ -115,26 +121,25 @@ impl Canonicalization { |
443 | } |
444 | } |
445 | |
446 | - pub fn hash_headers<'x, T: HashImpl>( |
447 | + pub fn canonical_headers<'a>( |
448 | &self, |
449 | - headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
450 | - ) -> impl AsRef<[u8]> { |
451 | - let mut hasher = T::hasher(); |
452 | - self.canonicalize_headers(headers, &mut hasher); |
453 | - hasher.complete() |
454 | + headers: Vec<(&'a [u8], &'a [u8])>, |
455 | + ) -> CanonicalHeaders<'a> { |
456 | + CanonicalHeaders { |
457 | + canonicalization: *self, |
458 | + headers, |
459 | + } |
460 | } |
461 | |
462 | - pub fn hash_body<T: HashImpl>(&self, body: &[u8], l: u64) -> impl AsRef<[u8]> { |
463 | - let mut hasher = T::hasher(); |
464 | - self.canonicalize_body( |
465 | - if l == 0 || body.is_empty() { |
466 | + pub fn canonical_body<'a>(&self, body: &'a [u8], l: u64) -> CanonicalBody<'a> { |
467 | + CanonicalBody { |
468 | + canonicalization: *self, |
469 | + body: if l == 0 || body.is_empty() { |
470 | body |
471 | } else { |
472 | &body[..std::cmp::min(l as usize, body.len())] |
473 | }, |
474 | - &mut hasher, |
475 | - ); |
476 | - hasher.complete() |
477 | + } |
478 | } |
479 | |
480 | pub fn serialize_name(&self, writer: &mut impl Writer) { |
481 | @@ -146,13 +151,10 @@ impl Canonicalization { |
482 | } |
483 | |
484 | impl Signature { |
485 | - #[allow(clippy::while_let_on_iterator)] |
486 | pub(crate) fn canonicalize<'x>( |
487 | &self, |
488 | mut message: impl HeaderStream<'x>, |
489 | - header_hasher: &mut impl Writer, |
490 | - body_hasher: &mut impl Writer, |
491 | - ) -> (usize, Vec<String>) { |
492 | + ) -> (usize, CanonicalHeaders<'x>, Vec<String>, CanonicalBody<'x>) { |
493 | let mut headers = Vec::with_capacity(self.h.len()); |
494 | let mut found_headers = vec![false; self.h.len()]; |
495 | let mut signed_headers = Vec::with_capacity(self.h.len()); |
496 | @@ -171,9 +173,8 @@ impl Signature { |
497 | |
498 | let body = message.body(); |
499 | let body_len = body.len(); |
500 | - self.ch |
501 | - .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher); |
502 | - self.cb.canonicalize_body(body, body_hasher); |
503 | + let canonical_headers = self.ch.canonical_headers(headers); |
504 | + let canonical_body = self.ch.canonical_body(&body, u64::MAX); |
505 | |
506 | // Add any missing headers |
507 | signed_headers.reverse(); |
508 | @@ -183,13 +184,29 @@ impl Signature { |
509 | } |
510 | } |
511 | |
512 | - (body_len, signed_headers) |
513 | + (body_len, canonical_headers, signed_headers, canonical_body) |
514 | + } |
515 | + } |
516 | + |
517 | + pub struct CanonicalHeaders<'a> { |
518 | + canonicalization: Canonicalization, |
519 | + headers: Vec<(&'a [u8], &'a [u8])>, |
520 | + } |
521 | + |
522 | + impl<'a> Writable for CanonicalHeaders<'a> { |
523 | + fn write(self, writer: &mut impl Writer) { |
524 | + self.canonicalization |
525 | + .canonicalize_headers(self.headers.into_iter().rev(), writer) |
526 | } |
527 | } |
528 | |
529 | #[cfg(test)] |
530 | mod test { |
531 | - use crate::{common::headers::HeaderIterator, dkim::Canonicalization}; |
532 | + use super::{CanonicalBody, CanonicalHeaders}; |
533 | + use crate::{ |
534 | + common::headers::{HeaderIterator, Writable}, |
535 | + dkim::Canonicalization, |
536 | + }; |
537 | |
538 | #[test] |
539 | #[allow(clippy::needless_collect)] |
540 | @@ -253,13 +270,19 @@ mod test { |
541 | (Canonicalization::Simple, simple_headers, simple_body), |
542 | ] { |
543 | let mut headers = Vec::new(); |
544 | - let mut body = Vec::new(); |
545 | - |
546 | - canonicalization |
547 | - .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers); |
548 | - canonicalization.canonicalize_body(raw_body, &mut body); |
549 | - |
550 | + CanonicalHeaders { |
551 | + canonicalization, |
552 | + headers: parsed_headers.iter().cloned().rev().collect(), |
553 | + } |
554 | + .write(&mut headers); |
555 | assert_eq!(expected_headers, String::from_utf8(headers).unwrap()); |
556 | + |
557 | + let mut body = Vec::new(); |
558 | + CanonicalBody { |
559 | + canonicalization, |
560 | + body: &raw_body, |
561 | + } |
562 | + .write(&mut body); |
563 | assert_eq!(expected_body, String::from_utf8(body).unwrap()); |
564 | } |
565 | } |
566 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
567 | index 5b47db7..7d9cf3f 100644 |
568 | --- a/src/dkim/sign.rs |
569 | +++ b/src/dkim/sign.rs |
570 | @@ -12,11 +12,12 @@ use std::time::SystemTime; |
571 | |
572 | use mail_builder::encoders::base64::base64_encode; |
573 | |
574 | - use super::{DkimSigner, Done, Signature}; |
575 | + use super::{canonicalize::CanonicalHeaders, DkimSigner, Done, Signature}; |
576 | + |
577 | use crate::{ |
578 | common::{ |
579 | - crypto::{HashContext, SigningKey}, |
580 | - headers::{ChainedHeaderIterator, HeaderIterator, HeaderStream}, |
581 | + crypto::SigningKey, |
582 | + headers::{ChainedHeaderIterator, HeaderIterator, HeaderStream, Writable, Writer}, |
583 | }, |
584 | Error, |
585 | }; |
586 | @@ -54,13 +55,9 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
587 | message: impl HeaderStream<'x>, |
588 | now: u64, |
589 | ) -> crate::Result<Signature> { |
590 | - let mut body_hasher = self.key.hasher(); |
591 | - let mut header_hasher = self.key.hasher(); |
592 | - |
593 | // Canonicalize headers and body |
594 | - let (body_len, signed_headers) = |
595 | - self.template |
596 | - .canonicalize(message, &mut header_hasher, &mut body_hasher); |
597 | + let (body_len, canonical_headers, signed_headers, canonical_body) = |
598 | + self.template.canonicalize(message); |
599 | |
600 | if signed_headers.is_empty() { |
601 | return Err(Error::NoHeadersFound); |
602 | @@ -68,7 +65,8 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
603 | |
604 | // Create Signature |
605 | let mut signature = self.template.clone(); |
606 | - signature.bh = base64_encode(body_hasher.complete().as_ref())?; |
607 | + let body_hash = self.key.hash(canonical_body); |
608 | + signature.bh = base64_encode(body_hash.as_ref())?; |
609 | signature.t = now; |
610 | signature.x = if signature.x > 0 { |
611 | now + signature.x |
612 | @@ -80,11 +78,11 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
613 | signature.l = body_len as u64; |
614 | } |
615 | |
616 | - // Add signature to hash |
617 | - signature.write(&mut header_hasher, false); |
618 | - |
619 | // Sign |
620 | - let b = self.key.sign(header_hasher.complete())?; |
621 | + let b = self.key.sign(SignableMessage { |
622 | + headers: canonical_headers, |
623 | + signature: &signature, |
624 | + })?; |
625 | |
626 | // Encode |
627 | signature.b = base64_encode(&b)?; |
628 | @@ -93,6 +91,18 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
629 | } |
630 | } |
631 | |
632 | + pub(super) struct SignableMessage<'a> { |
633 | + headers: CanonicalHeaders<'a>, |
634 | + signature: &'a Signature, |
635 | + } |
636 | + |
637 | + impl<'a> Writable for SignableMessage<'a> { |
638 | + fn write(self, writer: &mut impl Writer) { |
639 | + self.headers.write(writer); |
640 | + self.signature.write(writer, false); |
641 | + } |
642 | + } |
643 | + |
644 | #[cfg(test)] |
645 | #[allow(unused)] |
646 | mod test { |