use std::time::SystemTime;
use rama_core::error::{BoxError, ErrorContext};
use yasna::{
Tag,
models::{GeneralizedTime, ObjectIdentifier, UTCTime},
};
fn oid_ecdsa_sha256() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2])
}
fn oid_rsa_sha256() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[1, 2, 840, 113549, 1, 1, 11])
}
fn oid_authority_key_id() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[2, 5, 29, 35])
}
fn oid_crl_number() -> ObjectIdentifier {
ObjectIdentifier::from_slice(&[2, 5, 29, 20])
}
#[derive(Debug, Clone, Copy)]
pub enum CrlSignatureAlgorithm {
EcdsaSha256,
RsaSha256,
}
#[derive(Debug, Clone, Copy)]
pub struct RevokedEntry<'a> {
pub serial: &'a [u8],
pub revocation_date: SystemTime,
}
pub struct CrlParams<'a> {
pub issuer_name_der: &'a [u8],
pub authority_key_id: &'a [u8],
pub this_update: SystemTime,
pub next_update: SystemTime,
pub crl_number: u64,
pub revoked: &'a [RevokedEntry<'a>],
}
pub fn build_crl(
params: &CrlParams<'_>,
alg: CrlSignatureAlgorithm,
sign_tbs: impl FnOnce(&[u8]) -> Result<Vec<u8>, BoxError>,
) -> Result<Vec<u8>, BoxError> {
let this_update = x509_time(params.this_update)?;
let next_update = x509_time(params.next_update)?;
let revoked = params
.revoked
.iter()
.map(|e| Ok::<_, BoxError>((e.serial, x509_time(e.revocation_date)?)))
.collect::<Result<Vec<_>, _>>()?;
let aki_value = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next()
.write_tagged_implicit(Tag::context(0), |w| w.write_bytes(params.authority_key_id));
});
});
let crl_number_value = yasna::construct_der(|w| w.write_u64(params.crl_number));
let tbs_der = yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_i64(1);
write_alg(w.next(), alg);
w.next().write_der(params.issuer_name_der);
write_time(w.next(), &this_update);
write_time(w.next(), &next_update);
if !revoked.is_empty() {
w.next().write_sequence_of(|w| {
for (serial, date) in &revoked {
w.next().write_sequence(|w| {
w.next().write_bigint_bytes(serial, true);
write_time(w.next(), date);
});
}
});
}
w.next().write_tagged(Tag::context(0), |w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_oid(&oid_authority_key_id());
w.next().write_bytes(&aki_value);
});
w.next().write_sequence(|w| {
w.next().write_oid(&oid_crl_number());
w.next().write_bytes(&crl_number_value);
});
});
});
});
});
let signature = sign_tbs(&tbs_der)?;
Ok(yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_der(&tbs_der);
write_alg(w.next(), alg);
w.next().write_bitvec_bytes(&signature, signature.len() * 8);
});
}))
}
#[must_use]
pub fn crl_distribution_point_der(uri: &str) -> Vec<u8> {
yasna::construct_der(|w| {
w.write_sequence(|w| {
w.next().write_sequence(|w| {
w.next().write_tagged(Tag::context(0), |w| {
w.write_tagged_implicit(Tag::context(0), |w| {
w.write_sequence(|w| {
w.next().write_tagged_implicit(Tag::context(6), |w| {
w.write_bytes(uri.as_bytes());
});
});
});
});
});
});
})
}
fn write_alg(w: yasna::DERWriter<'_>, alg: CrlSignatureAlgorithm) {
w.write_sequence(|w| match alg {
CrlSignatureAlgorithm::EcdsaSha256 => {
w.next().write_oid(&oid_ecdsa_sha256());
}
CrlSignatureAlgorithm::RsaSha256 => {
w.next().write_oid(&oid_rsa_sha256());
w.next().write_null();
}
});
}
enum X509Time {
Utc(UTCTime),
General(GeneralizedTime),
}
fn x509_time(t: SystemTime) -> Result<X509Time, BoxError> {
let secs = t
.duration_since(SystemTime::UNIX_EPOCH)
.context("crl: timestamp before unix epoch")?
.as_secs();
let odt = time::OffsetDateTime::from_unix_timestamp(secs as i64)
.map_err(|e| BoxError::from(format!("crl: invalid timestamp: {e}")))?;
Ok(if odt.year() < 2050 {
X509Time::Utc(UTCTime::from_datetime(odt))
} else {
X509Time::General(GeneralizedTime::from_datetime(odt))
})
}
fn write_time(w: yasna::DERWriter<'_>, t: &X509Time) {
match t {
X509Time::Utc(u) => w.write_utctime(u),
X509Time::General(g) => w.write_generalized_time(g),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
const T0: u64 = 1_800_000_000;
fn params<'a>(issuer: &'a [u8], revoked: &'a [RevokedEntry<'a>]) -> CrlParams<'a> {
CrlParams {
issuer_name_der: issuer,
authority_key_id: &[0xAB; 20],
this_update: SystemTime::UNIX_EPOCH + Duration::from_secs(T0),
next_update: SystemTime::UNIX_EPOCH + Duration::from_secs(T0 + 7 * 86_400),
crl_number: 1,
revoked,
}
}
#[test]
fn builds_wellformed_empty_crl() {
let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
let mut signed_tbs: Vec<u8> = Vec::new();
let der = build_crl(
¶ms(&issuer, &[]),
CrlSignatureAlgorithm::EcdsaSha256,
|tbs| {
signed_tbs = tbs.to_vec();
Ok(vec![0xDE, 0xAD, 0xBE, 0xEF])
},
)
.expect("build crl");
assert!(
!signed_tbs.is_empty(),
"tbsCertList was handed to the signer"
);
yasna::parse_der(&der, |r| {
r.read_sequence(|r| {
let tbs = r.next().read_der()?;
assert_eq!(tbs, signed_tbs, "embedded tbs == signed tbs");
r.next().read_sequence(|r| {
let oid = r.next().read_oid()?;
assert_eq!(oid, oid_ecdsa_sha256());
Ok(())
})?;
let (sig, bits) = r.next().read_bitvec_bytes()?;
assert_eq!(sig, vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(bits, 32);
Ok(())
})
})
.expect("parse CertificateList");
}
#[test]
fn revoked_serial_present() {
let issuer = yasna::construct_der(|w| w.write_sequence(|_| {}));
let revoked = [RevokedEntry {
serial: &[0x12, 0x34, 0x56],
revocation_date: SystemTime::UNIX_EPOCH + Duration::from_secs(T0),
}];
let der = build_crl(
¶ms(&issuer, &revoked),
CrlSignatureAlgorithm::RsaSha256,
|_| Ok(vec![0x00]),
)
.expect("build crl");
let serials = yasna::parse_der(&der, |r| {
r.read_sequence(|r| {
let serials = r.next().read_sequence(|r| {
let _version = r.next().read_i64()?;
let _alg = r.next().read_der()?;
let _issuer = r.next().read_der()?;
let _this = r.next().read_der()?;
let _next = r.next().read_der()?;
let mut serials: Vec<Vec<u8>> = Vec::new();
r.next().read_sequence_of(|r| {
r.read_sequence(|r| {
let (serial, _pos) = r.next().read_bigint_bytes()?;
let _date = r.next().read_der()?;
serials.push(serial);
Ok(())
})
})?;
let _exts = r.next().read_der()?;
Ok(serials)
})?;
let _alg = r.next().read_der()?;
let _sig = r.next().read_bitvec_bytes()?;
Ok(serials)
})
})
.expect("parse tbsCertList");
assert_eq!(serials, vec![vec![0x12, 0x34, 0x56]]);
}
#[test]
fn crl_distribution_point_is_a_sequence_carrying_the_uri() {
let uri = "http://127.0.0.1:9999/abc.crl";
let der = crl_distribution_point_der(uri);
yasna::parse_der(&der, |r| {
r.read_sequence(|r| {
let _dp = r.next().read_der()?;
Ok(())
})
})
.expect("CRLDistributionPoints is a SEQUENCE");
assert!(
der.windows(uri.len()).any(|w| w == uri.as_bytes()),
"URI present in the distribution point"
);
}
}