Module X509.Validation

Chain Validation.

A chain of pairwise signed X.509 certificates is sent to the endpoint, which use these to authenticate the other endpoint. Usually a set of trust anchors is configured on the endpoint, and the chain needs to be rooted in one of the trust anchors. In reality, chains may be incomplete or reversed, and there can be multiple paths from the leaf certificate to a trust anchor.

RFC 5280 specifies a path validation algorithm for authenticating chains, but this does not handle multiple possible paths. RFC 4158 describes possible path building strategies.

This module provides path building, chain of trust verification, trust anchor (certificate authority) validation, and validation via a fingerprint list (for a trust on first use implementation).

type signature_error = [
  1. | `Bad_signature of Distinguished_name.t * string
  2. | `Bad_encoding of Distinguished_name.t * string * string
  3. | `Hash_not_allowed of Distinguished_name.t * [ `MD5 | `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ]
  4. | `Unsupported_keytype of Distinguished_name.t * Public_key.t
  5. | `Unsupported_algorithm of Distinguished_name.t * string
  6. | `Msg of string
]

The type of signature verification errors.

val pp_signature_error : signature_error Fmt.t

pp_signature_error ppf sige pretty-prints the signature error sige on ppf.

Certificate Authorities

type ca_error = [
  1. | signature_error
  2. | `CAIssuerSubjectMismatch of Certificate.t
  3. | `CAInvalidVersion of Certificate.t
  4. | `CACertificateExpired of Certificate.t * Ptime.t option
  5. | `CAInvalidExtensions of Certificate.t
]

The polymorphic variant of possible certificate authorities failures.

val pp_ca_error : ca_error Fmt.t

pp_ca_error ppf ca_error pretty-prints the CA error ca_error.

val valid_ca : ?allowed_hashes:Digestif.hash' list -> ?time:Ptime.t -> Certificate.t -> (unit, [> ca_error ]) Stdlib.result

valid_ca ~allowed_hashes ~time certificate is result, which is Ok () if the given certificate is self-signed with any hash algorithm of hash_allowlist (defaults to any hash), it is valid at time, its extensions are not present (if X.509 version 1 certificate), or are appropriate for a CA (BasicConstraints is present and true, KeyUsage extension contains keyCertSign).

val valid_cas : ?allowed_hashes:Digestif.hash' list -> ?time:Ptime.t -> Certificate.t list -> Certificate.t list

valid_cas ~allowed_hashes ~time certificates is valid_certificates, only those certificates which pass the valid_ca check.

Chain of trust verification

type leaf_validation_error = [
  1. | `LeafCertificateExpired of Certificate.t * Ptime.t option
  2. | `LeafInvalidIP of Certificate.t * Ipaddr.t option
  3. | `LeafInvalidName of Certificate.t * [ `host ] Domain_name.t option
  4. | `LeafInvalidVersion of Certificate.t
  5. | `LeafInvalidExtensions of Certificate.t
]

The polymorphic variant of a leaf certificate validation error.

type chain_validation_error = [
  1. | `IntermediateInvalidExtensions of Certificate.t
  2. | `IntermediateCertificateExpired of Certificate.t * Ptime.t option
  3. | `IntermediateInvalidVersion of Certificate.t
  4. | `ChainIssuerSubjectMismatch of Certificate.t * Certificate.t
  5. | `ChainAuthorityKeyIdSubjectKeyIdMismatch of Certificate.t * Certificate.t
  6. | `ChainInvalidPathlen of Certificate.t * int
  7. | `EmptyCertificateChain
  8. | `NoTrustAnchor of Certificate.t
  9. | `Revoked of Certificate.t
]

The polymorphic variant of a chain validation error.

val build_paths : Certificate.t -> Certificate.t list -> Certificate.t list list

build_paths server rest is paths, which are all possible certificate paths starting with server. These chains (C1..Cn) fulfill the predicate that each certificate Cn is issued by the next one in the chain (C(n+1)): the issuer of Cn matches the subject of C(n+1). This is as described in RFC 4158.

The polymorphic variant of a chain validation error: either the leaf certificate is problematic, or the chain itself.

val pp_chain_error : chain_error Fmt.t

pp_chain_error ppf chain_error pretty-prints the chain_error.

val verify_chain : ?ip:Ipaddr.t -> host:[ `host ] Domain_name.t option -> time:(unit -> Ptime.t option) -> ?revoked:(issuer:Certificate.t -> cert:Certificate.t -> bool) -> ?allowed_hashes:Digestif.hash' list -> anchors:Certificate.t list -> Certificate.t list -> (Certificate.t, [> chain_error ]) Stdlib.result

