std/x509
import std/x509 to use it. Source: src/std/x509.iyi at v0.18.0.
import std/x509::{Certificate, X509, X509Error} roots = Certificate.from_pem(File.read("/etc/ssl/certs/ca-certificates.crt")).or_panic
leaf = Certificate.parse(der).or_panic
chain = X509.verify(leaf, intermediates, roots, "example.com").or_panic
# the chain from leaf to root, or an X509Error saying why there is noneCertificate.parse reads a certificate's DER through std/asn1 and its key through std/rsa, std/ecdsa or std/curve25519: version, serial, issuer and subject (kept as their DER, which is what a chain matches), validity, key, signature, and the extensions a chain is checked by - basicConstraints, keyUsage, extKeyUsage, subjectAltName's DNS names and IP addresses, nameConstraints, and the key identifiers. An extension that appears twice is refused. One that is critical and not among these is kept by name, and a chain through its certificate is refused: RFC 5280 section 4.2 says a certificate with a critical extension it does not understand must not be used. The signature algorithm written inside the signed part must be the one written outside it.
X509.verify builds a chain from the leaf to one of the given roots, through the given intermediates, matching each certificate's issuer to a candidate's subject byte for byte and trying every candidate that matches before giving up, ten certificates at most. A certificate checked as an issuer must be valid at the time given, a CA by its basicConstraints, allowed keyCertSign if it carries keyUsage, within its pathLenConstraint, allowed the chain's purpose if it carries extKeyUsage (serverAuth for a server's certificate, the default; clientAuth for a client's, X509Purpose::Client), and the leaf's names must fall inside its nameConstraints; it must have signed the certificate below it. Name constraints are checked for DNS names and IP addresses; a constraint of any other kind refuses the chain rather than being passed over. The leaf must be valid at that time, not a CA (a CA's certificate is not a server's, as the Web PKI's verifiers hold, nor a client's), allowed the purpose if it carries extKeyUsage, and, when a host is given, name it.
A host is matched against subjectAltName only, never the subject's common name, as browsers and RFC 6125 have it: a DNS name exactly, without regard to case or a final dot, or as *.rest where the * is the whole of the leftmost label and stands for exactly one; an IP address, v4 or v6, against the iPAddress entries.
Signatures: RSA PKCS #1 v1.5 and PSS, and ECDSA, under SHA-256, SHA-384 and SHA-512, and Ed25519 (RFC 8410: the key's and the signature's AlgorithmIdentifier carry no parameters at all). A certificate signed under SHA-1 is refused, as every browser refuses one. A root's own signature is not checked: a root is trusted for being in the store, not for what it says about itself.
Pure computation: std/asn1, std/rsa, std/ecdsa, std/curve25519, std/base64 for PEM and std/idna for a host written in Unicode.
Certificatestruct
pub struct Certificate
def authority_key_id : Array(UInt8) | ::Nil
# basicConstraints: whether it was present, cA, and pathLenConstraint.
def basic_constraints : Bool
def ca : Bool
# subjectAltName's dNSName and iPAddress entries.
def dns_names : Array(String)
def excluded_dns : Array(String)
def excluded_ips : Array(Array(UInt8))
# extKeyUsage's OIDs; nil when the extension is absent.
def extended_key_usage : Array(String) | ::Nil
# Every CERTIFICATE block in a PEM text, read; the first one that is not
# a certificate is the answer.
def self.from_pem(text : String) : Array(Certificate) | X509Error
def initialize(raw : Array(UInt8), tbs : Array(UInt8), version : Int32, serial : Array(UInt8), issuer : Array(UInt8), subject : Array(UInt8), subject_name : String, issuer_name : String, not_before : Time, not_after : Time, public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey, signature_algorithm : String, signature_parameters : DerValue | ::Nil, signature : Array(UInt8), extensions : X509Extensions)
def ip_addresses : Array(Array(UInt8))
# Issuer and subject as their DER: two names are the same name when
# these are the same bytes.
def issuer : Array(UInt8)
def issuer_name : String
# keyUsage's bits, bit 0 digitalSignature to bit 8 decipherOnly; nil
# when the extension is absent.
def key_usage : Int32 | ::Nil
# Whether *host* is a name this certificate is for: a DNS name or an IP
# address in its subjectAltName (see the module's header).
def matches_host?(host : String) : Bool
def not_after : Time
def not_before : Time
def other_constraints : Bool
# The certificate *der* holds (RFC 5280 section 4.1).
def self.parse(der : Array(UInt8)) : Certificate | X509Error
def path_length : Int32 | ::Nil
# The DER of every CERTIFICATE block in a PEM text, unread: a store
# can keep what it can read and count the rest.
def self.pem_blocks(text : String) : Array(Array(UInt8)) | X509Error
# nameConstraints: DNS names and IP ranges (address and mask), and
# whether a constraint of any other kind is there.
def permitted_dns : Array(String)
def permitted_ips : Array(Array(UInt8))
def public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey
# The whole certificate as it was read, and its signed part.
def raw : Array(UInt8)
# The serial number's two's-complement bytes, as written.
def serial : Array(UInt8)
def signature : Array(UInt8)
# The signature algorithm's OID, and the signature.
def signature_algorithm : String
# Why *issuer*'s key did not make this certificate's signature, or nil.
def signature_error(issuer : Certificate) : String | ::Nil
# Whether *issuer*'s key made this certificate's signature.
def signed_by?(issuer : Certificate) : Bool
def subject : Array(UInt8)
def subject_key_id : Array(UInt8) | ::Nil
# The subject and issuer written out for a person, `CN=..., O=...`.
def subject_name : String
def tbs : Array(UInt8)
# Critical extensions this does not understand, by OID.
def unhandled_critical : Array(String)
def valid_at?(time : Time) : Bool
def version : Int32
endX509struct
pub struct X509
def initialize
# The DER of every block labelled *label* in a PEM text - `"CERTIFICATE"`
# or `"PRIVATE KEY"` - each checked to be base64 before it is decoded.
def self.pem(text : String, label : String) : Array(Array(UInt8)) | X509Error
# The chain from *leaf* to one of *roots*, through *intermediates*, for
# *host* (nil: any) at *now* - leaf first, root last - or why there is
# none. *purpose* is what the leaf is for: a server's certificate
# (serverAuth, the default) or a client's (clientAuth), each certificate
# that carries extKeyUsage allowing it. See the module's header for what
# else is checked.
def self.verify(leaf : Certificate, intermediates : Array(Certificate), roots : Array(Certificate), host : String | ::Nil = nil, now : Time = Time.utc, purpose : X509Purpose = X509Purpose::Server) : Array(Certificate) | X509Error
endX509Errorstruct
What reading a certificate, or checking a chain, answers when it fails.
pub struct X509Error
def initialize(message : String)
def to_s : String
endX509Purposeenum
What a chain is checked for: a TLS server's certificate or a client's (RFC 5280 section 4.2.1.12's id-kp-serverAuth and id-kp-clientAuth).
pub enum X509Purpose : Int32
Server = 0
Client = 1
def holder : String
def oid : String
# The usage's name, as a sentence says it.
def usage : String
endError for X509Errorimpl
impl Error for X509Error
def message : String
end
X.509 certificates read and chains checked (RFC 5280), written in iyi: what a TLS client asks of a server's certificates.