Commit
+396 -331 +/-13 browse
1 | diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs |
2 | index 1c31c3c..f6f84c0 100644 |
3 | --- a/examples/arc_seal.rs |
4 | +++ b/examples/arc_seal.rs |
5 | @@ -10,10 +10,12 @@ |
6 | |
7 | use mail_auth::{ |
8 | arc::ArcSealer, |
9 | - common::{crypto::RsaKey, headers::HeaderWriter}, |
10 | + common::{ |
11 | + crypto::{RsaKey, Sha256}, |
12 | + headers::HeaderWriter, |
13 | + }, |
14 | AuthenticatedMessage, AuthenticationResults, Resolver, |
15 | }; |
16 | - use sha2::Sha256; |
17 | |
18 | const TEST_MESSAGE: &str = include_str!("../resources/arc/001.txt"); |
19 | |
20 | diff --git a/examples/dkim_sign.rs b/examples/dkim_sign.rs |
21 | index a05e196..edf9fa0 100644 |
22 | --- a/examples/dkim_sign.rs |
23 | +++ b/examples/dkim_sign.rs |
24 | @@ -9,11 +9,14 @@ |
25 | */ |
26 | |
27 | use mail_auth::{ |
28 | - common::{crypto::Ed25519Key, crypto::RsaKey, headers::HeaderWriter}, |
29 | + common::{ |
30 | + crypto::RsaKey, |
31 | + crypto::{Ed25519Key, Sha256}, |
32 | + headers::HeaderWriter, |
33 | + }, |
34 | dkim::DkimSigner, |
35 | }; |
36 | use mail_parser::decoders::base64::base64_decode; |
37 | - use sha2::Sha256; |
38 | |
39 | const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY----- |
40 | MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC |
41 | diff --git a/src/arc/builder.rs b/src/arc/builder.rs |
42 | index f5780d4..57c5ed8 100644 |
43 | --- a/src/arc/builder.rs |
44 | +++ b/src/arc/builder.rs |
45 | @@ -8,10 +8,8 @@ |
46 | * except according to those terms. |
47 | */ |
48 | |
49 | - use sha2::Sha256; |
50 | - |
51 | use crate::{ |
52 | - common::crypto::SigningKey, |
53 | + common::crypto::{Sha256, SigningKey}, |
54 | dkim::{Canonicalization, Done, NeedDomain, NeedHeaders, NeedSelector}, |
55 | }; |
56 | |
57 | diff --git a/src/arc/mod.rs b/src/arc/mod.rs |
58 | index ed4ea20..a285379 100644 |
59 | --- a/src/arc/mod.rs |
60 | +++ b/src/arc/mod.rs |
61 | @@ -14,11 +14,9 @@ pub mod parse; |
62 | pub mod seal; |
63 | pub mod verify; |
64 | |
65 | - use sha2::Sha256; |
66 | - |
67 | use crate::{ |
68 | common::{ |
69 | - crypto::{Algorithm, SigningKey}, |
70 | + crypto::{Algorithm, Sha256, SigningKey}, |
71 | headers::Header, |
72 | verify::VerifySignature, |
73 | }, |
74 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
75 | index 88561f3..038b147 100644 |
76 | --- a/src/arc/seal.rs |
77 | +++ b/src/arc/seal.rs |
78 | @@ -8,13 +8,15 @@ |
79 | * except according to those terms. |
80 | */ |
81 | |
82 | - use std::{io::Write, time::SystemTime}; |
83 | + use std::time::SystemTime; |
84 | |
85 | use mail_builder::encoders::base64::base64_encode; |
86 | - use sha2::{Digest, Sha256}; |
87 | |
88 | use crate::{ |
89 | - common::{crypto::SigningKey, headers::Writer}, |
90 | + common::{ |
91 | + crypto::{HashContext, Sha256, SigningKey}, |
92 | + headers::Writer, |
93 | + }, |
94 | dkim::{Canonicalization, Done}, |
95 | ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, |
96 | }; |
97 | @@ -72,7 +74,7 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
98 | .duration_since(SystemTime::UNIX_EPOCH) |
99 | .map(|d| d.as_secs()) |
100 | .unwrap_or(0); |
101 | - set.signature.bh = base64_encode(&body_hasher.finalize())?; |
102 | + set.signature.bh = base64_encode(body_hasher.complete().as_ref())?; |
103 | set.signature.t = now; |
104 | set.signature.x = if set.signature.x > 0 { |
105 | now + set.signature.x |
106 | @@ -88,11 +90,11 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
107 | set.signature.write(&mut header_hasher, false); |
108 | |
109 | // Sign |
110 | - let b = self.key.sign(&header_hasher.finalize())?; |
111 | + let b = self.key.sign(header_hasher.complete())?; |
112 | set.signature.b = base64_encode(&b)?; |
113 | |
114 | // Hash ARC chain |
115 | - let mut header_hasher = Sha256::new(); |
116 | + let mut header_hasher = self.key.hasher(); |
117 | if !arc_output.set.is_empty() { |
118 | Canonicalization::Relaxed.canonicalize_headers( |
119 | &mut arc_output.set.iter().flat_map(|set| { |
120 | @@ -109,11 +111,11 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> { |
121 | // Hash ARC headers for the current instance |
122 | set.results.write(&mut header_hasher, set.seal.i, false); |
123 | set.signature.write(&mut header_hasher, false); |
124 | - header_hasher.write_all(b"\r\n")?; |
125 | + header_hasher.write(b"\r\n"); |
126 | set.seal.write(&mut header_hasher, false); |
127 | |
128 | // Seal |
129 | - let b = self.key.sign(&header_hasher.finalize())?; |
130 | + let b = self.key.sign(header_hasher.complete())?; |
131 | set.seal.b = base64_encode(&b)?; |
132 | |
133 | Ok(set) |
134 | @@ -166,12 +168,11 @@ mod test { |
135 | use std::time::{Duration, Instant}; |
136 | |
137 | use mail_parser::decoders::base64::base64_decode; |
138 | - use sha2::Sha256; |
139 | |
140 | use crate::{ |
141 | arc::ArcSealer, |
142 | common::{ |
143 | - crypto::{Ed25519Key, RsaKey, SigningKey}, |
144 | + crypto::{Ed25519Key, RsaKey, Sha256, SigningKey}, |
145 | headers::HeaderWriter, |
146 | parse::TxtRecordParser, |
147 | verify::DomainKey, |
148 | diff --git a/src/common/base32.rs b/src/common/base32.rs |
149 | index 8207e4f..0a24a60 100644 |
150 | --- a/src/common/base32.rs |
151 | +++ b/src/common/base32.rs |
152 | @@ -8,7 +8,7 @@ |
153 | * except according to those terms. |
154 | */ |
155 | |
156 | - use std::io; |
157 | + use super::headers::Writer; |
158 | |
159 | pub(crate) static BASE32_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
160 | |
161 | @@ -68,30 +68,21 @@ impl Base32Writer { |
162 | } |
163 | } |
164 | |
165 | - impl io::Write for Base32Writer { |
166 | - fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { |
167 | - let start_pos = self.pos; |
168 | - |
169 | - for &byte in bytes { |
170 | + impl Writer for Base32Writer { |
171 | + fn write(&mut self, buf: &[u8]) { |
172 | + for &byte in buf { |
173 | self.push_byte(byte, false); |
174 | } |
175 | - |
176 | - Ok(self.pos - start_pos) |
177 | - } |
178 | - |
179 | - fn flush(&mut self) -> io::Result<()> { |
180 | - Ok(()) |
181 | } |
182 | } |
183 | |
184 | #[cfg(test)] |
185 | mod tests { |
186 | - |
187 | - use std::io::Write; |
188 | - |
189 | - use sha1::{Digest, Sha1}; |
190 | - |
191 | - use crate::common::base32::Base32Writer; |
192 | + use crate::common::{ |
193 | + base32::Base32Writer, |
194 | + crypto::{HashContext, HashImpl, Sha1}, |
195 | + headers::Writer, |
196 | + }; |
197 | |
198 | #[test] |
199 | fn base32_hash() { |
200 | @@ -100,9 +91,9 @@ mod tests { |
201 | ("two.example.net", "ZTZGRRV3F45A4U6HLDKBF3ZCOW4V2AJX"), |
202 | ] { |
203 | let mut writer = Base32Writer::with_capacity(10); |
204 | - let mut hash = Sha1::new(); |
205 | - hash.update(test.as_bytes()); |
206 | - writer.write_all(&hash.finalize()[..]).ok(); |
207 | + let mut hash = Sha1::hasher(); |
208 | + hash.write(test.as_bytes()); |
209 | + writer.write(hash.complete().as_ref()); |
210 | assert_eq!(writer.finalize(), expected_result); |
211 | } |
212 | } |
213 | diff --git a/src/common/crypto.rs b/src/common/crypto.rs |
214 | deleted file mode 100644 |
215 | index 1aaa007..0000000 |
216 | --- a/src/common/crypto.rs |
217 | +++ /dev/null |
218 | @@ -1,257 +0,0 @@ |
219 | - use std::{io, marker::PhantomData}; |
220 | - |
221 | - use ed25519_dalek::Signer; |
222 | - use rsa::{ |
223 | - pkcs1::DecodeRsaPrivateKey, |
224 | - pkcs8::{AssociatedOid, ObjectIdentifier}, |
225 | - PaddingScheme, PublicKey as _, RsaPrivateKey, |
226 | - }; |
227 | - use sha1::{digest::Output, Sha1}; |
228 | - use sha2::{digest::Digest, Sha256}; |
229 | - |
230 | - use crate::{dkim::Canonicalization, Error, Result}; |
231 | - |
232 | - use super::headers::Writer; |
233 | - |
234 | - pub trait SigningKey { |
235 | - type Hasher: Digest + Writer; |
236 | - |
237 | - fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>>; |
238 | - |
239 | - fn hasher(&self) -> Self::Hasher { |
240 | - Self::Hasher::new() |
241 | - } |
242 | - |
243 | - fn algorithm(&self) -> Algorithm; |
244 | - } |
245 | - |
246 | - #[derive(Debug, Clone)] |
247 | - pub struct RsaKey<T> { |
248 | - inner: RsaPrivateKey, |
249 | - padding: PhantomData<T>, |
250 | - } |
251 | - |
252 | - impl<T: Digest + AssociatedOid + io::Write> RsaKey<T> { |
253 | - /// Creates a new RSA private key from a PKCS1 PEM string. |
254 | - pub fn from_pkcs1_pem(private_key_pem: &str) -> Result<Self> { |
255 | - let inner = RsaPrivateKey::from_pkcs1_pem(private_key_pem) |
256 | - .map_err(|err| Error::CryptoError(err.to_string()))?; |
257 | - |
258 | - Ok(RsaKey { |
259 | - inner, |
260 | - padding: PhantomData, |
261 | - }) |
262 | - } |
263 | - |
264 | - /// Creates a new RSA private key from a PKCS1 binary slice. |
265 | - pub fn from_pkcs1_der(private_key_bytes: &[u8]) -> Result<Self> { |
266 | - let inner = RsaPrivateKey::from_pkcs1_der(private_key_bytes) |
267 | - .map_err(|err| Error::CryptoError(err.to_string()))?; |
268 | - |
269 | - Ok(RsaKey { |
270 | - inner, |
271 | - padding: PhantomData, |
272 | - }) |
273 | - } |
274 | - } |
275 | - |
276 | - impl SigningKey for RsaKey<Sha1> { |
277 | - type Hasher = Sha1; |
278 | - |
279 | - fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> { |
280 | - self.inner |
281 | - .sign(PaddingScheme::new_pkcs1v15_sign::<Self::Hasher>(), data) |
282 | - .map_err(|err| Error::CryptoError(err.to_string())) |
283 | - } |
284 | - |
285 | - fn algorithm(&self) -> Algorithm { |
286 | - Algorithm::RsaSha1 |
287 | - } |
288 | - } |
289 | - |
290 | - impl SigningKey for RsaKey<Sha256> { |
291 | - type Hasher = Sha256; |
292 | - |
293 | - fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> { |
294 | - self.inner |
295 | - .sign(PaddingScheme::new_pkcs1v15_sign::<Self::Hasher>(), data) |
296 | - .map_err(|err| Error::CryptoError(err.to_string())) |
297 | - } |
298 | - |
299 | - fn algorithm(&self) -> Algorithm { |
300 | - Algorithm::RsaSha256 |
301 | - } |
302 | - } |
303 | - |
304 | - pub struct Ed25519Key { |
305 | - inner: ed25519_dalek::Keypair, |
306 | - } |
307 | - |
308 | - impl Ed25519Key { |
309 | - /// Creates an Ed25519 private key |
310 | - pub fn from_bytes(public_key_bytes: &[u8], private_key_bytes: &[u8]) -> crate::Result<Self> { |
311 | - Ok(Self { |
312 | - inner: ed25519_dalek::Keypair { |
313 | - public: ed25519_dalek::PublicKey::from_bytes(public_key_bytes) |
314 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
315 | - secret: ed25519_dalek::SecretKey::from_bytes(private_key_bytes) |
316 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
317 | - }, |
318 | - }) |
319 | - } |
320 | - } |
321 | - |
322 | - impl SigningKey for Ed25519Key { |
323 | - type Hasher = Sha256; |
324 | - |
325 | - fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> { |
326 | - Ok(self.inner.sign(data.as_ref()).to_bytes().to_vec()) |
327 | - } |
328 | - |
329 | - fn algorithm(&self) -> Algorithm { |
330 | - Algorithm::Ed25519Sha256 |
331 | - } |
332 | - } |
333 | - |
334 | - pub trait VerifyingKey { |
335 | - fn verify<'a>( |
336 | - &self, |
337 | - headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
338 | - signature: &[u8], |
339 | - canonicalication: Canonicalization, |
340 | - algorithm: Algorithm, |
341 | - ) -> Result<()>; |
342 | - } |
343 | - |
344 | - pub(crate) enum VerifyingKeyType { |
345 | - Rsa, |
346 | - Ed25519, |
347 | - } |
348 | - |
349 | - impl VerifyingKeyType { |
350 | - pub(crate) fn verifying_key( |
351 | - &self, |
352 | - bytes: &[u8], |
353 | - ) -> Result<Box<dyn VerifyingKey + Sync + Send>> { |
354 | - Ok(match self { |
355 | - Self::Rsa => { |
356 | - let inner = |
357 | - <rsa::RsaPublicKey as rsa::pkcs8::DecodePublicKey>::from_public_key_der(bytes) |
358 | - .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(bytes)) |
359 | - .map_err(|err| Error::CryptoError(err.to_string()))?; |
360 | - |
361 | - Box::new(RsaPublicKey { inner }) as Box<dyn VerifyingKey + Sync + Send> |
362 | - } |
363 | - Self::Ed25519 => Box::new(Ed25519PublicKey { |
364 | - inner: ed25519_dalek::PublicKey::from_bytes(bytes) |
365 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
366 | - }), |
367 | - }) |
368 | - } |
369 | - } |
370 | - |
371 | - pub(crate) struct RsaPublicKey { |
372 | - inner: rsa::RsaPublicKey, |
373 | - } |
374 | - |
375 | - impl VerifyingKey for RsaPublicKey { |
376 | - fn verify<'a>( |
377 | - &self, |
378 | - headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
379 | - signature: &[u8], |
380 | - canonicalization: Canonicalization, |
381 | - algorithm: Algorithm, |
382 | - ) -> Result<()> { |
383 | - match algorithm { |
384 | - Algorithm::RsaSha256 => { |
385 | - let hash = canonicalization.hash_headers::<Sha256>(headers); |
386 | - self.inner |
387 | - .verify( |
388 | - PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
389 | - hash.as_ref(), |
390 | - signature, |
391 | - ) |
392 | - .map_err(|_| Error::FailedVerification) |
393 | - } |
394 | - Algorithm::RsaSha1 => { |
395 | - let hash = canonicalization.hash_headers::<Sha1>(headers); |
396 | - self.inner |
397 | - .verify( |
398 | - PaddingScheme::new_pkcs1v15_sign::<Sha1>(), |
399 | - hash.as_ref(), |
400 | - signature, |
401 | - ) |
402 | - .map_err(|_| Error::FailedVerification) |
403 | - } |
404 | - Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms), |
405 | - } |
406 | - } |
407 | - } |
408 | - |
409 | - pub(crate) struct Ed25519PublicKey { |
410 | - inner: ed25519_dalek::PublicKey, |
411 | - } |
412 | - |
413 | - impl VerifyingKey for Ed25519PublicKey { |
414 | - fn verify<'a>( |
415 | - &self, |
416 | - headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
417 | - signature: &[u8], |
418 | - canonicalization: Canonicalization, |
419 | - algorithm: Algorithm, |
420 | - ) -> Result<()> { |
421 | - if !matches!(algorithm, Algorithm::Ed25519Sha256) { |
422 | - return Err(Error::IncompatibleAlgorithms); |
423 | - } |
424 | - |
425 | - let hash = canonicalization.hash_headers::<Sha256>(headers); |
426 | - self.inner |
427 | - .verify_strict( |
428 | - hash.as_ref(), |
429 | - &ed25519_dalek::Signature::from_bytes(signature) |
430 | - .map_err(|err| Error::CryptoError(err.to_string()))?, |
431 | - ) |
432 | - .map_err(|_| Error::FailedVerification) |
433 | - } |
434 | - } |
435 | - |
436 | - impl Writer for Sha1 { |
437 | - fn write(&mut self, buf: &[u8]) { |
438 | - self.update(buf); |
439 | - } |
440 | - } |
441 | - |
442 | - impl Writer for Sha256 { |
443 | - fn write(&mut self, buf: &[u8]) { |
444 | - self.update(buf); |
445 | - } |
446 | - } |
447 | - |
448 | - #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
449 | - #[repr(u64)] |
450 | - pub enum HashAlgorithm { |
451 | - Sha1 = R_HASH_SHA1, |
452 | - Sha256 = R_HASH_SHA256, |
453 | - } |
454 | - |
455 | - impl TryFrom<&ObjectIdentifier> for HashAlgorithm { |
456 | - type Error = Error; |
457 | - |
458 | - fn try_from(oid: &ObjectIdentifier) -> Result<Self> { |
459 | - match oid { |
460 | - oid if oid == &Sha256::OID => Ok(HashAlgorithm::Sha256), |
461 | - oid if oid == &Sha1::OID => Ok(HashAlgorithm::Sha1), |
462 | - _ => Err(Error::CryptoError("Unsupported hash algorithm".to_string())), |
463 | - } |
464 | - } |
465 | - } |
466 | - |
467 | - #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
468 | - pub enum Algorithm { |
469 | - RsaSha1, |
470 | - RsaSha256, |
471 | - Ed25519Sha256, |
472 | - } |
473 | - |
474 | - pub(crate) const R_HASH_SHA1: u64 = 0x01; |
475 | - pub(crate) const R_HASH_SHA256: u64 = 0x02; |
476 | diff --git a/src/common/crypto/mod.rs b/src/common/crypto/mod.rs |
477 | new file mode 100644 |
478 | index 0000000..6c8693d |
479 | --- /dev/null |
480 | +++ b/src/common/crypto/mod.rs |
481 | @@ -0,0 +1,104 @@ |
482 | + use sha1::{digest::Output, Digest}; |
483 | + |
484 | + use crate::{dkim::Canonicalization, Result}; |
485 | + |
486 | + use super::headers::Writer; |
487 | + |
488 | + mod rust_crypto; |
489 | + pub use rust_crypto::{Ed25519Key, RsaKey}; |
490 | + pub(crate) use rust_crypto::{Ed25519PublicKey, RsaPublicKey}; |
491 | + |
492 | + pub trait SigningKey { |
493 | + type Hasher: HashImpl; |
494 | + |
495 | + fn sign(&self, data: HashOutput) -> Result<Vec<u8>>; |
496 | + |
497 | + fn hasher(&self) -> <Self::Hasher as HashImpl>::Context { |
498 | + <Self::Hasher as HashImpl>::hasher() |
499 | + } |
500 | + |
501 | + fn algorithm(&self) -> Algorithm; |
502 | + } |
503 | + |
504 | + pub trait VerifyingKey { |
505 | + fn verify<'a>( |
506 | + &self, |
507 | + headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
508 | + signature: &[u8], |
509 | + canonicalication: Canonicalization, |
510 | + algorithm: Algorithm, |
511 | + ) -> Result<()>; |
512 | + } |
513 | + |
514 | + pub(crate) enum VerifyingKeyType { |
515 | + Rsa, |
516 | + Ed25519, |
517 | + } |
518 | + |
519 | + impl VerifyingKeyType { |
520 | + pub(crate) fn verifying_key( |
521 | + &self, |
522 | + bytes: &[u8], |
523 | + ) -> Result<Box<dyn VerifyingKey + Send + Sync>> { |
524 | + match self { |
525 | + Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes), |
526 | + Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes), |
527 | + } |
528 | + } |
529 | + } |
530 | + |
531 | + pub trait HashContext: Writer + Sized { |
532 | + fn complete(self) -> HashOutput; |
533 | + } |
534 | + |
535 | + pub trait HashImpl { |
536 | + type Context: HashContext; |
537 | + |
538 | + fn hasher() -> Self::Context; |
539 | + } |
540 | + |
541 | + #[derive(Clone, Copy)] |
542 | + pub struct Sha1; |
543 | + |
544 | + #[derive(Clone, Copy)] |
545 | + pub struct Sha256; |
546 | + |
547 | + #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
548 | + #[repr(u64)] |
549 | + pub enum HashAlgorithm { |
550 | + Sha1 = R_HASH_SHA1, |
551 | + Sha256 = R_HASH_SHA256, |
552 | + } |
553 | + |
554 | + impl HashAlgorithm { |
555 | + pub fn hash(&self, data: &[u8]) -> HashOutput { |
556 | + match self { |
557 | + Self::Sha1 => HashOutput::Sha1(sha1::Sha1::digest(data)), |
558 | + Self::Sha256 => HashOutput::Sha256(sha2::Sha256::digest(data)), |
559 | + } |
560 | + } |
561 | + } |
562 | + |
563 | + pub enum HashOutput { |
564 | + Sha1(Output<sha1::Sha1>), |
565 | + Sha256(Output<sha2::Sha256>), |
566 | + } |
567 | + |
568 | + impl AsRef<[u8]> for HashOutput { |
569 | + fn as_ref(&self) -> &[u8] { |
570 | + match self { |
571 | + Self::Sha1(output) => output.as_ref(), |
572 | + Self::Sha256(output) => output.as_ref(), |
573 | + } |
574 | + } |
575 | + } |
576 | + |
577 | + #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
578 | + pub enum Algorithm { |
579 | + RsaSha1, |
580 | + RsaSha256, |
581 | + Ed25519Sha256, |
582 | + } |
583 | + |
584 | + pub(crate) const R_HASH_SHA1: u64 = 0x01; |
585 | + pub(crate) const R_HASH_SHA256: u64 = 0x02; |
586 | diff --git a/src/common/crypto/rust_crypto.rs b/src/common/crypto/rust_crypto.rs |
587 | new file mode 100644 |
588 | index 0000000..178fb2d |
589 | --- /dev/null |
590 | +++ b/src/common/crypto/rust_crypto.rs |
591 | @@ -0,0 +1,231 @@ |
592 | + use std::marker::PhantomData; |
593 | + |
594 | + use ed25519_dalek::Signer; |
595 | + use rsa::{pkcs1::DecodeRsaPrivateKey, PaddingScheme, PublicKey as _, RsaPrivateKey}; |
596 | + use sha2::digest::Digest; |
597 | + |
598 | + use crate::{common::headers::Writer, dkim::Canonicalization, Error, Result}; |
599 | + |
600 | + use super::{Algorithm, HashContext, HashImpl, HashOutput, Sha1, Sha256, SigningKey, VerifyingKey}; |
601 | + |
602 | + #[derive(Clone, Debug)] |
603 | + pub struct RsaKey<T> { |
604 | + inner: RsaPrivateKey, |
605 | + padding: PhantomData<T>, |
606 | + } |
607 | + |
608 | + impl<T: HashImpl> RsaKey<T> { |
609 | + /// Creates a new RSA private key from a PKCS1 PEM string. |
610 | + pub fn from_pkcs1_pem(private_key_pem: &str) -> Result<Self> { |
611 | + let inner = RsaPrivateKey::from_pkcs1_pem(private_key_pem) |
612 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
613 | + |
614 | + Ok(RsaKey { |
615 | + inner, |
616 | + padding: PhantomData, |
617 | + }) |
618 | + } |
619 | + |
620 | + /// Creates a new RSA private key from a PKCS1 binary slice. |
621 | + pub fn from_pkcs1_der(private_key_bytes: &[u8]) -> Result<Self> { |
622 | + let inner = RsaPrivateKey::from_pkcs1_der(private_key_bytes) |
623 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
624 | + |
625 | + Ok(RsaKey { |
626 | + inner, |
627 | + padding: PhantomData, |
628 | + }) |
629 | + } |
630 | + } |
631 | + |
632 | + impl SigningKey for RsaKey<Sha1> { |
633 | + type Hasher = Sha1; |
634 | + |
635 | + fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
636 | + self.inner |
637 | + .sign( |
638 | + PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(), |
639 | + data.as_ref(), |
640 | + ) |
641 | + .map_err(|err| Error::CryptoError(err.to_string())) |
642 | + } |
643 | + |
644 | + fn algorithm(&self) -> Algorithm { |
645 | + Algorithm::RsaSha1 |
646 | + } |
647 | + } |
648 | + |
649 | + impl SigningKey for RsaKey<Sha256> { |
650 | + type Hasher = Sha256; |
651 | + |
652 | + fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
653 | + self.inner |
654 | + .sign( |
655 | + PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(), |
656 | + data.as_ref(), |
657 | + ) |
658 | + .map_err(|err| Error::CryptoError(err.to_string())) |
659 | + } |
660 | + |
661 | + fn algorithm(&self) -> Algorithm { |
662 | + Algorithm::RsaSha256 |
663 | + } |
664 | + } |
665 | + |
666 | + pub struct Ed25519Key { |
667 | + inner: ed25519_dalek::Keypair, |
668 | + } |
669 | + |
670 | + impl Ed25519Key { |
671 | + /// Creates an Ed25519 private key |
672 | + pub fn from_bytes(public_key_bytes: &[u8], private_key_bytes: &[u8]) -> crate::Result<Self> { |
673 | + Ok(Self { |
674 | + inner: ed25519_dalek::Keypair { |
675 | + public: ed25519_dalek::PublicKey::from_bytes(public_key_bytes) |
676 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
677 | + secret: ed25519_dalek::SecretKey::from_bytes(private_key_bytes) |
678 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
679 | + }, |
680 | + }) |
681 | + } |
682 | + } |
683 | + |
684 | + impl SigningKey for Ed25519Key { |
685 | + type Hasher = Sha256; |
686 | + |
687 | + fn sign(&self, data: HashOutput) -> Result<Vec<u8>> { |
688 | + Ok(self.inner.sign(data.as_ref()).to_bytes().to_vec()) |
689 | + } |
690 | + |
691 | + fn algorithm(&self) -> Algorithm { |
692 | + Algorithm::Ed25519Sha256 |
693 | + } |
694 | + } |
695 | + |
696 | + pub(crate) struct RsaPublicKey { |
697 | + inner: rsa::RsaPublicKey, |
698 | + } |
699 | + |
700 | + impl RsaPublicKey { |
701 | + pub(crate) fn verifying_key_from_bytes( |
702 | + bytes: &[u8], |
703 | + ) -> Result<Box<dyn VerifyingKey + Send + Sync>> { |
704 | + Ok(Box::new(RsaPublicKey { |
705 | + inner: <rsa::RsaPublicKey as rsa::pkcs8::DecodePublicKey>::from_public_key_der(bytes) |
706 | + .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(bytes)) |
707 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
708 | + })) |
709 | + } |
710 | + } |
711 | + |
712 | + impl VerifyingKey for RsaPublicKey { |
713 | + fn verify<'a>( |
714 | + &self, |
715 | + headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
716 | + signature: &[u8], |
717 | + canonicalization: Canonicalization, |
718 | + algorithm: Algorithm, |
719 | + ) -> Result<()> { |
720 | + match algorithm { |
721 | + Algorithm::RsaSha256 => { |
722 | + let hash = canonicalization.hash_headers::<Sha256>(headers); |
723 | + self.inner |
724 | + .verify( |
725 | + PaddingScheme::new_pkcs1v15_sign::<sha2::Sha256>(), |
726 | + hash.as_ref(), |
727 | + signature, |
728 | + ) |
729 | + .map_err(|_| Error::FailedVerification) |
730 | + } |
731 | + Algorithm::RsaSha1 => { |
732 | + let hash = canonicalization.hash_headers::<Sha1>(headers); |
733 | + self.inner |
734 | + .verify( |
735 | + PaddingScheme::new_pkcs1v15_sign::<sha1::Sha1>(), |
736 | + hash.as_ref(), |
737 | + signature, |
738 | + ) |
739 | + .map_err(|_| Error::FailedVerification) |
740 | + } |
741 | + Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms), |
742 | + } |
743 | + } |
744 | + } |
745 | + |
746 | + pub(crate) struct Ed25519PublicKey { |
747 | + inner: ed25519_dalek::PublicKey, |
748 | + } |
749 | + |
750 | + impl Ed25519PublicKey { |
751 | + pub(crate) fn verifying_key_from_bytes( |
752 | + bytes: &[u8], |
753 | + ) -> Result<Box<dyn VerifyingKey + Send + Sync>> { |
754 | + Ok(Box::new(Ed25519PublicKey { |
755 | + inner: ed25519_dalek::PublicKey::from_bytes(bytes) |
756 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
757 | + })) |
758 | + } |
759 | + } |
760 | + |
761 | + impl VerifyingKey for Ed25519PublicKey { |
762 | + fn verify<'a>( |
763 | + &self, |
764 | + headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
765 | + signature: &[u8], |
766 | + canonicalization: Canonicalization, |
767 | + algorithm: Algorithm, |
768 | + ) -> Result<()> { |
769 | + if !matches!(algorithm, Algorithm::Ed25519Sha256) { |
770 | + return Err(Error::IncompatibleAlgorithms); |
771 | + } |
772 | + |
773 | + let hash = canonicalization.hash_headers::<Sha256>(headers); |
774 | + self.inner |
775 | + .verify_strict( |
776 | + hash.as_ref(), |
777 | + &ed25519_dalek::Signature::from_bytes(signature) |
778 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
779 | + ) |
780 | + .map_err(|_| Error::FailedVerification) |
781 | + } |
782 | + } |
783 | + |
784 | + impl Writer for sha1::Sha1 { |
785 | + fn write(&mut self, buf: &[u8]) { |
786 | + self.update(buf); |
787 | + } |
788 | + } |
789 | + |
790 | + impl Writer for sha2::Sha256 { |
791 | + fn write(&mut self, buf: &[u8]) { |
792 | + self.update(buf); |
793 | + } |
794 | + } |
795 | + |
796 | + impl HashImpl for Sha1 { |
797 | + type Context = sha1::Sha1; |
798 | + |
799 | + fn hasher() -> Self::Context { |
800 | + <Self::Context as Digest>::new() |
801 | + } |
802 | + } |
803 | + |
804 | + impl HashImpl for Sha256 { |
805 | + type Context = sha2::Sha256; |
806 | + |
807 | + fn hasher() -> Self::Context { |
808 | + <Self::Context as Digest>::new() |
809 | + } |
810 | + } |
811 | + |
812 | + impl HashContext for sha1::Sha1 { |
813 | + fn complete(self) -> HashOutput { |
814 | + HashOutput::Sha1(self.finalize()) |
815 | + } |
816 | + } |
817 | + |
818 | + impl HashContext for sha2::Sha256 { |
819 | + fn complete(self) -> HashOutput { |
820 | + HashOutput::Sha256(self.finalize()) |
821 | + } |
822 | + } |
823 | diff --git a/src/common/message.rs b/src/common/message.rs |
824 | index d55b158..0f1ab40 100644 |
825 | --- a/src/common/message.rs |
826 | +++ b/src/common/message.rs |
827 | @@ -9,10 +9,12 @@ |
828 | */ |
829 | |
830 | use mail_parser::{parsers::MessageStream, HeaderValue}; |
831 | - use sha1::Sha1; |
832 | - use sha2::Sha256; |
833 | |
834 | - use crate::{arc, common::crypto::HashAlgorithm, dkim, AuthenticatedMessage}; |
835 | + use crate::{ |
836 | + arc, |
837 | + common::crypto::{HashAlgorithm, Sha1, Sha256}, |
838 | + dkim, AuthenticatedMessage, |
839 | + }; |
840 | |
841 | use super::headers::{AuthenticatedHeader, Header, HeaderParser}; |
842 | |
843 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
844 | index 7ad70bd..16b6b4f 100644 |
845 | --- a/src/dkim/canonicalize.rs |
846 | +++ b/src/dkim/canonicalize.rs |
847 | @@ -8,9 +8,10 @@ |
848 | * except according to those terms. |
849 | */ |
850 | |
851 | - use sha1::Digest; |
852 | - |
853 | - use crate::common::headers::{HeaderIterator, Writer}; |
854 | + use crate::common::{ |
855 | + crypto::{HashContext, HashImpl}, |
856 | + headers::{HeaderIterator, Writer}, |
857 | + }; |
858 | |
859 | use super::{Canonicalization, Signature}; |
860 | |
861 | @@ -114,17 +115,17 @@ impl Canonicalization { |
862 | } |
863 | } |
864 | |
865 | - pub fn hash_headers<'x, T: Digest + Writer>( |
866 | + pub fn hash_headers<'x, T: HashImpl>( |
867 | &self, |
868 | headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>, |
869 | ) -> impl AsRef<[u8]> { |
870 | - let mut hasher = T::new(); |
871 | + let mut hasher = T::hasher(); |
872 | self.canonicalize_headers(headers, &mut hasher); |
873 | - hasher.finalize() |
874 | + hasher.complete() |
875 | } |
876 | |
877 | - pub fn hash_body<T: Digest + Writer>(&self, body: &[u8], l: u64) -> impl AsRef<[u8]> { |
878 | - let mut hasher = T::new(); |
879 | + pub fn hash_body<T: HashImpl>(&self, body: &[u8], l: u64) -> impl AsRef<[u8]> { |
880 | + let mut hasher = T::hasher(); |
881 | self.canonicalize_body( |
882 | if l == 0 || body.is_empty() { |
883 | body |
884 | @@ -133,7 +134,7 @@ impl Canonicalization { |
885 | }, |
886 | &mut hasher, |
887 | ); |
888 | - hasher.finalize() |
889 | + hasher.complete() |
890 | } |
891 | |
892 | pub fn serialize_name(&self, writer: &mut impl Writer) { |
893 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
894 | index d264b1a..0cea162 100644 |
895 | --- a/src/dkim/sign.rs |
896 | +++ b/src/dkim/sign.rs |
897 | @@ -11,10 +11,12 @@ |
898 | use std::time::SystemTime; |
899 | |
900 | use mail_builder::encoders::base64::base64_encode; |
901 | - use sha1::Digest; |
902 | |
903 | use super::{DkimSigner, Done, Signature}; |
904 | - use crate::{common::crypto::SigningKey, Error}; |
905 | + use crate::{ |
906 | + common::crypto::{HashContext, SigningKey}, |
907 | + Error, |
908 | + }; |
909 | |
910 | impl<T: SigningKey> DkimSigner<T, Done> { |
911 | /// Signs a message. |
912 | @@ -44,7 +46,7 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
913 | |
914 | // Create Signature |
915 | let mut signature = self.template.clone(); |
916 | - signature.bh = base64_encode(&body_hasher.finalize())?; |
917 | + signature.bh = base64_encode(&body_hasher.complete().as_ref())?; |
918 | signature.t = now; |
919 | signature.x = if signature.x > 0 { |
920 | now + signature.x |
921 | @@ -60,7 +62,7 @@ impl<T: SigningKey> DkimSigner<T, Done> { |
922 | signature.write(&mut header_hasher, false); |
923 | |
924 | // Sign |
925 | - let b = self.key.sign(&header_hasher.finalize())?; |
926 | + let b = self.key.sign(header_hasher.complete())?; |
927 | |
928 | // Encode |
929 | signature.b = base64_encode(&b)?; |
930 | @@ -74,12 +76,11 @@ mod test { |
931 | use std::time::{Duration, Instant}; |
932 | |
933 | use mail_parser::decoders::base64::base64_decode; |
934 | - use sha2::Sha256; |
935 | use trust_dns_resolver::proto::op::ResponseCode; |
936 | |
937 | use crate::{ |
938 | common::{ |
939 | - crypto::{Ed25519Key, RsaKey}, |
940 | + crypto::{Ed25519Key, RsaKey, Sha256}, |
941 | parse::TxtRecordParser, |
942 | verify::DomainKey, |
943 | }, |
944 | diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs |
945 | index 626e9e1..26099f9 100644 |
946 | --- a/src/dkim/verify.rs |
947 | +++ b/src/dkim/verify.rs |
948 | @@ -8,14 +8,12 @@ |
949 | * except according to those terms. |
950 | */ |
951 | |
952 | - use std::{io::Write, time::SystemTime}; |
953 | - |
954 | - use sha1::{Digest, Sha1}; |
955 | - use sha2::Sha256; |
956 | + use std::time::SystemTime; |
957 | |
958 | use crate::{ |
959 | common::{ |
960 | base32::Base32Writer, |
961 | + headers::Writer, |
962 | verify::{DomainKey, VerifySignature}, |
963 | }, |
964 | is_within_pct, AuthenticatedMessage, DkimOutput, DkimResult, Error, Resolver, |
965 | @@ -131,18 +129,10 @@ impl Resolver { |
966 | |
967 | if found { |
968 | let mut query_domain = match &signature.atpsh { |
969 | - Some(HashAlgorithm::Sha256) => { |
970 | - let mut writer = Base32Writer::with_capacity(40); |
971 | - let mut hash = Sha256::new(); |
972 | - hash.update(signature.d.as_bytes()); |
973 | - writer.write_all(&hash.finalize()[..]).ok(); |
974 | - writer.finalize() |
975 | - } |
976 | - Some(HashAlgorithm::Sha1) => { |
977 | + Some(algorithm) => { |
978 | let mut writer = Base32Writer::with_capacity(40); |
979 | - let mut hash = Sha1::new(); |
980 | - hash.update(signature.d.as_bytes()); |
981 | - writer.write_all(&hash.finalize()[..]).ok(); |
982 | + let output = algorithm.hash(signature.d.as_bytes()); |
983 | + writer.write(output.as_ref()); |
984 | writer.finalize() |
985 | } |
986 | None => signature.d.to_string(), |