///|
/// An algorithm identifier (RFC 5280, 4.1.1.2).
pub struct AlgorithmIdentifier {
algorithm : @asn1.ObjectIdentifier
/// The algorithm parameters, which are algorithm specific and may be absent.
parameters : @asn1.Der?
}
///|
/// The display name of the algorithm.
pub fn AlgorithmIdentifier::name(self : AlgorithmIdentifier) -> String {
algorithm_display_name(self.algorithm)
}
///|
/// A validity period (RFC 5280, 4.1.2.5).
pub struct Validity {
not_before : @asn1.Asn1Time
not_after : @asn1.Asn1Time
}
///|
/// A subject public key (RFC 5280, 4.1.2.7).
pub struct SubjectPublicKeyInfo {
algorithm : AlgorithmIdentifier
/// The BIT STRING payload, with the unused trailing bits already cleared.
public_key : Bytes
unused_bits : Int
}
///|
/// The number of bits in the public key BIT STRING payload.
///
/// For RSA keys this is the size of the encoded `RSAPublicKey` structure, not
/// the key size; use `rsa_modulus_bits` for that.
pub fn SubjectPublicKeyInfo::public_key_bits(
self : SubjectPublicKeyInfo,
) -> Int {
self.public_key.length() * 8 - self.unused_bits
}
///|
/// For an RSA key, the modulus size in bits.
///
/// The modulus lives in the PKCS#1 `RSAPublicKey` structure carried inside the
/// BIT STRING.
pub fn SubjectPublicKeyInfo::rsa_modulus_bits(
self : SubjectPublicKeyInfo,
) -> Int raise X509Error {
let value = @asn1.DerReader::from_bytes(self.public_key).read_single() catch {
err => raise InvalidDer(err.to_string())
}
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() == 0 {
raise Malformed("RSAPublicKey must contain a modulus")
}
let modulus = children[0].as_integer() catch {
err => raise InvalidDer(err.to_string())
}
modulus.bit_length()
}
///|
/// A certificate extension (RFC 5280, 4.1.2.9).
pub struct Extension {
oid : @asn1.ObjectIdentifier
critical : Bool
/// The DER content of `extnValue`.
value : Bytes
}
///|
/// Builds an extension value.
///
/// `value` is the DER content of `extnValue`, as it appears on a certificate.
pub fn Extension::new(
oid : @asn1.ObjectIdentifier,
value : Bytes,
critical? : Bool = false,
) -> Extension {
{ oid, critical, value, }
}
///|
/// The display name of the extension.
pub fn Extension::name(self : Extension) -> String {
extension_display_name(self.oid)
}
///|
/// Decodes the extension value, whose shape depends on the extension.
pub fn Extension::decode_value(self : Extension) -> @asn1.Der raise X509Error {
@asn1.DerReader::from_bytes(self.value).read_single() catch {
err => raise InvalidDer(err.to_string())
}
}
///|
/// The signed body of a certificate (RFC 5280, 4.1.2).
pub struct TbsCertificate {
version : Int
serial_number : @bigint.BigInt
signature_algorithm : AlgorithmIdentifier
issuer : Name
validity : Validity
subject : Name
subject_public_key_info : SubjectPublicKeyInfo
extensions : Array[Extension]
}
///|
/// A parsed certificate (RFC 5280, 4.1).
pub struct Certificate {
/// The original DER encoding, so a certificate can be round tripped.
raw : Bytes
tbs : TbsCertificate
signature_algorithm : AlgorithmIdentifier
signature : Bytes
}
///|
/// Whether the issuer and subject names are identical.
pub fn Certificate::is_self_signed(self : Certificate) -> Bool {
self.tbs.issuer.to_string() == self.tbs.subject.to_string()
}
///|
/// Parses an algorithm identifier.
fn parse_algorithm_identifier(
value : @asn1.Der,
) -> AlgorithmIdentifier raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() == 0 || children.length() > 2 {
raise Malformed("AlgorithmIdentifier must have one or two elements")
}
let algorithm = children[0].as_object_identifier() catch {
err => raise InvalidDer(err.to_string())
}
{
algorithm,
parameters: if children.length() == 2 {
Some(children[1])
} else {
None
},
}
}
///|
/// Renders an attribute value, falling back to the RFC 4514 `#` hexadecimal
/// form for value types that are not strings.
fn attribute_value_text(value : @asn1.Der) -> String {
value.as_string() catch {
_ => {
let encoded = value.encode() catch { _ => b"" }
"#" + @hex.encode(encoded)
}
}
}
///|
/// Parses an X.501 name (an RDNSequence).
fn parse_name(value : @asn1.Der) -> Name raise X509Error {
let rdn_elements = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let rdns : Array[RelativeDistinguishedName] = []
for rdn_element in rdn_elements {
let atv_elements = rdn_element.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let attributes : Array[AttributeTypeAndValue] = []
for atv in atv_elements {
let parts = atv.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if parts.length() != 2 {
raise Malformed("AttributeTypeAndValue must have two elements")
}
let oid = parts[0].as_object_identifier() catch {
err => raise InvalidDer(err.to_string())
}
attributes.push({ oid, value: attribute_value_text(parts[1]), })
}
rdns.push({ attributes, })
}
{ rdns, }
}
///|
/// Parses a validity period.
fn parse_validity(value : @asn1.Der) -> Validity raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() != 2 {
raise Malformed("Validity must contain exactly two times")
}
{
not_before: children[0].as_time() catch {
err => raise InvalidDer(err.to_string())
},
not_after: children[1].as_time() catch {
err => raise InvalidDer(err.to_string())
},
}
}
///|
/// Parses a subject public key info structure.
fn parse_subject_public_key_info(
value : @asn1.Der,
) -> SubjectPublicKeyInfo raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() != 2 {
raise Malformed("SubjectPublicKeyInfo must have two elements")
}
let algorithm = parse_algorithm_identifier(children[0])
let (unused_bits, public_key) = children[1].as_bit_string() catch {
err => raise InvalidDer(err.to_string())
}
{ algorithm, public_key, unused_bits, }
}
///|
/// Parses a DER `SEQUENCE OF Extension`.
fn parse_extension_list(value : @asn1.Der) -> Array[Extension] raise X509Error {
let items = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let extensions : Array[Extension] = []
for item in items {
let parts = item.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if parts.length() < 2 || parts.length() > 3 {
raise Malformed("Extension must have two or three elements")
}
let oid = parts[0].as_object_identifier() catch {
err => raise InvalidDer(err.to_string())
}
let mut critical = false
let mut index = 1
if parts.length() == 3 {
critical = parts[1].as_boolean() catch {
err => raise InvalidDer(err.to_string())
}
index = 2
}
extensions.push({
oid,
critical,
value: parts[index].as_octet_string() catch {
err => raise InvalidDer(err.to_string())
},
})
}
extensions
}
///|
/// Parses the `[3] EXPLICIT` Extensions field of a certificate.
fn parse_extensions(value : @asn1.Der) -> Array[Extension] raise X509Error {
let list = value.as_explicit(3) catch {
err => raise InvalidDer(err.to_string())
}
parse_extension_list(list)
}
///|
/// Parses the TBSCertificate.
fn parse_tbs(value : @asn1.Der) -> TbsCertificate raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
let mut index = 0
let mut version = 1
if children[0].tag().cls == @asn1.ContextSpecific {
let inner = children[0].as_explicit(0) catch {
err => raise InvalidDer(err.to_string())
}
let encoded = inner.as_integer() catch {
err => raise InvalidDer(err.to_string())
}
version = encoded.to_int() + 1
if version < 1 || version > 3 {
raise Malformed("unsupported certificate version")
}
index = 1
}
if children.length() < index + 6 {
raise Malformed("TBSCertificate is missing required fields")
}
let serial_number = children[index].as_integer() catch {
err => raise InvalidDer(err.to_string())
}
index = index + 1
let signature_algorithm = parse_algorithm_identifier(children[index])
index = index + 1
let issuer = parse_name(children[index])
index = index + 1
let validity = parse_validity(children[index])
index = index + 1
let subject = parse_name(children[index])
index = index + 1
let subject_public_key_info = parse_subject_public_key_info(children[index])
index = index + 1
let mut extensions : Array[Extension] = []
while index < children.length() {
let extra = children[index]
// [1] issuerUniqueID and [2] subjectUniqueID are ignored.
if extra.tag().cls == @asn1.ContextSpecific && extra.tag().number == 3 {
extensions = parse_extensions(extra)
}
index = index + 1
}
{
version,
serial_number,
signature_algorithm,
issuer,
validity,
subject,
subject_public_key_info,
extensions,
}
}
///|
/// Parses a certificate from its DER encoding.
pub fn parse_certificate(bytes : BytesView) -> Certificate 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())
}
parse_certificate_value(value, raw~)
}
///|
/// Parses a certificate from an already decoded DER value.
///
/// `raw` is the encoding that the value was decoded from, kept so the original
/// bytes can be recovered.
pub fn parse_certificate_value(
value : @asn1.Der,
raw? : Bytes = b"",
) -> Certificate raise X509Error {
let children = value.as_sequence() catch {
err => raise InvalidDer(err.to_string())
}
if children.length() != 3 {
raise Malformed("Certificate must contain three elements")
}
let tbs = parse_tbs(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 from PEM text.
pub fn parse_pem_certificate(text : StringView) -> Certificate raise X509Error {
let block = @pem.decode_pem_single(text) catch {
err => raise InvalidPem(err.to_string())
}
parse_certificate(block.data[:])
}
///|
/// Describes the public key algorithm and, for RSA, the modulus size.
fn key_description(info : SubjectPublicKeyInfo) -> String {
let name = info.algorithm.name()
let bits = if info.algorithm.algorithm.to_string() == "1.2.840.113549.1.1.1" {
info.rsa_modulus_bits() catch {
_ => info.public_key_bits()
}
} else {
info.public_key_bits()
}
name + ", " + bits.to_string() + " bits"
}
///|
/// Renders a multi-line summary of the certificate, similar to the output of
/// `openssl x509 -text`.
pub fn Certificate::to_string(self : Certificate) -> String {
let buffer = StringBuilder()
buffer.write_string("Certificate\n")
buffer.write_string(" Version: v")
buffer.write_string(self.tbs.version.to_string())
buffer.write_string("\n")
buffer.write_string(" Serial number: ")
buffer.write_string(self.tbs.serial_number.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(" Validity\n")
buffer.write_string(" Not before: ")
buffer.write_string(self.tbs.validity.not_before.to_string())
buffer.write_string("\n")
buffer.write_string(" Not after: ")
buffer.write_string(self.tbs.validity.not_after.to_string())
buffer.write_string("\n")
buffer.write_string(" Subject: ")
buffer.write_string(self.tbs.subject.to_string())
buffer.write_string("\n")
buffer.write_string(" Public key: ")
buffer.write_string(key_description(self.tbs.subject_public_key_info))
buffer.write_string("\n")
buffer.write_string(" 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")
}
let alternative_names = self.subject_alt_names()
if alternative_names.length() > 0 {
buffer.write_string(" Subject alternative names (")
buffer.write_string(alternative_names.length().to_string())
buffer.write_string(")\n")
for name in alternative_names {
buffer.write_string(" ")
buffer.write_string(name.kind())
buffer.write_string(": ")
buffer.write_string(name.to_string())
buffer.write_string("\n")
}
}
buffer.to_string()
}