use std::sync::Arc;
use std::time::Duration;
use chrono::{DateTime, Timelike, Utc};
use ed25519_dalek::{Signature, VerifyingKey};
use rcgen::{
BasicConstraints, Certificate, CertificateParams, CustomExtension, IsCa, KeyPair, PKCS_ED25519,
};
use time::OffsetDateTime;
use x509_parser::oid_registry::OID_SIG_ED25519;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::ca::certificate::{CertifiedKey, IssuedCertificate};
use crate::ca::rcgen_bridge::{IdentitySigner, PresentedKey, distinguished_name};
use crate::ca::request::verify_certificate_request;
use crate::error::{TrustError, TrustResult};
use crate::hex_lower;
use crate::keys::Ed25519Identity;
#[derive(Debug)]
pub struct CertificateAuthority {
identity: Arc<Ed25519Identity>,
}
impl CertificateAuthority {
pub fn new(identity: Ed25519Identity) -> Self {
Self {
identity: Arc::new(identity),
}
}
pub fn public_key_bytes(&self) -> [u8; 32] {
self.identity.public_key_bytes()
}
pub fn issue_certificate(
&self,
subject: &str,
ttl: Duration,
extensions: Vec<CustomExtension>,
) -> TrustResult<IssuedCertificate> {
let (issued_at, expires_at) = validity_window(subject, ttl)?;
let issuer_key = self.issuer_key_pair()?;
let issuer_cert = self.issuer_certificate(&issuer_key)?;
let subject_key = KeyPair::generate_for(&PKCS_ED25519).map_err(|e| {
TrustError::CertificateGeneration {
reason: format!("failed to generate Ed25519 subject keypair: {e}"),
}
})?;
let params = leaf_params(subject, issued_at, expires_at, extensions)?;
let certificate = params
.signed_by(&subject_key, &issuer_cert, &issuer_key)
.map_err(|e| TrustError::CertificateGeneration {
reason: format!("failed to sign certificate: {e}"),
})?;
IssuedCertificate::from_der_and_keypair(
certificate.der().to_vec(),
&subject_key,
expires_at,
self.identity.public_key_bytes(),
)
}
pub fn issue_certificate_for_request(
&self,
request_der: &[u8],
subject: &str,
ttl: Duration,
extensions: Vec<CustomExtension>,
) -> TrustResult<CertifiedKey> {
let (issued_at, expires_at) = validity_window(subject, ttl)?;
let request = verify_certificate_request(request_der)?;
if request.common_name() != subject {
return Err(TrustError::CertificateGeneration {
reason: format!(
"certificate-signing request asks for common name {:?} but issuance was \
requested for subject {subject:?}",
request.common_name()
),
});
}
let issuer_key = self.issuer_key_pair()?;
let issuer_cert = self.issuer_certificate(&issuer_key)?;
let presented = PresentedKey::new(*request.subject_public_key());
let params = leaf_params(subject, issued_at, expires_at, extensions)?;
let certificate = params
.signed_by(&presented, &issuer_cert, &issuer_key)
.map_err(|e| TrustError::CertificateGeneration {
reason: format!("failed to sign certificate: {e}"),
})?;
CertifiedKey::from_der_and_public_key(
certificate.der().to_vec(),
request.subject_public_key(),
expires_at,
self.identity.public_key_bytes(),
)
}
pub fn verify_certificate_chain(&self, cert_der: &[u8]) -> TrustResult<()> {
verify_certificate_chain(cert_der, &self.identity.public_key_bytes())
}
fn issuer_key_pair(&self) -> TrustResult<KeyPair> {
let remote = IdentitySigner::new(Arc::clone(&self.identity));
KeyPair::from_remote(Box::new(remote)).map_err(|e| TrustError::CertificateGeneration {
reason: format!("failed to construct issuer key from identity: {e}"),
})
}
fn issuer_certificate(&self, issuer_key: &KeyPair) -> TrustResult<Certificate> {
let mut params = CertificateParams::new(Vec::<String>::new()).map_err(|e| {
TrustError::CertificateGeneration {
reason: format!("failed to build issuer parameters: {e}"),
}
})?;
let common_name = hex_lower(&self.identity.public_key_bytes());
params.distinguished_name = distinguished_name(&common_name);
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
params
.self_signed(issuer_key)
.map_err(|e| TrustError::CertificateGeneration {
reason: format!("failed to build issuer certificate: {e}"),
})
}
}
fn validity_window(subject: &str, ttl: Duration) -> TrustResult<(DateTime<Utc>, DateTime<Utc>)> {
if subject.trim().is_empty() {
return Err(TrustError::CertificateGeneration {
reason: "certificate subject must not be empty".to_string(),
});
}
if ttl.is_zero() {
return Err(TrustError::CertificateGeneration {
reason: "certificate TTL must be positive".to_string(),
});
}
let issued_at = Utc::now();
let ttl = chrono::Duration::from_std(ttl).map_err(|e| TrustError::CertificateGeneration {
reason: format!("certificate TTL is out of representable range: {e}"),
})?;
let expires_at =
issued_at
.checked_add_signed(ttl)
.ok_or_else(|| TrustError::CertificateGeneration {
reason: "certificate expiry overflowed the supported date range".to_string(),
})?;
let expires_at =
expires_at
.with_nanosecond(0)
.ok_or_else(|| TrustError::CertificateGeneration {
reason: "certificate expiry could not be truncated to whole seconds".to_string(),
})?;
Ok((issued_at, expires_at))
}
fn leaf_params(
subject: &str,
issued_at: DateTime<Utc>,
expires_at: DateTime<Utc>,
extensions: Vec<CustomExtension>,
) -> TrustResult<CertificateParams> {
let mut params = CertificateParams::new(Vec::<String>::new()).map_err(|e| {
TrustError::CertificateGeneration {
reason: format!("failed to build certificate parameters: {e}"),
}
})?;
params.distinguished_name = distinguished_name(subject);
params.is_ca = IsCa::ExplicitNoCa;
params.not_before = to_offset_date_time(issued_at)?;
params.not_after = to_offset_date_time(expires_at)?;
params.custom_extensions = extensions;
Ok(params)
}
pub fn verify_certificate_chain(cert_der: &[u8], issuer_public_key: &[u8; 32]) -> TrustResult<()> {
verify_certificate_chain_at(cert_der, issuer_public_key, Utc::now())
}
pub fn verify_certificate_chain_at(
cert_der: &[u8],
issuer_public_key: &[u8; 32],
at: DateTime<Utc>,
) -> TrustResult<()> {
let (_, certificate) =
X509Certificate::from_der(cert_der).map_err(|e| TrustError::CertificateParsing {
reason: format!("failed to parse certificate DER: {e:?}"),
})?;
if certificate.subject().as_raw() == certificate.issuer().as_raw() {
return Err(TrustError::CertificateVerification {
reason: "self-signed certificate rejected (issuer equals subject)".to_string(),
});
}
if certificate.signature_algorithm.algorithm != OID_SIG_ED25519 {
return Err(TrustError::CertificateVerification {
reason: "certificate signature algorithm is not Ed25519".to_string(),
});
}
let tbs = certificate.tbs_certificate.as_ref();
let signature_bytes: &[u8] = &certificate.signature_value.data;
let signature_array: &[u8; 64] =
signature_bytes
.try_into()
.map_err(|_err| TrustError::CertificateVerification {
reason: format!(
"certificate signature must be 64 bytes for Ed25519, got {}",
signature_bytes.len()
),
})?;
let signature = Signature::from_bytes(signature_array);
let verifying_key = VerifyingKey::from_bytes(issuer_public_key).map_err(|_err| {
TrustError::CertificateVerification {
reason: "issuer public key is not a valid Ed25519 point".to_string(),
}
})?;
verifying_key
.verify_strict(tbs, &signature)
.map_err(|_err| TrustError::CertificateVerification {
reason: "certificate signature did not verify against the issuer public key"
.to_string(),
})?;
check_validity_window(&certificate, at)
}
fn check_validity_window(certificate: &X509Certificate<'_>, at: DateTime<Utc>) -> TrustResult<()> {
let validity = certificate.validity();
let not_before = datetime_from_asn1_timestamp(validity.not_before.timestamp(), "notBefore")?;
let not_after = datetime_from_asn1_timestamp(validity.not_after.timestamp(), "notAfter")?;
if at < not_before {
return Err(TrustError::CertificateVerification {
reason: format!(
"certificate not yet valid: notBefore is {not_before}, checked at {at}"
),
});
}
if at > not_after {
return Err(TrustError::CertificateVerification {
reason: format!("certificate expired: notAfter was {not_after}, checked at {at}"),
});
}
Ok(())
}
fn datetime_from_asn1_timestamp(timestamp: i64, field: &str) -> TrustResult<DateTime<Utc>> {
DateTime::<Utc>::from_timestamp(timestamp, 0).ok_or_else(|| TrustError::CertificateParsing {
reason: format!(
"certificate {field} timestamp {timestamp} is outside the representable date range"
),
})
}
fn to_offset_date_time(instant: DateTime<Utc>) -> TrustResult<OffsetDateTime> {
OffsetDateTime::from_unix_timestamp(instant.timestamp()).map_err(|e| {
TrustError::CertificateGeneration {
reason: format!("certificate validity instant is out of range: {e}"),
}
})
}
#[cfg(test)]
#[path = "authority_tests.rs"]
mod tests;