I am new to Rust and have finally found something to learn from that keeps me interested throughout the steep learning curve; encryption.
I was wondering if Rust is mature enough to produce the same output as the following openssl
command but without using openssl
.
sudo openssl req \
-new \
-newkey rsa:2048 \
-nodes \
-keyout $HOME/www_example_com.key \
-out $HOME/www_example_com.csr \
-config /etc/ssl/openssl.cnf \
-subj "/C=US/ST=Utah/L=Salt Lake City/O=Xmpl, Inc./OU=IT Department/CN=www.example.com/[email protected]"
So far, I have found the creates Crate rsa & Struct std::path::Path which allows me to create RSA Public & Private keys of (n) bits and export them as PEM files.
I then found the Crate rcgen which seems to include Struct rcgen::CertificateSigningRequest and this is where I get lost.
How can I produce the same output as openssl command?
The rust code so far:
use rsa::{RsaPrivateKey, RsaPublicKey, pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey, LineEnding}};
use std::path::Path;
use rcgen::CertificateSigningRequest;
fn main() {
let mut rng = rand::thread_rng();
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);
// Encode private key to PEM
// let priv_pem = priv_key.to_pkcs1_pem(LineEnding::LF).expect("failed to encode private key");
// println!("Private Key PEM:\n{:#?}", priv_pem);
// Optionally, write to a file
priv_key.write_pkcs1_pem_file(Path::new("www_example_com_prv.key"), LineEnding::LF).expect("failed to write private key");
// Encode public key to PEM
// let pub_pem = pub_key.to_pkcs1_pem(LineEnding::LF).expect("failed to encode public key");
// println!("Public Key PEM:\n{:#?}", pub_pem);
// Optionally, write to a file
pub_key.write_pkcs1_pem_file(Path::new("www_example_com_pub.key"), LineEnding::LF).expect("failed to write public key");
println!("RSA {}bit Private Key generated.", bits);
println!("RSA {}bit Public Key generated.", bits);
}
Creating a CSR with rcgen is actually straightforward: you specify the certificate parameters and keys with CertificateParams
, create a Certificate
instance with Certificate::from_params()
and can use it to create a CSR with Certificate#serialize_request_pem()
:
Example:
use rcgen::{Certificate, CertificateParams, DnType, DistinguishedName, PKCS_RSA_SHA256, KeyPair};
fn main() {
let pkcs8 = "-----BEGIN PRIVATE KEY-----
MIIEv...fke4LZo5mOS10
-----END PRIVATE KEY-----";
let keypair = KeyPair::from_pem(pkcs8).unwrap();
let mut distinguished_name = DistinguishedName::new();
distinguished_name.push(DnType::CountryName, "US");
distinguished_name.push(DnType::StateOrProvinceName, "Utah");
distinguished_name.push(DnType::LocalityName, "Salt Lake City");
distinguished_name.push(DnType::OrganizationName, "Xmpl, Inc.");
distinguished_name.push(DnType::OrganizationalUnitName, "IT Department");
distinguished_name.push(DnType::CommonName, "www.example.com");
distinguished_name.push(DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]), "[email protected]");
let mut params: CertificateParams = Default::default();
params.alg = &PKCS_RSA_SHA256;
params.distinguished_name = distinguished_name;
params.key_pair = Some(keypair);
let cert: Certificate = Certificate::from_params(params).unwrap();
println!("{}", cert.serialize_request_pem().unwrap());
}
Note that the private key in the above example code must be in PKCS#8 format, whereas your code uses PKCS#1. Switching to PKCS#8 is simple:
use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}};
fn main() {
let mut rng = rand::thread_rng();
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);
// Encode private key in PKCS#8 format to PEM // added for private key in PKCS#8 format
let priv_pkcs8_pem = priv_key.to_pkcs8_pem(LineEnding::LF).expect("failed to encode private key");
println!("Private Key PEM (PKCS#8):\n{:#?}", priv_pkcs8_pem);
// Encode public key in X.509/SPKI format to PEM // added for public key in X.509/SPKI format
let pub_spki_pem = pub_key.to_public_key_pem(LineEnding::LF).expect("failed to encode public key");
println!("Public Key PEM (X.509/SPKI):\n{:#?}", pub_spki_pem);
}
The CSR created in this way corresponds to the CSR created with OpenSSL (v3.0.0).
Note: Actually, a new key pair is automatically generated if no key is specified in the certificate parameters. However, this does not work for RSA, but only for ECDSA and EdDSA, see the source code. For this reason, for RSA an existing key pair must be applied or a key pair must be generated beforehand.