Commit
+238 -187 +/-15 browse
1 | diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs |
2 | index 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 |
29 | index 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 |
65 | index 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 |
80 | index 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 |
95 | index 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 |
110 | index 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 |
248 | index 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 |
267 | new file mode 100644 |
268 | index 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 |
414 | index 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 |
431 | index 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 |
443 | index 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 |
457 | index 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 |
505 | index 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 |
529 | index 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 |
746 | index 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>, |