Commit
+394 -0 +/-7 browse
1 | diff --git a/Cargo.toml b/Cargo.toml |
2 | index 28914eb..e02e2ae 100644 |
3 | --- a/Cargo.toml |
4 | +++ b/Cargo.toml |
5 | @@ -29,7 +29,9 @@ mail-parser = { version = "0.8", git = "https://github.com/stalwartlabs/mail-par |
6 | mail-builder = { version = "0.2.5", git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] } |
7 | parking_lot = "0.12.0" |
8 | quick-xml = "0.27.1" |
9 | + ring = { version = "0.16.20", optional = true } |
10 | rsa = { version = "0.7.0", optional = true } |
11 | + rustls-pemfile = { version = "1", optional = true } |
12 | serde = { version = "1.0", features = ["derive"] } |
13 | serde_json = "1.0" |
14 | sha1 = { version = "0.10", features = ["oid"], optional = true } |
15 | @@ -39,3 +41,4 @@ zip = "0.6.3" |
16 | |
17 | [dev-dependencies] |
18 | tokio = { version = "1.16", features = ["net", "io-util", "time", "rt-multi-thread", "macros"] } |
19 | + rustls-pemfile = "1" |
20 | diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs |
21 | index beda3a6..b7d32cb 100644 |
22 | --- a/examples/arc_seal.rs |
23 | +++ b/examples/arc_seal.rs |
24 | @@ -55,7 +55,11 @@ async fn main() { |
25 | // Seal message |
26 | if arc_result.can_be_sealed() { |
27 | // Seal the e-mail message using RSA-SHA256 |
28 | + #[cfg(feature = "rust-crypto")] |
29 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
30 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
31 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
32 | + |
33 | let arc_set = ArcSealer::from_key(pk_rsa) |
34 | .domain("example.org") |
35 | .selector("default") |
36 | diff --git a/examples/dkim_sign.rs b/examples/dkim_sign.rs |
37 | index edf9fa0..bb96b6e 100644 |
38 | --- a/examples/dkim_sign.rs |
39 | +++ b/examples/dkim_sign.rs |
40 | @@ -46,7 +46,15 @@ I'm going to need those TPS reports ASAP. So, if you could do that, that'd be gr |
41 | |
42 | fn main() { |
43 | // Sign an e-mail message using RSA-SHA256 |
44 | + #[cfg(feature = "rust-crypto")] |
45 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
46 | + #[cfg(all( |
47 | + feature = "ring", |
48 | + feature = "rustls-pemfile", |
49 | + not(feature = "rust-crypto") |
50 | + ))] |
51 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
52 | + |
53 | let signature_rsa = DkimSigner::from_key(pk_rsa) |
54 | .domain("example.com") |
55 | .selector("default") |
56 | @@ -55,11 +63,19 @@ fn main() { |
57 | .unwrap(); |
58 | |
59 | // Sign an e-mail message using ED25519-SHA256 |
60 | + #[cfg(feature = "rust-crypto")] |
61 | let pk_ed = Ed25519Key::from_bytes( |
62 | &base64_decode(ED25519_PUBLIC_KEY.as_bytes()).unwrap(), |
63 | &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
64 | ) |
65 | .unwrap(); |
66 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
67 | + let pk_ed = Ed25519Key::from_seed_and_public_key( |
68 | + &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
69 | + &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(), |
70 | + ) |
71 | + .unwrap(); |
72 | + |
73 | let signature_ed = DkimSigner::from_key(pk_ed) |
74 | .domain("example.com") |
75 | .selector("default-ed") |
76 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
77 | index 34ead1c..bc44bbe 100644 |
78 | --- a/src/arc/seal.rs |
79 | +++ b/src/arc/seal.rs |
80 | @@ -230,6 +230,10 @@ mod test { |
81 | const ED25519_PUBLIC_KEY: &str = |
82 | "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="; |
83 | |
84 | + #[cfg(any( |
85 | + feature = "rust-crypto", |
86 | + all(feature = "ring", feature = "rustls-pemfile") |
87 | + ))] |
88 | #[tokio::test] |
89 | async fn arc_seal() { |
90 | let message = concat!( |
91 | @@ -263,7 +267,10 @@ mod test { |
92 | let pk_ed_private = base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(); |
93 | |
94 | // Create DKIM-signed message |
95 | + #[cfg(feature = "rust-crypto")] |
96 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
97 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
98 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
99 | let mut raw_message = DkimSigner::from_key(pk_rsa) |
100 | .domain("manchego.org") |
101 | .selector("rsa") |
102 | @@ -275,14 +282,20 @@ mod test { |
103 | |
104 | // Verify and seal the message 50 times |
105 | for _ in 0..25 { |
106 | + #[cfg(feature = "rust-crypto")] |
107 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
108 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
109 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
110 | |
111 | raw_message = arc_verify_and_seal( |
112 | &resolver, |
113 | &raw_message, |
114 | "scamorza.org", |
115 | "ed", |
116 | + #[cfg(feature = "rust-crypto")] |
117 | Ed25519Key::from_bytes(&pk_ed_public, &pk_ed_private).unwrap(), |
118 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
119 | + Ed25519Key::from_seed_and_public_key(&pk_ed_private, &pk_ed_public).unwrap(), |
120 | ) |
121 | .await; |
122 | raw_message = |
123 | diff --git a/src/common/crypto/mod.rs b/src/common/crypto/mod.rs |
124 | index ad25c26..caadf17 100644 |
125 | --- a/src/common/crypto/mod.rs |
126 | +++ b/src/common/crypto/mod.rs |
127 | @@ -12,6 +12,13 @@ pub use rust_crypto::{Ed25519Key, RsaKey}; |
128 | #[cfg(feature = "rust-crypto")] |
129 | pub(crate) use rust_crypto::{Ed25519PublicKey, RsaPublicKey}; |
130 | |
131 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
132 | + mod ring_impls; |
133 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
134 | + pub use ring_impls::{Ed25519Key, RsaKey}; |
135 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
136 | + pub(crate) use ring_impls::{Ed25519PublicKey, RsaPublicKey}; |
137 | + |
138 | pub trait SigningKey { |
139 | type Hasher: HashImpl; |
140 | |
141 | @@ -51,6 +58,10 @@ impl VerifyingKeyType { |
142 | Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes), |
143 | #[cfg(feature = "rust-crypto")] |
144 | Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes), |
145 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
146 | + Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes), |
147 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
148 | + Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes), |
149 | } |
150 | } |
151 | } |
152 | @@ -93,12 +104,27 @@ impl HashAlgorithm { |
153 | data.write(&mut hasher); |
154 | HashOutput::RustCryptoSha256(hasher.finalize()) |
155 | } |
156 | + #[cfg(all(feature = "ring", not(feature = "sha1")))] |
157 | + Self::Sha1 => { |
158 | + let mut hasher = |
159 | + ring::digest::Context::new(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY); |
160 | + data.write(&mut hasher); |
161 | + HashOutput::Ring(hasher.finish()) |
162 | + } |
163 | + #[cfg(all(feature = "ring", not(feature = "sha2")))] |
164 | + Self::Sha256 => { |
165 | + let mut hasher = ring::digest::Context::new(&ring::digest::SHA256); |
166 | + data.write(&mut hasher); |
167 | + HashOutput::Ring(hasher.finish()) |
168 | + } |
169 | } |
170 | } |
171 | } |
172 | |
173 | #[non_exhaustive] |
174 | pub enum HashOutput { |
175 | + #[cfg(feature = "ring")] |
176 | + Ring(ring::digest::Digest), |
177 | #[cfg(feature = "sha1")] |
178 | RustCryptoSha1(Output<sha1::Sha1>), |
179 | #[cfg(feature = "sha2")] |
180 | @@ -108,6 +134,8 @@ pub enum HashOutput { |
181 | impl AsRef<[u8]> for HashOutput { |
182 | fn as_ref(&self) -> &[u8] { |
183 | match self { |
184 | + #[cfg(feature = "ring")] |
185 | + Self::Ring(output) => output.as_ref(), |
186 | #[cfg(feature = "sha1")] |
187 | Self::RustCryptoSha1(output) => output.as_ref(), |
188 | #[cfg(feature = "sha2")] |
189 | diff --git a/src/common/crypto/ring_impls.rs b/src/common/crypto/ring_impls.rs |
190 | new file mode 100644 |
191 | index 0000000..7c454b6 |
192 | --- /dev/null |
193 | +++ b/src/common/crypto/ring_impls.rs |
194 | @@ -0,0 +1,287 @@ |
195 | + use std::marker::PhantomData; |
196 | + |
197 | + use ring::digest::{Context, SHA1_FOR_LEGACY_USE_ONLY, SHA256}; |
198 | + use ring::rand::SystemRandom; |
199 | + use ring::signature::{ |
200 | + Ed25519KeyPair, RsaKeyPair, UnparsedPublicKey, ED25519, |
201 | + RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY, RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY, |
202 | + RSA_PKCS1_SHA256, |
203 | + }; |
204 | + |
205 | + use crate::{ |
206 | + common::headers::{Writable, Writer}, |
207 | + dkim::Canonicalization, |
208 | + Error, Result, |
209 | + }; |
210 | + |
211 | + use super::{Algorithm, HashContext, HashImpl, HashOutput, Sha1, Sha256, SigningKey, VerifyingKey}; |
212 | + |
213 | + #[derive(Debug)] |
214 | + pub struct RsaKey<T> { |
215 | + inner: RsaKeyPair, |
216 | + rng: SystemRandom, |
217 | + padding: PhantomData<T>, |
218 | + } |
219 | + |
220 | + impl<T: HashImpl> RsaKey<T> { |
221 | + #[cfg(feature = "rustls-pemfile")] |
222 | + pub fn from_pkcs8_pem(pkcs8_pem: &str) -> Result<Self> { |
223 | + let item = rustls_pemfile::read_one(&mut pkcs8_pem.as_bytes()) |
224 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
225 | + |
226 | + let pkcs8_der = match item { |
227 | + Some(rustls_pemfile::Item::PKCS8Key(key)) => key, |
228 | + _ => return Err(Error::CryptoError("No PKCS8 key found in PEM".to_string())), |
229 | + }; |
230 | + |
231 | + Self::from_pkcs8_der(&pkcs8_der) |
232 | + } |
233 | + |
234 | + /// Creates a new RSA private key from PKCS8 DER-encoded bytes. |
235 | + pub fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self> { |
236 | + Ok(Self { |
237 | + inner: RsaKeyPair::from_pkcs8(pkcs8_der) |
238 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
239 | + rng: SystemRandom::new(), |
240 | + padding: PhantomData, |
241 | + }) |
242 | + } |
243 | + |
244 | + #[cfg(feature = "rustls-pemfile")] |
245 | + pub fn from_rsa_pem(rsa_pem: &str) -> Result<Self> { |
246 | + let item = rustls_pemfile::read_one(&mut rsa_pem.as_bytes()) |
247 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
248 | + |
249 | + let rsa_der = match item { |
250 | + Some(rustls_pemfile::Item::RSAKey(key)) => key, |
251 | + _ => return Err(Error::CryptoError("No RSA key found in PEM".to_string())), |
252 | + }; |
253 | + |
254 | + Self::from_der(&rsa_der) |
255 | + } |
256 | + |
257 | + /// Creates a new RSA private key from a PKCS1 binary slice. |
258 | + pub fn from_der(der: &[u8]) -> Result<Self> { |
259 | + Ok(Self { |
260 | + inner: RsaKeyPair::from_der(der).map_err(|err| Error::CryptoError(err.to_string()))?, |
261 | + rng: SystemRandom::new(), |
262 | + padding: PhantomData, |
263 | + }) |
264 | + } |
265 | + } |
266 | + |
267 | + impl SigningKey for RsaKey<Sha256> { |
268 | + type Hasher = Sha256; |
269 | + |
270 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>> { |
271 | + let mut data = Vec::with_capacity(256); |
272 | + input.write(&mut data); |
273 | + |
274 | + let mut signature = vec![0; self.inner.public_modulus_len()]; |
275 | + self.inner |
276 | + .sign(&RSA_PKCS1_SHA256, &self.rng, &data, &mut signature) |
277 | + .map_err(|err| Error::CryptoError(err.to_string()))?; |
278 | + Ok(signature) |
279 | + } |
280 | + |
281 | + fn algorithm(&self) -> Algorithm { |
282 | + Algorithm::RsaSha256 |
283 | + } |
284 | + } |
285 | + |
286 | + pub struct Ed25519Key { |
287 | + inner: Ed25519KeyPair, |
288 | + } |
289 | + |
290 | + impl Ed25519Key { |
291 | + pub fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self> { |
292 | + Ok(Self { |
293 | + inner: Ed25519KeyPair::from_pkcs8(pkcs8_der) |
294 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
295 | + }) |
296 | + } |
297 | + |
298 | + pub fn from_pkcs8_maybe_unchecked_der(pkcs8_der: &[u8]) -> Result<Self> { |
299 | + Ok(Self { |
300 | + inner: Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8_der) |
301 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
302 | + }) |
303 | + } |
304 | + |
305 | + pub fn from_seed_and_public_key(seed: &[u8], public_key: &[u8]) -> Result<Self> { |
306 | + Ok(Self { |
307 | + inner: Ed25519KeyPair::from_seed_and_public_key(seed, public_key) |
308 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
309 | + }) |
310 | + } |
311 | + } |
312 | + |
313 | + impl SigningKey for Ed25519Key { |
314 | + type Hasher = Sha256; |
315 | + |
316 | + fn sign(&self, input: impl Writable) -> Result<Vec<u8>> { |
317 | + let mut data = Vec::with_capacity(256); |
318 | + input.write(&mut data); |
319 | + Ok(self.inner.sign(&data).as_ref().to_vec()) |
320 | + } |
321 | + |
322 | + fn algorithm(&self) -> Algorithm { |
323 | + Algorithm::Ed25519Sha256 |
324 | + } |
325 | + } |
326 | + |
327 | + pub(crate) struct RsaPublicKey { |
328 | + sha1: UnparsedPublicKey<Vec<u8>>, |
329 | + sha2: UnparsedPublicKey<Vec<u8>>, |
330 | + } |
331 | + |
332 | + impl RsaPublicKey { |
333 | + pub(crate) fn verifying_key_from_bytes( |
334 | + bytes: &[u8], |
335 | + ) -> Result<Box<dyn VerifyingKey + Send + Sync>> { |
336 | + let key = try_strip_rsa_prefix(bytes); |
337 | + Ok(Box::new(Self { |
338 | + sha1: UnparsedPublicKey::new( |
339 | + &RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY, |
340 | + key.to_vec(), |
341 | + ), |
342 | + sha2: UnparsedPublicKey::new( |
343 | + &RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY, |
344 | + key.to_vec(), |
345 | + ), |
346 | + })) |
347 | + } |
348 | + } |
349 | + |
350 | + /// Try to strip an ASN.1 DER-encoded RSA public key prefix |
351 | + /// |
352 | + /// Returns the original slice if the prefix is not found. |
353 | + fn try_strip_rsa_prefix(bytes: &[u8]) -> &[u8] { |
354 | + let orig = bytes; |
355 | + if bytes[0] != DER_SEQUENCE_TAG { |
356 | + return orig; |
357 | + } |
358 | + |
359 | + let (_, bytes) = decode_multi_byte_len(&bytes[1..]); |
360 | + if bytes[0] != DER_SEQUENCE_TAG { |
361 | + return orig; |
362 | + } |
363 | + |
364 | + let (byte_len, bytes) = decode_multi_byte_len(&bytes[1..]); |
365 | + if bytes[0] != DER_OBJECT_ID_TAG || byte_len != 13 { |
366 | + return orig; |
367 | + } |
368 | + |
369 | + let bytes = &bytes[13..]; // skip the RSA encryption OID |
370 | + if bytes[0] != DER_BIT_STRING_TAG { |
371 | + return orig; |
372 | + } |
373 | + |
374 | + let (_, bytes) = decode_multi_byte_len(&bytes[1..]); |
375 | + &bytes[1..] // skip the unused bits byte |
376 | + } |
377 | + |
378 | + fn decode_multi_byte_len(bytes: &[u8]) -> (usize, &[u8]) { |
379 | + if bytes[0] & 0x80 == 0 { |
380 | + return (bytes[0] as usize, &bytes[1..]); |
381 | + } |
382 | + |
383 | + let len_len = (bytes[0] & 0x7f) as usize; |
384 | + let mut len = 0; |
385 | + for i in 0..len_len { |
386 | + len = (len << 8) | bytes[1 + i] as usize; |
387 | + } |
388 | + |
389 | + (len, &bytes[len_len + 1..]) |
390 | + } |
391 | + |
392 | + const DER_OBJECT_ID_TAG: u8 = 0x06; |
393 | + const DER_BIT_STRING_TAG: u8 = 0x03; |
394 | + const DER_SEQUENCE_TAG: u8 = 0x30; |
395 | + |
396 | + impl VerifyingKey for RsaPublicKey { |
397 | + fn verify<'a>( |
398 | + &self, |
399 | + headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
400 | + signature: &[u8], |
401 | + canonicalization: Canonicalization, |
402 | + algorithm: Algorithm, |
403 | + ) -> Result<()> { |
404 | + let mut data = Vec::with_capacity(256); |
405 | + canonicalization.canonicalize_headers(headers, &mut data); |
406 | + |
407 | + match algorithm { |
408 | + Algorithm::RsaSha256 => self |
409 | + .sha2 |
410 | + .verify(&data, signature) |
411 | + .map_err(|_| Error::FailedVerification), |
412 | + Algorithm::RsaSha1 => self |
413 | + .sha1 |
414 | + .verify(&data, signature) |
415 | + .map_err(|_| Error::FailedVerification), |
416 | + Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms), |
417 | + } |
418 | + } |
419 | + } |
420 | + |
421 | + pub(crate) struct Ed25519PublicKey { |
422 | + inner: UnparsedPublicKey<Vec<u8>>, |
423 | + } |
424 | + |
425 | + impl Ed25519PublicKey { |
426 | + pub(crate) fn verifying_key_from_bytes( |
427 | + bytes: &[u8], |
428 | + ) -> Result<Box<dyn VerifyingKey + Send + Sync>> { |
429 | + Ok(Box::new(Self { |
430 | + inner: UnparsedPublicKey::new(&ED25519, bytes.to_vec()), |
431 | + })) |
432 | + } |
433 | + } |
434 | + |
435 | + impl VerifyingKey for Ed25519PublicKey { |
436 | + fn verify<'a>( |
437 | + &self, |
438 | + headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>, |
439 | + signature: &[u8], |
440 | + canonicalization: Canonicalization, |
441 | + algorithm: Algorithm, |
442 | + ) -> Result<()> { |
443 | + if !matches!(algorithm, Algorithm::Ed25519Sha256) { |
444 | + return Err(Error::IncompatibleAlgorithms); |
445 | + } |
446 | + |
447 | + let mut data = Vec::with_capacity(256); |
448 | + canonicalization.canonicalize_headers(headers, &mut data); |
449 | + self.inner |
450 | + .verify(&data, signature) |
451 | + .map_err(|err| Error::CryptoError(err.to_string())) |
452 | + } |
453 | + } |
454 | + |
455 | + impl HashImpl for Sha1 { |
456 | + type Context = Context; |
457 | + |
458 | + fn hasher() -> Self::Context { |
459 | + Context::new(&SHA1_FOR_LEGACY_USE_ONLY) |
460 | + } |
461 | + } |
462 | + |
463 | + impl HashImpl for Sha256 { |
464 | + type Context = Context; |
465 | + |
466 | + fn hasher() -> Self::Context { |
467 | + Context::new(&SHA256) |
468 | + } |
469 | + } |
470 | + |
471 | + impl HashContext for Context { |
472 | + fn complete(self) -> HashOutput { |
473 | + HashOutput::Ring(self.finish()) |
474 | + } |
475 | + } |
476 | + |
477 | + impl Writer for Context { |
478 | + fn write(&mut self, data: &[u8]) { |
479 | + self.update(data); |
480 | + } |
481 | + } |
482 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
483 | index b9d6c27..3a68c24 100644 |
484 | --- a/src/dkim/sign.rs |
485 | +++ b/src/dkim/sign.rs |
486 | @@ -140,8 +140,15 @@ mod test { |
487 | const ED25519_PUBLIC_KEY: &str = |
488 | "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="; |
489 | |
490 | + #[cfg(any( |
491 | + feature = "rust-crypto", |
492 | + all(feature = "ring", feature = "rustls-pemfile") |
493 | + ))] |
494 | #[test] |
495 | fn dkim_sign() { |
496 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
497 | + let pk = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
498 | + #[cfg(feature = "rust-crypto")] |
499 | let pk = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
500 | let signature = DkimSigner::from_key(pk) |
501 | .domain("stalw.art") |
502 | @@ -160,6 +167,7 @@ mod test { |
503 | 311923920, |
504 | ) |
505 | .unwrap(); |
506 | + |
507 | assert_eq!( |
508 | concat!( |
509 | "dkim-signature:v=1; a=rsa-sha256; s=default; d=stalw.art; ", |
510 | @@ -177,6 +185,10 @@ mod test { |
511 | ); |
512 | } |
513 | |
514 | + #[cfg(any( |
515 | + feature = "rust-crypto", |
516 | + all(feature = "ring", feature = "rustls-pemfile") |
517 | + ))] |
518 | #[tokio::test] |
519 | async fn dkim_sign_verify() { |
520 | let message = concat!( |
521 | @@ -202,11 +214,18 @@ mod test { |
522 | ); |
523 | |
524 | // Create private keys |
525 | + #[cfg(feature = "rust-crypto")] |
526 | let pk_ed = Ed25519Key::from_bytes( |
527 | &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(), |
528 | &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
529 | ) |
530 | .unwrap(); |
531 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
532 | + let pk_ed = Ed25519Key::from_seed_and_public_key( |
533 | + &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
534 | + &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(), |
535 | + ) |
536 | + .unwrap(); |
537 | |
538 | // Create resolver |
539 | let resolver = Resolver::new_system_conf().unwrap(); |
540 | @@ -230,7 +249,10 @@ mod test { |
541 | } |
542 | |
543 | // Test RSA-SHA256 relaxed/relaxed |
544 | + #[cfg(feature = "rust-crypto")] |
545 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
546 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
547 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
548 | verify( |
549 | &resolver, |
550 | DkimSigner::from_key(pk_rsa) |
551 | @@ -260,7 +282,10 @@ mod test { |
552 | .await; |
553 | |
554 | // Test RSA-SHA256 simple/simple with duplicated headers |
555 | + #[cfg(feature = "rust-crypto")] |
556 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
557 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
558 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
559 | verify( |
560 | &resolver, |
561 | DkimSigner::from_key(pk_rsa) |
562 | @@ -283,7 +308,10 @@ mod test { |
563 | .await; |
564 | |
565 | // Test RSA-SHA256 simple/relaxed with fixed body length |
566 | + #[cfg(feature = "rust-crypto")] |
567 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
568 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
569 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
570 | verify( |
571 | &resolver, |
572 | DkimSigner::from_key(pk_rsa) |
573 | @@ -300,7 +328,10 @@ mod test { |
574 | .await; |
575 | |
576 | // Test AUID not matching domain |
577 | + #[cfg(feature = "rust-crypto")] |
578 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
579 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
580 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
581 | verify( |
582 | &resolver, |
583 | DkimSigner::from_key(pk_rsa) |
584 | @@ -316,7 +347,10 @@ mod test { |
585 | .await; |
586 | |
587 | // Test expired signature and reporting |
588 | + #[cfg(feature = "rust-crypto")] |
589 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
590 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
591 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
592 | let r = verify( |
593 | &resolver, |
594 | DkimSigner::from_key(pk_rsa) |
595 | @@ -337,7 +371,10 @@ mod test { |
596 | assert_eq!(r.as_deref(), Some("dkim-failures@example.com")); |
597 | |
598 | // Verify ATPS (failure) |
599 | + #[cfg(feature = "rust-crypto")] |
600 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
601 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
602 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
603 | verify( |
604 | &resolver, |
605 | DkimSigner::from_key(pk_rsa) |
606 | @@ -354,7 +391,10 @@ mod test { |
607 | .await; |
608 | |
609 | // Verify ATPS (success) |
610 | + #[cfg(feature = "rust-crypto")] |
611 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
612 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
613 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
614 | #[cfg(any(test, feature = "test"))] |
615 | resolver.txt_add( |
616 | "UN42N5XOV642KXRXRQIYANHCOUPGQL5LT4WTBKYT2IJFLBWODFDQ._atps.example.com.".to_string(), |
617 | @@ -377,7 +417,10 @@ mod test { |
618 | .await; |
619 | |
620 | // Verify ATPS (success - no hash) |
621 | + #[cfg(feature = "rust-crypto")] |
622 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
623 | + #[cfg(all(feature = "ring", not(feature = "rust-crypto")))] |
624 | + let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap(); |
625 | #[cfg(any(test, feature = "test"))] |
626 | resolver.txt_add( |
627 | "example.com._atps.example.com.".to_string(), |