Commit
Author: Mauro D [mauro@stalw.art]
Committer: GitHub [noreply@github.com] Fri, 09 Dec 2022 08:48:32 +0000
Hash: 6d550de778a5a656471d2a86f509132efaa6b1c4
Timestamp: Fri, 09 Dec 2022 08:48:32 +0000 (2 years ago)

+238 -187 +/-15 browse
Merge pull request #3 from InstantDomain/crypto
Merge pull request #3 from InstantDomain/crypto

Rework crypto API
1diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs
2index 7364ede..460b898 100644
3--- a/examples/arc_seal.rs
4+++ b/examples/arc_seal.rs
5 @@ -9,9 +9,11 @@
6 */
7
8 use mail_auth::{
9- arc::ArcSet, common::headers::HeaderWriter, AuthenticatedMessage, AuthenticationResults,
10- PrivateKey, Resolver,
11+ arc::ArcSet,
12+ common::{crypto::RsaKey, headers::HeaderWriter},
13+ AuthenticatedMessage, AuthenticationResults, Resolver,
14 };
15+ use sha2::Sha256;
16
17 const TEST_MESSAGE: &str = include_str!("../resources/arc/001.txt");
18
19 @@ -51,7 +53,7 @@ async fn main() {
20 // Seal message
21 if arc_result.can_be_sealed() {
22 // Seal the e-mail message using RSA-SHA256
23- let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
24+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
25 let arc_set = ArcSet::new(&auth_results)
26 .domain("example.org")
27 .selector("default")
28 diff --git a/examples/dkim_sign.rs b/examples/dkim_sign.rs
29index 07f6d04..9bf3bfe 100644
30--- a/examples/dkim_sign.rs
31+++ b/examples/dkim_sign.rs
32 @@ -8,8 +8,12 @@
33 * except according to those terms.
34 */
35
36- use mail_auth::{common::headers::HeaderWriter, dkim::Signature, PrivateKey};
37+ use mail_auth::{
38+ common::{crypto::Ed25519Key, crypto::RsaKey, headers::HeaderWriter},
39+ dkim::Signature,
40+ };
41 use mail_parser::decoders::base64::base64_decode;
42+ use sha2::Sha256;
43
44 const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
45 MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
46 @@ -39,7 +43,7 @@ I'm going to need those TPS reports ASAP. So, if you could do that, that'd be gr
47
48 fn main() {
49 // Sign an e-mail message using RSA-SHA256
50- let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
51+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
52 let signature_rsa = Signature::new()
53 .headers(["From", "To", "Subject"])
54 .domain("example.com")
55 @@ -48,7 +52,7 @@ fn main() {
56 .unwrap();
57
58 // Sign an e-mail message using ED25519-SHA256
59- let pk_ed = PrivateKey::from_ed25519(
60+ let pk_ed = Ed25519Key::from_bytes(
61 &base64_decode(ED25519_PUBLIC_KEY.as_bytes()).unwrap(),
62 &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
63 )
64 diff --git a/src/arc/headers.rs b/src/arc/headers.rs
65index 381477b..384c053 100644
66--- a/src/arc/headers.rs
67+++ b/src/arc/headers.rs
68 @@ -11,8 +11,8 @@
69 use std::io;
70
71 use crate::{
72- common::headers::HeaderWriter,
73- dkim::{Algorithm, Canonicalization},
74+ common::{crypto::Algorithm, headers::HeaderWriter},
75+ dkim::Canonicalization,
76 AuthenticationResults,
77 };
78
79 diff --git a/src/arc/mod.rs b/src/arc/mod.rs
80index 01326f0..5ad709a 100644
81--- a/src/arc/mod.rs
82+++ b/src/arc/mod.rs
83 @@ -16,8 +16,8 @@ pub mod verify;
84 use std::borrow::Cow;
85
86 use crate::{
87- common::{headers::Header, verify::VerifySignature},
88- dkim::{Algorithm, Canonicalization},
89+ common::{crypto::Algorithm, headers::Header, verify::VerifySignature},
90+ dkim::Canonicalization,
91 ArcOutput, AuthenticationResults, DkimResult,
92 };
93
94 diff --git a/src/arc/parse.rs b/src/arc/parse.rs
95index 8089ded..21e3ed1 100644
96--- a/src/arc/parse.rs
97+++ b/src/arc/parse.rs
98 @@ -11,8 +11,8 @@
99 use mail_parser::decoders::base64::base64_decode_stream;
100
101 use crate::{
102- common::parse::TagParser,
103- dkim::{parse::SignatureParser, Algorithm, Canonicalization},
104+ common::{crypto::Algorithm, parse::TagParser},
105+ dkim::{parse::SignatureParser, Canonicalization},
106 Error,
107 };
108
109 diff --git a/src/arc/seal.rs b/src/arc/seal.rs
110index f963ca5..cada5a8 100644
111--- a/src/arc/seal.rs
112+++ b/src/arc/seal.rs
113 @@ -10,15 +10,12 @@
114
115 use std::{borrow::Cow, io::Write, time::SystemTime};
116
117- use ed25519_dalek::Signer;
118 use mail_builder::encoders::base64::base64_encode;
119- use rsa::PaddingScheme;
120- use sha1::Digest;
121- use sha2::Sha256;
122+ use sha2::{Digest, Sha256};
123
124 use crate::{
125- dkim::{Algorithm, Canonicalization},
126- ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, PrivateKey,
127+ common::crypto::SigningKey, dkim::Canonicalization, ArcOutput, AuthenticatedMessage,
128+ AuthenticationResults, DkimResult, Error,
129 };
130
131 use super::{ArcSet, ChainValidation, Seal, Signature};
132 @@ -36,20 +33,15 @@ impl<'x> ArcSet<'x> {
133 mut self,
134 message: &'x AuthenticatedMessage<'x>,
135 arc_output: &ArcOutput,
136- with_key: &PrivateKey,
137+ with_key: &impl SigningKey<Hasher = Sha256>,
138 ) -> crate::Result<Self> {
139 if !arc_output.can_be_sealed() {
140 return Err(Error::ARCInvalidCV);
141 }
142
143 // Set a=
144- if let PrivateKey::Ed25519(_) = with_key {
145- self.signature.a = Algorithm::Ed25519Sha256;
146- self.seal.a = Algorithm::Ed25519Sha256;
147- } else {
148- self.signature.a = Algorithm::RsaSha256;
149- self.seal.a = Algorithm::RsaSha256;
150- }
151+ self.signature.a = with_key.algorithm();
152+ self.seal.a = with_key.algorithm();
153
154 // Set i= and cv=
155 if arc_output.set.is_empty() {
156 @@ -67,8 +59,8 @@ impl<'x> ArcSet<'x> {
157 }
158
159 // Create hashes
160- let mut body_hasher = Sha256::new();
161- let mut header_hasher = Sha256::new();
162+ let mut body_hasher = with_key.hasher();
163+ let mut header_hasher = with_key.hasher();
164
165 // Canonicalize headers and body
166 let (body_len, signed_headers) =
167 @@ -100,18 +92,7 @@ impl<'x> ArcSet<'x> {
168 self.signature.write(&mut header_hasher, false)?;
169
170 // Sign
171- let b = match with_key {
172- PrivateKey::Rsa(private_key) => private_key
173- .sign(
174- PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
175- &header_hasher.finalize(),
176- )
177- .map_err(|err| Error::CryptoError(err.to_string()))?,
178- PrivateKey::Ed25519(key_pair) => {
179- key_pair.sign(&header_hasher.finalize()).to_bytes().to_vec()
180- }
181- PrivateKey::None => return Err(Error::MissingParameters),
182- };
183+ let b = with_key.sign(&header_hasher.finalize())?;
184 self.signature.b = base64_encode(&b)?;
185
186 // Hash ARC chain
187 @@ -136,18 +117,7 @@ impl<'x> ArcSet<'x> {
188 self.seal.write(&mut header_hasher, false)?;
189
190 // Seal
191- let b = match with_key {
192- PrivateKey::Rsa(private_key) => private_key
193- .sign(
194- PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
195- &header_hasher.finalize(),
196- )
197- .map_err(|err| Error::CryptoError(err.to_string()))?,
198- PrivateKey::Ed25519(key_pair) => {
199- key_pair.sign(&header_hasher.finalize()).to_bytes().to_vec()
200- }
201- PrivateKey::None => return Err(Error::MissingParameters),
202- };
203+ let b = with_key.sign(&header_hasher.finalize())?;
204 self.seal.b = base64_encode(&b)?;
205
206 Ok(self)
207 @@ -244,12 +214,17 @@ mod test {
208 use std::time::{Duration, Instant};
209
210 use mail_parser::decoders::base64::base64_decode;
211+ use sha2::Sha256;
212
213 use crate::{
214 arc::ArcSet,
215- common::{headers::HeaderWriter, parse::TxtRecordParser},
216+ common::{
217+ crypto::{Ed25519Key, RsaKey, SigningKey},
218+ headers::HeaderWriter,
219+ parse::TxtRecordParser,
220+ },
221 dkim::{DomainKey, Signature},
222- AuthenticatedMessage, AuthenticationResults, DkimResult, PrivateKey, Resolver,
223+ AuthenticatedMessage, AuthenticationResults, DkimResult, Resolver,
224 };
225
226 const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
227 @@ -305,8 +280,8 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
228 );
229
230 // Create private keys
231- let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
232- let pk_ed = PrivateKey::from_ed25519(
233+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
234+ let pk_ed = Ed25519Key::from_bytes(
235 &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(),
236 &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
237 )
238 @@ -338,7 +313,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
239 raw_message: &str,
240 d: &str,
241 s: &str,
242- pk: &PrivateKey,
243+ pk: &impl SigningKey<Hasher = Sha256>,
244 ) -> String {
245 let message = AuthenticatedMessage::parse(raw_message.as_bytes()).unwrap();
246 let dkim_result = resolver.verify_dkim(&message).await;
247 diff --git a/src/arc/verify.rs b/src/arc/verify.rs
248index e78abd1..9cdf373 100644
249--- a/src/arc/verify.rs
250+++ b/src/arc/verify.rs
251 @@ -14,8 +14,12 @@ use sha1::Sha1;
252 use sha2::Sha256;
253
254 use crate::{
255- common::{headers::Header, verify::VerifySignature},
256- dkim::{verify::Verifier, Algorithm, Canonicalization, DomainKey, HashAlgorithm},
257+ common::{
258+ crypto::{Algorithm, HashAlgorithm},
259+ headers::Header,
260+ verify::VerifySignature,
261+ },
262+ dkim::{verify::Verifier, Canonicalization, DomainKey},
263 ArcOutput, AuthenticatedMessage, DkimResult, Error, Resolver,
264 };
265
266 diff --git a/src/common/crypto.rs b/src/common/crypto.rs
267new file mode 100644
268index 0000000..2d4a4d1
269--- /dev/null
270+++ b/src/common/crypto.rs
271 @@ -0,0 +1,141 @@
272+ use std::{io, marker::PhantomData};
273+
274+ use ed25519_dalek::Signer;
275+ use rsa::{
276+ pkcs1::DecodeRsaPrivateKey,
277+ pkcs8::{AssociatedOid, ObjectIdentifier},
278+ PaddingScheme, RsaPrivateKey,
279+ };
280+ use sha1::{digest::Output, Sha1};
281+ use sha2::{digest::Digest, Sha256};
282+
283+ use crate::{Error, Result};
284+
285+ pub trait SigningKey {
286+ type Hasher: Digest + AssociatedOid + io::Write;
287+
288+ fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>>;
289+
290+ fn hasher(&self) -> Self::Hasher {
291+ Self::Hasher::new()
292+ }
293+
294+ fn algorithm(&self) -> Algorithm;
295+ }
296+
297+ #[derive(Debug)]
298+ pub struct RsaKey<T> {
299+ inner: RsaPrivateKey,
300+ padding: PhantomData<T>,
301+ }
302+
303+ impl<T: Digest + AssociatedOid + io::Write> RsaKey<T> {
304+ /// Creates a new RSA private key from a PKCS1 PEM string.
305+ pub fn from_rsa_pkcs1_pem(private_key_pem: &str) -> Result<Self> {
306+ let inner = RsaPrivateKey::from_pkcs1_pem(private_key_pem)
307+ .map_err(|err| Error::CryptoError(err.to_string()))?;
308+
309+ Ok(RsaKey {
310+ inner,
311+ padding: PhantomData,
312+ })
313+ }
314+
315+ /// Creates a new RSA private key from a PKCS1 binary slice.
316+ pub fn from_rsa_pkcs1_der(private_key_bytes: &[u8]) -> Result<Self> {
317+ let inner = RsaPrivateKey::from_pkcs1_der(private_key_bytes)
318+ .map_err(|err| Error::CryptoError(err.to_string()))?;
319+
320+ Ok(RsaKey {
321+ inner,
322+ padding: PhantomData,
323+ })
324+ }
325+ }
326+
327+ impl SigningKey for RsaKey<Sha1> {
328+ type Hasher = Sha1;
329+
330+ fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> {
331+ self.inner
332+ .sign(PaddingScheme::new_pkcs1v15_sign::<Self::Hasher>(), data)
333+ .map_err(|err| Error::CryptoError(err.to_string()))
334+ }
335+
336+ fn algorithm(&self) -> Algorithm {
337+ Algorithm::RsaSha1
338+ }
339+ }
340+
341+ impl SigningKey for RsaKey<Sha256> {
342+ type Hasher = Sha256;
343+
344+ fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> {
345+ self.inner
346+ .sign(PaddingScheme::new_pkcs1v15_sign::<Self::Hasher>(), data)
347+ .map_err(|err| Error::CryptoError(err.to_string()))
348+ }
349+
350+ fn algorithm(&self) -> Algorithm {
351+ Algorithm::RsaSha256
352+ }
353+ }
354+
355+ pub struct Ed25519Key {
356+ inner: ed25519_dalek::Keypair,
357+ }
358+
359+ impl Ed25519Key {
360+ /// Creates an Ed25519 private key
361+ pub fn from_bytes(public_key_bytes: &[u8], private_key_bytes: &[u8]) -> crate::Result<Self> {
362+ Ok(Self {
363+ inner: ed25519_dalek::Keypair {
364+ public: ed25519_dalek::PublicKey::from_bytes(public_key_bytes)
365+ .map_err(|err| Error::CryptoError(err.to_string()))?,
366+ secret: ed25519_dalek::SecretKey::from_bytes(private_key_bytes)
367+ .map_err(|err| Error::CryptoError(err.to_string()))?,
368+ },
369+ })
370+ }
371+ }
372+
373+ impl SigningKey for Ed25519Key {
374+ type Hasher = Sha256;
375+
376+ fn sign(&self, data: &Output<Self::Hasher>) -> Result<Vec<u8>> {
377+ Ok(self.inner.sign(data.as_ref()).to_bytes().to_vec())
378+ }
379+
380+ fn algorithm(&self) -> Algorithm {
381+ Algorithm::Ed25519Sha256
382+ }
383+ }
384+
385+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
386+ #[repr(u64)]
387+ pub enum HashAlgorithm {
388+ Sha1 = R_HASH_SHA1,
389+ Sha256 = R_HASH_SHA256,
390+ }
391+
392+ impl TryFrom<&ObjectIdentifier> for HashAlgorithm {
393+ type Error = Error;
394+
395+ fn try_from(oid: &ObjectIdentifier) -> Result<Self> {
396+ match oid {
397+ oid if oid == &Sha256::OID => Ok(HashAlgorithm::Sha256),
398+ oid if oid == &Sha1::OID => Ok(HashAlgorithm::Sha1),
399+ _ => Err(Error::CryptoError("Unsupported hash algorithm".to_string())),
400+ }
401+ }
402+ }
403+
404+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
405+ pub enum Algorithm {
406+ RsaSha1,
407+ RsaSha256,
408+ Ed25519Sha256,
409+ }
410+
411+ pub(crate) const R_HASH_SHA1: u64 = 0x01;
412+ pub(crate) const R_HASH_SHA256: u64 = 0x02;
413 diff --git a/src/common/message.rs b/src/common/message.rs
414index f5793b8..fe144c3 100644
415--- a/src/common/message.rs
416+++ b/src/common/message.rs
417 @@ -12,11 +12,7 @@ use mail_parser::{parsers::MessageStream, HeaderValue};
418 use sha1::Sha1;
419 use sha2::Sha256;
420
421- use crate::{
422- arc,
423- dkim::{self, HashAlgorithm},
424- AuthenticatedMessage,
425- };
426+ use crate::{arc, common::crypto::HashAlgorithm, dkim, AuthenticatedMessage};
427
428 use super::headers::{AuthenticatedHeader, Header, HeaderParser};
429
430 diff --git a/src/common/mod.rs b/src/common/mod.rs
431index 8ba3e43..bdb338d 100644
432--- a/src/common/mod.rs
433+++ b/src/common/mod.rs
434 @@ -10,6 +10,7 @@
435
436 pub mod auth_results;
437 pub mod base32;
438+ pub mod crypto;
439 pub mod headers;
440 pub mod lru;
441 pub mod message;
442 diff --git a/src/common/verify.rs b/src/common/verify.rs
443index e4a075e..b88f716 100644
444--- a/src/common/verify.rs
445+++ b/src/common/verify.rs
446 @@ -13,7 +13,8 @@ use sha1::Sha1;
447 use sha2::Sha256;
448
449 use crate::{
450- dkim::{Algorithm, DomainKey, PublicKey},
451+ common::crypto::Algorithm,
452+ dkim::{DomainKey, PublicKey},
453 Error,
454 };
455
456 diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs
457index 11b5fe4..c1832f1 100644
458--- a/src/dkim/mod.rs
459+++ b/src/dkim/mod.rs
460 @@ -13,7 +13,12 @@ use std::borrow::Cow;
461 use rsa::RsaPublicKey;
462
463 use crate::{
464- arc::Set, common::verify::VerifySignature, ArcOutput, DkimOutput, DkimResult, Error, Version,
465+ arc::Set,
466+ common::{
467+ crypto::{Algorithm, HashAlgorithm},
468+ verify::VerifySignature,
469+ },
470+ ArcOutput, DkimOutput, DkimResult, Error, Version,
471 };
472
473 pub mod canonicalize;
474 @@ -28,20 +33,6 @@ pub enum Canonicalization {
475 Simple,
476 }
477
478- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
479- #[repr(u64)]
480- pub enum HashAlgorithm {
481- Sha1 = R_HASH_SHA1,
482- Sha256 = R_HASH_SHA256,
483- }
484-
485- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
486- pub enum Algorithm {
487- RsaSha1,
488- RsaSha256,
489- Ed25519Sha256,
490- }
491-
492 #[derive(Debug, PartialEq, Eq, Clone, Default)]
493 pub struct Signature<'x> {
494 pub(crate) v: u32,
495 @@ -96,8 +87,6 @@ pub struct Atps {
496 pub(crate) d: Option<String>,
497 }
498
499- pub(crate) const R_HASH_SHA1: u64 = 0x01;
500- pub(crate) const R_HASH_SHA256: u64 = 0x02;
501 pub(crate) const R_SVC_ALL: u64 = 0x04;
502 pub(crate) const R_SVC_EMAIL: u64 = 0x08;
503 pub(crate) const R_FLAG_TESTING: u64 = 0x10;
504 diff --git a/src/dkim/parse.rs b/src/dkim/parse.rs
505index f07345c..ee13d43 100644
506--- a/src/dkim/parse.rs
507+++ b/src/dkim/parse.rs
508 @@ -479,12 +479,14 @@ mod test {
509 use rsa::{pkcs8::DecodePublicKey, RsaPublicKey};
510
511 use crate::{
512- common::parse::TxtRecordParser,
513+ common::{
514+ crypto::{Algorithm, R_HASH_SHA1, R_HASH_SHA256},
515+ parse::TxtRecordParser,
516+ },
517 dkim::{
518- Algorithm, Canonicalization, DomainKey, DomainKeyReport, PublicKey, Signature, Version,
519- RR_DNS, RR_EXPIRATION, RR_OTHER, RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG,
520- RR_VERIFICATION, R_FLAG_MATCH_DOMAIN, R_FLAG_TESTING, R_HASH_SHA1, R_HASH_SHA256,
521- R_SVC_ALL, R_SVC_EMAIL,
522+ Canonicalization, DomainKey, DomainKeyReport, PublicKey, Signature, Version, RR_DNS,
523+ RR_EXPIRATION, RR_OTHER, RR_POLICY, RR_SIGNATURE, RR_UNKNOWN_TAG, RR_VERIFICATION,
524+ R_FLAG_MATCH_DOMAIN, R_FLAG_TESTING, R_SVC_ALL, R_SVC_EMAIL,
525 },
526 };
527
528 diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs
529index 0c33add..cb77dde 100644
530--- a/src/dkim/sign.rs
531+++ b/src/dkim/sign.rs
532 @@ -8,45 +8,13 @@
533 * except according to those terms.
534 */
535
536- use std::{borrow::Cow, io, time::SystemTime};
537+ use std::{borrow::Cow, time::SystemTime};
538
539- use ed25519_dalek::Signer;
540 use mail_builder::encoders::base64::base64_encode;
541- use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::AssociatedOid, PaddingScheme, RsaPrivateKey};
542- use sha1::Sha1;
543- use sha2::{Digest, Sha256};
544+ use sha1::Digest;
545
546- use crate::{Error, PrivateKey};
547-
548- use super::{Algorithm, Canonicalization, HashAlgorithm, Signature};
549-
550- impl PrivateKey {
551- /// Creates a new RSA private key from a PKCS1 PEM string.
552- pub fn from_rsa_pkcs1_pem(private_key_pem: &str) -> crate::Result<Self> {
553- Ok(PrivateKey::Rsa(
554- RsaPrivateKey::from_pkcs1_pem(private_key_pem)
555- .map_err(|err| Error::CryptoError(err.to_string()))?,
556- ))
557- }
558-
559- /// Creates a new RSA private key from a PKCS1 binary slice.
560- pub fn from_rsa_pkcs1_der(private_key_bytes: &[u8]) -> crate::Result<Self> {
561- Ok(PrivateKey::Rsa(
562- RsaPrivateKey::from_pkcs1_der(private_key_bytes)
563- .map_err(|err| Error::CryptoError(err.to_string()))?,
564- ))
565- }
566-
567- /// Creates an Ed25519 private key
568- pub fn from_ed25519(public_key_bytes: &[u8], private_key_bytes: &[u8]) -> crate::Result<Self> {
569- Ok(PrivateKey::Ed25519(ed25519_dalek::Keypair {
570- public: ed25519_dalek::PublicKey::from_bytes(public_key_bytes)
571- .map_err(|err| Error::CryptoError(err.to_string()))?,
572- secret: ed25519_dalek::SecretKey::from_bytes(private_key_bytes)
573- .map_err(|err| Error::CryptoError(err.to_string()))?,
574- }))
575- }
576- }
577+ use super::{Canonicalization, HashAlgorithm, Signature};
578+ use crate::{common::crypto::SigningKey, Error};
579
580 impl<'x> Signature<'x> {
581 /// Creates a new DKIM signature.
582 @@ -59,40 +27,28 @@ impl<'x> Signature<'x> {
583
584 /// Signs a message.
585 #[inline(always)]
586- pub fn sign(mut self, message: &'x [u8], with_key: &PrivateKey) -> crate::Result<Self> {
587+ pub fn sign(mut self, message: &'x [u8], with_key: &impl SigningKey) -> crate::Result<Self> {
588 if !self.d.is_empty() && !self.s.is_empty() && !self.h.is_empty() {
589 let now = SystemTime::now()
590 .duration_since(SystemTime::UNIX_EPOCH)
591 .map(|d| d.as_secs())
592 .unwrap_or(0);
593- match (self.a, with_key) {
594- (Algorithm::RsaSha256, PrivateKey::Rsa(_)) => {
595- self.sign_::<Sha256>(message, with_key, now)
596- }
597- (Algorithm::RsaSha1, PrivateKey::Rsa(_)) => {
598- self.sign_::<Sha1>(message, with_key, now)
599- }
600- (_, PrivateKey::Ed25519(_)) => {
601- self.a = Algorithm::Ed25519Sha256;
602- self.sign_::<Sha256>(message, with_key, now)
603- }
604- (_, PrivateKey::Rsa(_)) => {
605- self.a = Algorithm::RsaSha256;
606- self.sign_::<Sha256>(message, with_key, now)
607- }
608- _ => Err(Error::IncompatibleAlgorithms),
609- }
610+
611+ self.a = with_key.algorithm();
612+ self.sign_(message, with_key, now)
613 } else {
614 Err(Error::MissingParameters)
615 }
616 }
617
618- fn sign_<T>(mut self, message: &'x [u8], with_key: &PrivateKey, now: u64) -> crate::Result<Self>
619- where
620- T: Digest + AssociatedOid + io::Write,
621- {
622- let mut body_hasher = T::new();
623- let mut header_hasher = T::new();
624+ fn sign_(
625+ mut self,
626+ message: &'x [u8],
627+ with_key: &impl SigningKey,
628+ now: u64,
629+ ) -> crate::Result<Self> {
630+ let mut body_hasher = with_key.hasher();
631+ let mut header_hasher = with_key.hasher();
632
633 // Canonicalize headers and body
634 let (body_len, signed_headers) =
635 @@ -115,18 +71,7 @@ impl<'x> Signature<'x> {
636 self.write(&mut header_hasher, false)?;
637
638 // Sign
639- let b = match with_key {
640- PrivateKey::Rsa(private_key) => private_key
641- .sign(
642- PaddingScheme::new_pkcs1v15_sign::<T>(),
643- &header_hasher.finalize(),
644- )
645- .map_err(|err| Error::CryptoError(err.to_string()))?,
646- PrivateKey::Ed25519(key_pair) => {
647- key_pair.sign(&header_hasher.finalize()).to_bytes().to_vec()
648- }
649- PrivateKey::None => return Err(Error::MissingParameters),
650- };
651+ let b = with_key.sign(&header_hasher.finalize())?;
652
653 // Encode
654 self.b = base64_encode(&b)?;
655 @@ -176,12 +121,6 @@ impl<'x> Signature<'x> {
656 self
657 }
658
659- /// Sets the algorithm to use (must be compatible with the private key provided).
660- pub fn algorithm(mut self, algorithm: Algorithm) -> Self {
661- self.a = algorithm;
662- self
663- }
664-
665 /// Include the body length in the signature.
666 pub fn body_length(mut self, body_length: bool) -> Self {
667 self.l = u64::from(body_length);
668 @@ -216,9 +155,12 @@ mod test {
669 use trust_dns_resolver::proto::op::ResponseCode;
670
671 use crate::{
672- common::parse::TxtRecordParser,
673+ common::{
674+ crypto::{Ed25519Key, RsaKey},
675+ parse::TxtRecordParser,
676+ },
677 dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport, HashAlgorithm, Signature},
678- AuthenticatedMessage, DkimOutput, DkimResult, PrivateKey, Resolver,
679+ AuthenticatedMessage, DkimOutput, DkimResult, Resolver,
680 };
681
682 const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
683 @@ -251,12 +193,12 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
684
685 #[test]
686 fn dkim_sign() {
687- let pk = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
688+ let pk = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
689 let signature = Signature::new()
690 .headers(["From", "To", "Subject"])
691 .domain("stalw.art")
692 .selector("default")
693- .sign_::<Sha256>(
694+ .sign_(
695 concat!(
696 "From: hello@stalw.art\r\n",
697 "To: dkim@stalw.art\r\n",
698 @@ -307,8 +249,8 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
699 );
700
701 // Create private keys
702- let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
703- let pk_ed = PrivateKey::from_ed25519(
704+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
705+ let pk_ed = Ed25519Key::from_bytes(
706 &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(),
707 &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
708 )
709 @@ -423,7 +365,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
710 .selector("default")
711 .expiration(12345)
712 .reporting(true)
713- .sign_::<Sha256>(message.as_bytes(), &pk_rsa, 12345)
714+ .sign_(message.as_bytes(), &pk_rsa, 12345)
715 .unwrap(),
716 message,
717 Err(super::Error::SignatureExpired),
718 @@ -443,7 +385,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
719 .selector("default")
720 .atps("example.com")
721 .atpsh(HashAlgorithm::Sha256)
722- .sign_::<Sha256>(message.as_bytes(), &pk_rsa, 12345)
723+ .sign_(message.as_bytes(), &pk_rsa, 12345)
724 .unwrap(),
725 message,
726 Err(super::Error::DNSRecordNotFound(ResponseCode::NXDomain)),
727 @@ -464,7 +406,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
728 .selector("default")
729 .atps("example.com")
730 .atpsh(HashAlgorithm::Sha256)
731- .sign_::<Sha256>(message.as_bytes(), &pk_rsa, 12345)
732+ .sign_(message.as_bytes(), &pk_rsa, 12345)
733 .unwrap(),
734 message,
735 Ok(()),
736 @@ -484,7 +426,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
737 .domain("example.com")
738 .selector("default")
739 .atps("example.com")
740- .sign_::<Sha256>(message.as_bytes(), &pk_rsa, 12345)
741+ .sign_(message.as_bytes(), &pk_rsa, 12345)
742 .unwrap(),
743 message,
744 Ok(()),
745 diff --git a/src/lib.rs b/src/lib.rs
746index b655369..3d90e4a 100644
747--- a/src/lib.rs
748+++ b/src/lib.rs
749 @@ -60,7 +60,7 @@
750 //!
751 //! ```rust
752 //! // Sign an e-mail message using RSA-SHA256
753- //! let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
754+ //! let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
755 //! let signature_rsa = Signature::new()
756 //! .headers(["From", "To", "Subject"])
757 //! .domain("example.com")
758 @@ -69,7 +69,7 @@
759 //! .unwrap();
760 //!
761 //! // Sign an e-mail message using ED25519-SHA256
762- //! let pk_ed = PrivateKey::from_ed25519(
763+ //! let pk_ed = Ed25519Key::from_bytes(
764 //! &base64_decode(ED25519_PUBLIC_KEY.as_bytes()).unwrap(),
765 //! &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
766 //! )
767 @@ -78,6 +78,7 @@
768 //! .headers(["From", "To", "Subject"])
769 //! .domain("example.com")
770 //! .selector("default-ed")
771+ //! .algorithm(Algorithm::Ed25519Sha256)
772 //! .sign(RFC5322_MESSAGE.as_bytes(), &pk_ed)
773 //! .unwrap();
774 //!
775 @@ -127,7 +128,7 @@
776 //! // Seal message
777 //! if arc_result.can_be_sealed() {
778 //! // Seal the e-mail message using RSA-SHA256
779- //! let pk_rsa = PrivateKey::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
780+ //! let pk_rsa = RsaKey::<Sha256>::from_rsa_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
781 //! let arc_set = ArcSet::new(&auth_results)
782 //! .domain("example.org")
783 //! .selector("default")
784 @@ -262,10 +263,9 @@ use std::{
785 };
786
787 use arc::Set;
788- use common::{headers::Header, lru::LruCache};
789- use dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport, HashAlgorithm};
790+ use common::{crypto::HashAlgorithm, headers::Header, lru::LruCache};
791+ use dkim::{Atps, Canonicalization, DomainKey, DomainKeyReport};
792 use dmarc::Dmarc;
793- use rsa::RsaPrivateKey;
794 use spf::{Macro, Spf};
795 use trust_dns_resolver::{proto::op::ResponseCode, TokioAsyncResolver};
796
797 @@ -277,12 +277,6 @@ pub mod report;
798 pub mod spf;
799
800 #[derive(Debug)]
801- pub enum PrivateKey {
802- Rsa(RsaPrivateKey),
803- Ed25519(ed25519_dalek::Keypair),
804- None,
805- }
806- #[derive(Debug)]
807 pub struct Resolver {
808 pub(crate) resolver: TokioAsyncResolver,
809 pub(crate) cache_txt: LruCache<String, Txt>,