Skip to main content

vynil_core/
key.rs

1//! Private key generation (RSA / ed25519 via OpenSSL).
2//!
3//! Feature `crypto` only. Rhai bindings are registered by `key_rhai_register`.
4
5use crate::{Error, Result};
6use openssl::{pkey::PKey, rsa::Rsa};
7#[cfg(feature = "rhai")] use rhai::Engine;
8
9/// Default RSA key size when `bits` is not specified.
10pub const DEFAULT_RSA_BITS: u32 = 4096;
11
12/// Generate a PEM-encoded private key. `algo` is `rsa` or `ed25519` (case-insensitive).
13/// `bits` is only used for RSA. Returns `Error::UnsupportedKeyAlgorithm` for unknown algos.
14pub fn gen_private_key(algo: &str, bits: u32) -> Result<String> {
15    let pem = match algo.to_ascii_lowercase().as_str() {
16        "ed25519" => PKey::generate_ed25519()?.private_key_to_pem_pkcs8()?,
17        "rsa" => PKey::from_rsa(Rsa::generate(bits)?)?.private_key_to_pem_pkcs8()?,
18        other => return Err(Error::UnsupportedKeyAlgorithm(other.to_string())),
19    };
20    String::from_utf8(pem).map_err(Error::UTF8)
21}
22
23#[cfg(feature = "rhai")]
24pub fn key_rhai_register(engine: &mut Engine) {
25    engine
26        .register_fn("gen_private_key", |algo: &str| -> crate::RhaiRes<String> {
27            gen_private_key(algo, DEFAULT_RSA_BITS).map_err(|e| format!("{e}").into())
28        })
29        .register_fn(
30            "gen_private_key",
31            |algo: &str, bits: i64| -> crate::RhaiRes<String> {
32                gen_private_key(algo, bits as u32).map_err(|e| format!("{e}").into())
33            },
34        );
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use openssl::pkey::Id;
41
42    #[test]
43    fn ed25519_produces_valid_pkcs8_pem() {
44        let pem = gen_private_key("ed25519", 0).unwrap();
45        assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----"));
46        let key = PKey::private_key_from_pem(pem.as_bytes()).unwrap();
47        assert_eq!(key.id(), Id::ED25519);
48    }
49
50    #[test]
51    fn rsa_produces_valid_pkcs8_pem() {
52        let pem = gen_private_key("rsa", 2048).unwrap();
53        assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----"));
54        let key = PKey::private_key_from_pem(pem.as_bytes()).unwrap();
55        assert_eq!(key.id(), Id::RSA);
56        assert_eq!(key.bits(), 2048);
57    }
58
59    #[test]
60    fn algorithm_is_case_insensitive() {
61        gen_private_key("ED25519", 0).unwrap();
62    }
63
64    #[test]
65    fn unknown_algorithm_is_an_error() {
66        assert!(gen_private_key("dsa", 0).is_err());
67    }
68
69    #[test]
70    fn two_generations_differ() {
71        let a = gen_private_key("ed25519", 0).unwrap();
72        let b = gen_private_key("ed25519", 0).unwrap();
73        assert_ne!(a, b);
74    }
75}