use std::fmt;
use chrono::{DateTime, Utc};
use ed25519_dalek::pkcs8::DecodePrivateKey;
use ed25519_dalek::{SigningKey, VerifyingKey};
use sha2::{Digest, Sha256};
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,
})
}
}
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;