use std::fmt;
use chrono::{DateTime, Utc};
use ed25519_dalek::pkcs8::DecodePrivateKey;
use ed25519_dalek::{SigningKey, VerifyingKey};
use sha2::{Digest, Sha256};
use x509_parser::oid_registry::OID_SIG_ED25519;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::error::{TrustError, TrustResult};
use crate::hex_lower;
pub struct IssuedCertificate {
pub der_bytes: Vec<u8>,
pub subject_signing_key: SigningKey,
pub subject_verifying_key: VerifyingKey,
pub fingerprint: [u8; 32],
pub expires_at: DateTime<Utc>,
pub issuer_public_key: [u8; 32],
}
impl IssuedCertificate {
pub fn from_der_and_keypair(
der_bytes: Vec<u8>,
subject_keypair: &rcgen::KeyPair,
expires_at: DateTime<Utc>,
issuer_public_key: [u8; 32],
) -> TrustResult<Self> {
let raw_public = subject_keypair.public_key_raw();
if raw_public.len() != 32 {
return Err(TrustError::CertificateGeneration {
reason: format!(
"subject public key must be 32 bytes for Ed25519, got {}",
raw_public.len()
),
});
}
let pkcs8 = subject_keypair.serialize_der();
let subject_signing_key =
SigningKey::from_pkcs8_der(&pkcs8).map_err(|e| TrustError::CertificateGeneration {
reason: format!("failed to load subject signing key from PKCS#8: {e}"),
})?;
let subject_verifying_key = subject_signing_key.verifying_key();
if subject_verifying_key.to_bytes().as_slice() != raw_public {
return Err(TrustError::CertificateGeneration {
reason: "subject keypair public and private halves do not match".to_string(),
});
}
let fingerprint: [u8; 32] = Sha256::digest(&der_bytes).into();
Ok(Self {
der_bytes,
subject_signing_key,
subject_verifying_key,
fingerprint,
expires_at,
issuer_public_key,
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct CertifiedKey {
pub der_bytes: Vec<u8>,
pub subject_public_key: [u8; 32],
pub fingerprint: [u8; 32],
pub expires_at: DateTime<Utc>,
pub issuer_public_key: [u8; 32],
}
impl CertifiedKey {
pub fn from_der_and_public_key(
der_bytes: Vec<u8>,
subject_public_key: &[u8; 32],
expires_at: DateTime<Utc>,
issuer_public_key: [u8; 32],
) -> TrustResult<Self> {
assert_der_binds_subject_key(&der_bytes, subject_public_key)?;
let fingerprint: [u8; 32] = Sha256::digest(&der_bytes).into();
Ok(Self {
der_bytes,
subject_public_key: *subject_public_key,
fingerprint,
expires_at,
issuer_public_key,
})
}
}
impl fmt::Debug for CertifiedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CertifiedKey")
.field("der_bytes_len", &self.der_bytes.len())
.field("subject_public_key", &hex_lower(&self.subject_public_key))
.field("fingerprint", &hex_lower(&self.fingerprint))
.field("expires_at", &self.expires_at)
.field("issuer_public_key", &hex_lower(&self.issuer_public_key))
.finish()
}
}
pub fn certificate_subject_public_key(cert_der: &[u8]) -> TrustResult<[u8; 32]> {
let (_, certificate) =
X509Certificate::from_der(cert_der).map_err(|e| TrustError::CertificateParsing {
reason: format!("failed to parse certificate DER: {e:?}"),
})?;
let spki = &certificate.tbs_certificate.subject_pki;
if spki.algorithm.algorithm != OID_SIG_ED25519 {
return Err(TrustError::CertificateParsing {
reason: "certificate subject key algorithm is not Ed25519".to_string(),
});
}
if spki.algorithm.parameters.is_some() {
return Err(TrustError::CertificateParsing {
reason: "Ed25519 subject key algorithm must carry no parameters (RFC 8410)".to_string(),
});
}
if spki.subject_public_key.unused_bits != 0 {
return Err(TrustError::CertificateParsing {
reason: "certificate subject key BIT STRING must have no unused bits".to_string(),
});
}
let data = spki.subject_public_key.data.as_ref();
data.try_into()
.map_err(|_err| TrustError::CertificateParsing {
reason: format!(
"certificate subject key must be 32 bytes for Ed25519, got {}",
data.len()
),
})
}
fn assert_der_binds_subject_key(der_bytes: &[u8], expected: &[u8; 32]) -> TrustResult<()> {
let subject_key = certificate_subject_public_key(der_bytes).map_err(|e| {
TrustError::CertificateGeneration {
reason: format!("freshly issued certificate could not be read back: {e}"),
}
})?;
if &subject_key != expected {
return Err(TrustError::CertificateGeneration {
reason: "freshly issued certificate binds a different subject key than requested"
.to_string(),
});
}
Ok(())
}
impl fmt::Debug for IssuedCertificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IssuedCertificate")
.field("der_bytes_len", &self.der_bytes.len())
.field("subject_signing_key", &"[REDACTED]")
.field(
"subject_verifying_key",
&hex_lower(&self.subject_verifying_key.to_bytes()),
)
.field("fingerprint", &hex_lower(&self.fingerprint))
.field("expires_at", &self.expires_at)
.field("issuer_public_key", &hex_lower(&self.issuer_public_key))
.finish()
}
}
#[cfg(test)]
#[path = "certificate_tests.rs"]
mod tests;