use crate::cert::lenient_certificate_serial_number;
use crate::der::Tag;
use crate::signed_data::{self, SignedData};
use crate::verify_cert::Budget;
use crate::x509::{remember_extension, set_extension_once, Extension};
use crate::{der, public_values_eq, Error, SignatureAlgorithm, Time};
#[cfg(feature = "alloc")]
use std::collections::HashMap;
use private::Sealed;
pub trait CertRevocationList: Sealed {
fn issuer(&self) -> &[u8];
fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert>, Error>;
fn verify_signature(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
issuer_spki: &[u8],
) -> Result<(), Error>;
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[allow(dead_code)] #[derive(Debug, Clone)]
pub struct OwnedCertRevocationList {
revoked_certs: HashMap<Vec<u8>, OwnedRevokedCert>,
issuer: Vec<u8>,
signed_data: signed_data::OwnedSignedData,
}
#[cfg(feature = "alloc")]
impl Sealed for OwnedCertRevocationList {}
#[cfg(feature = "alloc")]
impl CertRevocationList for OwnedCertRevocationList {
fn issuer(&self) -> &[u8] {
&self.issuer
}
fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert>, Error> {
Ok(self
.revoked_certs
.get(serial)
.map(|owned_revoked_cert| owned_revoked_cert.borrow()))
}
fn verify_signature(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
issuer_spki: &[u8],
) -> Result<(), Error> {
signed_data::verify_signed_data(
supported_sig_algs,
untrusted::Input::from(issuer_spki),
&self.signed_data.borrow(),
&mut Budget::default(),
)
}
}
#[derive(Debug)]
pub struct BorrowedCertRevocationList<'a> {
signed_data: SignedData<'a>,
issuer: untrusted::Input<'a>,
revoked_certs: untrusted::Input<'a>,
}
impl<'a> BorrowedCertRevocationList<'a> {
pub fn from_der(crl_der: &'a [u8]) -> Result<Self, Error> {
let reader = untrusted::Input::from(crl_der);
let (tbs_cert_list, signed_data) = reader.read_all(Error::BadDer, |crl_der| {
der::nested_limited(
crl_der,
Tag::Sequence,
Error::BadDer,
|signed_der| SignedData::from_der(signed_der, der::MAX_DER_SIZE),
der::MAX_DER_SIZE,
)
})?;
let crl = tbs_cert_list.read_all(Error::BadDer, |tbs_cert_list| {
if der::small_nonnegative_integer(tbs_cert_list)? != 1 {
return Err(Error::UnsupportedCrlVersion);
}
let signature = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?;
if !public_values_eq(signature, signed_data.algorithm) {
return Err(Error::SignatureAlgorithmMismatch);
}
let issuer = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?;
der::time_choice(tbs_cert_list)?;
der::time_choice(tbs_cert_list)?;
let revoked_certs = if tbs_cert_list.peek(Tag::Sequence.into()) {
der::expect_tag_and_get_value_limited(
tbs_cert_list,
Tag::Sequence,
der::MAX_DER_SIZE,
)?
} else {
untrusted::Input::from(&[])
};
let mut crl = BorrowedCertRevocationList {
signed_data,
issuer,
revoked_certs,
};
der::nested(
tbs_cert_list,
Tag::ContextSpecificConstructed0,
Error::MalformedExtensions,
|tagged| {
der::nested_of_mut(
tagged,
Tag::Sequence,
Tag::Sequence,
Error::BadDer,
|extension| {
crl.remember_extension(&Extension::parse(extension)?)
},
)
},
)?;
Ok(crl)
})?;
Ok(crl)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_owned(&self) -> Result<OwnedCertRevocationList, Error> {
let revoked_certs = self
.into_iter()
.collect::<Result<Vec<_>, _>>()?
.iter()
.map(|revoked_cert| (revoked_cert.serial_number.to_vec(), revoked_cert.to_owned()))
.collect::<HashMap<_, _>>();
Ok(OwnedCertRevocationList {
signed_data: self.signed_data.to_owned(),
issuer: self.issuer.as_slice_less_safe().to_vec(),
revoked_certs,
})
}
fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
remember_extension(extension, |id| {
match id {
20 => {
extension.value.read_all(Error::InvalidCrlNumber, |der| {
let crl_number = ring::io::der::positive_integer(der)
.map_err(|_| Error::InvalidCrlNumber)?
.big_endian_without_leading_zero();
if crl_number.len() <= 20 {
Ok(crl_number)
} else {
Err(Error::InvalidCrlNumber)
}
})?;
Ok(())
}
27 => Err(Error::UnsupportedDeltaCrl),
28 => Ok(()),
35 => Ok(()),
_ => extension.unsupported(),
}
})
}
}
impl Sealed for BorrowedCertRevocationList<'_> {}
impl CertRevocationList for BorrowedCertRevocationList<'_> {
fn issuer(&self) -> &[u8] {
self.issuer.as_slice_less_safe()
}
fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert>, Error> {
for revoked_cert_result in self {
match revoked_cert_result {
Err(e) => return Err(e),
Ok(revoked_cert) => {
if revoked_cert.serial_number.eq(serial) {
return Ok(Some(revoked_cert));
}
}
}
}
Ok(None)
}
fn verify_signature(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
issuer_spki: &[u8],
) -> Result<(), Error> {
signed_data::verify_signed_data(
supported_sig_algs,
untrusted::Input::from(issuer_spki),
&self.signed_data,
&mut Budget::default(),
)
}
}
impl<'a> IntoIterator for &'a BorrowedCertRevocationList<'a> {
type Item = Result<BorrowedRevokedCert<'a>, Error>;
type IntoIter = RevokedCerts<'a>;
fn into_iter(self) -> Self::IntoIter {
RevokedCerts {
reader: untrusted::Reader::new(self.revoked_certs),
}
}
}
#[derive(Debug)]
pub struct RevokedCerts<'a> {
reader: untrusted::Reader<'a>,
}
impl<'a> Iterator for RevokedCerts<'a> {
type Item = Result<BorrowedRevokedCert<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
(!self.reader.at_end()).then(|| BorrowedRevokedCert::from_der(&mut self.reader))
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug)]
pub struct OwnedRevokedCert {
pub serial_number: Vec<u8>,
pub revocation_date: Time,
pub reason_code: Option<RevocationReason>,
pub invalidity_date: Option<Time>,
}
#[cfg(feature = "alloc")]
impl OwnedRevokedCert {
pub fn borrow(&self) -> BorrowedRevokedCert {
BorrowedRevokedCert {
serial_number: &self.serial_number,
revocation_date: self.revocation_date,
reason_code: self.reason_code,
invalidity_date: self.invalidity_date,
}
}
}
#[derive(Debug)]
pub struct BorrowedRevokedCert<'a> {
pub serial_number: &'a [u8],
pub revocation_date: Time,
pub reason_code: Option<RevocationReason>,
pub invalidity_date: Option<Time>,
}
impl<'a> BorrowedRevokedCert<'a> {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_owned(&self) -> OwnedRevokedCert {
OwnedRevokedCert {
serial_number: self.serial_number.to_vec(),
revocation_date: self.revocation_date,
reason_code: self.reason_code,
invalidity_date: self.invalidity_date,
}
}
fn from_der(der: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
der::nested(der, Tag::Sequence, Error::BadDer, |der| {
let serial_number = lenient_certificate_serial_number(der)
.map_err(|_| Error::InvalidSerialNumber)?
.as_slice_less_safe();
let revocation_date = der::time_choice(der)?;
let mut revoked_cert = BorrowedRevokedCert {
serial_number,
revocation_date,
reason_code: None,
invalidity_date: None,
};
if der.at_end() {
return Ok(revoked_cert);
}
let ext_seq = der::expect_tag_and_get_value(der, Tag::Sequence)?;
if ext_seq.is_empty() {
return Ok(revoked_cert);
}
let mut reader = untrusted::Reader::new(ext_seq);
loop {
der::nested(&mut reader, Tag::Sequence, Error::BadDer, |ext_der| {
revoked_cert.remember_extension(&Extension::parse(ext_der)?)
})?;
if reader.at_end() {
break;
}
}
Ok(revoked_cert)
})
}
fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
remember_extension(extension, |id| {
match id {
21 => set_extension_once(&mut self.reason_code, || {
RevocationReason::from_der(extension.value)
}),
24 => set_extension_once(&mut self.invalidity_date, || {
extension.value.read_all(Error::BadDer, der::time_choice)
}),
29 => Err(Error::UnsupportedIndirectCrl),
_ => extension.unsupported(),
}
})
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[allow(missing_docs)] pub enum RevocationReason {
Unspecified = 0,
KeyCompromise = 1,
CaCompromise = 2,
AffiliationChanged = 3,
Superseded = 4,
CessationOfOperation = 5,
CertificateHold = 6,
RemoveFromCrl = 8,
PrivilegeWithdrawn = 9,
AaCompromise = 10,
}
impl RevocationReason {
fn from_der(value: untrusted::Input<'_>) -> Result<Self, Error> {
value.read_all(Error::BadDer, |enumerated_reason| {
let value = der::expect_tag(enumerated_reason, Tag::Enum)?;
Self::try_from(value.value().read_all(Error::BadDer, |reason| {
reason.read_byte().map_err(|_| Error::BadDer)
})?)
})
}
}
impl TryFrom<u8> for RevocationReason {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(RevocationReason::Unspecified),
1 => Ok(RevocationReason::KeyCompromise),
2 => Ok(RevocationReason::CaCompromise),
3 => Ok(RevocationReason::AffiliationChanged),
4 => Ok(RevocationReason::Superseded),
5 => Ok(RevocationReason::CessationOfOperation),
6 => Ok(RevocationReason::CertificateHold),
8 => Ok(RevocationReason::RemoveFromCrl),
9 => Ok(RevocationReason::PrivilegeWithdrawn),
10 => Ok(RevocationReason::AaCompromise),
_ => Err(Error::UnsupportedRevocationReason),
}
}
}
mod private {
pub trait Sealed {}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use crate::{Error, RevocationReason};
#[test]
fn revocation_reasons() {
let testcases: Vec<(u8, RevocationReason)> = vec![
(0, RevocationReason::Unspecified),
(1, RevocationReason::KeyCompromise),
(2, RevocationReason::CaCompromise),
(3, RevocationReason::AffiliationChanged),
(4, RevocationReason::Superseded),
(5, RevocationReason::CessationOfOperation),
(6, RevocationReason::CertificateHold),
(8, RevocationReason::RemoveFromCrl),
(9, RevocationReason::PrivilegeWithdrawn),
(10, RevocationReason::AaCompromise),
];
for tc in testcases.iter() {
let (id, expected) = tc;
let actual = <u8 as TryInto<RevocationReason>>::try_into(*id)
.expect("unexpected reason code conversion error");
assert_eq!(actual, *expected);
#[cfg(feature = "alloc")]
{
println!("{:?}", actual);
}
}
let res = <u8 as TryInto<RevocationReason>>::try_into(7);
assert!(matches!(res, Err(Error::UnsupportedRevocationReason)));
}
#[test]
#[cfg(feature = "alloc")]
#[allow(clippy::redundant_clone, clippy::clone_on_copy)]
fn test_derived_traits() {
let crl = crate::crl::BorrowedCertRevocationList::from_der(include_bytes!(
"../tests/crls/crl.valid.der"
))
.unwrap();
println!("{:?}", crl);
let owned_crl = crl.to_owned().unwrap();
println!("{:?}", owned_crl); let _ = owned_crl.clone();
let mut revoked_certs = crl.into_iter();
println!("{:?}", revoked_certs);
let revoked_cert = revoked_certs.next().unwrap().unwrap();
println!("{:?}", revoked_cert);
let owned_revoked_cert = revoked_cert.to_owned();
println!("{:?}", owned_revoked_cert); let _ = owned_revoked_cert.clone(); }
}