Commit
+227 -225 +/-9 browse
1 | diff --git a/src/arc/headers.rs b/src/arc/headers.rs |
2 | index 384c053..007c5d9 100644 |
3 | --- a/src/arc/headers.rs |
4 | +++ b/src/arc/headers.rs |
5 | @@ -8,10 +8,11 @@ |
6 | * except according to those terms. |
7 | */ |
8 | |
9 | - use std::io; |
10 | - |
11 | use crate::{ |
12 | - common::{crypto::Algorithm, headers::HeaderWriter}, |
13 | + common::{ |
14 | + crypto::Algorithm, |
15 | + headers::{HeaderWriter, Writer}, |
16 | + }, |
17 | dkim::Canonicalization, |
18 | AuthenticationResults, |
19 | }; |
20 | @@ -19,44 +20,44 @@ use crate::{ |
21 | use super::{ArcSet, ChainValidation, Seal, Signature}; |
22 | |
23 | impl<'x> Signature<'x> { |
24 | - pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
25 | + pub(crate) fn write(&self, writer: &mut impl Writer, as_header: bool) { |
26 | let (header, new_line) = match self.ch { |
27 | Canonicalization::Relaxed if !as_header => (&b"arc-message-signature:"[..], &b" "[..]), |
28 | _ => (&b"ARC-Message-Signature: "[..], &b"\r\n\t"[..]), |
29 | }; |
30 | - writer.write_all(header)?; |
31 | - writer.write_all(b"i=")?; |
32 | - writer.write_all(self.i.to_string().as_bytes())?; |
33 | - writer.write_all(b"; a=")?; |
34 | - writer.write_all(match self.a { |
35 | + writer.write(header); |
36 | + writer.write(b"i="); |
37 | + writer.write(self.i.to_string().as_bytes()); |
38 | + writer.write(b"; a="); |
39 | + writer.write(match self.a { |
40 | Algorithm::RsaSha256 => b"rsa-sha256", |
41 | Algorithm::RsaSha1 => b"rsa-sha1", |
42 | Algorithm::Ed25519Sha256 => b"ed25519-sha256", |
43 | - })?; |
44 | + }); |
45 | for (tag, value) in [(&b"; s="[..], &self.s), (&b"; d="[..], &self.d)] { |
46 | - writer.write_all(tag)?; |
47 | - writer.write_all(value.as_bytes())?; |
48 | + writer.write(tag); |
49 | + writer.write(value.as_bytes()); |
50 | } |
51 | - writer.write_all(b"; c=")?; |
52 | - self.ch.serialize_name(&mut writer)?; |
53 | - writer.write_all(b"/")?; |
54 | - self.cb.serialize_name(&mut writer)?; |
55 | + writer.write(b"; c="); |
56 | + self.ch.serialize_name(writer); |
57 | + writer.write(b"/"); |
58 | + self.cb.serialize_name(writer); |
59 | |
60 | - writer.write_all(b";")?; |
61 | - writer.write_all(new_line)?; |
62 | + writer.write(b";"); |
63 | + writer.write(new_line); |
64 | |
65 | let mut bw = 1; |
66 | for (num, h) in self.h.iter().enumerate() { |
67 | if bw + h.len() + 1 >= 76 { |
68 | - writer.write_all(new_line)?; |
69 | + writer.write(new_line); |
70 | bw = 1; |
71 | } |
72 | if num > 0 { |
73 | - bw += writer.write(b":")?; |
74 | + writer.write_len(b":", &mut bw); |
75 | } else { |
76 | - bw += writer.write(b"h=")?; |
77 | + writer.write_len(b"h=", &mut bw); |
78 | } |
79 | - bw += writer.write(h.as_bytes())?; |
80 | + writer.write_len(h.as_bytes(), &mut bw); |
81 | } |
82 | |
83 | for (tag, value) in [ |
84 | @@ -66,133 +67,126 @@ impl<'x> Signature<'x> { |
85 | ] { |
86 | if value > 0 { |
87 | let value = value.to_string(); |
88 | - bw += writer.write(b";")?; |
89 | + writer.write_len(b";", &mut bw); |
90 | if bw + tag.len() + value.len() >= 76 { |
91 | - writer.write_all(new_line)?; |
92 | + writer.write(new_line); |
93 | bw = 1; |
94 | } else { |
95 | - bw += writer.write(b" ")?; |
96 | + writer.write_len(b" ", &mut bw); |
97 | } |
98 | |
99 | - bw += writer.write(tag)?; |
100 | - bw += writer.write(value.as_bytes())?; |
101 | + writer.write_len(tag, &mut bw); |
102 | + writer.write_len(value.as_bytes(), &mut bw); |
103 | } |
104 | } |
105 | |
106 | for (tag, value) in [(&b"; bh="[..], &self.bh), (&b"; b="[..], &self.b)] { |
107 | - bw += writer.write(tag)?; |
108 | + writer.write_len(tag, &mut bw); |
109 | for &byte in value { |
110 | - bw += writer.write(&[byte])?; |
111 | + writer.write_len(&[byte], &mut bw); |
112 | if bw >= 76 { |
113 | - writer.write_all(new_line)?; |
114 | + writer.write(new_line); |
115 | bw = 1; |
116 | } |
117 | } |
118 | } |
119 | |
120 | - writer.write_all(b";")?; |
121 | + writer.write(b";"); |
122 | if as_header { |
123 | - writer.write_all(b"\r\n")?; |
124 | + writer.write(b"\r\n"); |
125 | } |
126 | - Ok(()) |
127 | } |
128 | } |
129 | |
130 | impl<'x> Seal<'x> { |
131 | - pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
132 | + pub(crate) fn write(&self, writer: &mut impl Writer, as_header: bool) { |
133 | let (header, new_line) = if !as_header { |
134 | (&b"arc-seal:"[..], &b" "[..]) |
135 | } else { |
136 | (&b"ARC-Seal: "[..], &b"\r\n\t"[..]) |
137 | }; |
138 | |
139 | - writer.write_all(header)?; |
140 | - writer.write_all(b"i=")?; |
141 | - writer.write_all(self.i.to_string().as_bytes())?; |
142 | - writer.write_all(b"; a=")?; |
143 | - writer.write_all(match self.a { |
144 | + writer.write(header); |
145 | + writer.write(b"i="); |
146 | + writer.write(self.i.to_string().as_bytes()); |
147 | + writer.write(b"; a="); |
148 | + writer.write(match self.a { |
149 | Algorithm::RsaSha256 => b"rsa-sha256", |
150 | Algorithm::RsaSha1 => b"rsa-sha1", |
151 | Algorithm::Ed25519Sha256 => b"ed25519-sha256", |
152 | - })?; |
153 | + }); |
154 | for (tag, value) in [(&b"; s="[..], &self.s), (&b"; d="[..], &self.d)] { |
155 | - writer.write_all(tag)?; |
156 | - writer.write_all(value.as_bytes())?; |
157 | + writer.write(tag); |
158 | + writer.write(value.as_bytes()); |
159 | } |
160 | - writer.write_all(b"; cv=")?; |
161 | - writer.write_all(match self.cv { |
162 | + writer.write(b"; cv="); |
163 | + writer.write(match self.cv { |
164 | ChainValidation::None => b"none", |
165 | ChainValidation::Fail => b"fail", |
166 | ChainValidation::Pass => b"pass", |
167 | - })?; |
168 | + }); |
169 | |
170 | - writer.write_all(b";")?; |
171 | - writer.write_all(new_line)?; |
172 | + writer.write(b";"); |
173 | + writer.write(new_line); |
174 | |
175 | let mut bw = 1; |
176 | if self.t > 0 { |
177 | - bw += writer.write(b"t=")?; |
178 | - bw += writer.write(self.t.to_string().as_bytes())?; |
179 | - bw += writer.write(b"; ")?; |
180 | + writer.write_len(b"t=", &mut bw); |
181 | + writer.write_len(self.t.to_string().as_bytes(), &mut bw); |
182 | + writer.write_len(b"; ", &mut bw); |
183 | } |
184 | |
185 | - bw += writer.write(b"b=")?; |
186 | + writer.write_len(b"b=", &mut bw); |
187 | for &byte in &self.b { |
188 | - bw += writer.write(&[byte])?; |
189 | + writer.write_len(&[byte], &mut bw); |
190 | if bw >= 76 { |
191 | - writer.write_all(new_line)?; |
192 | + writer.write(new_line); |
193 | bw = 1; |
194 | } |
195 | } |
196 | |
197 | - writer.write_all(b";")?; |
198 | + writer.write(b";"); |
199 | if as_header { |
200 | - writer.write_all(b"\r\n")?; |
201 | + writer.write(b"\r\n"); |
202 | } |
203 | - Ok(()) |
204 | } |
205 | } |
206 | |
207 | impl<'x> AuthenticationResults<'x> { |
208 | - pub(crate) fn write( |
209 | - &self, |
210 | - mut writer: impl io::Write, |
211 | - i: u32, |
212 | - as_header: bool, |
213 | - ) -> io::Result<()> { |
214 | - writer.write_all(if !as_header { |
215 | + pub(crate) fn write(&self, writer: &mut impl Writer, i: u32, as_header: bool) { |
216 | + writer.write(if !as_header { |
217 | b"arc-authentication-results:" |
218 | } else { |
219 | b"ARC-Authentication-Results: " |
220 | - })?; |
221 | - writer.write_all(b"i=")?; |
222 | - writer.write_all(i.to_string().as_bytes())?; |
223 | - writer.write_all(b"; ")?; |
224 | - writer.write_all(self.hostname.as_bytes())?; |
225 | + }); |
226 | + writer.write(b"i="); |
227 | + writer.write(i.to_string().as_bytes()); |
228 | + writer.write(b"; "); |
229 | + writer.write(self.hostname.as_bytes()); |
230 | if !as_header { |
231 | let mut last_is_space = false; |
232 | for &ch in self.auth_results.as_bytes() { |
233 | if !ch.is_ascii_whitespace() { |
234 | if last_is_space { |
235 | - writer.write_all(&[b' '])?; |
236 | + writer.write(&[b' ']); |
237 | last_is_space = false; |
238 | } |
239 | - writer.write_all(&[ch])?; |
240 | + writer.write(&[ch]); |
241 | } else { |
242 | last_is_space = true; |
243 | } |
244 | } |
245 | } else { |
246 | - writer.write_all(self.auth_results.as_bytes())?; |
247 | + writer.write(self.auth_results.as_bytes()); |
248 | } |
249 | - writer.write_all(b"\r\n") |
250 | + writer.write(b"\r\n"); |
251 | } |
252 | } |
253 | |
254 | impl<'x> HeaderWriter for ArcSet<'x> { |
255 | - fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
256 | - self.seal.write(&mut writer, true)?; |
257 | - self.signature.write(&mut writer, true)?; |
258 | - self.results.write(&mut writer, self.seal.i, true) |
259 | + fn write_header(&self, writer: &mut impl Writer) { |
260 | + self.seal.write(writer, true); |
261 | + self.signature.write(writer, true); |
262 | + self.results.write(writer, self.seal.i, true); |
263 | } |
264 | } |
265 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
266 | index 17602c1..80c2cab 100644 |
267 | --- a/src/arc/seal.rs |
268 | +++ b/src/arc/seal.rs |
269 | @@ -14,8 +14,9 @@ use mail_builder::encoders::base64::base64_encode; |
270 | use sha2::{Digest, Sha256}; |
271 | |
272 | use crate::{ |
273 | - common::crypto::SigningKey, dkim::Canonicalization, ArcOutput, AuthenticatedMessage, |
274 | - AuthenticationResults, DkimResult, Error, |
275 | + common::{crypto::SigningKey, headers::Writer}, |
276 | + dkim::Canonicalization, |
277 | + ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, |
278 | }; |
279 | |
280 | use super::{ArcSet, ChainValidation, Seal, Signature}; |
281 | @@ -89,7 +90,7 @@ impl<'x> ArcSet<'x> { |
282 | } |
283 | |
284 | // Add signature to hash |
285 | - self.signature.write(&mut header_hasher, false)?; |
286 | + self.signature.write(&mut header_hasher, false); |
287 | |
288 | // Sign |
289 | let b = with_key.sign(&header_hasher.finalize())?; |
290 | @@ -107,14 +108,14 @@ impl<'x> ArcSet<'x> { |
291 | ] |
292 | }), |
293 | &mut header_hasher, |
294 | - )?; |
295 | + ); |
296 | } |
297 | |
298 | // Hash ARC headers for the current instance |
299 | - self.results.write(&mut header_hasher, self.seal.i, false)?; |
300 | - self.signature.write(&mut header_hasher, false)?; |
301 | + self.results.write(&mut header_hasher, self.seal.i, false); |
302 | + self.signature.write(&mut header_hasher, false); |
303 | header_hasher.write_all(b"\r\n")?; |
304 | - self.seal.write(&mut header_hasher, false)?; |
305 | + self.seal.write(&mut header_hasher, false); |
306 | |
307 | // Seal |
308 | let b = with_key.sign(&header_hasher.finalize())?; |
309 | @@ -173,8 +174,8 @@ impl<'x> Signature<'x> { |
310 | pub(crate) fn canonicalize( |
311 | &self, |
312 | message: &'x AuthenticatedMessage<'x>, |
313 | - header_hasher: impl Write, |
314 | - body_hasher: impl Write, |
315 | + header_hasher: &mut impl Writer, |
316 | + body_hasher: &mut impl Writer, |
317 | ) -> crate::Result<(usize, Vec<Cow<'x, str>>)> { |
318 | let mut headers = Vec::with_capacity(self.h.len()); |
319 | let mut found_headers = vec![false; self.h.len()]; |
320 | @@ -194,8 +195,8 @@ impl<'x> Signature<'x> { |
321 | |
322 | let body_len = message.body.len(); |
323 | self.ch |
324 | - .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher)?; |
325 | - self.cb.canonicalize_body(message.body, body_hasher)?; |
326 | + .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher); |
327 | + self.cb.canonicalize_body(message.body, body_hasher); |
328 | |
329 | // Add any missing headers |
330 | signed_headers.reverse(); |
331 | diff --git a/src/common/auth_results.rs b/src/common/auth_results.rs |
332 | index 3e727ca..dcfaa4d 100644 |
333 | --- a/src/common/auth_results.rs |
334 | +++ b/src/common/auth_results.rs |
335 | @@ -8,7 +8,7 @@ |
336 | * except according to those terms. |
337 | */ |
338 | |
339 | - use std::{borrow::Cow, fmt::Write, io, net::IpAddr}; |
340 | + use std::{borrow::Cow, fmt::Write, net::IpAddr}; |
341 | |
342 | use mail_builder::encoders::base64::base64_encode; |
343 | |
344 | @@ -17,7 +17,7 @@ use crate::{ |
345 | ReceivedSpf, SpfOutput, SpfResult, |
346 | }; |
347 | |
348 | - use super::headers::HeaderWriter; |
349 | + use super::headers::{HeaderWriter, Writer}; |
350 | |
351 | impl<'x> AuthenticationResults<'x> { |
352 | pub fn new(hostname: &'x str) -> Self { |
353 | @@ -118,23 +118,23 @@ impl<'x> AuthenticationResults<'x> { |
354 | } |
355 | |
356 | impl<'x> HeaderWriter for AuthenticationResults<'x> { |
357 | - fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
358 | - writer.write_all(b"Authentication-Results: ")?; |
359 | - writer.write_all(self.hostname.as_bytes())?; |
360 | + fn write_header(&self, writer: &mut impl Writer) { |
361 | + writer.write(b"Authentication-Results: "); |
362 | + writer.write(self.hostname.as_bytes()); |
363 | if !self.auth_results.is_empty() { |
364 | - writer.write_all(self.auth_results.as_bytes())?; |
365 | + writer.write(self.auth_results.as_bytes()); |
366 | } else { |
367 | - writer.write_all(b"; none")?; |
368 | + writer.write(b"; none"); |
369 | } |
370 | - writer.write_all(b"\r\n") |
371 | + writer.write(b"\r\n"); |
372 | } |
373 | } |
374 | |
375 | impl HeaderWriter for ReceivedSpf { |
376 | - fn write_header(&self, mut writer: impl io::Write) -> io::Result<()> { |
377 | - writer.write_all(b"Received-SPF: ")?; |
378 | - writer.write_all(self.received_spf.as_bytes())?; |
379 | - writer.write_all(b"\r\n") |
380 | + fn write_header(&self, writer: &mut impl Writer) { |
381 | + writer.write(b"Received-SPF: "); |
382 | + writer.write(self.received_spf.as_bytes()); |
383 | + writer.write(b"\r\n"); |
384 | } |
385 | } |
386 | |
387 | diff --git a/src/common/crypto.rs b/src/common/crypto.rs |
388 | index 6c5b17b..e37d859 100644 |
389 | --- a/src/common/crypto.rs |
390 | +++ b/src/common/crypto.rs |
391 | @@ -11,8 +11,10 @@ use sha2::{digest::Digest, Sha256}; |
392 | |
393 | use crate::{dkim::Canonicalization, Error, Result}; |
394 | |
395 | + use super::headers::Writer; |
396 | + |
397 | pub trait SigningKey { |
398 | - type Hasher: Digest + AssociatedOid + io::Write; |
399 | + type Hasher: Digest + Writer; |
400 | |
401 | fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>>; |
402 | |
403 | @@ -159,23 +161,23 @@ impl VerifyingKey for RsaPublicKey { |
404 | ) -> Result<()> { |
405 | match algorithm { |
406 | Algorithm::RsaSha256 => { |
407 | - let hash = canonicalization |
408 | - .hash_headers::<Sha256>(headers) |
409 | - .unwrap_or_default(); |
410 | + let hash = canonicalization.hash_headers::<Sha256>(headers); |
411 | self.inner |
412 | .verify( |
413 | PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
414 | - &hash, |
415 | + hash.as_ref(), |
416 | signature, |
417 | ) |
418 | .map_err(|_| Error::FailedVerification) |
419 | } |
420 | Algorithm::RsaSha1 => { |
421 | - let hash = canonicalization |
422 | - .hash_headers::<Sha1>(headers) |
423 | - .unwrap_or_default(); |
424 | + let hash = canonicalization.hash_headers::<Sha1>(headers); |
425 | self.inner |
426 | - .verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), &hash, signature) |
427 | + .verify( |
428 | + PaddingScheme::new_pkcs1v15_sign::<Sha1>(), |
429 | + hash.as_ref(), |
430 | + signature, |
431 | + ) |
432 | .map_err(|_| Error::FailedVerification) |
433 | } |
434 | Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms), |
435 | @@ -199,12 +201,10 @@ impl VerifyingKey for Ed25519PublicKey { |
436 | return Err(Error::IncompatibleAlgorithms); |
437 | } |
438 | |
439 | - let hash = canonicalization |
440 | - .hash_headers::<Sha256>(headers) |
441 | - .unwrap_or_default(); |
442 | + let hash = canonicalization.hash_headers::<Sha256>(headers); |
443 | self.inner |
444 | .verify_strict( |
445 | - &hash, |
446 | + hash.as_ref(), |
447 | &ed25519_dalek::Signature::from_bytes(signature) |
448 | .map_err(|err| Error::CryptoError(err.to_string()))?, |
449 | ) |
450 | @@ -212,6 +212,18 @@ impl VerifyingKey for Ed25519PublicKey { |
451 | } |
452 | } |
453 | |
454 | + impl Writer for Sha1 { |
455 | + fn write(&mut self, buf: &[u8]) { |
456 | + self.update(buf); |
457 | + } |
458 | + } |
459 | + |
460 | + impl Writer for Sha256 { |
461 | + fn write(&mut self, buf: &[u8]) { |
462 | + self.update(buf); |
463 | + } |
464 | + } |
465 | + |
466 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
467 | #[repr(u64)] |
468 | pub enum HashAlgorithm { |
469 | diff --git a/src/common/headers.rs b/src/common/headers.rs |
470 | index d2673eb..0b5370b 100644 |
471 | --- a/src/common/headers.rs |
472 | +++ b/src/common/headers.rs |
473 | @@ -9,7 +9,6 @@ |
474 | */ |
475 | |
476 | use std::{ |
477 | - io, |
478 | iter::{Enumerate, Peekable}, |
479 | slice::Iter, |
480 | }; |
481 | @@ -266,14 +265,29 @@ impl<'x> Iterator for HeaderParser<'x> { |
482 | } |
483 | |
484 | pub trait HeaderWriter: Sized { |
485 | - fn write_header(&self, writer: impl io::Write) -> io::Result<()>; |
486 | + fn write_header(&self, writer: &mut impl Writer); |
487 | fn to_header(&self) -> String { |
488 | let mut buf = Vec::new(); |
489 | - self.write_header(&mut buf).unwrap(); |
490 | + self.write_header(&mut buf); |
491 | String::from_utf8(buf).unwrap() |
492 | } |
493 | } |
494 | |
495 | + pub trait Writer { |
496 | + fn write(&mut self, buf: &[u8]); |
497 | + |
498 | + fn write_len(&mut self, buf: &[u8], len: &mut usize) { |
499 | + self.write(buf); |
500 | + *len += buf.len(); |
501 | + } |
502 | + } |
503 | + |
504 | + impl Writer for Vec<u8> { |
505 | + fn write(&mut self, buf: &[u8]) { |
506 | + self.extend(buf); |
507 | + } |
508 | + } |
509 | + |
510 | const FROM: u64 = (b'f' as u64) | (b'r' as u64) << 8 | (b'o' as u64) << 16 | (b'm' as u64) << 24; |
511 | const DKIM: u64 = (b'd' as u64) |
512 | | (b'k' as u64) << 8 |
513 | diff --git a/src/common/message.rs b/src/common/message.rs |
514 | index fe144c3..dd1ab6f 100644 |
515 | --- a/src/common/message.rs |
516 | +++ b/src/common/message.rs |
517 | @@ -147,10 +147,9 @@ impl<'x> AuthenticatedMessage<'x> { |
518 | // Calculate body hashes |
519 | for (cb, ha, l, bh) in &mut message.body_hashes { |
520 | *bh = match ha { |
521 | - HashAlgorithm::Sha256 => cb.hash_body::<Sha256>(message.body, *l), |
522 | - HashAlgorithm::Sha1 => cb.hash_body::<Sha1>(message.body, *l), |
523 | - } |
524 | - .unwrap_or_default(); |
525 | + HashAlgorithm::Sha256 => cb.hash_body::<Sha256>(message.body, *l).as_ref().to_vec(), |
526 | + HashAlgorithm::Sha1 => cb.hash_body::<Sha1>(message.body, *l).as_ref().to_vec(), |
527 | + }; |
528 | } |
529 | |
530 | // Sort ARC headers |
531 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
532 | index bf727d3..143fd6e 100644 |
533 | --- a/src/dkim/canonicalize.rs |
534 | +++ b/src/dkim/canonicalize.rs |
535 | @@ -8,19 +8,16 @@ |
536 | * except according to those terms. |
537 | */ |
538 | |
539 | - use std::{ |
540 | - borrow::Cow, |
541 | - io::{self}, |
542 | - }; |
543 | + use std::borrow::Cow; |
544 | |
545 | use sha1::Digest; |
546 | |
547 | - use crate::common::headers::HeaderIterator; |
548 | + use crate::common::headers::{HeaderIterator, Writer}; |
549 | |
550 | use super::{Canonicalization, Signature}; |
551 | |
552 | impl Canonicalization { |
553 | - pub fn canonicalize_body(&self, body: &[u8], mut hasher: impl io::Write) -> io::Result<()> { |
554 | + pub fn canonicalize_body(&self, body: &[u8], hasher: &mut impl Writer) { |
555 | let mut crlf_seq = 0; |
556 | |
557 | match self { |
558 | @@ -31,7 +28,7 @@ impl Canonicalization { |
559 | match ch { |
560 | b' ' | b'\t' => { |
561 | while crlf_seq > 0 { |
562 | - let _ = hasher.write(b"\r\n")?; |
563 | + hasher.write(b"\r\n"); |
564 | crlf_seq -= 1; |
565 | } |
566 | } |
567 | @@ -41,15 +38,15 @@ impl Canonicalization { |
568 | b'\r' => {} |
569 | _ => { |
570 | while crlf_seq > 0 { |
571 | - let _ = hasher.write(b"\r\n")?; |
572 | + hasher.write(b"\r\n"); |
573 | crlf_seq -= 1; |
574 | } |
575 | |
576 | if last_ch == b' ' || last_ch == b'\t' { |
577 | - let _ = hasher.write(b" ")?; |
578 | + hasher.write(b" "); |
579 | } |
580 | |
581 | - let _ = hasher.write(&[ch])?; |
582 | + hasher.write(&[ch]); |
583 | } |
584 | } |
585 | |
586 | @@ -65,78 +62,70 @@ impl Canonicalization { |
587 | b'\r' => {} |
588 | _ => { |
589 | while crlf_seq > 0 { |
590 | - let _ = hasher.write(b"\r\n")?; |
591 | + hasher.write(b"\r\n"); |
592 | crlf_seq -= 1; |
593 | } |
594 | - let _ = hasher.write(&[ch])?; |
595 | + hasher.write(&[ch]); |
596 | } |
597 | } |
598 | } |
599 | } |
600 | } |
601 | |
602 | - hasher.write_all(b"\r\n") |
603 | + hasher.write(b"\r\n"); |
604 | } |
605 | |
606 | pub fn canonicalize_headers<'x>( |
607 | &self, |
608 | headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
609 | - mut hasher: impl io::Write, |
610 | - ) -> io::Result<()> { |
611 | + hasher: &mut impl Writer, |
612 | + ) { |
613 | match self { |
614 | Canonicalization::Relaxed => { |
615 | for (name, value) in headers { |
616 | for &ch in name { |
617 | if !ch.is_ascii_whitespace() { |
618 | - let _ = hasher.write(&[ch.to_ascii_lowercase()])?; |
619 | + hasher.write(&[ch.to_ascii_lowercase()]); |
620 | } |
621 | } |
622 | - let _ = hasher.write(b":")?; |
623 | - let mut bytes_written = 0; |
624 | + hasher.write(b":"); |
625 | + let mut bw = 0; |
626 | let mut last_ch = 0; |
627 | |
628 | for &ch in value { |
629 | if !ch.is_ascii_whitespace() { |
630 | - if [b' ', b'\t'].contains(&last_ch) && bytes_written > 0 { |
631 | - bytes_written += hasher.write(b" ")?; |
632 | + if [b' ', b'\t'].contains(&last_ch) && bw > 0 { |
633 | + hasher.write_len(b" ", &mut bw); |
634 | } |
635 | - bytes_written += hasher.write(&[ch])?; |
636 | + hasher.write_len(&[ch], &mut bw); |
637 | } |
638 | last_ch = ch; |
639 | } |
640 | if last_ch == b'\n' { |
641 | - let _ = hasher.write(b"\r\n"); |
642 | + hasher.write(b"\r\n"); |
643 | } |
644 | } |
645 | } |
646 | Canonicalization::Simple => { |
647 | for (name, value) in headers { |
648 | - let _ = hasher.write(name)?; |
649 | - let _ = hasher.write(b":")?; |
650 | - let _ = hasher.write(value)?; |
651 | + hasher.write(name); |
652 | + hasher.write(b":"); |
653 | + hasher.write(value); |
654 | } |
655 | } |
656 | } |
657 | - |
658 | - Ok(()) |
659 | } |
660 | |
661 | - pub fn hash_headers<'x, T>( |
662 | + pub fn hash_headers<'x, T: Digest + Writer>( |
663 | &self, |
664 | headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
665 | - ) -> io::Result<Vec<u8>> |
666 | - where |
667 | - T: Digest + io::Write, |
668 | - { |
669 | + ) -> impl AsRef<[u8]> { |
670 | let mut hasher = T::new(); |
671 | - self.canonicalize_headers(headers, &mut hasher)?; |
672 | - Ok(hasher.finalize().to_vec()) |
673 | + self.canonicalize_headers(headers, &mut hasher); |
674 | + hasher.finalize() |
675 | } |
676 | |
677 | - pub fn hash_body<T>(&self, body: &[u8], l: u64) -> io::Result<Vec<u8>> |
678 | - where |
679 | - T: Digest + io::Write, |
680 | - { |
681 | + pub fn hash_body<T: Digest + Writer>(&self, body: &[u8], l: u64) -> impl AsRef<[u8]> { |
682 | let mut hasher = T::new(); |
683 | self.canonicalize_body( |
684 | if l == 0 || body.is_empty() { |
685 | @@ -145,15 +134,15 @@ impl Canonicalization { |
686 | &body[..std::cmp::min(l as usize, body.len())] |
687 | }, |
688 | &mut hasher, |
689 | - )?; |
690 | - Ok(hasher.finalize().to_vec()) |
691 | + ); |
692 | + hasher.finalize() |
693 | } |
694 | |
695 | - pub fn serialize_name(&self, mut writer: impl io::Write) -> io::Result<()> { |
696 | - writer.write_all(match self { |
697 | + pub fn serialize_name(&self, writer: &mut impl Writer) { |
698 | + writer.write(match self { |
699 | Canonicalization::Relaxed => b"relaxed", |
700 | Canonicalization::Simple => b"simple", |
701 | - }) |
702 | + }); |
703 | } |
704 | } |
705 | |
706 | @@ -162,9 +151,9 @@ impl<'x> Signature<'x> { |
707 | pub(crate) fn canonicalize( |
708 | &self, |
709 | message: &'x [u8], |
710 | - header_hasher: impl io::Write, |
711 | - body_hasher: impl io::Write, |
712 | - ) -> crate::Result<(usize, Vec<Cow<'x, str>>)> { |
713 | + header_hasher: &mut impl Writer, |
714 | + body_hasher: &mut impl Writer, |
715 | + ) -> (usize, Vec<Cow<'x, str>>) { |
716 | let mut headers_it = HeaderIterator::new(message); |
717 | let mut headers = Vec::with_capacity(self.h.len()); |
718 | let mut found_headers = vec![false; self.h.len()]; |
719 | @@ -188,8 +177,8 @@ impl<'x> Signature<'x> { |
720 | .unwrap_or_default(); |
721 | let body_len = body.len(); |
722 | self.ch |
723 | - .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher)?; |
724 | - self.cb.canonicalize_body(body, body_hasher)?; |
725 | + .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher); |
726 | + self.cb.canonicalize_body(body, body_hasher); |
727 | |
728 | // Add any missing headers |
729 | signed_headers.reverse(); |
730 | @@ -199,7 +188,7 @@ impl<'x> Signature<'x> { |
731 | } |
732 | } |
733 | |
734 | - Ok((body_len, signed_headers)) |
735 | + (body_len, signed_headers) |
736 | } |
737 | } |
738 | |
739 | @@ -272,11 +261,8 @@ mod test { |
740 | let mut body = Vec::new(); |
741 | |
742 | canonicalization |
743 | - .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers) |
744 | - .unwrap(); |
745 | - canonicalization |
746 | - .canonicalize_body(raw_body, &mut body) |
747 | - .unwrap(); |
748 | + .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers); |
749 | + canonicalization.canonicalize_body(raw_body, &mut body); |
750 | |
751 | assert_eq!(expected_headers, String::from_utf8(headers).unwrap()); |
752 | assert_eq!(expected_body, String::from_utf8(body).unwrap()); |
753 | diff --git a/src/dkim/headers.rs b/src/dkim/headers.rs |
754 | index 880d302..4f1bd53 100644 |
755 | --- a/src/dkim/headers.rs |
756 | +++ b/src/dkim/headers.rs |
757 | @@ -8,89 +8,86 @@ |
758 | * except according to those terms. |
759 | */ |
760 | |
761 | - use std::{ |
762 | - fmt::{Display, Formatter}, |
763 | - io, |
764 | - }; |
765 | + use std::fmt::{Display, Formatter}; |
766 | |
767 | - use crate::common::headers::HeaderWriter; |
768 | + use crate::common::headers::{HeaderWriter, Writer}; |
769 | |
770 | use super::{Algorithm, Canonicalization, HashAlgorithm, Signature}; |
771 | |
772 | impl<'x> Signature<'x> { |
773 | - pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
774 | + pub(crate) fn write(&self, writer: &mut impl Writer, as_header: bool) { |
775 | let (header, new_line) = match self.ch { |
776 | Canonicalization::Relaxed if !as_header => (&b"dkim-signature:"[..], &b" "[..]), |
777 | _ => (&b"DKIM-Signature: "[..], &b"\r\n\t"[..]), |
778 | }; |
779 | - writer.write_all(header)?; |
780 | - writer.write_all(b"v=1; a=")?; |
781 | - writer.write_all(match self.a { |
782 | + writer.write(header); |
783 | + writer.write(b"v=1; a="); |
784 | + writer.write(match self.a { |
785 | Algorithm::RsaSha256 => b"rsa-sha256", |
786 | Algorithm::RsaSha1 => b"rsa-sha1", |
787 | Algorithm::Ed25519Sha256 => b"ed25519-sha256", |
788 | - })?; |
789 | + }); |
790 | for (tag, value) in [(&b"; s="[..], &self.s), (&b"; d="[..], &self.d)] { |
791 | - writer.write_all(tag)?; |
792 | - writer.write_all(value.as_bytes())?; |
793 | + writer.write(tag); |
794 | + writer.write(value.as_bytes()); |
795 | } |
796 | - writer.write_all(b"; c=")?; |
797 | - self.ch.serialize_name(&mut writer)?; |
798 | - writer.write_all(b"/")?; |
799 | - self.cb.serialize_name(&mut writer)?; |
800 | + writer.write(b"; c="); |
801 | + self.ch.serialize_name(writer); |
802 | + writer.write(b"/"); |
803 | + self.cb.serialize_name(writer); |
804 | |
805 | if let Some(atps) = &self.atps { |
806 | - writer.write_all(b"; atps=")?; |
807 | - writer.write_all(atps.as_bytes())?; |
808 | - writer.write_all(b"; atpsh=")?; |
809 | - writer.write_all(match self.atpsh { |
810 | + writer.write(b"; atps="); |
811 | + writer.write(atps.as_bytes()); |
812 | + writer.write(b"; atpsh="); |
813 | + writer.write(match self.atpsh { |
814 | Some(HashAlgorithm::Sha256) => b"sha256", |
815 | Some(HashAlgorithm::Sha1) => b"sha1", |
816 | _ => b"none", |
817 | - })?; |
818 | + }); |
819 | } |
820 | if self.r { |
821 | - writer.write_all(b"; r=y")?; |
822 | + writer.write(b"; r=y"); |
823 | } |
824 | |
825 | - writer.write_all(b";")?; |
826 | - writer.write_all(new_line)?; |
827 | + writer.write(b";"); |
828 | + writer.write(new_line); |
829 | |
830 | let mut bw = 1; |
831 | for (num, h) in self.h.iter().enumerate() { |
832 | if bw + h.len() + 1 >= 76 { |
833 | - writer.write_all(new_line)?; |
834 | + writer.write(new_line); |
835 | bw = 1; |
836 | } |
837 | if num > 0 { |
838 | - bw += writer.write(b":")?; |
839 | + writer.write_len(b":", &mut bw); |
840 | } else { |
841 | - bw += writer.write(b"h=")?; |
842 | + writer.write_len(b"h=", &mut bw); |
843 | } |
844 | - bw += writer.write(h.as_bytes())?; |
845 | + writer.write_len(h.as_bytes(), &mut bw); |
846 | } |
847 | |
848 | if !self.i.is_empty() { |
849 | if bw + self.i.len() + 3 >= 76 { |
850 | - writer.write_all(b";")?; |
851 | - writer.write_all(new_line)?; |
852 | + writer.write(b";"); |
853 | + writer.write(new_line); |
854 | bw = 1; |
855 | } else { |
856 | - bw += writer.write(b"; ")?; |
857 | + writer.write_len(b"; ", &mut bw); |
858 | } |
859 | - bw += writer.write(b"i=")?; |
860 | + writer.write_len(b"i=", &mut bw); |
861 | |
862 | for &ch in self.i.as_bytes().iter() { |
863 | match ch { |
864 | 0..=0x20 | b';' | 0x7f..=u8::MAX => { |
865 | - bw += writer.write(format!("={:02X}", ch).as_bytes())?; |
866 | + writer.write_len(format!("={:02X}", ch).as_bytes(), &mut bw); |
867 | } |
868 | _ => { |
869 | - bw += writer.write(&[ch])?; |
870 | + writer.write_len(&[ch], &mut bw); |
871 | } |
872 | } |
873 | if bw >= 76 { |
874 | - writer.write_all(new_line)?; |
875 | + writer.write(new_line); |
876 | bw = 1; |
877 | } |
878 | } |
879 | @@ -103,48 +100,47 @@ impl<'x> Signature<'x> { |
880 | ] { |
881 | if value > 0 { |
882 | let value = value.to_string(); |
883 | - bw += writer.write(b";")?; |
884 | + writer.write_len(b";", &mut bw); |
885 | if bw + tag.len() + value.len() >= 76 { |
886 | - writer.write_all(new_line)?; |
887 | + writer.write(new_line); |
888 | bw = 1; |
889 | } else { |
890 | - bw += writer.write(b" ")?; |
891 | + writer.write_len(b" ", &mut bw); |
892 | } |
893 | |
894 | - bw += writer.write(tag)?; |
895 | - bw += writer.write(value.as_bytes())?; |
896 | + writer.write_len(tag, &mut bw); |
897 | + writer.write_len(value.as_bytes(), &mut bw); |
898 | } |
899 | } |
900 | |
901 | for (tag, value) in [(&b"; bh="[..], &self.bh), (&b"; b="[..], &self.b)] { |
902 | - bw += writer.write(tag)?; |
903 | + writer.write_len(tag, &mut bw); |
904 | for &byte in value { |
905 | - bw += writer.write(&[byte])?; |
906 | + writer.write_len(&[byte], &mut bw); |
907 | if bw >= 76 { |
908 | - writer.write_all(new_line)?; |
909 | + writer.write(new_line); |
910 | bw = 1; |
911 | } |
912 | } |
913 | } |
914 | |
915 | - writer.write_all(b";")?; |
916 | + writer.write(b";"); |
917 | if as_header { |
918 | - writer.write_all(b"\r\n")?; |
919 | + writer.write(b"\r\n"); |
920 | } |
921 | - Ok(()) |
922 | } |
923 | } |
924 | |
925 | impl<'x> HeaderWriter for Signature<'x> { |
926 | - fn write_header(&self, writer: impl io::Write) -> io::Result<()> { |
927 | - self.write(writer, true) |
928 | + fn write_header(&self, writer: &mut impl Writer) { |
929 | + self.write(writer, true); |
930 | } |
931 | } |
932 | |
933 | impl<'x> Display for Signature<'x> { |
934 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
935 | let mut buf = Vec::new(); |
936 | - self.write(&mut buf, false).map_err(|_| std::fmt::Error)?; |
937 | + self.write(&mut buf, false); |
938 | f.write_str(&String::from_utf8(buf).map_err(|_| std::fmt::Error)?) |
939 | } |
940 | } |
941 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
942 | index c9fdb10..a10d5c0 100644 |
943 | --- a/src/dkim/sign.rs |
944 | +++ b/src/dkim/sign.rs |
945 | @@ -52,7 +52,7 @@ impl<'x> Signature<'x> { |
946 | |
947 | // Canonicalize headers and body |
948 | let (body_len, signed_headers) = |
949 | - self.canonicalize(message, &mut header_hasher, &mut body_hasher)?; |
950 | + self.canonicalize(message, &mut header_hasher, &mut body_hasher); |
951 | |
952 | if signed_headers.is_empty() { |
953 | return Err(Error::NoHeadersFound); |
954 | @@ -68,7 +68,7 @@ impl<'x> Signature<'x> { |
955 | } |
956 | |
957 | // Add signature to hash |
958 | - self.write(&mut header_hasher, false)?; |
959 | + self.write(&mut header_hasher, false); |
960 | |
961 | // Sign |
962 | let b = with_key.sign(&header_hasher.finalize())?; |
963 | @@ -442,7 +442,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
964 | expect: Result<(), super::Error>, |
965 | ) -> Vec<DkimOutput<'x>> { |
966 | let mut message = Vec::with_capacity(message_.len() + 100); |
967 | - signature.write(&mut message, true).unwrap(); |
968 | + signature.write(&mut message, true); |
969 | message.extend_from_slice(message_.as_bytes()); |
970 | |
971 | let message = AuthenticatedMessage::parse(&message).unwrap(); |