verify_chain ~ip ~host ~time ~revoked ~allowed_hashes ~anchors chain is result, either Ok and the trust anchor used to verify the chain, or Error and the chain error. RFC 5280 describes the implemented path validation algorithm: The validity period of the given certificates is checked against the time. The signature algorithm must be present in allowed_hashes (defaults to SHA-2). The X509v3 extensions of the chain are checked, then a chain of trust from anchors to the server certificate is validated. The path length constraints are checked. The server certificate is checked to contain the given host, using Certificate.hostnames. If ip is specified, the certificate is checked to contain the given ip, using Certificate.ips. The returned certificate is the root of the chain, a member of the given list of anchors.

type fingerprint_validation_error = [
  1. | `InvalidFingerprint of Certificate.t * string * string
]

The polymorphic variant of a fingerprint validation error.

type validation_error = [
  1. | signature_error
  2. | leaf_validation_error
  3. | fingerprint_validation_error
  4. | `EmptyCertificateChain
  5. | `InvalidChain
]

The polymorphic variant of validation errors.

val pp_validation_error : validation_error Fmt.t

pp_validation_error ppf validation_error pretty-prints the validation_error.

type r = ((Certificate.t list * Certificate.t) option, validation_error) Stdlib.result
val verify_chain_of_trust : ?ip:Ipaddr.t -> host:[ `host ] Domain_name.t option -> time:(unit -> Ptime.t option) -> ?revoked:(issuer:Certificate.t -> cert:Certificate.t -> bool) -> ?allowed_hashes:Digestif.hash' list -> anchors:Certificate.t list -> Certificate.t list -> r

verify_chain_of_trust ~ip ~host ~time ~revoked ~allowed_hashes ~anchors certificates is result. First, all possible paths are constructed using the build_paths function, the first certificate of the chain is verified to be a valid leaf certificate (no BasicConstraints extension) and contains the given host (using Certificate.hostnames) or ip if specified (using Certificate.ips; if some path is valid, using verify_chain, the result will be Ok and contain the actual certificate chain and the trust anchor.

Fingerprint verification

val trust_key_fingerprint : ?ip:Ipaddr.t -> host:[ `host ] Domain_name.t option -> time:(unit -> Ptime.t option) -> hash:Digestif.hash' -> fingerprint:string -> Certificate.t list -> r

trust_key_fingerprint ~ip ~host ~time ~hash ~fingerprint certificates is result, the first element of certificates is verified against the given fingerprint using Public_key.fingerprint. If time is provided, the certificate has to be valid at the given timestamp. If host is provided, the certificate is checked for the given host (using Certificate.hostnames). If ip is provided, the certificate is checked to include this IP address (using Certificate.ips).

val trust_cert_fingerprint : ?ip:Ipaddr.t -> host:[ `host ] Domain_name.t option -> time:(unit -> Ptime.t option) -> hash:Digestif.hash' -> fingerprint:string -> Certificate.t list -> r

trust_cert_fingerprint host ~time ~hash ~fingerprint certificates is result, the first element of certificates is verified to match the given fingerprint using Certificate.fingerprint. If time is provided, the certificate is checked to be valid in at the given timestamp. If host is provided, the certificate is checked for the given host (using Certificate.hostnames). If ip is provided, the certificate is checked to include this IP address (using Certificate.ips). Note that public key pinning has advantages over certificate pinning.