///|
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")[:],
])
}///|
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")
}///|
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",
)
}///|
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()
}
}///|
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")
}///|
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")
}///|
/// 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")
}pub(all) suberror X509Error {
InvalidDer(String)
InvalidPem(String)
Malformed(String)
} derive(Debug)pub struct AuthorityKeyIdentifier {
key_identifier : Bytes?
authority_cert_issuer : Array[GeneralName]?
authority_cert_serial : BigInt?
}pub struct BasicConstraints {
ca : Bool
path_len : Int?
}pub struct Certificate {
raw : Bytes
tbs : TbsCertificate
signature_algorithm : AlgorithmIdentifier
signature : Bytes
}pub struct CertificateList {
raw : Bytes
tbs : TbsCertList
signature_algorithm : AlgorithmIdentifier
signature : Bytes
}fn CertificateList::is_revoked(self : CertificateList, serial_number : BigInt) -> RevokedCertificate?pub struct CertificationRequest {
raw : Bytes
info : CertificationRequestInfo
signature_algorithm : AlgorithmIdentifier
signature : Bytes
}pub struct CertificationRequestInfo {
version : Int
subject : Name
subject_public_key_info : SubjectPublicKeyInfo
attributes : Array[Attribute]
}pub(all) enum ExtensionValue {
SubjectAltName(Array[GeneralName])
IssuerAltName(Array[GeneralName])
KeyUsage(KeyUsage)
BasicConstraints(BasicConstraints)
ExtendedKeyUsage(Array[ObjectIdentifier])
SubjectKeyIdentifier(Bytes)
AuthorityKeyIdentifier(AuthorityKeyIdentifier)
Other
}pub(all) enum GeneralName {
OtherName(Bytes)
Rfc822Name(String)
DnsName(String)
X400Address(Bytes)
DirectoryName(Name)
EdiPartyName(Bytes)
UniformResourceIdentifier(String)
IpAddress(Bytes)
RegisteredId(ObjectIdentifier)
}pub struct KeyUsage {
value : Bytes
}pub(all) enum KeyUsageBit {
DigitalSignature
NonRepudiation
KeyEncipherment
DataEncipherment
KeyAgreement
KeyCertSign
CrlSign
EncipherOnly
DecipherOnly
}pub struct SubjectPublicKeyInfo {
algorithm : AlgorithmIdentifier
public_key : Bytes
unused_bits : Int
}pub struct TbsCertList {
version : Int
signature_algorithm : AlgorithmIdentifier
issuer : Name
this_update : Asn1Time
next_update : Asn1Time?
revoked_certificates : Array[RevokedCertificate]
extensions : Array[Extension]
}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]
}fn revocation_reason_name(code : Int) -> StringInstall
Download zipPure-MoonBit ASN.1 DER codec and X.509 certificate toolkit.