Commit
+112 -86 +/-11 browse
1 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
2 | index 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 |
35 | index 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 |
121 | index 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 |
225 | index 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 |
246 | index 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 |
291 | index 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 |
331 | index 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 |
356 | index 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 |
394 | index 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 |
409 | index 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 |
464 | index 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}; |