///|
/// Parses every PEM encoded certificate in a bundle (a file that may hold a
/// chain), skipping blocks with other labels such as private keys.
pub fn parse_certificate_bundle(
text : StringView,
) -> Array[Certificate] raise X509Error {
let blocks = @pem.decode_pem(text) catch {
err => raise InvalidPem(err.to_string())
}
let certificates : Array[Certificate] = []
for block in blocks {
if block.label == "CERTIFICATE" {
certificates.push(parse_certificate(block.data[:]))
}
}
certificates
}
///|
/// The subject key identifier extension, when present.
pub fn Certificate::subject_key_identifier(self : Certificate) -> Bytes? {
let extension = match self.extension("2.5.29.14") {
Some(extension) => extension
None => return None
}
let decoded = extension.decode() catch { _ => return None }
match decoded {
SubjectKeyIdentifier(identifier) => Some(identifier)
_ => None
}
}
///|
/// The authority key identifier extension, when present.
pub fn Certificate::authority_key_identifier(
self : Certificate,
) -> AuthorityKeyIdentifier? {
let extension = match self.extension("2.5.29.35") {
Some(extension) => extension
None => return None
}
let decoded = extension.decode() catch { _ => return None }
match decoded {
AuthorityKeyIdentifier(identifier) => Some(identifier)
_ => None
}
}
///|
/// The certificate in `pool` that issued this one.
///
/// Candidates must carry the issuer's subject name. When both sides have key
/// identifier extensions, an authority key identifier that matches the
/// candidate's subject key identifier wins; otherwise the first name match is
/// used. No signature is checked.
pub fn Certificate::issuer_in(
self : Certificate,
pool : Array[Certificate],
) -> Certificate? {
let authority = self.authority_key_identifier()
let mut fallback : Certificate? = None
for candidate in pool {
if candidate.raw == self.raw {
continue
}
if candidate.tbs.subject.to_string() != self.tbs.issuer.to_string() {
continue
}
match authority {
Some(identifier) =>
match identifier.key_identifier {
Some(key_identifier) =>
match candidate.subject_key_identifier() {
Some(subject_identifier) =>
if key_identifier == subject_identifier {
return Some(candidate)
}
None => ()
}
None => ()
}
None => ()
}
if fallback is None {
fallback = Some(candidate)
}
}
fallback
}
///|
/// Walks the issuer links inside `pool` and returns the chain, leaf first.
///
/// The walk stops at a self-signed certificate, when no issuer is found, or
/// when the next issuer is already in the chain. This is a structural walk, so
/// no signature is verified.
pub fn build_chain(
leaf : Certificate,
pool : Array[Certificate],
) -> Array[Certificate] {
let chain : Array[Certificate] = [leaf]
let mut current = leaf
while !current.is_self_signed() {
let issuer = match current.issuer_in(pool) {
Some(issuer) => issuer
None => return chain
}
for existing in chain {
if existing.raw == issuer.raw {
return chain
}
}
chain.push(issuer)
current = issuer
}
chain
}
///|
/// Renders one line per certificate, from leaf to root.
pub fn describe_chain(chain : Array[Certificate]) -> String {
let buffer = StringBuilder()
for i, certificate in chain {
buffer.write_string(i.to_string())
buffer.write_string(": ")
buffer.write_string(certificate.tbs.subject.to_string())
buffer.write_string("\n")
buffer.write_string(" issued by ")
buffer.write_string(certificate.tbs.issuer.to_string())
buffer.write_string(
if certificate.is_self_signed() {
" (self-signed)"
} else {
""
},
)
buffer.write_string("\n")
}
buffer.to_string()
}