package core:crypto/x509

⌘K
Ctrl+K
or
/

    Overview

    X.509 v3 certificate parsing, signature verification, and chain (path) validation.

    The parser is built on the strict DER reader in core:encoding/asn1 and is zero-copy where possible: the returned Certificate's byte-slice fields are views into the input DER, which must outlive it. The few allocated fields (the extension/SAN tables) are released with destroy.

    Input is DER. To parse a PEM certificate, decode it first with core:encoding/pem (label "CERTIFICATE") and pass the resulting bytes.

    A successful parse means the bytes were well-formed, NOT that the certificate is valid or trusted. The Certificate carries everything the verifier needs: raw_tbs (the exact byte range a signature covers), raw_spki (the range hashed for tls-server-end-point channel binding, RFC 5929, and SPKI pinning), and raw_issuer/raw_subject (for the RFC 5280 binary-comparison rule).

    Hostname verification (verify_hostname) implements the RFC 6125 subset modern clients use: subject alternative names only (no CommonName fallback), with at most one wildcard as the entire left-most label.

    Trust is established by verify_chain, which builds a path from a leaf to a supplied trust anchor through supplied intermediates and checks, for each certificate, validity, signature, name chaining, and the CA / keyCertSign / pathLenConstraint rules; verify_signature exposes the single-edge signature check on its own.

    LIMITATIONS:

    - Signature verification covers RSA PKCS#1 v1.5 and RSA-PSS (SHA-256/384/512), ECDSA P-256/P-384, and Ed25519. These paths return .Unsupported_Algorithm: SHA-1 (deprecated and rejected, RFC 9155), ECDSA P-521 (effectively dead in web PKI), and RSA-PSS naming a digest or MGF this package does not recognize. - Name constraints (RFC 5280 4.2.1.10) are enforced for the dNSName and iPAddress forms: a CA's permitted/excluded subtrees are checked against every subordinate certificate's SANs, regardless of the extension's criticality. A NameConstraints that uses any other base form (directoryName, rfc822Name, URI, otherName), a minimum/maximum, or that is malformed cannot be fully evaluated, so the whole chain is rejected (fail closed) rather than accepted unchecked. NOT enforced: the RFC 5280 rule that the extension be critical, and dNSName syntax validation (a leading-period constraint is accepted, as OpenSSL does). - REVOCATION IS NOT CHECKED. verify_chain performs NO CRL or OCSP revocation checking. Callers that need revocation (e.g. TLS clients) MUST supply it separately (OCSP stapling, CRLite, …). - Certificate policies / policy constraints are not evaluated, and there is no Public Suffix List: a (CABF-forbidden) wildcard such as "*.com" would match "host.com". As a backstop, verify_chain still fails closed on any uninterpreted CRITICAL extension (.Unhandled_Critical_Extension). - EKU is checked only when opts.required_eku is set, and then by RFC 5280 semantics: a certificate with no EKU extension is unrestricted (it is not required to assert the purpose); a certificate that DOES assert EKU must include the purpose, enforced across the leaf and every intermediate (EKU nesting). Leaf KeyUsage is not checked against the intended protocol use.

    Parsing is deliberately lenient wherever strictness is a validation concern rather than a structural one. Exception: Parser rejects duplicate extension OIDs (Duplicate_Extension, RFC 5280 section 4.2).

    - Only dNSName and iPAddress subject alternative names are decoded (into dns_names / ip_addresses). Other GeneralName forms (URI, rfc822Name, directoryName, otherName) are skipped; the raw SAN extension is still available via extensions. - Only the extensions path validation needs are decoded (BasicConstraints, KeyUsage, ExtKeyUsage, SubjectAltName, Subject/Authority Key Identifier; NameConstraints is decoded at verification time). All others (AIA, CRL distribution points, certificate policies, …) are left raw in extensions. - Subject and issuer are kept as raw DER (raw_subject / raw_issuer), which is what name chaining compares (the RFC 5280 binary rule). The attributes (CN, O, …) are decoded on demand by parse_dn, not at parse time; dn_get / dn_string read them out and serial_string formats the serial. - Unsupported public-key curves yield Public_Key_Algorithm.Unknown. - Non-conformant-but-extractable values are preserved: negative or over-long serials, and validity dates far in the future. Validity is stored as core:time.Time, which tops out near year 2262; dates beyond that (the RFC 5280 "99991231235959Z" no-expiration sentinel) saturate to that bound at parse time rather than failing, so they read as "effectively never expires". - Per-extension criticality rules (e.g. that subjectKeyIdentifier be non-critical) are left to the caller via Extension.critical, and a critical extension this package does not understand sets unhandled_critical rather than failing the parse.

    See: https://www.rfc-editor.org/rfc/rfc5280 https://www.rfc-editor.org/rfc/rfc6125 https://www.rfc-editor.org/rfc/rfc5929

    Types

    Certificate ¶

    Certificate :: struct {
    	// Raw views into the input DER.
    	raw:                     []u8,
    	// the whole Certificate element
    	raw_tbs:                 []u8,
    	// TBSCertificate, header included, the signed bytes
    	raw_issuer:              []u8,
    	// issuer Name element (RFC 5280 binary comparison)
    	raw_subject:             []u8,
    	// subject Name element
    	raw_spki:                []u8,
    	// SubjectPublicKeyInfo element; hash for tls-server-end-point / SPKI pinning
    	version:                 int,
    	// Certificate serial number as the raw DER INTEGER content (minimal two's-complement). 
    	// It is an opaque identifier, compare and display by these bytes. A positive serial whose top
    	// bit is set carries a leading 0x00 sign octet; a serial of 0 is the single octet {0x00}. RFC 5280 requires
    	// serials to be positive and <= 20 octets, but non-conformant (negative, zero, or over-long) serials are preserved
    	serial:                  []u8,
    	signature_algorithm:     Signature_Algorithm,
    	signature_oid:           []u8,
    	// OID content octets
    	signature:               []u8,
    	// Validity bounds. time.Time is i64 nanoseconds and tops out near year 2262; X.509 dates beyond that (notably the RFC 5280
    	// "99991231235959Z" no-expiration sentinel) saturate to that bound at parse time, so they compare as "effectively never expires"
    	// rather than overflowing. See asn1's _time_from_unix.
    	not_before:              time.Time,
    	not_after:               time.Time,
    	public_key_algorithm:    Public_Key_Algorithm,
    	// RSA: modulus and exponent magnitudes.
    	rsa_n:                   []u8,
    	rsa_e:                   []u8,
    	// ECDSA: the uncompressed point (0x04 || X || Y); Ed25519: the 32-byte key.
    	ec_point:                []u8,
    	// RSASSA-PSS parameters, decoded from the signatureAlgorithm's
    	// RSASSA-PSS-params (meaningful only when signature_algorithm ==
    	// .RSA_PSS; RFC 4055 defaults applied for omitted fields). pss_hash and
    	// pss_mgf_hash are hash.Algorithm.Invalid when the certificate names a
    	// digest this package cannot verify, which verify_signature reports as
    	// .Unsupported_Algorithm rather than a failure.
    	pss_hash:                crypto_hash.Algorithm,
    	pss_mgf_hash:            crypto_hash.Algorithm,
    	pss_salt_len:            int,
    	// BasicConstraints (basic_constraints_valid reports presence).
    	basic_constraints_valid: bool,
    	is_ca:                   bool,
    	max_path_len:            int,
    	// -1 when absent
    	has_key_usage:           bool,
    	key_usage:               bit_set[Key_Usage_Bit; u16],
    	has_ext_key_usage:       bool,
    	ext_key_usage:           bit_set[EKU_Bit; u8],
    	eku_has_unknown:         bool,
    	subject_key_id:          []u8,
    	authority_key_id:        []u8,
    	dns_names:               []string,
    	// ALLOCATED
    	ip_addresses:            [][]u8,
    	// Every extension, in order, including ones this package does not interpret. 
    	extensions:              []Extension,
    	// True if a critical extension other than the ones interpreted
    	// here was present. RFC 5280 requires a relying party to reject
    	// such a certificate at validation time; parsing still succeeds so
    	// the caller can inspect. 
    	// 
    	// The specific unhandled OIDs are recoverable by walking `extensions` 
    	// for entries with `critical = true` whose OID is none of the handled 
    	// ones (_OID_EXT_*).
    	unhandled_critical:      bool,
    }
     

    Certificate is a parsed X.509 v3 certificate. Byte-slice fields are views into the input DER (which must outlive the Certificate) The dns_names / ip_addresses / extensions slices are allocated (their elements still view the DER) and released by destroy.

    Related Procedures With Parameters

    Related Procedures With Returns

    DN_Attribute ¶

    DN_Attribute :: struct {
    	type:  DN_Attribute_Type,
    	value: string,
    	oid:   []u8,
    }
     

    DN_Attribute is one relative distinguished name: a type and its value. When type is Other, oid holds the raw attribute-type OID content octets. Values are emitted as UTF8String, except Country and Serial_Number which are PrintableString (X.520), the policy RFC 5280 section 4.1.2.4 advises.

    DN_Attribute_Type ¶

    DN_Attribute_Type :: enum int {
    	Common_Name,         // CN, 2.5.4.3
    	Country,             // C,  2.5.4.6
    	Locality,            // L,  2.5.4.7
    	State_Or_Province,   // ST, 2.5.4.8
    	Organization,        // O,  2.5.4.10
    	Organizational_Unit, // OU, 2.5.4.11
    	Serial_Number,       // 2.5.4.5
    	Other,               // any other attribute; its type OID is in `oid`
    }
     

    Distinguished-name attribute types the DN builder knows by name. Other carries any attribute outside this set via DN_Attribute.oid (used by parse_dn for attributes it does not recognize by name).

    Related Procedures With Parameters

    EKU_Bit ¶

    EKU_Bit :: enum u8 {
    	Server_Auth, 
    	Client_Auth, 
    	Code_Signing, 
    	Email_Protection, 
    	Time_Stamping, 
    	OCSP_Signing, 
    	Any, 
    }
     

    Extended key usage purposes (RFC 5280 section 4.2.1.12) recognized by name; unrecognized purposes set eku_has_unknown.

    Error ¶

    Error :: enum int {
    	None, 
    	Malformed,                    // DER-level violation, structural mismatch, or trailing garbage.
    	Unsupported_Version,          // Certificate version beyond v3.
    	Invalid_Validity,             // notBefore/notAfter missing or unparseable.
    	Invalid_Extension,            // A recognized extension's content didn't match its schema.
    	Duplicate_Extension,          // The same extension OID appeared more than once (RFC 5280 section 4.2 forbids this).
    	Hostname_Mismatch,            // Hostname verification: no SAN matched.
    	No_SAN,                       // Hostname verification: the certificate has no usable SANs of the queried kind.
    	Allocation_Failed,            // Allocating the extension/SAN tables failed.
    	// --- verification (verify_signature / verify_chain) ---
    	Signature_Invalid,            // A signature did not verify against the issuer's public key.
    	Unsupported_Algorithm,        // The signature or public-key algorithm is recognized but not implemented here.
    	Not_Yet_Valid,                // A certificate's notBefore is in the future relative to the supplied time.
    	Expired,                      // A certificate's notAfter is in the past relative to the supplied time.
    	Unknown_Authority,            // (no issuer found, failed name chaining, validity, CA constraints, or signature verification).
    	Unhandled_Critical_Extension, // Failed to handle a critical extension, automatic rejection
    	Incompatible_Usage,           // Lacks EKU, or EKU not authorized
    }

    Related Procedures With Returns

    Ext_Key_Usage ¶

    Ext_Key_Usage :: bit_set[EKU_Bit; u8]
     

    Ext_Key_Usage is the decoded set of recognized ExtKeyUsage purposes; unrecognized purposes set Certificate.eku_has_unknown.

    Related Procedures With Parameters

    Extension ¶

    Extension :: struct {
    	oid:      []u8,
    	critical: bool,
    	value:    []u8,
    }
     

    Extension is one raw entry from the TBS extensions list. oid is the OID content octets; value the extnValue OCTET STRING content.

    Key_Usage ¶

    Key_Usage :: bit_set[Key_Usage_Bit; u16]
     

    Key_Usage is the decoded KeyUsage extension bit set.

    Related Procedures With Parameters

    Key_Usage_Bit ¶

    Key_Usage_Bit :: enum u16 {
    	Digital_Signature  = 0, 
    	Content_Commitment = 1, 
    	Key_Encipherment   = 2, 
    	Data_Encipherment  = 3, 
    	Key_Agreement      = 4, 
    	Key_Cert_Sign      = 5, 
    	CRL_Sign           = 6, 
    	Encipher_Only      = 7, 
    	Decipher_Only      = 8, 
    }
     

    Key_Usage bits per RFC 5280 section 4.2.1.3

    Private_Key ¶

    Private_Key :: struct {
    	algorithm: Public_Key_Algorithm,
    	private:   []u8,
    	public:    []u8,
    	// RSA CRT components (big-endian magnitudes), used when algorithm == RSA.
    	rsa_n:     []u8,
    	// modulus
    	rsa_e:     []u8,
    	// public exponent
    	rsa_d:     []u8,
    	// private exponent
    	rsa_p:     []u8,
    	// prime1
    	rsa_q:     []u8,
    	// prime2
    	rsa_dp:    []u8,
    	// d mod (p-1)
    	rsa_dq:    []u8,
    	// d mod (q-1)
    	rsa_iq:    []u8,
    }
     

    Private_Key holds the RAW key material to serialize as a PKCS#8 PrivateKeyInfo (RFC 5208 / 5958). It is the bytes the crypto packages expose, which marshal_pkcs8 only assembles into DER: - ECDSA: private is the secret scalar (ecdsa.private_key_bytes), public (optional) the uncompressed point (ecdsa.public_key_bytes). - Ed25519: private is the 32-byte seed (ed25519.private_key_bytes). - RSA: the rsa_* CRT components, as big-endian magnitudes, from the rsa.private_key_{n,e,d,p,q,dp,dq,iq} accessors.

    Related Procedures With Parameters

    Public_Key ¶

    Public_Key :: struct {
    	algorithm: Public_Key_Algorithm,
    	rsa_n:     []u8,
    	rsa_e:     []u8,
    	ec_point:  []u8,
    }
     

    Public_Key holds the subject public-key material to encode into a SubjectPublicKeyInfo, mirroring the fields parse() extracts onto a Certificate: rsa_n/rsa_e (unsigned magnitudes) for RSA; ec_point for ECDSA (the uncompressed point 0x04||X||Y) and Ed25519 (the 32-byte key).

    Related Procedures With Parameters

    Public_Key_Algorithm ¶

    Public_Key_Algorithm :: enum int {
    	Unknown, 
    	RSA, 
    	ECDSA_P256, 
    	ECDSA_P384, 
    	ECDSA_P521, 
    	Ed25519, 
    }
     

    Public_Key_Algorithm identifies the certificate's subject public key type. Unknown covers key algorithms (or EC curves) this package does not decode; the SubjectPublicKeyInfo bytes remain available in raw_spki.

    Signature_Algorithm ¶

    Signature_Algorithm :: enum int {
    	Unknown, 
    	RSA_SHA1,     // obsolete; parsed for identification only
    	RSA_SHA256, 
    	RSA_SHA384, 
    	RSA_SHA512, 
    	RSA_PSS, 
    	ECDSA_SHA256, 
    	ECDSA_SHA384, 
    	ECDSA_SHA512, 
    	Ed25519, 
    }
     

    Signature_Algorithm covers the PKIX signature algorithms a client encounters in practice. RSASSA-PSS parameters are decoded into the Certificate's pss_* fields.

    Related Procedures With Parameters

    TBS_Certificate ¶

    TBS_Certificate :: struct {
    	serial:              []u8,
    	signature_algorithm: Signature_Algorithm,
    	issuer:              []DN_Attribute,
    	not_before:          time.Time,
    	not_after:           time.Time,
    	subject:             []DN_Attribute,
    	public_key:          Public_Key,
    	extensions:          [][]u8,
    }
     

    TBS_Certificate gathers the fields of a TBSCertificate to encode. issuer and subject are RDNSequences; serial is the serialNumber's unsigned magnitude; extensions is a list of pre-encoded Extension DER (from the marshal_ext_* helpers), embedded in order.

    Related Procedures With Parameters

    Verify_Options ¶

    Verify_Options :: struct {
    	// Trust anchors. A chain is accepted iff it terminates at one of
    	// these (matched by name + signature, as ordinary issuers). Usually
    	// self-signed roots, but any certificate trusted a priori works.
    	roots:         []^Certificate,
    	// Untrusted intermediates available to bridge the leaf to a root.
    	// Order does not matter; verify_chain searches them.
    	intermediates: []^Certificate,
    	// Reference time for every certificate's validity window.
    	current_time:  time.Time,
    	// If non-empty, the leaf must pass verify_hostname for this name.
    	dns_name:      string,
    	// If set, the leaf's ExtKeyUsage must permit this purpose (a leaf
    	// with no EKU extension is unrestricted and always passes). TLS
    	// clients pass .Server_Auth.
    	required_eku:  runtime.Maybe($T=EKU_Bit),
    }
     

    Verify_Options parameterizes verify_chain.

    Related Procedures With Parameters

    Constants

    This section is empty.

    Variables

    This section is empty.

    Procedures

    destroy ¶

    destroy :: proc(cert: ^Certificate, allocator := context.allocator) {…}

    dn_get ¶

    dn_get :: proc(attrs: []DN_Attribute, type: DN_Attribute_Type) -> (value: string, ok: bool) {…}
     

    dn_get returns the value of the first attribute of type (e.g. .Common_Name), and whether one was present.

    dn_string ¶

    @(require_results)
    dn_string :: proc(attrs: []DN_Attribute, allocator := context.allocator) -> string {…}
     

    dn_string renders attrs as an RFC 4514 string ("CN=leaf,O=Acme,C=US"): RDNs in reverse order, short names for the recognized attributes and the dotted OID for Other, with RFC 4514 section 2.4 special characters escaped. The returned string is the caller's to free.

    marshal_certificate ¶

    @(require_results)
    marshal_certificate :: proc(tbs_der: []u8, signature_algorithm: Signature_Algorithm, signature: []u8, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_certificate wraps a (separately signed) TBSCertificate into a complete X.509 Certificate. signature_algorithm must match the one inside the TBS (RFC 5280 section 4.1.1.2). The slice is the caller's to free.

    marshal_csr ¶

    @(require_results)
    marshal_csr :: proc(cri_der: []u8, signature_algorithm: Signature_Algorithm, signature: []u8, allocator := context.allocator) -> (csr_der: []u8, err: Error) {…}
     

    marshal_csr wraps a (separately signed) CertificationRequestInfo into a complete PKCS#10 CertificationRequest. signature is the raw signature value over cri_der, a DER ECDSA-Sig-Value for ECDSA, the 64-byte value for Ed25519, and signature_algorithm selects the matching AlgorithmIdentifier. The slice is the caller's to free.

    marshal_csr_info ¶

    @(require_results)
    marshal_csr_info :: proc(subject: []DN_Attribute, key: Public_Key, extensions: [][]u8 = nil, allocator := context.allocator) -> (cri_der: []u8, err: Error) {…}
     

    marshal_csr_info encodes the CertificationRequestInfo, the to-be-signed portion of a PKCS#10 CSR (RFC 2986): version v1, the subject DN, the subject public key, and the attributes set. Sign the returned bytes and pass them with the signature to marshal_csr. The slice is the caller's to free; subject, key, and extensions need not outlive the call.

    extensions is a list of pre-encoded Extension DER (from the marshal_ext_* helpers); when non-empty it is requested via a single PKCS#9 extensionRequest attribute (the standard way a CSR asks the CA to place extensions, SANs, key usage in the issued certificate). Pass nil for the empty attributes set.

    marshal_dn ¶

    @(require_results)
    marshal_dn :: proc(attrs: []DN_Attribute, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_dn encodes attrs as a DER Name (RDNSequence): one single-valued RelativeDistinguishedName per attribute, in the given order. The returned slice is the caller's to free; the attribute value bytes are copied into it, so attrs need not outlive the call.

    marshal_ext_basic_constraints ¶

    @(require_results)
    marshal_ext_basic_constraints :: proc(is_ca: bool, max_path_len: int, critical: bool, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_ext_basic_constraints encodes id-ce-basicConstraints: BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL } cA is emitted only when is_ca; pathLenConstraint only when is_ca and max_path_len >= 0 (a negative value means "absent").

    marshal_ext_ext_key_usage ¶

    @(require_results)
    marshal_ext_ext_key_usage :: proc(eku: bit_set[EKU_Bit; u8], critical: bool, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_ext_ext_key_usage encodes id-ce-extKeyUsage: ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId in EKU_Bit order.

    marshal_ext_key_usage ¶

    @(require_results)
    marshal_ext_key_usage :: proc(usage: bit_set[Key_Usage_Bit; u16], critical: bool, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_ext_key_usage encodes id-ce-keyUsage as a KeyUsage BIT STRING, minimally (trailing zero bits dropped, per DER named bit strings).

    marshal_ext_san ¶

    @(require_results)
    marshal_ext_san :: proc(dns_names: []string, ip_addresses: [][]u8, critical: bool, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_ext_san encodes id-ce-subjectAltName: GeneralNames ::= SEQUENCE OF GeneralName emitting dNSName [2] IA5String entries (in order) followed by iPAddress [7] OCTET STRING entries. IP values are the raw 4- or 16-octet address.

    marshal_pkcs8 ¶

    @(require_results)
    marshal_pkcs8 :: proc(key: Private_Key, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    Serializes key as a DER PKCS#8 PrivateKeyInfo. The crypto has already happened; this only nests the raw bytes. An unknown algorithm yields .Unsupported_Algorithm. The returned slice is the caller's to free.

    marshal_spki ¶

    @(require_results)
    marshal_spki :: proc(key: Public_Key, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_spki encodes key as a DER SubjectPublicKeyInfo, the inverse of the SPKI decoding in parse(); the returned slice is the caller's to free. Unknown / unsupported key algorithms yield .Unsupported_Algorithm.

    marshal_tbs_certificate ¶

    @(require_results)
    marshal_tbs_certificate :: proc(tbs: TBS_Certificate, allocator := context.allocator) -> (der: []u8, err: Error) {…}
     

    marshal_tbs_certificate encodes a v3 TBSCertificate (RFC 5280 section 4.1), the to-be-signed portion of a certificate. Sign the returned bytes and pass them with the signature to marshal_certificate. The slice is the caller's to free; the inputs need not outlive the call.

    parse ¶

    @(require_results)
    parse :: proc(der: []u8, allocator := context.allocator) -> (cert: Certificate, err: Error) {…}
     

    parse decodes one DER certificate. The returned Certificate holds views into der, which must outlive it; the allocated tables are released with destroy(). Trailing bytes after the certificate are an error.

    parse_dn ¶

    @(require_results)
    parse_dn :: proc(der: []u8, allocator := context.allocator) -> (attrs: []DN_Attribute, err: Error) {…}
     

    parse_dn decodes a DER Name (an RDNSequence, e.g. cert.raw_subject or cert.raw_issuer) into its attributes, the inverse of marshal_dn. Attributes outside the recognized set (see DN_Attribute_Type) come back as Other with their type OID in oid. The returned slice is the caller's to free; every value (and oid) is a VIEW into der, which must outlive the result.

    Values are taken as the raw content octets of the attribute value: correct for the UTF8String / PrintableString / IA5String forms certificates use in practice, but Teletex/BMP/Universal strings are NOT transcoded (returned as their raw bytes).

    private_key_clear ¶

    private_key_clear :: proc "contextless" (key: ^Private_Key) {…}
     

    private_key_clear securely wipes the secret material key references. The byte buffers are the caller's: this clears their secret contents in place (the public modulus / exponent / point are left untouched; only the slice headers are dropped). The DER that marshal_pkcs8 returns also holds the key, so wipe and free it separately.

    serial_string ¶

    @(require_results)
    serial_string :: proc(cert: ^Certificate, allocator := context.allocator) -> string {…}
     

    serial_string formats the certificate serial as upper-case colon-separated hex ("07:44:76:…"). The serial is an opaque identifier (up to 20 octets) Allocated String

    valid_at ¶

    @(require_results)
    valid_at :: proc "contextless" (cert: ^Certificate, now: time.Time) -> bool {…}
     

    valid_at returns true iff now falls within the certificate's validity window (inclusive on both ends, per RFC 5280 section 4.1.2.5). Obtain now from e.g. time.now().

    verify_chain ¶

    @(require_results)
    verify_chain :: proc(leaf: ^Certificate, opts: Verify_Options, allocator := context.allocator) -> (chain: []^Certificate, err: Error) {…}
     

    A certificate carrying a critical extension this package does not interpret fails the path closed (see .Unhandled_Critical_Extension). When opts.dns_name is set the leaf must pass verify_hostname; when opts.required_eku is set the leaf AND every intermediate must permit that purpose (e.g. an email-only sub-CA cannot issue a TLS server leaf).

    The trust anchor is treated as trusted input: it must be valid at opts.current_time and is name-chained + signature-checked as the issuer below it, but its CA authorization (basicConstraints / keyCertSign / pathLenConstraint) and its own self-signature are NOT re-checked. An expired anchor is still rejected; resilience to that comes from the search trying every other available anchor and intermediate.

    verify_hostname ¶

    @(require_results)
    verify_hostname :: proc(cert: ^Certificate, host: string) -> Error {…}
     

    verify_hostname checks host against the certificate's subject alternative names per RFC 6125:

    - IP-literal hosts match iPAddress SANs by byte equality only. - DNS hosts match dNSName SANs case-insensitively; one wildcard is permitted as the ENTIRE left-most label of the SAN ("*.example.com" matches "a.example.com" but never "a.b.example.com", "example.com", or partial labels like "f*.example.com"). - The legacy CommonName fallback is not implemented (deprecated since RFC 6125).

    Returns .None on match, .Hostname_Mismatch when SANs of the right kind exist but none match, and .No_SAN when the certificate carries no SAN of the queried kind.

    verify_signature ¶

    @(require_results)
    verify_signature :: proc(cert: ^Certificate, issuer: ^Certificate) -> Error {…}
     

    ============================================================ Signature verification and chain (path) validation.

    verify_signature checks that cert's signature was produced by the private key matching issuer's public key, over cert.raw_tbs (the signed TBSCertificate). It checks ONLY the cryptographic signature, not validity periods, names, basic constraints, or that issuer is actually authorized to issue cert; verify_chain does all of that.

    Returns .None on a good signature, .Signature_Invalid on a bad one, and .Unsupported_Algorithm when the signature algorithm (SHA-1, which is rejected per RFC 9155; ECDSA P-521; or RSA-PSS with an unrecognized digest) or the issuer key type is not implemented here.

    Procedure Groups

    This section is empty.

    Source Files

    Generation Information

    Generated with odin version dev-2026-09 (vendor "odin") Windows_amd64 @ 2026-10-01 00:18:38.976892800 +0000 UTC