use crate::Error;
use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P256_SHA256};
use std::time::{Duration, SystemTime};
use time::OffsetDateTime;
#[derive(Debug, Clone)]
pub struct MikeyKeyPair {
pub private_key: Vec<u8>,
pub public_key: Vec<u8>,
pub certificate: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct CertificateConfig {
pub common_name: String,
pub organization: String,
pub organizational_unit: String,
pub country: String,
pub state: String,
pub locality: String,
pub validity_duration: Duration,
pub key_size: usize,
}
impl Default for CertificateConfig {
fn default() -> Self {
Self {
common_name: "MIKEY-PKE Entity".to_string(),
organization: "Enterprise Communications".to_string(),
organizational_unit: "Secure Multimedia".to_string(),
country: "US".to_string(),
state: "California".to_string(),
locality: "San Francisco".to_string(),
validity_duration: Duration::from_secs(365 * 24 * 60 * 60), key_size: 2048,
}
}
}
impl CertificateConfig {
pub fn enterprise_server(hostname: &str) -> Self {
Self {
common_name: hostname.to_string(),
organization: "Enterprise Corp".to_string(),
organizational_unit: "Media Server".to_string(),
country: "US".to_string(),
state: "California".to_string(),
locality: "San Francisco".to_string(),
validity_duration: Duration::from_secs(2 * 365 * 24 * 60 * 60), key_size: 2048,
}
}
pub fn enterprise_client(user_id: &str) -> Self {
Self {
common_name: format!("User {}", user_id),
organization: "Enterprise Corp".to_string(),
organizational_unit: "Media Client".to_string(),
country: "US".to_string(),
state: "California".to_string(),
locality: "San Francisco".to_string(),
validity_duration: Duration::from_secs(365 * 24 * 60 * 60), key_size: 2048,
}
}
pub fn high_security(entity_name: &str) -> Self {
Self {
common_name: entity_name.to_string(),
organization: "Secure Communications Inc".to_string(),
organizational_unit: "High Security Division".to_string(),
country: "US".to_string(),
state: "Virginia".to_string(),
locality: "Washington DC".to_string(),
validity_duration: Duration::from_secs(90 * 24 * 60 * 60), key_size: 4096, }
}
}
pub fn generate_key_pair_and_certificate(config: CertificateConfig) -> Result<MikeyKeyPair, Error> {
let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
.map_err(|_| Error::CryptoError("Failed to generate private key".into()))?;
let private_key_der = key_pair.serialize_der();
let public_key_der = key_pair.public_key_raw().to_vec();
let mut params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, config.common_name);
dn.push(DnType::OrganizationName, config.organization);
dn.push(DnType::OrganizationalUnitName, config.organizational_unit);
dn.push(DnType::CountryName, config.country);
dn.push(DnType::StateOrProvinceName, config.state);
dn.push(DnType::LocalityName, config.locality);
params.distinguished_name = dn;
params.not_before = OffsetDateTime::from(SystemTime::now());
params.not_after = OffsetDateTime::from(SystemTime::now() + config.validity_duration);
let cert = params
.self_signed(&key_pair)
.map_err(|_| Error::CryptoError("Failed to generate certificate".into()))?;
let certificate_der = cert.der().to_vec();
Ok(MikeyKeyPair {
private_key: private_key_der,
public_key: public_key_der,
certificate: certificate_der,
})
}
pub fn generate_ca_certificate(config: CertificateConfig) -> Result<MikeyKeyPair, Error> {
let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
.map_err(|_| Error::CryptoError("Failed to generate CA private key".into()))?;
let private_key_der = key_pair.serialize_der();
let public_key_der = key_pair.public_key_raw().to_vec();
let mut params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, format!("{} CA", config.common_name));
dn.push(DnType::OrganizationName, config.organization);
dn.push(
DnType::OrganizationalUnitName,
"Certificate Authority".to_string(),
);
dn.push(DnType::CountryName, config.country);
dn.push(DnType::StateOrProvinceName, config.state);
dn.push(DnType::LocalityName, config.locality);
params.distinguished_name = dn;
params.not_before = OffsetDateTime::from(SystemTime::now());
params.not_after = OffsetDateTime::from(SystemTime::now() + config.validity_duration * 2);
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let cert = params
.self_signed(&key_pair)
.map_err(|_| Error::CryptoError("Failed to generate CA certificate".into()))?;
let certificate_der = cert.der().to_vec();
Ok(MikeyKeyPair {
private_key: private_key_der,
public_key: public_key_der,
certificate: certificate_der,
})
}
pub fn sign_certificate_with_ca(
ca_cert: &MikeyKeyPair,
subject_config: CertificateConfig,
) -> Result<MikeyKeyPair, Error> {
let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
.map_err(|_| Error::CryptoError("Failed to generate subject private key".into()))?;
let private_key_der = key_pair.serialize_der();
let public_key_der = key_pair.public_key_raw().to_vec();
let _ca_info = extract_certificate_info(&ca_cert.certificate)?;
let mut params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, subject_config.common_name);
dn.push(DnType::OrganizationName, subject_config.organization);
dn.push(
DnType::OrganizationalUnitName,
subject_config.organizational_unit,
);
dn.push(DnType::CountryName, subject_config.country);
dn.push(DnType::StateOrProvinceName, subject_config.state);
dn.push(DnType::LocalityName, subject_config.locality);
params.distinguished_name = dn;
params.not_before = OffsetDateTime::from(SystemTime::now());
params.not_after = OffsetDateTime::from(SystemTime::now() + subject_config.validity_duration);
let cert = params
.self_signed(&key_pair)
.map_err(|_| Error::CryptoError("Failed to generate subject certificate".into()))?;
let certificate_der = cert.der().to_vec();
Ok(MikeyKeyPair {
private_key: private_key_der,
public_key: public_key_der,
certificate: certificate_der,
})
}
pub fn validate_certificate_chain(subject_cert: &[u8], ca_cert: &[u8]) -> Result<(), Error> {
let (_, subject) = x509_parser::parse_x509_certificate(subject_cert)
.map_err(|_| Error::CryptoError("Failed to parse subject certificate".into()))?;
let (_, _ca) = x509_parser::parse_x509_certificate(ca_cert)
.map_err(|_| Error::CryptoError("Failed to parse CA certificate".into()))?;
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let not_before = subject.validity().not_before.timestamp();
if now < not_before as u64 {
return Err(Error::AuthenticationFailed(
"Certificate not yet valid".into(),
));
}
let not_after = subject.validity().not_after.timestamp();
if now > not_after as u64 {
return Err(Error::AuthenticationFailed(
"Certificate has expired".into(),
));
}
Ok(())
}
pub fn extract_certificate_info(cert_der: &[u8]) -> Result<CertificateInfo, Error> {
let (_, cert) = x509_parser::parse_x509_certificate(cert_der)
.map_err(|_| Error::CryptoError("Failed to parse certificate".into()))?;
let subject = cert.subject();
let issuer = cert.issuer();
Ok(CertificateInfo {
subject_cn: extract_cn_from_name(subject),
issuer_cn: extract_cn_from_name(issuer),
serial_number: format!("{:?}", cert.serial),
not_before: cert.validity().not_before.timestamp(),
not_after: cert.validity().not_after.timestamp(),
})
}
#[derive(Debug, Clone)]
pub struct CertificateInfo {
pub subject_cn: String,
pub issuer_cn: String,
pub serial_number: String,
pub not_before: i64,
pub not_after: i64,
}
fn extract_cn_from_name(name: &x509_parser::x509::X509Name) -> String {
for rdn in name.iter() {
for attr in rdn.iter() {
if let Ok(cn) = attr.attr_value().as_str() {
if attr.attr_type() == &x509_parser::oid_registry::OID_X509_COMMON_NAME {
return cn.to_string();
}
}
}
}
"Unknown".to_string()
}