pub mod extensions;
pub mod params;
use crate::error::{CertKitError, Result};
use der::{Encode, EncodePem};
use extensions::ToAndFromX509Extension;
use params::{CertificateParams, ExtensionParam};
use time::OffsetDateTime;
use x509_cert::certificate::CertificateInner;
use crate::issuer::Issuer;
use crate::key::KeyPair;
#[derive(Debug, Clone)]
pub enum SignatureAlgorithm {
Sha256WithRSA,
Sha256WithECDSA,
Sha384WithECDSA,
Sha512WithECDSA,
Ed25519,
}
impl From<SignatureAlgorithm> for x509_cert::spki::AlgorithmIdentifierOwned {
fn from(value: SignatureAlgorithm) -> Self {
match value {
SignatureAlgorithm::Sha256WithRSA => x509_cert::spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::SHA_256_WITH_RSA_ENCRYPTION,
parameters: None,
},
SignatureAlgorithm::Sha256WithECDSA => x509_cert::spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::ECDSA_WITH_SHA_256,
parameters: None,
},
SignatureAlgorithm::Sha384WithECDSA => x509_cert::spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::ECDSA_WITH_SHA_384,
parameters: None,
},
SignatureAlgorithm::Sha512WithECDSA => x509_cert::spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::ECDSA_WITH_SHA_512,
parameters: None,
},
SignatureAlgorithm::Ed25519 => x509_cert::spki::AlgorithmIdentifierOwned {
oid: const_oid::db::rfc8410::ID_ED_25519,
parameters: None,
},
}
}
}
impl std::fmt::Display for SignatureAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sha256WithRSA => write!(f, "SHA-256 with RSA"),
Self::Sha256WithECDSA => write!(f, "SHA-256 with ECDSA"),
Self::Sha384WithECDSA => write!(f, "SHA-384 with ECDSA"),
Self::Sha512WithECDSA => write!(f, "SHA-512 with ECDSA"),
Self::Ed25519 => write!(f, "Ed25519"),
}
}
}
#[derive(Debug, Clone)]
pub struct Certificate {
inner: CertificateInner,
}
impl Certificate {
pub(crate) fn from_inner(inner: CertificateInner) -> Self {
Self { inner }
}
#[cfg(test)]
pub(crate) fn inner(&self) -> &CertificateInner {
&self.inner
}
pub fn from_der(bytes: &[u8]) -> Result<Self> {
use der::Decode;
let inner = CertificateInner::from_der(bytes)
.map_err(|e| CertKitError::DecodingError(e.to_string()))?;
Ok(Self { inner })
}
pub fn from_pem(pem: &str) -> Result<Self> {
use der::DecodePem;
let inner = CertificateInner::from_pem(pem)
.map_err(|e| CertKitError::DecodingError(e.to_string()))?;
Ok(Self { inner })
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.starts_with(b"-----BEGIN") {
let pem = std::str::from_utf8(bytes)
.map_err(|e| CertKitError::DecodingError(format!("invalid UTF-8 in PEM: {e}")))?;
Self::from_pem(pem)
} else {
Self::from_der(bytes)
}
}
pub fn to_der(&self) -> Result<Vec<u8>> {
log::trace!("encoding certificate to DER");
self.inner
.to_der()
.map_err(|e| CertKitError::EncodingError(e.to_string()))
}
pub fn to_pem(&self) -> Result<String> {
log::trace!("encoding certificate to PEM");
self.inner
.to_pem(pkcs8::LineEnding::LF)
.map_err(|e| CertKitError::EncodingError(e.to_string()))
}
pub fn fingerprint(&self) -> Result<[u8; 32]> {
use sha2::Digest;
let der = self.to_der()?;
let hash = sha2::Sha256::digest(&der);
Ok(hash.into())
}
pub fn params(&self) -> Result<CertificateParams> {
let inner_tbs_cert = self.inner.tbs_certificate.clone();
let subject = params::DistinguishedName::from_x509_name(&inner_tbs_cert.subject)?;
let subject_public_key =
crate::key::PublicKey::from_x509spki(&inner_tbs_cert.subject_public_key_info)?;
let extensions: Vec<ExtensionParam> = inner_tbs_cert
.extensions
.unwrap_or_default()
.iter()
.map(|ext| ExtensionParam {
oid: ext.extn_id,
critical: ext.critical,
value: ext.extn_value.as_bytes().to_vec(),
})
.collect();
let usages = extensions
.iter()
.filter_map(|ext| {
if ext.oid == crate::cert::extensions::ExtendedKeyUsage::OID {
let eku: crate::cert::extensions::ExtendedKeyUsage =
ext.to_extension().unwrap_or_default();
Some(eku.usage)
} else {
None
}
})
.next()
.unwrap_or_default();
let basic_constraints = extensions
.iter()
.find(|ext| ext.oid == crate::cert::extensions::BasicConstraints::OID)
.map(|ext| {
ext.to_extension::<crate::cert::extensions::BasicConstraints>()
.unwrap_or_default()
})
.unwrap_or_default();
Ok(CertificateParams {
subject: subject.clone(),
subject_public_key,
usages,
is_ca: basic_constraints.is_ca,
max_path_length: basic_constraints.max_path_length,
extensions,
})
}
pub fn new_self_signed(cert_info: &CertificateParams, key: &KeyPair) -> Result<Self> {
let now = OffsetDateTime::now_utc();
Self::new_self_signed_with_expiration(cert_info, key, now, now + time::Duration::days(365))
}
pub fn new_self_signed_with_expiration(
cert_info: &CertificateParams,
key: &KeyPair,
not_before: OffsetDateTime,
not_after: OffsetDateTime,
) -> Result<Self> {
log::debug!(
"creating self-signed certificate for \"{}\"",
cert_info.subject.common_name
);
let subject_dn = cert_info.subject.clone();
let self_issuer = SelfIssuer {
name: subject_dn,
key,
};
let validity = params::Validity::new(not_before, not_after)?;
self_issuer.issue(cert_info, validity)
}
}
struct SelfIssuer<'a> {
name: params::DistinguishedName,
key: &'a KeyPair,
}
impl Issuer for SelfIssuer<'_> {
fn issuer_name(&self) -> Result<params::DistinguishedName> {
Ok(self.name.clone())
}
fn signing_key(&self) -> &KeyPair {
self.key
}
}
#[derive(Debug, Clone)]
pub struct CertificateWithPrivateKey {
cert: Certificate,
key: crate::key::KeyPair,
}
impl CertificateWithPrivateKey {
pub fn new(cert: Certificate, key: KeyPair) -> Self {
Self { cert, key }
}
pub fn cert(&self) -> &Certificate {
&self.cert
}
pub fn key(&self) -> &KeyPair {
&self.key
}
pub fn into_parts(self) -> (Certificate, KeyPair) {
(self.cert, self.key)
}
}
impl Issuer for CertificateWithPrivateKey {
fn issuer_name(&self) -> Result<params::DistinguishedName> {
let cert_info = self.cert.params()?;
Ok(cert_info.subject)
}
fn signing_key(&self) -> &KeyPair {
&self.key
}
}