Commit
Author: Dirkjan Ochtman [dirkjan@ochtman.nl]
Hash: bb58a33315adaea44a6c2d8e36fc7d88d8041abb
Timestamp: Wed, 14 Dec 2022 15:54:23 +0000 (1 year ago)

+394 -0 +/-7 browse
Add opt-in support for *ring*-based crypto
1diff --git a/Cargo.toml b/Cargo.toml
2index 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
21index 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
37index 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
77index 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
124index 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
190new file mode 100644
191index 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
483index 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(),