#X.509

    Parsing for the certificate structures defined by RFC 5280.

    #Building a certificate to parse

    The example below assembles a minimal v1 certificate with the asn1 encoder so that the parser can be exercised without a large fixture:

    ///|
    fn demo_certificate() -> Bytes {
    let common_name = try! @asn1.ObjectIdentifier::from_arcs([2L, 5L, 4L, 3L])
    let sha256_rsa = try! @asn1.ObjectIdentifier::from_arcs([
    1L, 2L, 840L, 113549L, 1L, 1L, 11L,
    ])
    let attribute = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(common_name)[:],
    try! @asn1.encode_utf8_string("example.com")[:],
    ])
    let name = try! @asn1.encode_sequence([
    try! @asn1.encode_set_of([attribute[:]])[:],
    ])
    let algorithm = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(sha256_rsa)[:],
    try! @asn1.encode_null()[:],
    ])
    let not_before = try! @asn1.encode_utc_time(
    @asn1.Asn1Time::new(2024, 1, 1, 0, 0, 0),
    )
    let not_after = try! @asn1.encode_generalized_time(
    @asn1.Asn1Time::new(2034, 1, 1, 0, 0, 0),
    )
    let validity = try! @asn1.encode_sequence([not_before[:], not_after[:]])
    let spki = try! @asn1.encode_sequence([
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x01\x02\x03")[:],
    ])
    let tbs = try! @asn1.encode_sequence([
    try! @asn1.encode_int(7)[:],
    algorithm[:],
    name[:],
    validity[:],
    name[:],
    spki[:],
    ])
    try! @asn1.encode_sequence([
    tbs[:],
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x00\x01")[:],
    ])
    }

    #Parsing

    ///|
    test "parse a certificate" {
    let certificate = try! @x509.parse_certificate(demo_certificate())
    inspect(certificate.tbs.version, content="1")
    inspect(certificate.tbs.serial_number.to_string(), content="7")
    inspect(certificate.tbs.subject.to_string(), content="CN=example.com")
    inspect(
    certificate.tbs.validity.not_after.to_string(),
    content="2034-01-01T00:00:00Z",
    )
    inspect(certificate.is_self_signed(), content="true")
    }

    PEM wrapped certificates are handled directly:

    ///|
    test "parse a PEM certificate" {
    let pem_text = try! @pem.encode_pem("CERTIFICATE", demo_certificate())
    let certificate = try! @x509.parse_pem_certificate(pem_text)
    inspect(
    certificate.tbs.subject.find("CN") is Some("example.com"),
    content="true",
    )
    }

    #Extensions

    Extension::decode understands the common extensions; unknown ones stay raw:

    ///|
    test "decode a key usage extension" {
    let extension = @x509.Extension::new(
    try! @asn1.ObjectIdentifier::from_string("2.5.29.15"),
    try! @asn1.encode_bit_string(b"\x80"),
    critical=true,
    )
    inspect(extension.name(), content="keyUsage")
    let decoded = try! extension.decode()
    match decoded {
    @x509.KeyUsage(usage) => {
    inspect(usage.has(@x509.DigitalSignature), content="true")
    inspect(usage.has(@x509.KeyCertSign), content="false")
    }
    _ => panic()
    }
    }

    ///|
    test "decode subject alternative names" {
    let dns_name = try! @asn1.encode_context_primitive(2, b"example.com")
    let general_names = try! @asn1.encode_sequence([dns_name[:]])
    let extension = @x509.Extension::new(
    try! @asn1.ObjectIdentifier::from_string("2.5.29.17"),
    general_names,
    )
    let decoded = try! extension.decode()
    match decoded {
    @x509.SubjectAltName(names) => {
    inspect(names.length(), content="1")
    inspect(names[0].to_string(), content="example.com")
    }
    _ => panic()
    }
    }

    #Certification requests

    PKCS#10 (RFC 2986) requests parse with the same helpers, and their requested extensions decode with the same code as certificate extensions:

    ///|
    fn demo_request() -> Bytes {
    let common_name = try! @asn1.ObjectIdentifier::from_string("2.5.4.3")
    let sha256_rsa = try! @asn1.ObjectIdentifier::from_string(
    "1.2.840.113549.1.1.11",
    )
    let attribute = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(common_name)[:],
    try! @asn1.encode_utf8_string("example.com")[:],
    ])
    let name = try! @asn1.encode_sequence([
    try! @asn1.encode_set_of([attribute[:]])[:],
    ])
    let algorithm = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(sha256_rsa)[:],
    try! @asn1.encode_null()[:],
    ])
    let spki = try! @asn1.encode_sequence([
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x01\x02\x03")[:],
    ])
    let attributes = try! @asn1.encode_tlv(
    @asn1.Tag::new(@asn1.ContextSpecific, 0, constructed=true),
    b""[:],
    )
    let info = try! @asn1.encode_sequence([
    try! @asn1.encode_int(0)[:],
    name[:],
    spki[:],
    attributes[:],
    ])
    try! @asn1.encode_sequence([
    info[:],
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x00\x01")[:],
    ])
    }

    ///|
    test "parse a certification request" {
    let request = try! @x509.parse_certification_request(demo_request())
    inspect(request.info.version, content="0")
    inspect(request.info.subject.to_string(), content="CN=example.com")
    inspect(request.info.attributes.length(), content="0")
    inspect(request.requested_extensions().length(), content="0")
    }

    #Revocation lists

    ///|
    fn demo_crl() -> Bytes {
    let common_name = try! @asn1.ObjectIdentifier::from_string("2.5.4.3")
    let sha256_rsa = try! @asn1.ObjectIdentifier::from_string(
    "1.2.840.113549.1.1.11",
    )
    let attribute = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(common_name)[:],
    try! @asn1.encode_utf8_string("example.com")[:],
    ])
    let name = try! @asn1.encode_sequence([
    try! @asn1.encode_set_of([attribute[:]])[:],
    ])
    let algorithm = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(sha256_rsa)[:],
    try! @asn1.encode_null()[:],
    ])
    let this_update = try! @asn1.encode_utc_time(
    @asn1.Asn1Time::new(2025, 1, 1, 0, 0, 0),
    )
    let tbs = try! @asn1.encode_sequence([algorithm[:], name[:], this_update[:]])
    try! @asn1.encode_sequence([
    tbs[:],
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x00\x01")[:],
    ])
    }

    ///|
    test "parse a revocation list" {
    let crl = try! @x509.parse_certificate_list(demo_crl())
    inspect(crl.tbs.version, content="1")
    inspect(crl.tbs.issuer.to_string(), content="CN=example.com")
    inspect(crl.tbs.this_update.to_string(), content="2025-01-01T00:00:00Z")
    inspect(crl.tbs.revoked_certificates.length(), content="0")
    inspect(crl.crl_number() is None, content="true")
    }

    #Chains

    parse_certificate_bundle reads every certificate in a PEM file, and build_chain walks the issuer links from a leaf up to its root. The walk is structural: no signature is verified. When both sides carry key identifier extensions the authority key identifier is matched against the candidate's subject key identifier; otherwise the issuer and subject names are compared.

    ///|
    /// Builds a certificate issued by `example.com`, so it chains to the one built
    /// by `demo_certificate`.
    fn demo_leaf_certificate() -> Bytes {
    let common_name = try! @asn1.ObjectIdentifier::from_string("2.5.4.3")
    let sha256_rsa = try! @asn1.ObjectIdentifier::from_string(
    "1.2.840.113549.1.1.11",
    )
    let attribute = (cn : String) => {
    try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(common_name)[:],
    try! @asn1.encode_utf8_string(cn)[:],
    ])
    }
    let name = (cn : String) => {
    try! @asn1.encode_sequence([try! @asn1.encode_set_of([attribute(cn)[:]])[:]])
    }
    let algorithm = try! @asn1.encode_sequence([
    try! @asn1.encode_object_identifier(sha256_rsa)[:],
    try! @asn1.encode_null()[:],
    ])
    let not_before = try! @asn1.encode_utc_time(
    @asn1.Asn1Time::new(2024, 1, 1, 0, 0, 0),
    )
    let not_after = try! @asn1.encode_generalized_time(
    @asn1.Asn1Time::new(2034, 1, 1, 0, 0, 0),
    )
    let validity = try! @asn1.encode_sequence([not_before[:], not_after[:]])
    let spki = try! @asn1.encode_sequence([
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x01\x02\x03")[:],
    ])
    let tbs = try! @asn1.encode_sequence([
    try! @asn1.encode_int(8)[:],
    algorithm[:],
    name("example.com")[:],
    validity[:],
    name("leaf.example.com")[:],
    spki[:],
    ])
    try! @asn1.encode_sequence([
    tbs[:],
    algorithm[:],
    try! @asn1.encode_bit_string(b"\x00\x01")[:],
    ])
    }

    ///|
    test "walk a chain" {
    let root = demo_certificate()
    let leaf = demo_leaf_certificate()
    let root_pem = try! @pem.encode_pem("CERTIFICATE", root)
    let leaf_pem = try! @pem.encode_pem("CERTIFICATE", leaf)
    let bundle = root_pem + leaf_pem
    let certificates = try! @x509.parse_certificate_bundle(bundle)
    inspect(certificates.length(), content="2")
    inspect(certificates[0].is_self_signed(), content="true")
    inspect(certificates[1].issuer_in(certificates) is Some(_), content="true")
    }

    X509Error

    pub(all) suberror X509Error {
    InvalidDer(String)
    InvalidPem(String)
    Malformed(String)
    } derive(
    Debug
    )

    Errors raised while parsing X.509 structures.

    X509Error::to_string

    fn X509Error::to_string(self : X509Error) -> String

    Renders the error as a human readable message.

    AlgorithmIdentifier

    An algorithm identifier (RFC 5280, 4.1.1.2).

    AlgorithmIdentifier::name

    fn AlgorithmIdentifier::name(self : AlgorithmIdentifier) -> String

    The display name of the algorithm.

    Attribute

    An attribute from a certification request (RFC 2986, 4.1).

    The values are kept as decoded DER because their shape depends on the attribute type.

    AttributeTypeAndValue

    pub struct AttributeTypeAndValue {
    oid :
    ObjectIdentifier

    value : String
    }

    One attribute inside a relative distinguished name.

    AuthorityKeyIdentifier

    pub struct AuthorityKeyIdentifier {
    key_identifier : Bytes?
    authority_cert_issuer : Array[GeneralName]?
    authority_cert_serial :
    BigInt
    ?
    }

    The authorityKeyIdentifier extension (RFC 5280, 4.2.1.1).

    BasicConstraints

    pub struct BasicConstraints {
    ca : Bool
    path_len : Int?
    }

    The basicConstraints extension (RFC 5280, 4.2.1.9).

    Certificate

    pub struct Certificate {
    raw : Bytes
    tbs : TbsCertificate
    signature_algorithm : AlgorithmIdentifier
    signature : Bytes
    }

    A parsed certificate (RFC 5280, 4.1).

    Certificate::authority_key_identifier

    fn Certificate::authority_key_identifier(self : Certificate) -> AuthorityKeyIdentifier?

    The authority key identifier extension, when present.

    Certificate::basic_constraints

    fn Certificate::basic_constraints(self : Certificate) -> BasicConstraints?

    The basic constraints, or None when the extension is absent.

    Certificate::extended_key_usage

    The extended key usages, or None when the extension is absent.

    Certificate::extension

    fn Certificate::extension(self : Certificate, oid : String) -> Extension?

    Finds an extension by its dotted object identifier.

    Certificate::is_ca

    fn Certificate::is_ca(self : Certificate) -> Bool

    Whether the certificate is a CA, according to basicConstraints.

    Certificate::is_self_signed

    fn Certificate::is_self_signed(self : Certificate) -> Bool

    Whether the issuer and subject names are identical.

    Certificate::issuer_in

    fn Certificate::issuer_in(self : Certificate, pool : Array[Certificate]) -> Certificate?

    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.

    Certificate::key_usage

    fn Certificate::key_usage(self : Certificate) -> KeyUsage?

    The key usage, or None when the extension is absent.

    Certificate::subject_alt_names

    fn Certificate::subject_alt_names(self : Certificate) -> Array[GeneralName]

    The subject alternative names, or an empty array when the extension is absent.

    Certificate::subject_key_identifier

    fn Certificate::subject_key_identifier(self : Certificate) -> Bytes?

    The subject key identifier extension, when present.

    Certificate::to_string

    fn Certificate::to_string(self : Certificate) -> String

    Renders a multi-line summary of the certificate, similar to the output of openssl x509 -text.

    CertificateList

    pub struct CertificateList {
    raw : Bytes
    tbs : TbsCertList
    signature_algorithm : AlgorithmIdentifier
    signature : Bytes
    }

    A certificate revocation list (RFC 5280, 5.1).

    CertificateList::crl_number

    The CRL number from the cRLNumber extension, when present.

    CertificateList::extension

    fn CertificateList::extension(self : CertificateList, oid : String) -> Extension?

    Finds an extension by its dotted object identifier.

    CertificateList::is_revoked

    The entry for a serial number, or None when the certificate is not revoked.

    CertificateList::to_string

    fn CertificateList::to_string(self : CertificateList) -> String

    Renders a summary of the CRL.

    CertificationRequest

    pub struct CertificationRequest {
    raw : Bytes
    info : CertificationRequestInfo
    signature_algorithm : AlgorithmIdentifier
    signature : Bytes
    }

    A PKCS#10 certification request (RFC 2986, 4.2).

    CertificationRequest::attribute

    fn CertificationRequest::attribute(self : CertificationRequest, oid : String) -> Attribute?

    Finds an attribute by its dotted object identifier.

    CertificationRequest::requested_extensions

    fn CertificationRequest::requested_extensions(self : CertificationRequest) -> Array[Extension]

    The extensions requested through the extensionRequest attribute, or an empty array when that attribute is absent or malformed.

    CertificationRequest::subject_alt_names

    The subject alternative names requested with the certificate.

    CertificationRequest::to_string

    fn CertificationRequest::to_string(self : CertificationRequest) -> String

    Renders a summary of the request.

    CertificationRequestInfo

    pub struct CertificationRequestInfo {
    version : Int
    subject : Name
    subject_public_key_info : SubjectPublicKeyInfo
    attributes : Array[Attribute]
    }

    The subject, public key and attributes being requested (RFC 2986, 4.1).

    Extension

    pub struct Extension {
    oid :
    ObjectIdentifier

    critical : Bool
    value : Bytes
    }

    A certificate extension (RFC 5280, 4.1.2.9).

    Extension::decode

    fn Extension::decode(self : Extension) -> ExtensionValue raise X509Error

    Decodes the extension value for the extensions this library understands.

    Extension::decode_value

    Decodes the extension value, whose shape depends on the extension.

    Extension::name

    fn Extension::name(self : Extension) -> String

    The display name of the extension.

    Extension::new

    Builds an extension value.

    value is the DER content of extnValue, as it appears on a certificate.

    ExtensionValue

    pub(all) enum ExtensionValue {
    SubjectAltName(Array[GeneralName])
    IssuerAltName(Array[GeneralName])
    KeyUsage(KeyUsage)
    BasicConstraints(BasicConstraints)
    ExtendedKeyUsage(Array[
    ObjectIdentifier
    ])
    SubjectKeyIdentifier(Bytes)
    AuthorityKeyIdentifier(AuthorityKeyIdentifier)
    Other
    }

    A decoded extension value, for the extensions that this library understands.

    GeneralName

    pub(all) enum GeneralName {
    OtherName(Bytes)
    Rfc822Name(String)
    DnsName(String)
    X400Address(Bytes)
    DirectoryName(Name)
    EdiPartyName(Bytes)
    UniformResourceIdentifier(String)
    IpAddress(Bytes)
    RegisteredId(
    ObjectIdentifier
    )
    }

    A general name (RFC 5280, 4.2.1.6).

    The address forms that this library does not model (otherName, x400Address and ediPartyName) keep their raw DER content.

    GeneralName::kind

    fn GeneralName::kind(self : GeneralName) -> String

    A short kind label for a general name, used in summaries.

    GeneralName::to_string

    fn GeneralName::to_string(self : GeneralName) -> String

    Renders a general name in the form used by certificate display tools.

    KeyUsage

    pub struct KeyUsage {
    value : Bytes
    }

    The keyUsage extension (RFC 5280, 4.2.1.3).

    KeyUsage::has

    fn KeyUsage::has(self : KeyUsage, bit : KeyUsageBit) -> Bool

    Whether a named bit is set.

    KeyUsage::to_string

    fn KeyUsage::to_string(self : KeyUsage) -> String

    Renders the set bits as a comma separated list.

    KeyUsageBit

    pub(all) enum KeyUsageBit {
    DigitalSignature
    NonRepudiation
    KeyEncipherment
    DataEncipherment
    KeyAgreement
    KeyCertSign
    CrlSign
    EncipherOnly
    DecipherOnly
    }

    The named bits of the keyUsage extension (RFC 5280, 4.2.1.3).

    KeyUsageBit::name

    fn KeyUsageBit::name(self : KeyUsageBit) -> String

    The display name of a key usage bit.

    Name

    pub struct Name {
    rdns : Array[RelativeDistinguishedName]
    }

    An X.501 name as it appears in a certificate: a sequence of RDNs.

    Name::find

    fn Name::find(self : Name, short_name : String) -> String?

    Returns the value of the first attribute with the given short name, for example "CN".

    Name::is_empty

    fn Name::is_empty(self : Name) -> Bool

    Whether the name has no components.

    Name::to_string

    fn Name::to_string(self : Name) -> String

    Renders the name in the RFC 4514 form, for example CN=example.com,O=Acme,C=CN.

    RFC 4514 prints the components in the reverse of their DER order, so the most specific attribute comes first.

    RelativeDistinguishedName

    pub struct RelativeDistinguishedName {
    attributes : Array[AttributeTypeAndValue]
    }

    A relative distinguished name: a set of attributes that must all hold.

    RevokedCertificate

    pub struct RevokedCertificate {
    serial_number :
    BigInt

    revocation_date :
    Asn1Time

    extensions : Array[Extension]
    }

    A revoked certificate entry (RFC 5280, 5.3).

    RevokedCertificate::reason

    fn RevokedCertificate::reason(self : RevokedCertificate) -> String?

    The revocation reason from the cRLReason entry extension, when present.

    SubjectPublicKeyInfo

    pub struct SubjectPublicKeyInfo {
    algorithm : AlgorithmIdentifier
    public_key : Bytes
    unused_bits : Int
    }

    A subject public key (RFC 5280, 4.1.2.7).

    SubjectPublicKeyInfo::public_key_bits

    fn SubjectPublicKeyInfo::public_key_bits(self : SubjectPublicKeyInfo) -> 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.

    SubjectPublicKeyInfo::rsa_modulus_bits

    fn SubjectPublicKeyInfo::rsa_modulus_bits(self : SubjectPublicKeyInfo) -> Int raise X509Error

    For an RSA key, the modulus size in bits.

    The modulus lives in the PKCS#1 RSAPublicKey structure carried inside the BIT STRING.

    TbsCertList

    pub struct TbsCertList {
    version : Int
    signature_algorithm : AlgorithmIdentifier
    issuer : Name
    this_update :
    Asn1Time

    next_update :
    Asn1Time
    ?
    revoked_certificates : Array[RevokedCertificate]
    extensions : Array[Extension]
    }

    The signed body of a certificate revocation list (RFC 5280, 5.1.2).

    TbsCertificate

    pub struct TbsCertificate {
    version : Int
    serial_number :
    BigInt

    signature_algorithm : AlgorithmIdentifier
    issuer : Name
    validity : Validity
    subject : Name
    subject_public_key_info : SubjectPublicKeyInfo
    extensions : Array[Extension]
    }

    The signed body of a certificate (RFC 5280, 4.1.2).

    Validity

    A validity period (RFC 5280, 4.1.2.5).

    algorithm_display_name

    fn algorithm_display_name(oid :
    ObjectIdentifier
    ) -> String

    The display name for a signature or public key algorithm, falling back to the dotted form of the object identifier.

    attribute_display_name

    fn attribute_display_name(oid :
    ObjectIdentifier
    ) -> String

    The display name for a distinguished name attribute, falling back to the dotted form of the object identifier.

    attribute_short_name

    The short attribute name that RFC 4514 uses for a distinguished name attribute, or None for attributes without a registered short name.

    build_chain

    fn build_chain(leaf : Certificate, pool : Array[Certificate]) -> Array[Certificate]

    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.

    describe_chain

    fn describe_chain(chain : Array[Certificate]) -> String

    Renders one line per certificate, from leaf to root.

    extended_key_usage_display_name

    fn extended_key_usage_display_name(oid :
    ObjectIdentifier
    ) -> String

    The display name for an extended key usage purpose.

    extension_display_name

    fn extension_display_name(oid :
    ObjectIdentifier
    ) -> String

    The display name for an extension, falling back to the dotted form of the object identifier.

    parse_certificate

    fn parse_certificate(bytes : BytesView) -> Certificate raise X509Error

    Parses a certificate from its DER encoding.

    parse_certificate_bundle

    fn parse_certificate_bundle(text : StringView) -> Array[Certificate] raise X509Error

    Parses every PEM encoded certificate in a bundle (a file that may hold a chain), skipping blocks with other labels such as private keys.

    parse_certificate_list

    fn parse_certificate_list(bytes : BytesView) -> CertificateList raise X509Error

    Parses a certificate revocation list from its DER encoding.

    parse_certificate_value

    fn parse_certificate_value(value :
    Der
    , raw? : Bytes) -> Certificate raise X509Error

    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.

    parse_certification_request

    fn parse_certification_request(bytes : BytesView) -> CertificationRequest raise X509Error

    Parses a certification request from its DER encoding.

    parse_pem_certificate

    fn parse_pem_certificate(text : StringView) -> Certificate raise X509Error

    Parses a certificate from PEM text.

    parse_pem_certificate_list

    fn parse_pem_certificate_list(text : StringView) -> CertificateList raise X509Error

    Parses a certificate revocation list from PEM text.

    parse_pem_certification_request

    fn parse_pem_certification_request(text : StringView) -> CertificationRequest raise X509Error

    Parses a certification request from PEM text.

    request_attribute_display_name

    fn request_attribute_display_name(oid :
    ObjectIdentifier
    ) -> String

    The display name for a certification request attribute.

    revocation_reason_name

    fn revocation_reason_name(code : Int) -> String

    The name of a revocation reason code (RFC 5280, 5.3.1).