mod validations;
use crate::cert::error::{CertificateError, PrivateKeyError};
use crate::cert::parsing::to_certificate_vec;
use crate::cert::{Certificate, PrivateKey};
use crate::spiffe_id::SpiffeId;
use crate::svid::x509::validations::{validate_leaf_certificate, validate_signing_certificates};
use std::convert::TryFrom as _;
use std::sync::Arc;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct X509Svid {
spiffe_id: SpiffeId,
cert_chain: Vec<Certificate>,
private_key: PrivateKey,
hint: Option<Arc<str>>,
}
#[derive(Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum X509SvidError {
#[error("no certificates found in chain")]
EmptyChain,
#[error("leaf certificate must not have CA flag set to true")]
LeafCertificateHasCaFlag,
#[error("leaf certificate must not have 'cRLSign' set as key usage")]
LeafCertificateHasCrlSign,
#[error("leaf certificate must not have 'keyCertSign' set as key usage")]
LeafCertificateHasKeyCertSign,
#[error("leaf certificate must have 'digitalSignature' set as key usage")]
LeafCertificateMissingDigitalSignature,
#[error("signing certificate must have CA flag set to true")]
SigningCertificateMissingCaFlag,
#[error("signing certificate must have 'keyCertSign' set as key usage")]
SigningCertificateMissingKeyCertSign,
#[error("{extension} extension is present but could not be parsed")]
UnparseableExtension {
extension: &'static str,
},
#[error(transparent)]
Certificate(#[from] CertificateError),
#[error("leaf certificate SPIFFE ID must have a non-root path component")]
LeafSpiffeIdMissingPath,
#[error(transparent)]
PrivateKey(#[from] PrivateKeyError),
}
impl X509Svid {
pub fn parse_from_der(
cert_chain_der: &[u8],
private_key_der: &[u8],
) -> Result<Self, X509SvidError> {
Self::parse_from_der_with_hint(cert_chain_der, private_key_der, None)
}
pub fn parse_from_der_with_hint(
cert_chain_der: &[u8],
private_key_der: &[u8],
hint: Option<Arc<str>>,
) -> Result<Self, X509SvidError> {
let cert_chain = to_certificate_vec(cert_chain_der)?;
let Some((leaf, rest)) = cert_chain.split_first() else {
return Err(X509SvidError::EmptyChain);
};
let spiffe_id = validate_leaf_certificate(leaf)?;
validate_signing_certificates(rest)?;
let private_key = PrivateKey::try_from(private_key_der)?;
Ok(Self {
spiffe_id,
cert_chain,
private_key,
hint,
})
}
pub const fn spiffe_id(&self) -> &SpiffeId {
&self.spiffe_id
}
pub fn cert_chain(&self) -> &[Certificate] {
&self.cert_chain
}
pub fn leaf(&self) -> &Certificate {
#[expect(clippy::panic, reason = "documented behavior")]
self.cert_chain
.first()
.unwrap_or_else(|| panic!("certificate chain is empty"))
}
pub const fn private_key(&self) -> &PrivateKey {
&self.private_key
}
pub fn hint(&self) -> Option<&str> {
self.hint.as_deref()
}
}