Commit
Author: Mauro D [mauro@stalw.art]
Committer: GitHub [noreply@github.com] Mon, 09 Jan 2023 16:05:37 +0000
Hash: 1d71dd8cd748af39c5783d0b5f4d867e8a84d85d
Timestamp: Mon, 09 Jan 2023 16:05:37 +0000 (1 year ago)

+396 -331 +/-13 browse
Merge pull request #7 from InstantDomain/ring
Merge pull request #7 from InstantDomain/ring

Add opt-in support for *ring*-based cryptography
1diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs
2index 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
21index 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
42index 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
58index 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
75index 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
149index 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
214deleted file mode 100644
215index 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
477new file mode 100644
478index 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
587new file mode 100644
588index 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
824index 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
844index 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
894index 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
945index 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(),