use core::ops::Deref;
use pki_types::{
CertificateDer, ServerName, SignatureVerificationAlgorithm, TrustAnchor, UnixTime,
};
use crate::crl::RevocationOptions;
use crate::error::Error;
use crate::subject_name::{verify_dns_names, verify_ip_address_names};
use crate::verify_cert::{self, ExtendedKeyUsageValidator, VerifiedPath};
use crate::{cert, signed_data};
pub struct EndEntityCert<'a> {
inner: cert::Cert<'a>,
}
impl<'a> TryFrom<&'a CertificateDer<'a>> for EndEntityCert<'a> {
type Error = Error;
fn try_from(cert: &'a CertificateDer<'a>) -> Result<Self, Self::Error> {
Ok(Self {
inner: cert::Cert::from_der(untrusted::Input::from(cert.as_ref()))?,
})
}
}
impl EndEntityCert<'_> {
#[allow(clippy::too_many_arguments)]
pub fn verify_for_usage<'p>(
&'p self,
supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
trust_anchors: &'p [TrustAnchor<'_>],
intermediate_certs: &'p [CertificateDer<'p>],
time: UnixTime,
usage: impl ExtendedKeyUsageValidator,
revocation: Option<RevocationOptions<'_>>,
verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>,
) -> Result<VerifiedPath<'p>, Error> {
verify_cert::ChainOptions {
eku: usage,
supported_sig_algs,
trust_anchors,
intermediate_certs,
revocation,
}
.build_chain(self, time, verify_path)
}
pub fn verify_is_valid_for_subject_name(
&self,
server_name: &ServerName<'_>,
) -> Result<(), Error> {
match server_name {
ServerName::DnsName(dns_name) => verify_dns_names(dns_name, &self.inner),
ServerName::IpAddress(ip_address) => verify_ip_address_names(ip_address, &self.inner),
_ => Err(Error::UnsupportedNameType),
}
}
pub fn verify_signature(
&self,
signature_alg: &dyn SignatureVerificationAlgorithm,
msg: &[u8],
signature: &[u8],
) -> Result<(), Error> {
signed_data::verify_signature(
signature_alg,
self.inner.spki,
untrusted::Input::from(msg),
untrusted::Input::from(signature),
)
}
}
impl<'a> Deref for EndEntityCert<'a> {
type Target = cert::Cert<'a>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
use crate::test_utils;
use crate::test_utils::RCGEN_SIGNATURE_ALG;
#[test]
fn printable_string_common_name() {
const DNS_NAME: &str = "test.example.com";
let issuer = test_utils::make_issuer("Test");
let ee_cert = {
let mut params = test_utils::end_entity_params(vec![DNS_NAME.to_string()]);
params.distinguished_name.push(
rcgen::DnType::CommonName,
rcgen::DnValue::PrintableString(
rcgen::string::PrintableString::try_from("example.com").unwrap(),
),
);
params
.signed_by(
&rcgen::KeyPair::generate_for(RCGEN_SIGNATURE_ALG).unwrap(),
&issuer,
)
.expect("failed to make ee cert (this is a test bug)")
};
expect_dns_name(ee_cert.der(), DNS_NAME);
}
#[test]
fn empty_sequence_common_name() {
let ee_cert_der = {
let bytes = include_bytes!("../tests/misc/empty_sequence_common_name.der");
CertificateDer::from(&bytes[..])
};
expect_dns_name(&ee_cert_der, "example.com");
}
fn expect_dns_name(der: &CertificateDer<'_>, name: &str) {
let cert =
EndEntityCert::try_from(der).expect("should parse end entity certificate correctly");
let mut names = cert.valid_dns_names();
assert_eq!(names.next(), Some(name));
assert_eq!(names.next(), None);
}
}