// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// The DER inside a PEM envelope (RFC 7468).
///
/// Everything outside the `-----BEGIN ...-----` and `-----END ...-----` lines
/// is dropped and the rest is base64-decoded. The label is not checked: what
/// the octets are is the caller's question, and a `CERTIFICATE` and a
/// `PUBLIC KEY` reach different readers from here.
///
/// Whitespace inside the body is ignored, which is what RFC 7468 §2 asks of a
/// parser and what makes a CRLF file read the same as an LF one.
pub fn pem(text : String) -> Bytes raise @asn1.Refused {
let body = StringBuilder()
let mut opened = false
let mut closed = false
for line in text.split("\n") {
let l = line.to_owned()
if l.has_prefix("-----BEGIN") {
opened = true
} else if l.has_prefix("-----END") {
closed = true
break
} else if opened {
body.write_string(l)
}
}
if !opened || !closed {
raise @asn1.Malformed("not a PEM envelope")
}
let der = @base64.decode_lossy(body.to_string())
if der.length() == 0 {
raise @asn1.Malformed("PEM envelope carries no DER")
}
der
}
///|
/// The DER of every PEM envelope in `text` with the label `label`, in order — a CA bundle such
/// as `ca-certificates.crt` or curl's `cacert.pem` is a run of `CERTIFICATE` envelopes with prose
/// between them. Envelopes with another label are passed over.
pub fn pems(
text : String,
label~ : String = "CERTIFICATE",
) -> Array[Bytes] raise @asn1.Refused {
let out : Array[Bytes] = []
let opening = "-----BEGIN " + label + "-----"
let closing = "-----END " + label + "-----"
let mut body = StringBuilder()
let mut inside = false
for line in text.split("\n") {
let l = line.trim_end(chars=" \t\r").to_owned()
if !inside {
if l == opening {
inside = true
body = StringBuilder()
}
} else if l == closing {
let der = @base64.decode(body.to_string()) catch {
_ => raise @asn1.Malformed("PEM body is not base64")
}
if der.length() == 0 {
raise @asn1.Malformed("PEM envelope carries no DER")
}
out.push(der)
inside = false
} else if l.has_prefix("-----") {
raise @asn1.Malformed("PEM envelope is not closed")
} else {
body.write_string(l.trim(chars=" \t").to_owned())
}
}
if inside {
raise @asn1.Malformed("PEM envelope is not closed")
}
out
}