Commit
Author: Mauro D [mauro@stalw.art]
Committer: GitHub [noreply@github.com] Fri, 09 Dec 2022 17:54:23 +0000
Hash: fe36eb50d20ebd7a02298ae098251e4c32797ee9
Timestamp: Fri, 09 Dec 2022 17:54:23 +0000 (1 year ago)

+112 -86 +/-11 browse
Merge pull request #5 from InstantDomain/hashing
Merge pull request #5 from InstantDomain/hashing

Make VerifyingKeys responsible for input hashing
1diff --git a/src/arc/seal.rs b/src/arc/seal.rs
2index cada5a8..1deea47 100644
3--- a/src/arc/seal.rs
4+++ b/src/arc/seal.rs
5 @@ -99,7 +99,7 @@ impl<'x> ArcSet<'x> {
6 let mut header_hasher = Sha256::new();
7 if !arc_output.set.is_empty() {
8 Canonicalization::Relaxed.canonicalize_headers(
9- arc_output.set.iter().flat_map(|set| {
10+ &mut arc_output.set.iter().flat_map(|set| {
11 [
12 (set.results.name, set.results.value),
13 (set.signature.name, set.signature.value),
14 @@ -194,7 +194,7 @@ impl<'x> Signature<'x> {
15
16 let body_len = message.body.len();
17 self.ch
18- .canonicalize_headers(headers.into_iter().rev(), header_hasher)?;
19+ .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher)?;
20 self.cb.canonicalize_body(message.body, body_hasher)?;
21
22 // Add any missing headers
23 @@ -222,8 +222,9 @@ mod test {
24 crypto::{Ed25519Key, RsaKey, SigningKey},
25 headers::HeaderWriter,
26 parse::TxtRecordParser,
27+ verify::DomainKey,
28 },
29- dkim::{DomainKey, Signature},
30+ dkim::Signature,
31 AuthenticatedMessage, AuthenticationResults, DkimResult, Resolver,
32 };
33
34 diff --git a/src/arc/verify.rs b/src/arc/verify.rs
35index 9cdf373..b778424 100644
36--- a/src/arc/verify.rs
37+++ b/src/arc/verify.rs
38 @@ -10,16 +10,13 @@
39
40 use std::time::SystemTime;
41
42- use sha1::Sha1;
43- use sha2::Sha256;
44-
45 use crate::{
46 common::{
47- crypto::{Algorithm, HashAlgorithm},
48+ crypto::HashAlgorithm,
49 headers::Header,
50- verify::VerifySignature,
51+ verify::{DomainKey, VerifySignature},
52 },
53- dkim::{verify::Verifier, Canonicalization, DomainKey},
54+ dkim::{verify::Verifier, Canonicalization},
55 ArcOutput, AuthenticatedMessage, DkimResult, Error, Resolver,
56 };
57
58 @@ -120,14 +117,7 @@ impl Resolver {
59
60 // Hash headers
61 let dkim_hdr_value = header.value.strip_signature();
62- let headers = message.signed_headers(&signature.h, header.name, &dkim_hdr_value);
63- let hh = match signature.a {
64- Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => {
65- signature.ch.hash_headers::<Sha256>(headers)
66- }
67- Algorithm::RsaSha1 => signature.ch.hash_headers::<Sha1>(headers),
68- }
69- .unwrap_or_default();
70+ let mut headers = message.signed_headers(&signature.h, header.name, &dkim_hdr_value);
71
72 // Obtain record
73 let record = match self.txt_lookup::<DomainKey>(signature.domain_key()).await {
74 @@ -138,7 +128,7 @@ impl Resolver {
75 };
76
77 // Verify signature
78- if let Err(err) = signature.verify(record.as_ref(), &hh) {
79+ if let Err(err) = record.verify(&mut headers, *signature, signature.ch) {
80 return output.with_result(DkimResult::Fail(err));
81 }
82
83 @@ -156,7 +146,7 @@ impl Resolver {
84
85 // Build Seal headers
86 let seal_signature = header.value.strip_signature();
87- let headers = output
88+ let mut headers = output
89 .set
90 .iter()
91 .take(pos)
92 @@ -173,16 +163,8 @@ impl Resolver {
93 (set.seal.name, &seal_signature),
94 ]);
95
96- let hh = match seal.a {
97- Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => {
98- Canonicalization::Relaxed.hash_headers::<Sha256>(headers)
99- }
100- Algorithm::RsaSha1 => Canonicalization::Relaxed.hash_headers::<Sha1>(headers),
101- }
102- .unwrap_or_default();
103-
104 // Verify ARC Seal
105- if let Err(err) = seal.verify(record.as_ref(), &hh) {
106+ if let Err(err) = record.verify(&mut headers, *seal, Canonicalization::Relaxed) {
107 return output.with_result(DkimResult::Fail(err));
108 }
109 }
110 @@ -201,7 +183,8 @@ mod test {
111 };
112
113 use crate::{
114- common::parse::TxtRecordParser, dkim::DomainKey, AuthenticatedMessage, DkimResult, Resolver,
115+ common::{parse::TxtRecordParser, verify::DomainKey},
116+ AuthenticatedMessage, DkimResult, Resolver,
117 };
118
119 #[tokio::test]
120 diff --git a/src/common/crypto.rs b/src/common/crypto.rs
121index b443c7d..e2c024a 100644
122--- a/src/common/crypto.rs
123+++ b/src/common/crypto.rs
124 @@ -9,7 +9,7 @@ use rsa::{
125 use sha1::{digest::Output, Sha1};
126 use sha2::{digest::Digest, Sha256};
127
128- use crate::{Error, Result};
129+ use crate::{dkim::Canonicalization, Error, Result};
130
131 pub trait SigningKey {
132 type Hasher: Digest + AssociatedOid + io::Write;
133 @@ -112,7 +112,13 @@ impl SigningKey for Ed25519Key {
134 }
135
136 pub trait VerifyingKey {
137- fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>;
138+ fn verify<'a>(
139+ &self,
140+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
141+ signature: &[u8],
142+ canonicalication: Canonicalization,
143+ algorithm: Algorithm,
144+ ) -> Result<()>;
145 }
146
147 pub(crate) enum VerifyingKeyType {
148 @@ -144,21 +150,35 @@ pub(crate) struct RsaPublicKey {
149 }
150
151 impl VerifyingKey for RsaPublicKey {
152- fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()> {
153+ fn verify<'a>(
154+ &self,
155+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
156+ signature: &[u8],
157+ canonicalization: Canonicalization,
158+ algorithm: Algorithm,
159+ ) -> Result<()> {
160 match algorithm {
161- Algorithm::RsaSha1 => self
162- .inner
163- .verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), hash, signature)
164- .map_err(|_| Error::FailedVerification),
165- Algorithm::RsaSha256 => self
166- .inner
167- .verify(
168- PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
169- hash,
170- signature,
171- )
172- .map_err(|_| Error::FailedVerification),
173- _ => Err(Error::IncompatibleAlgorithms),
174+ Algorithm::RsaSha256 => {
175+ let hash = canonicalization
176+ .hash_headers::<Sha256>(headers)
177+ .unwrap_or_default();
178+ self.inner
179+ .verify(
180+ PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
181+ &hash,
182+ signature,
183+ )
184+ .map_err(|_| Error::FailedVerification)
185+ }
186+ Algorithm::RsaSha1 => {
187+ let hash = canonicalization
188+ .hash_headers::<Sha1>(headers)
189+ .unwrap_or_default();
190+ self.inner
191+ .verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), &hash, signature)
192+ .map_err(|_| Error::FailedVerification)
193+ }
194+ Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms),
195 }
196 }
197 }
198 @@ -168,14 +188,23 @@ pub(crate) struct Ed25519PublicKey {
199 }
200
201 impl VerifyingKey for Ed25519PublicKey {
202- fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()> {
203+ fn verify<'a>(
204+ &self,
205+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
206+ signature: &[u8],
207+ canonicalization: Canonicalization,
208+ algorithm: Algorithm,
209+ ) -> Result<()> {
210 if !matches!(algorithm, Algorithm::Ed25519Sha256) {
211 return Err(Error::IncompatibleAlgorithms);
212 }
213
214+ let hash = canonicalization
215+ .hash_headers::<Sha256>(headers)
216+ .unwrap_or_default();
217 self.inner
218 .verify_strict(
219- hash,
220+ &hash,
221 &ed25519_dalek::Signature::from_bytes(signature)
222 .map_err(|err| Error::CryptoError(err.to_string()))?,
223 )
224 diff --git a/src/common/resolver.rs b/src/common/resolver.rs
225index 9a8b9ca..55c5506 100644
226--- a/src/common/resolver.rs
227+++ b/src/common/resolver.rs
228 @@ -23,7 +23,7 @@ use trust_dns_resolver::{
229 };
230
231 use crate::{
232- dkim::{Atps, DomainKey, DomainKeyReport},
233+ dkim::{Atps, DomainKeyReport},
234 dmarc::Dmarc,
235 spf::{Macro, Spf},
236 Error, Policy, Resolver, Txt, MX,
237 @@ -32,6 +32,7 @@ use crate::{
238 use super::{
239 lru::{DnsCache, LruCache},
240 parse::TxtRecordParser,
241+ verify::DomainKey,
242 };
243
244 impl Resolver {
245 diff --git a/src/common/verify.rs b/src/common/verify.rs
246index 1399ed8..1a54302 100644
247--- a/src/common/verify.rs
248+++ b/src/common/verify.rs
249 @@ -8,7 +8,30 @@
250 * except according to those terms.
251 */
252
253- use crate::{common::crypto::Algorithm, dkim::DomainKey};
254+ use crate::dkim::Canonicalization;
255+
256+ use super::crypto::{Algorithm, VerifyingKey};
257+
258+ pub struct DomainKey {
259+ pub(crate) p: Box<dyn VerifyingKey>,
260+ pub(crate) f: u64,
261+ }
262+
263+ impl DomainKey {
264+ pub(crate) fn verify<'a>(
265+ &self,
266+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
267+ input: &impl VerifySignature,
268+ canonicalization: Canonicalization,
269+ ) -> crate::Result<()> {
270+ self.p.verify(
271+ headers,
272+ input.signature(),
273+ canonicalization,
274+ input.algorithm(),
275+ )
276+ }
277+ }
278
279 pub(crate) trait VerifySignature {
280 fn selector(&self) -> &str;
281 @@ -29,8 +52,4 @@ pub(crate) trait VerifySignature {
282 key.push('.');
283 key
284 }
285-
286- fn verify(&self, record: &DomainKey, hh: &[u8]) -> crate::Result<()> {
287- record.p.verify(hh, self.signature(), self.algorithm())
288- }
289 }
290 diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs
291index 7ed0383..bf727d3 100644
292--- a/src/dkim/canonicalize.rs
293+++ b/src/dkim/canonicalize.rs
294 @@ -80,7 +80,7 @@ impl Canonicalization {
295
296 pub fn canonicalize_headers<'x>(
297 &self,
298- headers: impl Iterator<Item = (&'x [u8], &'x [u8])>,
299+ headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>,
300 mut hasher: impl io::Write,
301 ) -> io::Result<()> {
302 match self {
303 @@ -123,7 +123,7 @@ impl Canonicalization {
304
305 pub fn hash_headers<'x, T>(
306 &self,
307- headers: impl Iterator<Item = (&'x [u8], &'x [u8])>,
308+ headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>,
309 ) -> io::Result<Vec<u8>>
310 where
311 T: Digest + io::Write,
312 @@ -188,7 +188,7 @@ impl<'x> Signature<'x> {
313 .unwrap_or_default();
314 let body_len = body.len();
315 self.ch
316- .canonicalize_headers(headers.into_iter().rev(), header_hasher)?;
317+ .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher)?;
318 self.cb.canonicalize_body(body, body_hasher)?;
319
320 // Add any missing headers
321 @@ -272,7 +272,7 @@ mod test {
322 let mut body = Vec::new();
323
324 canonicalization
325- .canonicalize_headers(parsed_headers.clone().into_iter(), &mut headers)
326+ .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers)
327 .unwrap();
328 canonicalization
329 .canonicalize_body(raw_body, &mut body)
330 diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs
331index cafe4fc..51cfd40 100644
332--- a/src/dkim/mod.rs
333+++ b/src/dkim/mod.rs
334 @@ -13,7 +13,7 @@ use std::borrow::Cow;
335 use crate::{
336 arc::Set,
337 common::{
338- crypto::{Algorithm, HashAlgorithm, VerifyingKey},
339+ crypto::{Algorithm, HashAlgorithm},
340 verify::VerifySignature,
341 },
342 ArcOutput, DkimOutput, DkimResult, Error, Version,
343 @@ -64,11 +64,6 @@ impl Default for Canonicalization {
344 }
345 }
346
347- pub struct DomainKey {
348- pub(crate) p: Box<dyn VerifyingKey>,
349- pub(crate) f: u64,
350- }
351-
352 #[derive(Debug, PartialEq, Eq, Clone)]
353 pub struct DomainKeyReport {
354 pub(crate) ra: String,
355 diff --git a/src/dkim/parse.rs b/src/dkim/parse.rs
356index 56a5634..237c278 100644
357--- a/src/dkim/parse.rs
358+++ b/src/dkim/parse.rs
359 @@ -13,14 +13,14 @@ use std::slice::Iter;
360 use mail_parser::decoders::base64::base64_decode_stream;
361
362 use crate::{
363- common::{crypto::VerifyingKeyType, parse::*},
364+ common::{crypto::VerifyingKeyType, parse::*, verify::DomainKey},
365 dkim::{RR_EXPIRATION, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION},
366 Error,
367 };
368
369 use super::{
370- Algorithm, Atps, Canonicalization, DomainKey, DomainKeyReport, Flag, HashAlgorithm, Service,
371- Signature, Version, RR_DNS, RR_OTHER, RR_POLICY,
372+ Algorithm, Atps, Canonicalization, DomainKeyReport, Flag, HashAlgorithm, Service, Signature,
373+ Version, RR_DNS, RR_OTHER, RR_POLICY,
374 };
375
376 const ATPSH: u64 = (b'a' as u64)
377 @@ -463,11 +463,12 @@ mod test {
378 common::{
379 crypto::{Algorithm, R_HASH_SHA1, R_HASH_SHA256},
380 parse::TxtRecordParser,
381+ verify::DomainKey,
382 },
383 dkim::{
384- Canonicalization, DomainKey, DomainKeyReport, Signature, RR_DNS, RR_EXPIRATION,
385- RR_OTHER, RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION,
386- R_FLAG_MATCH_DOMAIN, R_FLAG_TESTING, R_SVC_ALL, R_SVC_EMAIL,
387+ Canonicalization, DomainKeyReport, Signature, RR_DNS, RR_EXPIRATION, RR_OTHER,
388+ RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION, R_FLAG_MATCH_DOMAIN,
389+ R_FLAG_TESTING, R_SVC_ALL, R_SVC_EMAIL,
390 },
391 };
392
393 diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs
394index cb77dde..7e694f3 100644
395--- a/src/dkim/sign.rs
396+++ b/src/dkim/sign.rs
397 @@ -158,8 +158,9 @@ mod test {
398 common::{
399 crypto::{Ed25519Key, RsaKey},
400 parse::TxtRecordParser,
401+ verify::DomainKey,
402 },
403- dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport, HashAlgorithm, Signature},
404+ dkim::{Atps, Canonicalization, DomainKeyReport, HashAlgorithm, Signature},
405 AuthenticatedMessage, DkimOutput, DkimResult, Resolver,
406 };
407
408 diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs
409index 3e91116..2cb335c 100644
410--- a/src/dkim/verify.rs
411+++ b/src/dkim/verify.rs
412 @@ -14,13 +14,16 @@ use sha1::{Digest, Sha1};
413 use sha2::Sha256;
414
415 use crate::{
416- common::{base32::Base32Writer, verify::VerifySignature},
417+ common::{
418+ base32::Base32Writer,
419+ verify::{DomainKey, VerifySignature},
420+ },
421 is_within_pct, AuthenticatedMessage, DkimOutput, DkimResult, Error, Resolver,
422 };
423
424 use super::{
425- Algorithm, Atps, DomainKey, DomainKeyReport, Flag, HashAlgorithm, Signature, RR_DNS,
426- RR_EXPIRATION, RR_OTHER, RR_SIGNATURE, RR_VERIFICATION,
427+ Atps, DomainKeyReport, Flag, HashAlgorithm, Signature, RR_DNS, RR_EXPIRATION, RR_OTHER,
428+ RR_SIGNATURE, RR_VERIFICATION,
429 };
430
431 impl Resolver {
432 @@ -105,17 +108,10 @@ impl Resolver {
433
434 // Hash headers
435 let dkim_hdr_value = header.value.strip_signature();
436- let headers = message.signed_headers(&signature.h, header.name, &dkim_hdr_value);
437- let hh = match signature.a {
438- Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => {
439- signature.ch.hash_headers::<Sha256>(headers)
440- }
441- Algorithm::RsaSha1 => signature.ch.hash_headers::<Sha1>(headers),
442- }
443- .unwrap_or_default();
444+ let mut headers = message.signed_headers(&signature.h, header.name, &dkim_hdr_value);
445
446 // Verify signature
447- if let Err(err) = signature.verify(record.as_ref(), &hh) {
448+ if let Err(err) = record.verify(&mut headers, signature, signature.ch) {
449 output.push(DkimOutput::fail(err).with_signature(signature));
450 continue;
451 }
452 @@ -375,8 +371,8 @@ mod test {
453 };
454
455 use crate::{
456- common::parse::TxtRecordParser,
457- dkim::{verify::Verifier, DomainKey},
458+ common::{parse::TxtRecordParser, verify::DomainKey},
459+ dkim::verify::Verifier,
460 AuthenticatedMessage, DkimResult, Resolver,
461 };
462
463 diff --git a/src/lib.rs b/src/lib.rs
464index 050b94e..c3de0f5 100644
465--- a/src/lib.rs
466+++ b/src/lib.rs
467 @@ -262,8 +262,8 @@ use std::{
468 };
469
470 use arc::Set;
471- use common::{crypto::HashAlgorithm, headers::Header, lru::LruCache};
472- use dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport};
473+ use common::{crypto::HashAlgorithm, headers::Header, lru::LruCache, verify::DomainKey};
474+ use dkim::{Atps, Canonicalization, DomainKeyReport};
475 use dmarc::Dmarc;
476 use spf::{Macro, Spf};
477 use trust_dns_resolver::{proto::op::ResponseCode, TokioAsyncResolver};