Commit
+109 -164 +/-5 browse
1 | diff --git a/src/common/crypto.rs b/src/common/crypto.rs |
2 | index 2d4a4d1..b443c7d 100644 |
3 | --- a/src/common/crypto.rs |
4 | +++ b/src/common/crypto.rs |
5 | @@ -4,7 +4,7 @@ use ed25519_dalek::Signer; |
6 | use rsa::{ |
7 | pkcs1::DecodeRsaPrivateKey, |
8 | pkcs8::{AssociatedOid, ObjectIdentifier}, |
9 | - PaddingScheme, RsaPrivateKey, |
10 | + PaddingScheme, PublicKey as _, RsaPrivateKey, |
11 | }; |
12 | use sha1::{digest::Output, Sha1}; |
13 | use sha2::{digest::Digest, Sha256}; |
14 | @@ -111,6 +111,78 @@ impl SigningKey for Ed25519Key { |
15 | } |
16 | } |
17 | |
18 | + pub trait VerifyingKey { |
19 | + fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>; |
20 | + } |
21 | + |
22 | + pub(crate) enum VerifyingKeyType { |
23 | + Rsa, |
24 | + Ed25519, |
25 | + } |
26 | + |
27 | + impl VerifyingKeyType { |
28 | + pub(crate) fn new(&self, bytes: &[u8]) -> Result<Box<dyn VerifyingKey>> { |
29 | + Ok(match self { |
30 | + Self::Rsa => { |
31 | + let inner = |
32 | + <rsa::RsaPublicKey as rsa::pkcs8::DecodePublicKey>::from_public_key_der(bytes) |
33 | + .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(bytes)) |
34 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
35 | + |
36 | + Box::new(RsaPublicKey { inner }) as Box<dyn VerifyingKey> |
37 | + } |
38 | + Self::Ed25519 => Box::new(Ed25519PublicKey { |
39 | + inner: ed25519_dalek::PublicKey::from_bytes(bytes) |
40 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
41 | + }), |
42 | + }) |
43 | + } |
44 | + } |
45 | + |
46 | + pub(crate) struct RsaPublicKey { |
47 | + inner: rsa::RsaPublicKey, |
48 | + } |
49 | + |
50 | + impl VerifyingKey for RsaPublicKey { |
51 | + fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()> { |
52 | + match algorithm { |
53 | + Algorithm::RsaSha1 => self |
54 | + .inner |
55 | + .verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), hash, signature) |
56 | + .map_err(|_| Error::FailedVerification), |
57 | + Algorithm::RsaSha256 => self |
58 | + .inner |
59 | + .verify( |
60 | + PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
61 | + hash, |
62 | + signature, |
63 | + ) |
64 | + .map_err(|_| Error::FailedVerification), |
65 | + _ => Err(Error::IncompatibleAlgorithms), |
66 | + } |
67 | + } |
68 | + } |
69 | + |
70 | + pub(crate) struct Ed25519PublicKey { |
71 | + inner: ed25519_dalek::PublicKey, |
72 | + } |
73 | + |
74 | + impl VerifyingKey for Ed25519PublicKey { |
75 | + fn verify(&self, hash: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()> { |
76 | + if !matches!(algorithm, Algorithm::Ed25519Sha256) { |
77 | + return Err(Error::IncompatibleAlgorithms); |
78 | + } |
79 | + |
80 | + self.inner |
81 | + .verify_strict( |
82 | + hash, |
83 | + &ed25519_dalek::Signature::from_bytes(signature) |
84 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
85 | + ) |
86 | + .map_err(|_| Error::FailedVerification) |
87 | + } |
88 | + } |
89 | + |
90 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
91 | #[repr(u64)] |
92 | pub enum HashAlgorithm { |
93 | diff --git a/src/common/verify.rs b/src/common/verify.rs |
94 | index b88f716..25fdac5 100644 |
95 | --- a/src/common/verify.rs |
96 | +++ b/src/common/verify.rs |
97 | @@ -8,15 +8,7 @@ |
98 | * except according to those terms. |
99 | */ |
100 | |
101 | - use rsa::PaddingScheme; |
102 | - use sha1::Sha1; |
103 | - use sha2::Sha256; |
104 | - |
105 | - use crate::{ |
106 | - common::crypto::Algorithm, |
107 | - dkim::{DomainKey, PublicKey}, |
108 | - Error, |
109 | - }; |
110 | + use crate::{common::crypto::Algorithm, dkim::DomainKey}; |
111 | |
112 | pub(crate) trait VerifySignature { |
113 | fn s(&self) -> &str; |
114 | @@ -39,34 +31,6 @@ pub(crate) trait VerifySignature { |
115 | } |
116 | |
117 | fn verify(&self, record: &DomainKey, hh: &[u8]) -> crate::Result<()> { |
118 | - match (&self.a(), &record.p) { |
119 | - (Algorithm::RsaSha256, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
120 | - public_key, |
121 | - PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
122 | - hh, |
123 | - self.b(), |
124 | - ) |
125 | - .map_err(|_| Error::FailedVerification), |
126 | - |
127 | - (Algorithm::RsaSha1, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
128 | - public_key, |
129 | - PaddingScheme::new_pkcs1v15_sign::<Sha1>(), |
130 | - hh, |
131 | - self.b(), |
132 | - ) |
133 | - .map_err(|_| Error::FailedVerification), |
134 | - |
135 | - (Algorithm::Ed25519Sha256, PublicKey::Ed25519(public_key)) => public_key |
136 | - .verify_strict( |
137 | - hh, |
138 | - &ed25519_dalek::Signature::from_bytes(self.b()) |
139 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
140 | - ) |
141 | - .map_err(|_| Error::FailedVerification), |
142 | - |
143 | - (_, PublicKey::Revoked) => Err(Error::RevokedPublicKey), |
144 | - |
145 | - (_, _) => Err(Error::IncompatibleAlgorithms), |
146 | - } |
147 | + record.p.verify(hh, self.b(), self.a()) |
148 | } |
149 | } |
150 | diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs |
151 | index c1832f1..65374ed 100644 |
152 | --- a/src/dkim/mod.rs |
153 | +++ b/src/dkim/mod.rs |
154 | @@ -10,12 +10,10 @@ |
155 | |
156 | use std::borrow::Cow; |
157 | |
158 | - use rsa::RsaPublicKey; |
159 | - |
160 | use crate::{ |
161 | arc::Set, |
162 | common::{ |
163 | - crypto::{Algorithm, HashAlgorithm}, |
164 | + crypto::{Algorithm, HashAlgorithm, VerifyingKey}, |
165 | verify::VerifySignature, |
166 | }, |
167 | ArcOutput, DkimOutput, DkimResult, Error, Version, |
168 | @@ -66,10 +64,8 @@ impl Default for Canonicalization { |
169 | } |
170 | } |
171 | |
172 | - #[derive(Debug, PartialEq, Eq, Clone)] |
173 | pub struct DomainKey { |
174 | - pub(crate) v: Version, |
175 | - pub(crate) p: PublicKey, |
176 | + pub(crate) p: Box<dyn VerifyingKey>, |
177 | pub(crate) f: u64, |
178 | } |
179 | |
180 | @@ -132,13 +128,6 @@ impl From<Service> for u64 { |
181 | } |
182 | } |
183 | |
184 | - #[derive(Debug, PartialEq, Eq, Clone)] |
185 | - pub(crate) enum PublicKey { |
186 | - Rsa(RsaPublicKey), |
187 | - Ed25519(ed25519_dalek::PublicKey), |
188 | - Revoked, |
189 | - } |
190 | - |
191 | impl From<Algorithm> for HashAlgorithm { |
192 | fn from(a: Algorithm) -> Self { |
193 | match a { |
194 | diff --git a/src/dkim/parse.rs b/src/dkim/parse.rs |
195 | index ee13d43..56a5634 100644 |
196 | --- a/src/dkim/parse.rs |
197 | +++ b/src/dkim/parse.rs |
198 | @@ -11,17 +11,16 @@ |
199 | use std::slice::Iter; |
200 | |
201 | use mail_parser::decoders::base64::base64_decode_stream; |
202 | - use rsa::RsaPublicKey; |
203 | |
204 | use crate::{ |
205 | - common::parse::*, |
206 | + common::{crypto::VerifyingKeyType, parse::*}, |
207 | dkim::{RR_EXPIRATION, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION}, |
208 | Error, |
209 | }; |
210 | |
211 | use super::{ |
212 | - Algorithm, Atps, Canonicalization, DomainKey, DomainKeyReport, Flag, HashAlgorithm, PublicKey, |
213 | - Service, Signature, Version, RR_DNS, RR_OTHER, RR_POLICY, |
214 | + Algorithm, Atps, Canonicalization, DomainKey, DomainKeyReport, Flag, HashAlgorithm, Service, |
215 | + Signature, Version, RR_DNS, RR_OTHER, RR_POLICY, |
216 | }; |
217 | |
218 | const ATPSH: u64 = (b'a' as u64) |
219 | @@ -241,24 +240,14 @@ impl SignatureParser for Iter<'_, u8> { |
220 | } |
221 | } |
222 | |
223 | - enum KeyType { |
224 | - Rsa, |
225 | - Ed25519, |
226 | - None, |
227 | - } |
228 | - |
229 | impl TxtRecordParser for DomainKey { |
230 | #[allow(clippy::while_let_on_iterator)] |
231 | fn parse(header: &[u8]) -> crate::Result<Self> { |
232 | let header_len = header.len(); |
233 | let mut header = header.iter(); |
234 | - let mut record = DomainKey { |
235 | - v: Version::V1, |
236 | - p: PublicKey::Revoked, |
237 | - f: 0, |
238 | - }; |
239 | - let mut k = KeyType::None; |
240 | - let mut public_key = Vec::new(); |
241 | + let mut flags = 0; |
242 | + let mut key_type = VerifyingKeyType::Rsa; |
243 | + let mut public_key = None; |
244 | |
245 | while let Some(key) = header.key() { |
246 | match key { |
247 | @@ -267,26 +256,27 @@ impl TxtRecordParser for DomainKey { |
248 | return Err(Error::InvalidRecordType); |
249 | } |
250 | } |
251 | - H => record.f |= header.flags::<HashAlgorithm>(), |
252 | + H => flags |= header.flags::<HashAlgorithm>(), |
253 | P => { |
254 | - public_key = |
255 | - base64_decode_stream(&mut header, header_len, b';').unwrap_or_default() |
256 | + if let Some(bytes) = base64_decode_stream(&mut header, header_len, b';') { |
257 | + public_key = Some(bytes); |
258 | + } |
259 | } |
260 | - S => record.f |= header.flags::<Service>(), |
261 | - T => record.f |= header.flags::<Flag>(), |
262 | + S => flags |= header.flags::<Service>(), |
263 | + T => flags |= header.flags::<Flag>(), |
264 | K => { |
265 | if let Some(ch) = header.next_skip_whitespaces() { |
266 | match ch { |
267 | b'r' | b'R' => { |
268 | if header.match_bytes(b"sa") && header.seek_tag_end() { |
269 | - k = KeyType::Rsa; |
270 | + key_type = VerifyingKeyType::Rsa; |
271 | } else { |
272 | return Err(Error::UnsupportedKeyType); |
273 | } |
274 | } |
275 | b'e' | b'E' => { |
276 | if header.match_bytes(b"d25519") && header.seek_tag_end() { |
277 | - k = KeyType::Ed25519; |
278 | + key_type = VerifyingKeyType::Ed25519; |
279 | } else { |
280 | return Err(Error::UnsupportedKeyType); |
281 | } |
282 | @@ -304,21 +294,13 @@ impl TxtRecordParser for DomainKey { |
283 | } |
284 | } |
285 | |
286 | - if !public_key.is_empty() { |
287 | - record.p = match k { |
288 | - KeyType::Rsa | KeyType::None => PublicKey::Rsa( |
289 | - <RsaPublicKey as rsa::pkcs8::DecodePublicKey>::from_public_key_der(&public_key) |
290 | - .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(&public_key)) |
291 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
292 | - ), |
293 | - KeyType::Ed25519 => PublicKey::Ed25519( |
294 | - ed25519_dalek::PublicKey::from_bytes(&public_key) |
295 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
296 | - ), |
297 | - } |
298 | + match public_key { |
299 | + Some(public_key) => Ok(DomainKey { |
300 | + p: key_type.new(&public_key)?, |
301 | + f: flags, |
302 | + }), |
303 | + _ => Err(Error::InvalidRecordType), |
304 | } |
305 | - |
306 | - Ok(record) |
307 | } |
308 | } |
309 | |
310 | @@ -476,7 +458,6 @@ impl ItemParser for Service { |
311 | #[cfg(test)] |
312 | mod test { |
313 | use mail_parser::decoders::base64::base64_decode; |
314 | - use rsa::{pkcs8::DecodePublicKey, RsaPublicKey}; |
315 | |
316 | use crate::{ |
317 | common::{ |
318 | @@ -484,8 +465,8 @@ mod test { |
319 | parse::TxtRecordParser, |
320 | }, |
321 | dkim::{ |
322 | - Canonicalization, DomainKey, DomainKeyReport, PublicKey, Signature, Version, RR_DNS, |
323 | - RR_EXPIRATION, RR_OTHER, RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION, |
324 | + Canonicalization, DomainKey, DomainKeyReport, Signature, RR_DNS, RR_EXPIRATION, |
325 | + RR_OTHER, RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION, |
326 | R_FLAG_MATCH_DOMAIN, R_FLAG_TESTING, R_SVC_ALL, R_SVC_EMAIL, |
327 | }, |
328 | }; |
329 | @@ -651,26 +632,7 @@ mod test { |
330 | "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi", |
331 | "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB", |
332 | ), |
333 | - DomainKey { |
334 | - v: Version::V1, |
335 | - p: PublicKey::Rsa( |
336 | - RsaPublicKey::from_public_key_der( |
337 | - &base64_decode( |
338 | - concat!( |
339 | - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ", |
340 | - "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt", |
341 | - "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v", |
342 | - "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi", |
343 | - "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB", |
344 | - ) |
345 | - .as_bytes(), |
346 | - ) |
347 | - .unwrap(), |
348 | - ) |
349 | - .unwrap(), |
350 | - ), |
351 | - f: 0, |
352 | - }, |
353 | + 0, |
354 | ), |
355 | ( |
356 | concat!( |
357 | @@ -685,35 +647,12 @@ mod test { |
358 | "p5wMedWasaPS74TZ1b7tI39ncp6QIDAQAB ; t= y : s :yy:x;", |
359 | "s=*:email;; h= sha1:sha 256:other;; n=ignore these notes " |
360 | ), |
361 | - DomainKey { |
362 | - v: Version::V1, |
363 | - p: PublicKey::Rsa( |
364 | - RsaPublicKey::from_public_key_der( |
365 | - &base64_decode( |
366 | - concat!( |
367 | - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvz", |
368 | - "wKQIIWzQXv0nihasFTT3+JO23hXCge+ESWNxCJdVLxKL5e", |
369 | - "dxrumEU3DnrPeGD6q6E/vjoXwBabpm8F5o96MEPm7v12O5", |
370 | - "IIK7wx7gIJiQWvexwh+GJvW4aFFa0g13Ai75UdZjGFNKHA", |
371 | - "EGeLmkQYybK/EHW5ymRlSg3g8zydJGEcI/melLCiBoShHjf", |
372 | - "ZFJEThxLmPHNSi+KOUMypxqYHd7hzg6W7qnq6t9puZYXMWj", |
373 | - "6tEaf6ORWgb7DOXZSTJJjAJPBWa2+UrxXX6Ro7L7Xy1zzeY", |
374 | - "FCk8W5vmn0wMgGpjkWw0ljJWNwIpxZAj9p5wMedWasaPS74", |
375 | - "TZ1b7tI39ncp6QIDAQAB", |
376 | - ) |
377 | - .as_bytes(), |
378 | - ) |
379 | - .unwrap(), |
380 | - ) |
381 | - .unwrap(), |
382 | - ), |
383 | - f: R_HASH_SHA1 |
384 | - | R_HASH_SHA256 |
385 | - | R_SVC_ALL |
386 | - | R_SVC_EMAIL |
387 | - | R_FLAG_MATCH_DOMAIN |
388 | - | R_FLAG_TESTING, |
389 | - }, |
390 | + R_HASH_SHA1 |
391 | + | R_HASH_SHA256 |
392 | + | R_SVC_ALL |
393 | + | R_SVC_EMAIL |
394 | + | R_FLAG_MATCH_DOMAIN |
395 | + | R_FLAG_TESTING, |
396 | ), |
397 | ( |
398 | concat!( |
399 | @@ -722,29 +661,11 @@ mod test { |
400 | "hpV673NdAtaCVGNyx/fTYtvyyFe9DH2tmm/ijLlygDRboSkIJ4NHZjK++48hk", |
401 | "NP8/htqWHS+CvwWT4Qgs0NtB7Re9bQIDAQAB" |
402 | ), |
403 | - DomainKey { |
404 | - v: Version::V1, |
405 | - p: PublicKey::Rsa( |
406 | - RsaPublicKey::from_public_key_der( |
407 | - &base64_decode( |
408 | - concat!( |
409 | - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYtb/9Sh8nGKV7exhUFS", |
410 | - "+cBNXlHgO1CxD9zIfQd5ztlq1LO7g38dfmFpQafh9lKgqPBTolFhZxhF1yUNT", |
411 | - "hpV673NdAtaCVGNyx/fTYtvyyFe9DH2tmm/ijLlygDRboSkIJ4NHZjK++48hk", |
412 | - "NP8/htqWHS+CvwWT4Qgs0NtB7Re9bQIDAQAB" |
413 | - ) |
414 | - .as_bytes(), |
415 | - ) |
416 | - .unwrap(), |
417 | - ) |
418 | - .unwrap(), |
419 | - ), |
420 | - f: 0, |
421 | - }, |
422 | + 0, |
423 | ), |
424 | ] { |
425 | assert_eq!( |
426 | - DomainKey::parse(record.as_bytes()).unwrap(), |
427 | + DomainKey::parse(record.as_bytes()).unwrap().f, |
428 | expected_result |
429 | ); |
430 | } |
431 | diff --git a/src/lib.rs b/src/lib.rs |
432 | index 80f7638..050b94e 100644 |
433 | --- a/src/lib.rs |
434 | +++ b/src/lib.rs |
435 | @@ -275,7 +275,6 @@ pub mod dmarc; |
436 | pub mod report; |
437 | pub mod spf; |
438 | |
439 | - #[derive(Debug)] |
440 | pub struct Resolver { |
441 | pub(crate) resolver: TokioAsyncResolver, |
442 | pub(crate) cache_txt: LruCache<String, Txt>, |
443 | @@ -287,7 +286,7 @@ pub struct Resolver { |
444 | pub(crate) verify_policy: Policy, |
445 | } |
446 | |
447 | - #[derive(Debug, Clone, PartialEq, Eq)] |
448 | + #[derive(Clone)] |
449 | pub(crate) enum Txt { |
450 | Spf(Arc<Spf>), |
451 | SpfMacro(Arc<Macro>), |