Commit
+218 -220 +/-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..0879594 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,9 +161,7 @@ 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 | @@ -171,9 +171,7 @@ impl VerifyingKey for RsaPublicKey { |
415 | .map_err(|_| Error::FailedVerification) |
416 | } |
417 | Algorithm::RsaSha1 => { |
418 | - let hash = canonicalization |
419 | - .hash_headers::<Sha1>(headers) |
420 | - .unwrap_or_default(); |
421 | + let hash = canonicalization.hash_headers::<Sha1>(headers); |
422 | self.inner |
423 | .verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), &hash, signature) |
424 | .map_err(|_| Error::FailedVerification) |
425 | @@ -199,9 +197,7 @@ impl VerifyingKey for Ed25519PublicKey { |
426 | return Err(Error::IncompatibleAlgorithms); |
427 | } |
428 | |
429 | - let hash = canonicalization |
430 | - .hash_headers::<Sha256>(headers) |
431 | - .unwrap_or_default(); |
432 | + let hash = canonicalization.hash_headers::<Sha256>(headers); |
433 | self.inner |
434 | .verify_strict( |
435 | &hash, |
436 | @@ -212,6 +208,18 @@ impl VerifyingKey for Ed25519PublicKey { |
437 | } |
438 | } |
439 | |
440 | + impl Writer for Sha1 { |
441 | + fn write(&mut self, buf: &[u8]) { |
442 | + self.update(buf); |
443 | + } |
444 | + } |
445 | + |
446 | + impl Writer for Sha256 { |
447 | + fn write(&mut self, buf: &[u8]) { |
448 | + self.update(buf); |
449 | + } |
450 | + } |
451 | + |
452 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
453 | #[repr(u64)] |
454 | pub enum HashAlgorithm { |
455 | diff --git a/src/common/headers.rs b/src/common/headers.rs |
456 | index d2673eb..0b5370b 100644 |
457 | --- a/src/common/headers.rs |
458 | +++ b/src/common/headers.rs |
459 | @@ -9,7 +9,6 @@ |
460 | */ |
461 | |
462 | use std::{ |
463 | - io, |
464 | iter::{Enumerate, Peekable}, |
465 | slice::Iter, |
466 | }; |
467 | @@ -266,14 +265,29 @@ impl<'x> Iterator for HeaderParser<'x> { |
468 | } |
469 | |
470 | pub trait HeaderWriter: Sized { |
471 | - fn write_header(&self, writer: impl io::Write) -> io::Result<()>; |
472 | + fn write_header(&self, writer: &mut impl Writer); |
473 | fn to_header(&self) -> String { |
474 | let mut buf = Vec::new(); |
475 | - self.write_header(&mut buf).unwrap(); |
476 | + self.write_header(&mut buf); |
477 | String::from_utf8(buf).unwrap() |
478 | } |
479 | } |
480 | |
481 | + pub trait Writer { |
482 | + fn write(&mut self, buf: &[u8]); |
483 | + |
484 | + fn write_len(&mut self, buf: &[u8], len: &mut usize) { |
485 | + self.write(buf); |
486 | + *len += buf.len(); |
487 | + } |
488 | + } |
489 | + |
490 | + impl Writer for Vec<u8> { |
491 | + fn write(&mut self, buf: &[u8]) { |
492 | + self.extend(buf); |
493 | + } |
494 | + } |
495 | + |
496 | const FROM: u64 = (b'f' as u64) | (b'r' as u64) << 8 | (b'o' as u64) << 16 | (b'm' as u64) << 24; |
497 | const DKIM: u64 = (b'd' as u64) |
498 | | (b'k' as u64) << 8 |
499 | diff --git a/src/common/message.rs b/src/common/message.rs |
500 | index fe144c3..b7423c2 100644 |
501 | --- a/src/common/message.rs |
502 | +++ b/src/common/message.rs |
503 | @@ -149,8 +149,7 @@ impl<'x> AuthenticatedMessage<'x> { |
504 | *bh = match ha { |
505 | HashAlgorithm::Sha256 => cb.hash_body::<Sha256>(message.body, *l), |
506 | HashAlgorithm::Sha1 => cb.hash_body::<Sha1>(message.body, *l), |
507 | - } |
508 | - .unwrap_or_default(); |
509 | + }; |
510 | } |
511 | |
512 | // Sort ARC headers |
513 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
514 | index bf727d3..49e0300 100644 |
515 | --- a/src/dkim/canonicalize.rs |
516 | +++ b/src/dkim/canonicalize.rs |
517 | @@ -8,19 +8,16 @@ |
518 | * except according to those terms. |
519 | */ |
520 | |
521 | - use std::{ |
522 | - borrow::Cow, |
523 | - io::{self}, |
524 | - }; |
525 | + use std::borrow::Cow; |
526 | |
527 | use sha1::Digest; |
528 | |
529 | - use crate::common::headers::HeaderIterator; |
530 | + use crate::common::headers::{HeaderIterator, Writer}; |
531 | |
532 | use super::{Canonicalization, Signature}; |
533 | |
534 | impl Canonicalization { |
535 | - pub fn canonicalize_body(&self, body: &[u8], mut hasher: impl io::Write) -> io::Result<()> { |
536 | + pub fn canonicalize_body(&self, body: &[u8], hasher: &mut impl Writer) { |
537 | let mut crlf_seq = 0; |
538 | |
539 | match self { |
540 | @@ -31,7 +28,7 @@ impl Canonicalization { |
541 | match ch { |
542 | b' ' | b'\t' => { |
543 | while crlf_seq > 0 { |
544 | - let _ = hasher.write(b"\r\n")?; |
545 | + hasher.write(b"\r\n"); |
546 | crlf_seq -= 1; |
547 | } |
548 | } |
549 | @@ -41,15 +38,15 @@ impl Canonicalization { |
550 | b'\r' => {} |
551 | _ => { |
552 | while crlf_seq > 0 { |
553 | - let _ = hasher.write(b"\r\n")?; |
554 | + hasher.write(b"\r\n"); |
555 | crlf_seq -= 1; |
556 | } |
557 | |
558 | if last_ch == b' ' || last_ch == b'\t' { |
559 | - let _ = hasher.write(b" ")?; |
560 | + hasher.write(b" "); |
561 | } |
562 | |
563 | - let _ = hasher.write(&[ch])?; |
564 | + hasher.write(&[ch]); |
565 | } |
566 | } |
567 | |
568 | @@ -65,78 +62,70 @@ impl Canonicalization { |
569 | b'\r' => {} |
570 | _ => { |
571 | while crlf_seq > 0 { |
572 | - let _ = hasher.write(b"\r\n")?; |
573 | + hasher.write(b"\r\n"); |
574 | crlf_seq -= 1; |
575 | } |
576 | - let _ = hasher.write(&[ch])?; |
577 | + hasher.write(&[ch]); |
578 | } |
579 | } |
580 | } |
581 | } |
582 | } |
583 | |
584 | - hasher.write_all(b"\r\n") |
585 | + hasher.write(b"\r\n"); |
586 | } |
587 | |
588 | pub fn canonicalize_headers<'x>( |
589 | &self, |
590 | headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
591 | - mut hasher: impl io::Write, |
592 | - ) -> io::Result<()> { |
593 | + hasher: &mut impl Writer, |
594 | + ) { |
595 | match self { |
596 | Canonicalization::Relaxed => { |
597 | for (name, value) in headers { |
598 | for &ch in name { |
599 | if !ch.is_ascii_whitespace() { |
600 | - let _ = hasher.write(&[ch.to_ascii_lowercase()])?; |
601 | + hasher.write(&[ch.to_ascii_lowercase()]); |
602 | } |
603 | } |
604 | - let _ = hasher.write(b":")?; |
605 | - let mut bytes_written = 0; |
606 | + hasher.write(b":"); |
607 | + let mut bw = 0; |
608 | let mut last_ch = 0; |
609 | |
610 | for &ch in value { |
611 | if !ch.is_ascii_whitespace() { |
612 | - if [b' ', b'\t'].contains(&last_ch) && bytes_written > 0 { |
613 | - bytes_written += hasher.write(b" ")?; |
614 | + if [b' ', b'\t'].contains(&last_ch) && bw > 0 { |
615 | + hasher.write_len(b" ", &mut bw); |
616 | } |
617 | - bytes_written += hasher.write(&[ch])?; |
618 | + hasher.write_len(&[ch], &mut bw); |
619 | } |
620 | last_ch = ch; |
621 | } |
622 | if last_ch == b'\n' { |
623 | - let _ = hasher.write(b"\r\n"); |
624 | + hasher.write(b"\r\n"); |
625 | } |
626 | } |
627 | } |
628 | Canonicalization::Simple => { |
629 | for (name, value) in headers { |
630 | - let _ = hasher.write(name)?; |
631 | - let _ = hasher.write(b":")?; |
632 | - let _ = hasher.write(value)?; |
633 | + hasher.write(name); |
634 | + hasher.write(b":"); |
635 | + hasher.write(value); |
636 | } |
637 | } |
638 | } |
639 | - |
640 | - Ok(()) |
641 | } |
642 | |
643 | - pub fn hash_headers<'x, T>( |
644 | + pub fn hash_headers<'x, T: Digest + Writer>( |
645 | &self, |
646 | headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
647 | - ) -> io::Result<Vec<u8>> |
648 | - where |
649 | - T: Digest + io::Write, |
650 | - { |
651 | + ) -> Vec<u8> { |
652 | let mut hasher = T::new(); |
653 | - self.canonicalize_headers(headers, &mut hasher)?; |
654 | - Ok(hasher.finalize().to_vec()) |
655 | + self.canonicalize_headers(headers, &mut hasher); |
656 | + hasher.finalize().to_vec() |
657 | } |
658 | |
659 | - pub fn hash_body<T>(&self, body: &[u8], l: u64) -> io::Result<Vec<u8>> |
660 | - where |
661 | - T: Digest + io::Write, |
662 | - { |
663 | + pub fn hash_body<T: Digest + Writer>(&self, body: &[u8], l: u64) -> Vec<u8> { |
664 | let mut hasher = T::new(); |
665 | self.canonicalize_body( |
666 | if l == 0 || body.is_empty() { |
667 | @@ -145,15 +134,15 @@ impl Canonicalization { |
668 | &body[..std::cmp::min(l as usize, body.len())] |
669 | }, |
670 | &mut hasher, |
671 | - )?; |
672 | - Ok(hasher.finalize().to_vec()) |
673 | + ); |
674 | + hasher.finalize().to_vec() |
675 | } |
676 | |
677 | - pub fn serialize_name(&self, mut writer: impl io::Write) -> io::Result<()> { |
678 | - writer.write_all(match self { |
679 | + pub fn serialize_name(&self, writer: &mut impl Writer) { |
680 | + writer.write(match self { |
681 | Canonicalization::Relaxed => b"relaxed", |
682 | Canonicalization::Simple => b"simple", |
683 | - }) |
684 | + }); |
685 | } |
686 | } |
687 | |
688 | @@ -162,9 +151,9 @@ impl<'x> Signature<'x> { |
689 | pub(crate) fn canonicalize( |
690 | &self, |
691 | message: &'x [u8], |
692 | - header_hasher: impl io::Write, |
693 | - body_hasher: impl io::Write, |
694 | - ) -> crate::Result<(usize, Vec<Cow<'x, str>>)> { |
695 | + header_hasher: &mut impl Writer, |
696 | + body_hasher: &mut impl Writer, |
697 | + ) -> (usize, Vec<Cow<'x, str>>) { |
698 | let mut headers_it = HeaderIterator::new(message); |
699 | let mut headers = Vec::with_capacity(self.h.len()); |
700 | let mut found_headers = vec![false; self.h.len()]; |
701 | @@ -188,8 +177,8 @@ impl<'x> Signature<'x> { |
702 | .unwrap_or_default(); |
703 | let body_len = body.len(); |
704 | self.ch |
705 | - .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher)?; |
706 | - self.cb.canonicalize_body(body, body_hasher)?; |
707 | + .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher); |
708 | + self.cb.canonicalize_body(body, body_hasher); |
709 | |
710 | // Add any missing headers |
711 | signed_headers.reverse(); |
712 | @@ -199,7 +188,7 @@ impl<'x> Signature<'x> { |
713 | } |
714 | } |
715 | |
716 | - Ok((body_len, signed_headers)) |
717 | + (body_len, signed_headers) |
718 | } |
719 | } |
720 | |
721 | @@ -272,11 +261,8 @@ mod test { |
722 | let mut body = Vec::new(); |
723 | |
724 | canonicalization |
725 | - .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers) |
726 | - .unwrap(); |
727 | - canonicalization |
728 | - .canonicalize_body(raw_body, &mut body) |
729 | - .unwrap(); |
730 | + .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers); |
731 | + canonicalization.canonicalize_body(raw_body, &mut body); |
732 | |
733 | assert_eq!(expected_headers, String::from_utf8(headers).unwrap()); |
734 | assert_eq!(expected_body, String::from_utf8(body).unwrap()); |
735 | diff --git a/src/dkim/headers.rs b/src/dkim/headers.rs |
736 | index 880d302..4f1bd53 100644 |
737 | --- a/src/dkim/headers.rs |
738 | +++ b/src/dkim/headers.rs |
739 | @@ -8,89 +8,86 @@ |
740 | * except according to those terms. |
741 | */ |
742 | |
743 | - use std::{ |
744 | - fmt::{Display, Formatter}, |
745 | - io, |
746 | - }; |
747 | + use std::fmt::{Display, Formatter}; |
748 | |
749 | - use crate::common::headers::HeaderWriter; |
750 | + use crate::common::headers::{HeaderWriter, Writer}; |
751 | |
752 | use super::{Algorithm, Canonicalization, HashAlgorithm, Signature}; |
753 | |
754 | impl<'x> Signature<'x> { |
755 | - pub(crate) fn write(&self, mut writer: impl io::Write, as_header: bool) -> io::Result<()> { |
756 | + pub(crate) fn write(&self, writer: &mut impl Writer, as_header: bool) { |
757 | let (header, new_line) = match self.ch { |
758 | Canonicalization::Relaxed if !as_header => (&b"dkim-signature:"[..], &b" "[..]), |
759 | _ => (&b"DKIM-Signature: "[..], &b"\r\n\t"[..]), |
760 | }; |
761 | - writer.write_all(header)?; |
762 | - writer.write_all(b"v=1; a=")?; |
763 | - writer.write_all(match self.a { |
764 | + writer.write(header); |
765 | + writer.write(b"v=1; a="); |
766 | + writer.write(match self.a { |
767 | Algorithm::RsaSha256 => b"rsa-sha256", |
768 | Algorithm::RsaSha1 => b"rsa-sha1", |
769 | Algorithm::Ed25519Sha256 => b"ed25519-sha256", |
770 | - })?; |
771 | + }); |
772 | for (tag, value) in [(&b"; s="[..], &self.s), (&b"; d="[..], &self.d)] { |
773 | - writer.write_all(tag)?; |
774 | - writer.write_all(value.as_bytes())?; |
775 | + writer.write(tag); |
776 | + writer.write(value.as_bytes()); |
777 | } |
778 | - writer.write_all(b"; c=")?; |
779 | - self.ch.serialize_name(&mut writer)?; |
780 | - writer.write_all(b"/")?; |
781 | - self.cb.serialize_name(&mut writer)?; |
782 | + writer.write(b"; c="); |
783 | + self.ch.serialize_name(writer); |
784 | + writer.write(b"/"); |
785 | + self.cb.serialize_name(writer); |
786 | |
787 | if let Some(atps) = &self.atps { |
788 | - writer.write_all(b"; atps=")?; |
789 | - writer.write_all(atps.as_bytes())?; |
790 | - writer.write_all(b"; atpsh=")?; |
791 | - writer.write_all(match self.atpsh { |
792 | + writer.write(b"; atps="); |
793 | + writer.write(atps.as_bytes()); |
794 | + writer.write(b"; atpsh="); |
795 | + writer.write(match self.atpsh { |
796 | Some(HashAlgorithm::Sha256) => b"sha256", |
797 | Some(HashAlgorithm::Sha1) => b"sha1", |
798 | _ => b"none", |
799 | - })?; |
800 | + }); |
801 | } |
802 | if self.r { |
803 | - writer.write_all(b"; r=y")?; |
804 | + writer.write(b"; r=y"); |
805 | } |
806 | |
807 | - writer.write_all(b";")?; |
808 | - writer.write_all(new_line)?; |
809 | + writer.write(b";"); |
810 | + writer.write(new_line); |
811 | |
812 | let mut bw = 1; |
813 | for (num, h) in self.h.iter().enumerate() { |
814 | if bw + h.len() + 1 >= 76 { |
815 | - writer.write_all(new_line)?; |
816 | + writer.write(new_line); |
817 | bw = 1; |
818 | } |
819 | if num > 0 { |
820 | - bw += writer.write(b":")?; |
821 | + writer.write_len(b":", &mut bw); |
822 | } else { |
823 | - bw += writer.write(b"h=")?; |
824 | + writer.write_len(b"h=", &mut bw); |
825 | } |
826 | - bw += writer.write(h.as_bytes())?; |
827 | + writer.write_len(h.as_bytes(), &mut bw); |
828 | } |
829 | |
830 | if !self.i.is_empty() { |
831 | if bw + self.i.len() + 3 >= 76 { |
832 | - writer.write_all(b";")?; |
833 | - writer.write_all(new_line)?; |
834 | + writer.write(b";"); |
835 | + writer.write(new_line); |
836 | bw = 1; |
837 | } else { |
838 | - bw += writer.write(b"; ")?; |
839 | + writer.write_len(b"; ", &mut bw); |
840 | } |
841 | - bw += writer.write(b"i=")?; |
842 | + writer.write_len(b"i=", &mut bw); |
843 | |
844 | for &ch in self.i.as_bytes().iter() { |
845 | match ch { |
846 | 0..=0x20 | b';' | 0x7f..=u8::MAX => { |
847 | - bw += writer.write(format!("={:02X}", ch).as_bytes())?; |
848 | + writer.write_len(format!("={:02X}", ch).as_bytes(), &mut bw); |
849 | } |
850 | _ => { |
851 | - bw += writer.write(&[ch])?; |
852 | + writer.write_len(&[ch], &mut bw); |
853 | } |
854 | } |
855 | if bw >= 76 { |
856 | - writer.write_all(new_line)?; |
857 | + writer.write(new_line); |
858 | bw = 1; |
859 | } |
860 | } |
861 | @@ -103,48 +100,47 @@ impl<'x> Signature<'x> { |
862 | ] { |
863 | if value > 0 { |
864 | let value = value.to_string(); |
865 | - bw += writer.write(b";")?; |
866 | + writer.write_len(b";", &mut bw); |
867 | if bw + tag.len() + value.len() >= 76 { |
868 | - writer.write_all(new_line)?; |
869 | + writer.write(new_line); |
870 | bw = 1; |
871 | } else { |
872 | - bw += writer.write(b" ")?; |
873 | + writer.write_len(b" ", &mut bw); |
874 | } |
875 | |
876 | - bw += writer.write(tag)?; |
877 | - bw += writer.write(value.as_bytes())?; |
878 | + writer.write_len(tag, &mut bw); |
879 | + writer.write_len(value.as_bytes(), &mut bw); |
880 | } |
881 | } |
882 | |
883 | for (tag, value) in [(&b"; bh="[..], &self.bh), (&b"; b="[..], &self.b)] { |
884 | - bw += writer.write(tag)?; |
885 | + writer.write_len(tag, &mut bw); |
886 | for &byte in value { |
887 | - bw += writer.write(&[byte])?; |
888 | + writer.write_len(&[byte], &mut bw); |
889 | if bw >= 76 { |
890 | - writer.write_all(new_line)?; |
891 | + writer.write(new_line); |
892 | bw = 1; |
893 | } |
894 | } |
895 | } |
896 | |
897 | - writer.write_all(b";")?; |
898 | + writer.write(b";"); |
899 | if as_header { |
900 | - writer.write_all(b"\r\n")?; |
901 | + writer.write(b"\r\n"); |
902 | } |
903 | - Ok(()) |
904 | } |
905 | } |
906 | |
907 | impl<'x> HeaderWriter for Signature<'x> { |
908 | - fn write_header(&self, writer: impl io::Write) -> io::Result<()> { |
909 | - self.write(writer, true) |
910 | + fn write_header(&self, writer: &mut impl Writer) { |
911 | + self.write(writer, true); |
912 | } |
913 | } |
914 | |
915 | impl<'x> Display for Signature<'x> { |
916 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
917 | let mut buf = Vec::new(); |
918 | - self.write(&mut buf, false).map_err(|_| std::fmt::Error)?; |
919 | + self.write(&mut buf, false); |
920 | f.write_str(&String::from_utf8(buf).map_err(|_| std::fmt::Error)?) |
921 | } |
922 | } |
923 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
924 | index c9fdb10..a10d5c0 100644 |
925 | --- a/src/dkim/sign.rs |
926 | +++ b/src/dkim/sign.rs |
927 | @@ -52,7 +52,7 @@ impl<'x> Signature<'x> { |
928 | |
929 | // Canonicalize headers and body |
930 | let (body_len, signed_headers) = |
931 | - self.canonicalize(message, &mut header_hasher, &mut body_hasher)?; |
932 | + self.canonicalize(message, &mut header_hasher, &mut body_hasher); |
933 | |
934 | if signed_headers.is_empty() { |
935 | return Err(Error::NoHeadersFound); |
936 | @@ -68,7 +68,7 @@ impl<'x> Signature<'x> { |
937 | } |
938 | |
939 | // Add signature to hash |
940 | - self.write(&mut header_hasher, false)?; |
941 | + self.write(&mut header_hasher, false); |
942 | |
943 | // Sign |
944 | let b = with_key.sign(&header_hasher.finalize())?; |
945 | @@ -442,7 +442,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
946 | expect: Result<(), super::Error>, |
947 | ) -> Vec<DkimOutput<'x>> { |
948 | let mut message = Vec::with_capacity(message_.len() + 100); |
949 | - signature.write(&mut message, true).unwrap(); |
950 | + signature.write(&mut message, true); |
951 | message.extend_from_slice(message_.as_bytes()); |
952 | |
953 | let message = AuthenticatedMessage::parse(&message).unwrap(); |