mod err;
use std::{
fs,
io::{self, ErrorKind},
net::IpAddr,
path::Path
};
use openssl::{
asn1::Asn1Time,
bn::{BigNum, MsbOption},
ec::{EcGroup, EcKey},
error::ErrorStack,
hash::MessageDigest,
nid::Nid,
pkey::{PKey, Private},
rsa::Rsa,
x509::{
X509, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder,
extension::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage,
SubjectAlternativeName, SubjectKeyIdentifier
}
}
};
use rand::{
Rng, RngExt,
distr::{Distribution, StandardUniform}
};
use strum::EnumCount;
pub use err::Error;
#[derive(Debug, EnumCount)]
enum KeyType {
Rsa2048,
Rsa4096,
EcP256
}
impl Distribution<KeyType> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> KeyType {
match rng.random_range(0..KeyType::COUNT) {
0 => KeyType::Rsa2048,
1 => KeyType::Rsa4096,
2 => KeyType::EcP256,
_ => unimplemented!()
}
}
}
fn create_key() -> Result<PKey<Private>, ErrorStack> {
let kt: KeyType = rand::rng().sample(StandardUniform);
match kt {
KeyType::Rsa2048 => {
let rsa = Rsa::generate(2048)?;
let privkey = PKey::from_rsa(rsa)?;
Ok(privkey)
}
KeyType::Rsa4096 => {
let rsa = Rsa::generate(4096)?;
let privkey = PKey::from_rsa(rsa)?;
Ok(privkey)
}
KeyType::EcP256 => {
let nid = Nid::X9_62_PRIME256V1;
let group = EcGroup::from_curve_name(nid)?;
let ec = EcKey::generate(&group)?;
let privkey = PKey::from_ec_key(ec)?;
Ok(privkey)
}
}
}
fn mk_ca_cert(privkey: &PKey<Private>) -> Result<X509, ErrorStack> {
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_text("C", "US")?;
x509_name.append_entry_by_text("ST", "TX")?;
x509_name.append_entry_by_text("O", "La Cosa Nostra")?;
x509_name.append_entry_by_text("CN", "Not Suspicious")?;
let x509_name = x509_name.build();
let mut cert_builder = X509::builder()?;
cert_builder.set_version(2)?;
let serial_number = {
let mut serial = BigNum::new()?;
serial.rand(159, MsbOption::MAYBE_ZERO, false)?;
serial.to_asn1_integer()?
};
cert_builder.set_serial_number(&serial_number)?;
cert_builder.set_subject_name(&x509_name)?;
cert_builder.set_issuer_name(&x509_name)?;
cert_builder.set_pubkey(privkey)?;
let not_before = Asn1Time::days_from_now(0)?;
cert_builder.set_not_before(¬_before)?;
let not_after = Asn1Time::days_from_now(365)?;
cert_builder.set_not_after(¬_after)?;
cert_builder
.append_extension(BasicConstraints::new().critical().ca().build()?)?;
cert_builder.append_extension(
KeyUsage::new()
.critical()
.key_cert_sign()
.crl_sign()
.build()?
)?;
#[allow(deprecated)]
let ext = X509Extension::new_nid(
None,
None,
Nid::NETSCAPE_COMMENT,
"Will sign anything for ice cream."
)?;
cert_builder.append_extension(ext)?;
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&cert_builder.x509v3_context(None, None))?;
cert_builder.append_extension(subject_key_identifier)?;
cert_builder.sign(privkey, MessageDigest::sha256())?;
let cert = cert_builder.build();
Ok(cert)
}
pub fn mk_ca() -> Result<PkiIdent, Error> {
let key = create_key()?;
let cert = mk_ca_cert(&key)?;
Ok(PkiIdent { key, cert })
}
#[derive(Debug, Clone)]
pub struct PkiIdent {
pub key: PKey<Private>,
pub cert: X509
}
impl PkiIdent {
pub fn to_pem(&self) -> Result<(Vec<u8>, Vec<u8>), Error> {
let keyder = self.key.private_key_to_pem_pkcs8()?;
let certder = self.cert.to_pem()?;
Ok((keyder, certder))
}
pub fn to_der(&self) -> Result<(Vec<u8>, Vec<u8>), Error> {
let keyder = self.key.private_key_to_der()?;
let certder = self.cert.to_der()?;
Ok((keyder, certder))
}
pub fn load_pem(name: &str) -> Result<Self, Error> {
let fname = format!("{name}.key.pem");
let keybuf = fs::read(fname)?;
let key = PKey::private_key_from_pem(&keybuf)?;
let fname = format!("{name}.cert.pem");
let certbuf = fs::read(fname)?;
let cert = X509::from_pem(&certbuf)?;
let ident = Self { key, cert };
Ok(ident)
}
pub fn load_ca() -> Result<Self, Error> {
Self::load_pem("ca")
}
pub fn save_pem(&self, name: &str, force: bool) -> Result<(), Error> {
let fname = format!("{name}.key.pem");
let keyfile = Path::new(&fname);
if keyfile.exists() && !force {
Err(io::Error::from(ErrorKind::AlreadyExists))?;
}
let fname = format!("{name}.cert.pem");
let certfile = Path::new(&fname);
if certfile.exists() && !force {
Err(io::Error::from(ErrorKind::AlreadyExists))?;
}
let key_pem = self.key.private_key_to_pem_pkcs8()?;
fs::write(keyfile, key_pem)?;
let cert_pem = self.cert.to_pem()?;
fs::write(certfile, cert_pem)?;
Ok(())
}
}
pub fn mk_server<I, T>(
ca: &PkiIdent,
name: &str,
names: I
) -> Result<PkiIdent, Error>
where
I: IntoIterator<Item = T>,
T: AsRef<str>
{
let privkey = create_key()?;
let req = mk_request(&privkey, name)?;
let mut cert_builder = X509::builder()?;
cert_builder.set_version(2)?;
let serial_number = {
let mut serial = BigNum::new()?;
serial.rand(159, MsbOption::MAYBE_ZERO, false)?;
serial.to_asn1_integer()?
};
cert_builder.set_serial_number(&serial_number)?;
cert_builder.set_subject_name(req.subject_name())?;
cert_builder.set_issuer_name(ca.cert.subject_name())?;
cert_builder.set_pubkey(&privkey)?;
let not_before = Asn1Time::days_from_now(0)?;
cert_builder.set_not_before(¬_before)?;
let not_after = Asn1Time::days_from_now(365)?;
cert_builder.set_not_after(¬_after)?;
cert_builder.append_extension(BasicConstraints::new().build()?)?;
cert_builder.append_extension(
KeyUsage::new()
.critical()
.non_repudiation()
.digital_signature()
.key_encipherment()
.build()?
)?;
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
cert_builder.append_extension(subject_key_identifier)?;
let auth_key_identifier = AuthorityKeyIdentifier::new()
.keyid(false)
.issuer(false)
.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
cert_builder.append_extension(auth_key_identifier)?;
let mut subject_alt_name = SubjectAlternativeName::new();
for nm in names {
let nm = nm.as_ref();
if nm.parse::<IpAddr>().is_ok() {
subject_alt_name.ip(nm);
} else {
subject_alt_name.dns(nm);
}
}
let subject_alt_name = subject_alt_name
.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
cert_builder.append_extension(subject_alt_name)?;
cert_builder.sign(&ca.key, MessageDigest::sha256())?;
let cert = cert_builder.build();
Ok(PkiIdent { key: privkey, cert })
}
fn mk_request(
privkey: &PKey<Private>,
cn: &str
) -> Result<X509Req, ErrorStack> {
let mut req_builder = X509ReqBuilder::new()?;
req_builder.set_pubkey(privkey)?;
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_text("C", "US")?;
x509_name.append_entry_by_text("ST", "TX")?;
x509_name.append_entry_by_text("O", "La Cosa Nostra")?;
x509_name.append_entry_by_text("CN", cn)?;
let x509_name = x509_name.build();
req_builder.set_subject_name(&x509_name)?;
req_builder.sign(privkey, MessageDigest::sha256())?;
let req = req_builder.build();
Ok(req)
}