///|
/// A revoked certificate entry (RFC 5280, 5.3).
pub struct RevokedCertificate {
serial_number : @bigint.BigInt
revocation_date : @asn1.Asn1Time
extensions : Array[Extension]
}
///|
/// The signed body of a certificate revocation list (RFC 5280, 5.1.2).
pub struct TbsCertList {
version : Int
signature_algorithm : AlgorithmIdentifier
issuer : Name
this_update : @asn1.Asn1Time
next_update : @asn1.Asn1Time?
revoked_certificates : Array[RevokedCertificate]
extensions : Array[Extension]
}
///|
/// A certificate revocation list (RFC 5280, 5.1).
pub struct CertificateList {
/// The original DER encoding, so a CRL can be round tripped.
raw : Bytes
tbs : TbsCertList
signature_algorithm : AlgorithmIdentifier
signature : Bytes
}
///|
/// The name of a revocation reason code (RFC 5280, 5.3.1).
pub fn revocation_reason_name(code : Int) -> String {
match code {
0 => "unspecified"
1 => "keyCompromise"
2 => "cACompromise"
3 => "affiliationChanged"
4 => "superseded"
5 => "cessationOfOperation"
6 => "certificateHold"
8 => "removeFromCRL"
9 => "privilegeWithdrawn"
10 => "aACompromise"
_ => "unknown"
}
}
///|
/// The revocation reason from the `cRLReason` entry extension, when present.
pub fn RevokedCertificate::reason(self : RevokedCertificate) -> String? {
for extension in self.extensions {
if extension.oid.to_string() == "2.5.29.21" {
let code = decode_extension_integer(extension) catch { _ => return None }
return Some(revocation_reason_name(code))
}
}
None
}
///|
/// Decodes an extension whose value is a bare INTEGER or ENUMERATED.
fn decode_extension_integer(extension : Extension) -> Int raise X509Error {
let value = extension.decode_value()
let content = value.content() catch {
err => raise InvalidDer(err.to_string())
}
(@asn1.decode_integer(content) catch {
err => raise InvalidDer(err.to_string())
}).to_int()
}
///|
/// Finds an extension by its dotted object identifier.
pub fn CertificateList::extension(
self : CertificateList,
oid : String,
) -> Extension? {
for extension in self.tbs.extensions {
if extension.oid.to_string() == oid {
return Some(extension)
}
}
None
}
///|
/// The CRL number from the `cRLNumber` extension, when present.
pub fn CertificateList::crl_number(self : CertificateList) -> @bigint.BigInt? {
let extension = match self.extension("2.5.29.20") {
Some(extension) => extension
None => return None
}
let value = extension.decode_value() catch { _ => return None }
let content = value.content() catch { _ => return None }
Some(@asn1.decode_integer(content)) catch {
_ => None
}
}
///|
/// The entry for a serial number, or `None` when the certificate is not
/// revoked.
pub fn CertificateList::is_revoked(
self : CertificateList,
serial_number : @bigint.BigInt,
) -> RevokedCertificate? {
for entry in self.tbs.revoked_certificates {
if entry.serial_number == serial_number {
return Some(entry)
}
}
None
}
///|
/// Renders a summary of the CRL.
pub fn CertificateList::to_string(self : CertificateList) -> String {
let buffer = StringBuilder()
buffer.write_string("Certificate revocation list\n")
buffer.write_string(" Version: ")
buffer.write_string(self.tbs.version.to_string())
buffer.write_string("\n")
buffer.write_string(" Signature algorithm: ")
buffer.write_string(self.signature_algorithm.name())
buffer.write_string(" (")
buffer.write_string(self.signature_algorithm.algorithm.to_string())
buffer.write_string(")\n")
buffer.write_string(" Issuer: ")
buffer.write_string(self.tbs.issuer.to_string())
buffer.write_string("\n")
buffer.write_string(" This update: ")
buffer.write_string(self.tbs.this_update.to_string())
buffer.write_string("\n")
match self.tbs.next_update {
Some(next) => {
buffer.write_string(" Next update: ")
buffer.write_string(next.to_string())
buffer.write_string("\n")
}
None => ()
}
buffer.write_string(" Revoked certificates (")
buffer.write_string(self.tbs.revoked_certificates.length().to_string())
buffer.write_string(")\n")
for entry in self.tbs.revoked_certificates {
buffer.write_string(" serial ")
buffer.write_string(entry.serial_number.to_string())
buffer.write_string(" revoked ")
buffer.write_string(entry.revocation_date.to_string())
match entry.reason() {
Some(reason) => {
buffer.write_string(" reason=")
buffer.write_string(reason)
}
None => ()
}
buffer.write_string("\n")
}
if self.tbs.extensions.length() > 0 {
buffer.write_string(" CRL extensions (")
buffer.write_string(self.tbs.extensions.length().to_string())
buffer.write_string(")\n")
for extension in self.tbs.extensions {
buffer.write_string(" ")
buffer.write_string(extension.name())
buffer.write_string(" (")
buffer.write_string(extension.oid.to_string())
buffer.write_string(") critical=")
buffer.write_string(if extension.critical { "true" } else { "false" })
buffer.write_string("\n")
}
}
match self.crl_number() {
Some(number) => {
buffer.write_string(" CRL number: ")
buffer.write_string(number.to_string())
buffer.write_string("\n")
}
None => ()
}
buffer.write_string(" Signature: ")
buffer.write_string(self.signature.length().to_string())
buffer.write_string(" bytes\n")
buffer.to_string()
}
///|
/// Parses one revoked certificate entry.
fn parse_revoked_certificate(
value : @asn1.Der,
) -> RevokedCertificate raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() < 2 || children.length() > 3 {
raise Malformed(
"a revoked certificate entry must have two or three elements",
)
}
let serial_number = children[0].as_integer() catch {
err => raise InvalidDer(err.to_string())
}
let revocation_date = children[1].as_time() catch {
err => raise InvalidDer(err.to_string())
}
let extensions = if children.length() == 3 {
parse_extension_list(children[2])
} else {
[]
}
{ serial_number, revocation_date, extensions, }
}
///|
/// Parses the TBSCertList.
fn parse_tbs_cert_list(value : @asn1.Der) -> TbsCertList raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let mut index = 0
let mut version = 1
let first_tag = children[0].tag()
if first_tag.cls == @asn1.Universal && first_tag.number == @asn1.TAG_INTEGER {
let encoded = children[0].as_integer() catch {
err => raise InvalidDer(err.to_string())
}
version = encoded.to_int() + 1
if version < 1 || version > 2 {
raise Malformed("unsupported CRL version")
}
index = 1
}
if children.length() < index + 3 {
raise Malformed("TBSCertList is missing required fields")
}
let signature_algorithm = parse_algorithm_identifier(children[index])
index = index + 1
let issuer = parse_name(children[index])
index = index + 1
let this_update = children[index].as_time() catch {
err => raise InvalidDer(err.to_string())
}
index = index + 1
let mut next_update : @asn1.Asn1Time? = None
if index < children.length() {
let tag = children[index].tag()
if tag.cls == @asn1.Universal &&
(
tag.number == @asn1.TAG_UTC_TIME ||
tag.number == @asn1.TAG_GENERALIZED_TIME
) {
next_update = Some(
children[index].as_time() catch {
err => raise InvalidDer(err.to_string())
},
)
index = index + 1
}
}
let revoked_certificates : Array[RevokedCertificate] = []
if index < children.length() {
let tag = children[index].tag()
if tag.cls == @asn1.Universal && tag.number == @asn1.TAG_SEQUENCE {
let entries = children[index].as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
for entry in entries {
revoked_certificates.push(parse_revoked_certificate(entry))
}
index = index + 1
}
}
let mut extensions : Array[Extension] = []
if index < children.length() {
let tag = children[index].tag()
if tag.cls == @asn1.ContextSpecific && tag.number == 0 {
let list = children[index].as_explicit(0) catch {
err => raise InvalidDer(err.to_string())
}
extensions = parse_extension_list(list)
index = index + 1
}
}
if index < children.length() {
raise Malformed("TBSCertList has unexpected trailing elements")
}
{
version,
signature_algorithm,
issuer,
this_update,
next_update,
revoked_certificates,
extensions,
}
}
///|
/// Parses a certificate revocation list from its DER encoding.
pub fn parse_certificate_list(
bytes : BytesView,
) -> CertificateList raise X509Error {
let raw = @buffer.from_bytes(bytes).to_bytes()
let value = @asn1.DerReader::new(bytes).read_single() catch {
err => raise InvalidDer(err.to_string())
}
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() != 3 {
raise Malformed("CertificateList must have three elements")
}
let tbs = parse_tbs_cert_list(children[0])
let signature_algorithm = parse_algorithm_identifier(children[1])
let (unused_bits, signature) = children[2].as_bit_string() catch {
err => raise InvalidDer(err.to_string())
}
if unused_bits != 0 {
raise Malformed("signature BIT STRING must have no unused bits")
}
{ raw, tbs, signature_algorithm, signature, }
}
///|
/// Parses a certificate revocation list from PEM text.
pub fn parse_pem_certificate_list(
text : StringView,
) -> CertificateList raise X509Error {
let block = @pem.decode_pem_single(text) catch {
err => raise InvalidPem(err.to_string())
}
parse_certificate_list(block.data[:])
}