1

I have a large amount of tests in my Rust code, and I require a RSA key pair for each of them. However, generating RSA key pairs is expensive and takes 3-4 seconds. I can reuse a single RSA key pair across all tests, but I'm not sure how to do that. At the moment, I'm generating an RSA key pair for each test separately.

Update: The tests are async tests and need to use the key pairs as Arcs, so lazy_static! won't work (returns reference)

What I have right now:

use rsa::{hash, PaddingScheme, PublicKey, RSAPublicKey};

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_1() {
        let (pub_key, priv_key) = new_keypair();
        // ...
    }

    #[tokio::test]
    async fn test_2() {
        let (pub_key, priv_key) = new_keypair();
        // ...
    }

    // ...

    fn new_keypair() -> (RSAPublicKey, RSAPrivateKey) {
        use rand::rngs::OsRng;
        let mut rng = OsRng;
        let bits = 2048;
        let private_key =
            RSAPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
        let public_key = RSAPublicKey::from(&private_key);
        (public_key, private_key)
    }
}

(pseudocode for) What I need:

use rsa::{hash, PaddingScheme, PublicKey, RSAPublicKey};

#[cfg(test)]
mod tests {
    use super::*;

    // Pseudo-code
    #[tokio::test_main]
    async fn main() {
        let (pub_key, priv_key) = new_keypair();
        run_tests(pub_key, priv_key);
    }

    #[tokio::test]
    async fn test_1(pub_key: RSAPublicKey, priv_key: RSAPrivateKey) {
        // ...
    }

    #[tokio::test]
    async fn test_2(pub_key: RSAPublicKey, priv_key: RSAPrivateKey) {
        // ...
    }

    // ...

    fn new_keypair() -> (RSAPublicKey, RSAPrivateKey) {
        use rand::rngs::OsRng;
        let mut rng = OsRng;
        let bits = 2048;
        let private_key =
            RSAPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
        let public_key = RSAPublicKey::from(&private_key);
        (public_key, private_key)
    }
}
Lumin
  • 373
  • 6
  • 21
  • test are mean to be independent if you want to reuse same data, just call test_1 and test_2 inside your "main_test" – Stargateur Apr 08 '21 at 13:41
  • Would https://stackoverflow.com/a/46379677/147192 answer your question? In the answer, there are multiple solutions presented and one of them uses `lazy_static` to only compute something once. – Matthieu M. Apr 08 '21 at 13:46
  • @Stargateur I don't want to lose parallel testing (or implement it myself, if possible). Additionally, I lose cargo's output / abstractions (separate output for each test, capturing output for successful tests) – Lumin Apr 08 '21 at 13:52
  • You can use [`lazy_static`](https://crates.io/crates/lazy_static) to create a global key pair. Note however that the test harness may choose to run tests in different processes, which would create a new key pair for each process. – Jmb Apr 08 '21 at 13:53
  • well you could put these test in ignore list so there are not run every time, only for final workflow, I think you should keep your tests that way. – Stargateur Apr 08 '21 at 13:55
  • Please do make sure your tests are not dependent on having random key pairs, I've seen problems where a smaller private exponent or a encryption / signing result without zero padding fails horribly. – Maarten Bodewes Apr 08 '21 at 17:13

1 Answers1

1

You can use lazy_static to initialize the key pair only once. However, with this approach, you will only be able to work with shared references. If that is not a problem for your use case, the following code should get you started.

Edited in response to update: The same principle also applies when dealing with other types. The following code uses Arc and async tests.

use rsa::RSAPrivateKey;
use std::sync::Arc;

pub async fn consume(key: Arc<RSAPrivateKey>) {
    // unimplemented!("")
}

#[cfg(test)]
mod tests {
    use super::*;
    use lazy_static::lazy_static;
    use rsa::{RSAPrivateKey, RSAPublicKey};
    use std::sync::Arc;

    lazy_static! {
        static ref PRIV_KEY: Arc<RSAPrivateKey> = Arc::new(new_priv_key());
        static ref PUB_KEY: Arc<RSAPublicKey> = Arc::new(PRIV_KEY.to_public_key());
    }

    #[tokio::test]
    async fn test_1() {
        let priv_key = PRIV_KEY.clone();

        consume(priv_key).await
    }

    fn new_priv_key() -> RSAPrivateKey {
        use rand::rngs::OsRng;
        let mut rng = OsRng;
        let bits = 2048;
        let private_key =
            RSAPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
        private_key
    }
}

Based on the documentation of RSAPrivateKey, you might not even need the RSAPublicKey, since RSAPrivateKey implements the PublicKey trait.

Korrat
  • 307
  • 1
  • 6
  • I tried this out, however it won't work because I need a thread safe `Arc` (required by external crate) -- Any other way I could do this? – Lumin Apr 08 '21 at 14:26
  • Where do you need the `Arc`? Inside the test? Perhaps you could update your question with a full test? – Korrat Apr 08 '21 at 14:45
  • The tests are async tests (I see now that I should have specified this earlier) and need to pass `Arc`s to crate functions - Updated the question accordingly – Lumin Apr 08 '21 at 14:57