mte/unikernel/duniverse/ocaml-x509/lib/x509.mli
2025-11-11 02:07:51 +01:00

1248 lines
51 KiB
OCaml

(** X509 encoding, generation, and validation.
[X509] is a module for handling X.509 certificates and supplementary
material (such as public and private RSA or EC keys), as described in
{{:https://tools.ietf.org/html/rfc5280}RFC 5280}. X.509 describes a
hierarchical public key infrastructure, where all trust is delegated to
certificate authorities (CA). The task of a CA is to sign certificate
signing requests (CSR), which turns them into certificates, after
verification that the requestor is eligible.
An X.509 certificate is an authentication token: a public key, a subject
(e.g. server name), a validity period, optionally a purpose (usage), and
various other optional {{!Extension}Extensions}. The overall approach of
this package is to support decoding what is present in the real world,
including weak ciphers (various validation functions support an allow list
to avoid using weak hashes in chains if needed).
The public keys of trusted CAs are distributed with the software, or
configured manually. When an endpoint connects, it presents its
certificate chain, which are pairwise signed certificates. This chain is
verified: the signatures have to be valid, the last certificate must be
signed by a trusted CA, the name has to match the expected name, all
certificates must be valid at the current time, and the purpose of each
certificate must match its usage. An alternative validator checks that the
hash of the server certificate matches the given hash.
This module uses the [result] type for errors. No provided binging raises
an exception. Provided submodules include decoders and encoders (ASN.1 DER
and PEM encoding) of X.509v3 {{!Certificate}certificates},
{{!Distinguished_name}distinguished names}, {{!Public_key}public keys} and
{{!Private_key}private keys}
({{:http://tools.ietf.org/html/rfc5208}PKCS 8, RFC 5208}), and
{{!Signing_request}certificate signing requests}
({{:http://tools.ietf.org/html/rfc2986}PKCS 10, RFC 2986},
both use parts of
{{:https://tools.ietf.org/html/rfc2985}PKCS 9, RFC 2985}),
{{!Validation} certificate validation} by construction of
{{!Authenticator} authenticators}. Name validation, as defined in
{{:https://tools.ietf.org/html/rfc6125}RFC 6125}, is also implemented.
The archive format for certificates and private keys,
{{:https://tools.ietf.org/html/rfc7292}PKCS 12, RFC 7292}, is
implemented in the {!PKCS12} submodule. While PKCS 12 decryption
supports the weak algorithm used by default by widely used software (RC2!),
the encryption path only supports AES.
Missing is the handling of online certificate status protocol. Some X.509v3
extensions are not handled, but only parsed, such as name constraints. If any
extension is marked as critical in a certificate, but not handled, the
validation will fail.
{e v1.0.6 - {{:https://github.com/mirleft/ocaml-x509 }homepage}} *)
(** Hostnames (strict, wildcard), used for validation. *)
module Host : sig
(** The polymorphic variant for hostname validation. *)
type t = [ `Strict | `Wildcard ] * [ `host ] Domain_name.t
(** [pp ppf host] pretty-prints [host] on [ppf]: if it is a wildcard, "*." is
prefixed to the domain name. *)
val pp : t Fmt.t
(** The module for a set of hostnames. *)
module Set : sig
include Set.S with type elt = t
(** [pp ppf host_set] pretty-prints the [host_set]. *)
val pp : t Fmt.t
end
end
(** Types of keys *)
module Key_type : sig
(** The polymorphic variant of key types. *)
type t = [ `RSA | `ED25519 | `P256 | `P384 | `P521 ]
val strings : (string * t) list
(** [strings] is an associative list of string and key_type pairs. Useful for
{{:https://erratique.ch/software/cmdliner}cmdliner} (Arg.enum). *)
val to_string : t -> string
(** [to_string kt] is a string representation of [kt]. *)
val of_string : string -> (t, [> `Msg of string ]) result
(** [of_string s] is [Ok key_type] if the string could be decoded as
[key_type], or an [Error _]. *)
val pp : t Fmt.t
(** [pp ppf t] is a pretty printer of [t] on [ppf]. *)
(** The type of signature schemes. *)
type signature_scheme = [ `RSA_PSS | `RSA_PKCS1 | `ECDSA | `ED25519 ]
val pp_signature_scheme : signature_scheme Fmt.t
(** [pp_signature_scheme ppf s] is a pretty-printer of [s] on [ppf]. *)
val supports_signature_scheme : t -> signature_scheme -> bool
(** [supports_scheme key_type scheme] is [true] if the signature [scheme]
is supported with [key type]. *)
end
(** Public keys *)
module Public_key : sig
(** Public keys as specified in {{:http://tools.ietf.org/html/rfc5208}PKCS 8}
are supported in this module. *)
(** {1 The type for public keys} *)
(** The polymorphic variant of public keys, with
{{:http://tools.ietf.org/html/rfc5208}PKCS 8} encoding and decoding to
PEM. *)
type t = [
| `RSA of Mirage_crypto_pk.Rsa.pub
| `ED25519 of Mirage_crypto_ec.Ed25519.pub
| `P256 of Mirage_crypto_ec.P256.Dsa.pub
| `P384 of Mirage_crypto_ec.P384.Dsa.pub
| `P521 of Mirage_crypto_ec.P521.Dsa.pub
]
(** {1 Operations on public keys} *)
(** [pp ppf pub] pretty-prints the public key [pub] on [ppf]. *)
val pp : t Fmt.t
(** [id public_key] is [digest], the 160-bit [`SHA1] hash of the BIT
STRING subjectPublicKey (excluding tag, length, and number of
unused bits) for publicKeyInfo of [public_key].
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.2}RFC 5280, 4.2.1.2, variant (1)} *)
val id : t -> string
(** [fingerprint ?hash public_key] is [digest], the hash (by
default SHA256) of the DER encoded public key (equivalent to
[openssl x509 -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -HASH]). *)
val fingerprint : ?hash:Digestif.hash' -> t -> string
(** [key_type public_key] is its [key_type]. *)
val key_type : t -> Key_type.t
(** {1 Cryptographic verify operation} *)
(** [verify hash ~scheme ~signature key data] verifies whether the [signature]
on [data] is valid using the [key], or not. The [signature] must be in
ASN.1 DER encoding. The [scheme] defaults to [`RSA_PSS] for RSA,
[`ED25519] for ED25519, and [`ECDSA] for other EC keys. *)
val verify : Digestif.hash' ->
?scheme:Key_type.signature_scheme ->
signature:string -> t ->
[ `Message of string | `Digest of string ] ->
(unit, [> `Msg of string ]) result
(** {1 Decoding and encoding in ASN.1 DER and PEM format} *)
(** [encode_der pk] is [buffer], the ASN.1 encoding of the given public key. *)
val encode_der : t -> string
(** [decode_der buffer] is [pubkey], the public key of the ASN.1 encoded buffer. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** [decode_pem pem] is [t], where the public key of [pem] is extracted *)
val decode_pem : string -> (t, [> `Msg of string ]) result
(** [encode_pem public_key] is [pem], the pem encoded public key. *)
val encode_pem : t -> string
end
(** Private keys *)
module Private_key : sig
(** Private keys as defined in
{{:http://tools.ietf.org/html/rfc5208}PKCS 8}: decoding and encoding
in PEM format *)
(** {1 The type for private keys} *)
(** The polymorphic variant of private keys. *)
type t = [
| `RSA of Mirage_crypto_pk.Rsa.priv
| `ED25519 of Mirage_crypto_ec.Ed25519.priv
| `P256 of Mirage_crypto_ec.P256.Dsa.priv
| `P384 of Mirage_crypto_ec.P384.Dsa.priv
| `P521 of Mirage_crypto_ec.P521.Dsa.priv
]
(** {1 Constructing private keys} *)
(** [generate ~seed ~bits type] generates a private key of the given
key type. The argument [bits] is only used for the bit length of RSA keys.
If [seed] is provided, this is used to seed the random number generator.
*)
val generate : ?seed:string -> ?bits:int -> Key_type.t -> t
(** [of_octets data type] decodes the buffer as private key. Only supported
for elliptic curve keys. *)
val of_octets : string -> Key_type.t -> (t, [> `Msg of string ]) result
(** [of_string ~seed_or_data ~bits type data] attempts to decode the data as a
private key. If [seed_or_data] is provided and [`Seed], the [data] is
taken as seed and {!generate} is used. If it is [`Data], {!of_octets} is
used with the Base64 decoded [data]. By default, if [type] is RSA, the
data is used as seed, otherwise directly as the private key data. *)
val of_string : ?seed_or_data:[`Seed | `Data] -> ?bits:int -> Key_type.t ->
string -> (t, [> `Msg of string ]) result
(** {1 Operations on private keys} *)
(** [key_type priv] is the key type of [priv]. *)
val key_type : t -> Key_type.t
(** [public priv] is the corresponding public key of [priv]. *)
val public : t -> Public_key.t
(** {1 Cryptographic sign operation} *)
(** [sign hash ~scheme key data] signs [data] with [key] using [hash] and
[scheme]. If [data] is [`Message _], the [hash] will be applied before
the signature. The [scheme] defaults to [`RSA_PSS] for RSA keys,
[`ED25519] for ED25519, and [`ECDSA] for other EC keys. *)
val sign : Digestif.hash' ->
?scheme:Key_type.signature_scheme ->
t -> [ `Digest of string | `Message of string ] ->
(string, [> `Msg of string ]) result
(** {1 Decoding and encoding in ASN.1 DER and PEM format} *)
(** [decode_der der] is [t], where the private key of [der] is
extracted. It must be in PKCS8 (RFC 5208, Section 5) PrivateKeyInfo
structure. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** [encode_der key] is [der], the encoded private key as PKCS8 (RFC 5208,
Section 5) PrivateKeyInfo structure. *)
val encode_der : t -> string
(** [decode_pem pem] is [t], where the private key of [pem] is
extracted. Both RSA PRIVATE KEY and PRIVATE KEY stanzas are supported. *)
val decode_pem : string -> (t, [> `Msg of string ]) result
(** [encode_pem key] is [pem], the encoded private key (using [PRIVATE KEY]). *)
val encode_pem : t -> string
end
(** X.500 distinguished name *)
module Distinguished_name : sig
(** The variant of a relative distinguished name component, as defined in
X.500: an attribute type and value. *)
type attribute =
| CN of string
| Serialnumber of string
| C of string
| L of string
| ST of string
| O of string
| OU of string
| T of string
| DNQ of string
| Mail of string
| DC of string
| Given_name of string
| Surname of string
| Initials of string
| Pseudonym of string
| Generation of string
| Street of string
| Userid of string
| Other of Asn.oid * string
(** Relative_distinguished_name is a set of attributes. *)
module Relative_distinguished_name : Set.S with type elt = attribute
(** A distinguished name is a list of relative distinguished names, starting
with the most significant component. *)
type t = Relative_distinguished_name.t list
(** [equal a b] is [true] if the distinguished names [a] and [b] are equal. *)
val equal : t -> t -> bool
(** [make_pp ()] creates a customized pretty-printer for {!t}.
@param format
Determines RDN order, escaping rules, and the default spacing:
- [`RFC4514] produces the
{{:https://tools.ietf.org/html/rfc4514}RFC4514}.
RDNs are written in reverse order of the ASN.1 representation and
spacing defaults to tight.
- [`OpenSSL] produces the a format similar to OpenSSL. RDNs are written
in the order of the ASN.1 representation, and spacing defaults to
loose.
- [`OSF] emits RDNs in the order they occur in the ASN.1 representation,
each prefixed by a slashes, using tight spacing. This format is
designed by analogy to RFC4514, substituting slash for comma an
semicolon, and may currently not be fully compliant with the OSF
specifications.
@param spacing
Determines whether to add space around separators:
- [`Tight] to not add any redundant space,
- [`Medium] to add space after comma and around plus signs, and
- [`Loose] to also add space around equality signs.
This parameter is currently ignored for the OSF format.
The pretty-printer can be wrapped in a box to control line breaking and
set it apart, otherwise the RDN components will flow with the surrounding
text. *)
val make_pp :
format: [`RFC4514 | `OpenSSL | `OSF] ->
?spacing: [`Tight | `Medium | `Loose] ->
unit -> t Fmt.t
(** [pp ppf dn] pretty-prints the distinguished name. This is currently
[Fmt.hbox (make_pp ~format:`OSF ())]. If your application relies on the
precise format, it is advicable to create a custom formatter with
{!make_pp} to guard against future changes to the default format. *)
val pp : t Fmt.t
(** [common_name t] is [Some x] if the distinguished name [t] contains a
[CN x], [None] otherwise. *)
val common_name : t -> string option
(** [decode_der cs] is [dn], the ASN.1 decoded distinguished name of [cs]. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** [encode_der dn] is [octets], the ASN.1 encoded representation of the
distinguished name [dn]. *)
val encode_der : t -> string
end
(** A list of [general_name]s is the value of both
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.6}subjectAltName}
and
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.7}IssuerAltName}
extension. *)
module General_name : sig
type _ k =
| Other : Asn.oid -> string list k
| Rfc_822 : string list k
| DNS : string list k
| X400_address : unit k
| Directory : Distinguished_name.t list k
| EDI_party : (string option * string) list k
| URI : string list k
| IP : string list k
| Registered_id : Asn.oid list k
include Gmap.S with type 'a key = 'a k
val pp : t Fmt.t
end
(** X.509v3 extensions *)
module Extension : sig
(** The polymorphic variant of
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.3}key usages}. *)
type key_usage = [
| `Digital_signature
| `Content_commitment
| `Key_encipherment
| `Data_encipherment
| `Key_agreement
| `Key_cert_sign
| `CRL_sign
| `Encipher_only
| `Decipher_only
]
(** The polymorphic variant of
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.12}extended key usages}. *)
type extended_key_usage = [
| `Any
| `Server_auth
| `Client_auth
| `Code_signing
| `Email_protection
| `Ipsec_end
| `Ipsec_tunnel
| `Ipsec_user
| `Time_stamping
| `Ocsp_signing
| `Other of Asn.oid
]
(** The authority key identifier, as present in the
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.1}Authority Key Identifier}
extension. *)
type authority_key_id = string option * General_name.t * string option
(** The private key usage period, as defined in
{{:https://tools.ietf.org/html/rfc3280#section-4.2.1.4}RFC 3280}. *)
type priv_key_usage_period = [
| `Interval of Ptime.t * Ptime.t
| `Not_after of Ptime.t
| `Not_before of Ptime.t
]
(** Name constraints, as defined in
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.10}RFC 5280}. *)
type name_constraint = (General_name.b * int * int option) list
(** Certificate policies, the
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.4}policy extension}. *)
type policy = [ `Any | `Something of Asn.oid ]
(** Type of
{{:https://tools.ietf.org/html/rfc5280#section-5.3.1}revocation reasons}
for a given distribution point. *)
type reason = [
| `Unspecified
| `Key_compromise
| `CA_compromise
| `Affiliation_changed
| `Superseded
| `Cessation_of_operation
| `Certificate_hold
| `Remove_from_CRL
| `Privilege_withdrawn
| `AA_compromise
]
(** Distribution point name, either a full one using general names, or a
relative one using a distinguished name. *)
type distribution_point_name =
[ `Full of General_name.t
| `Relative of Distinguished_name.t ]
(** {{:https://tools.ietf.org/html/rfc5280#section-4.2.1.13}Distribution point},
consisting of an optional name, an optional list of allowed reasons, and
an optional issuer. *)
type distribution_point =
distribution_point_name option *
reason list option *
General_name.t option
(** The type of an extension: the critical flag and the value itself. *)
type 'a extension = bool * 'a
(** The type of supported
{{:https://tools.ietf.org/html/rfc5280#section-4.2}X509v3} and
{{:https://tools.ietf.org/html/rfc5280#section-5.2}CRL} extensions. *)
type _ k =
| Unsupported : Asn.oid -> string extension k
| Subject_alt_name : General_name.t extension k
| Authority_key_id : authority_key_id extension k
| Subject_key_id : string extension k
| Issuer_alt_name : General_name.t extension k
| Key_usage : key_usage list extension k
| Ext_key_usage : extended_key_usage list extension k
| Basic_constraints : (bool * int option) extension k
| CRL_number : int extension k
| Delta_CRL_indicator : int extension k
| Priv_key_period : priv_key_usage_period extension k
| Name_constraints : (name_constraint * name_constraint) extension k
| CRL_distribution_points : distribution_point list extension k
| Issuing_distribution_point : (distribution_point_name option * bool * bool * reason list option * bool * bool) extension k
| Freshest_CRL : distribution_point list extension k
| Reason : reason extension k
| Invalidity_date : Ptime.t extension k
| Certificate_issuer : General_name.t extension k
| Policies : policy list extension k
include Gmap.S with type 'a key = 'a k
(** [critical ext_key ext_value] is the critical bit in [ext_value]. *)
val critical : 'a key -> 'a -> bool
(** [pp ppf ext_map] pretty-prints the extension map. *)
val pp : t Fmt.t
(** [pp' custom ppf ext_map] pretty-prints the extension map using [custom]
to print [Unsupported _] extensions. *)
val pp' : (Asn.oid * string) Fmt.t -> t Fmt.t
end
(** X509v3 certificate *)
module Certificate : sig
(** [decode_pkcs1_digest_info buffer] is [hash, signature], the hash and raw
signature of the given [buffer] in ASN.1 DER encoding, or an error. *)
val decode_pkcs1_digest_info : string ->
([ `MD5 | `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ] * string, [> `Msg of string ]) result
(** [encode_pkcs1_digest_info (hash, signature)] is [data], the ASN.1 DER
encoded hash and signature. *)
val encode_pkcs1_digest_info : [ `MD5 | `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ] * string -> string
(** {1 Abstract certificate type} *)
(** The abstract type of a certificate. *)
type t
(** [pp ppf cert] pretty-prints the certificate. *)
val pp : t Fmt.t
(** [pp' pp_custom_extensions ppf cert] pretty-prints the certificate using
[pp_custom_extensions] for [Extension.Unsupported _] extension. *)
val pp' : (Asn.oid * string) Fmt.t -> t Fmt.t
(** {1 Encoding and decoding in ASN.1 DER and PEM format} *)
(** [decode_der octets] is [certificate], the ASN.1 decoded [certificate]
or an error. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** [encode_der certificate] is [octets], the ASN.1 encoded representation of
the [certificate]. *)
val encode_der : t -> string
(** [decode_pem_multiple pem] is [t list], where all certificates of the [pem]
are extracted *)
val decode_pem_multiple : string -> (t list, [> `Msg of string ]) result
(** [fold_decode_pem_multiple fn acc pem] is a fold of the function [fn],
with the initial accumulator [acc], over the certificates extracted
(and potential parsing errors) from the [pem]. *)
val fold_decode_pem_multiple : ('a -> (t, [> `Msg of string ]) result -> 'a) -> 'a -> string -> 'a
(** [decode_pem pem] is [t], where the single certificate of the
[pem] is extracted *)
val decode_pem : string -> (t, [> `Msg of string ]) result
(** [encode_pem_multiple certificates] is [pem], the pem encoded certificates. *)
val encode_pem_multiple : t list -> string
(** [encode_pem certificate] is [pem], the pem encoded certificate. *)
val encode_pem : t -> string
(** {1 Operations on certificates} *)
(** [supports_keytype certificate key_type] is [result], whether public key of
the [certificate] matches the given [key_type]. *)
val supports_keytype : t -> Key_type.t -> bool
(** [public_key certificate] is [pk], the public key of the [certificate]. *)
val public_key : t -> Public_key.t
(** [signature_algorithm certificate] is the algorithm used for the signature. *)
val signature_algorithm : t ->
(Key_type.signature_scheme * Digestif.hash') option
(** [hostnames certficate] is the set of domain names this
[certificate] is valid for. Currently, these are the DNS names of the
{{:https://tools.ietf.org/html/rfc5280#section-4.2.1.6}Subject Alternative Name}
extension, if present, or otherwise the singleton set containing the common
name of the certificate subject. *)
val hostnames : t -> Host.Set.t
(** [supports_hostname certificate hostname] is [result], whether the
[certificate] contains the given [hostname], using {!hostnames}. *)
val supports_hostname : t -> [`host] Domain_name.t -> bool
(** [ips certificate] are the IP addresses the certificate is valid
for (as specified in SubjectAlternativeName extensioni). *)
val ips : t -> Ipaddr.Set.t
(** [supports_ip cert ip] is [true] if the [ip] is mentioned in
the SubjectAlternativeName extension, [false] otherwise. *)
val supports_ip : t -> Ipaddr.t -> bool
(** [fingerprint hash cert] is [digest], the digest of [cert] using the
specified [hash] algorithm *)
val fingerprint : Digestif.hash' -> t -> string
(** [subject certificate] is [dn], the subject as distinguished name of
the [certificate]. *)
val subject : t -> Distinguished_name.t
(** [issuer certificate] is [dn], the issuer as distinguished name of
the [certificate]. *)
val issuer : t -> Distinguished_name.t
(** [serial certificate] is [sn], the serial number of the [certificate].
A serial is a positive number of at most 20 octets. 0 is supported. A
negative serial number is supported when decoding a certificate, but when
encoding, an octet of 0 is prepended making it positive. *)
val serial : t -> string
(** [validity certificate] is [from, until], the validity of the certificate. *)
val validity : t -> Ptime.t * Ptime.t
(** [extensions certificate] is the extension map of [certificate]. *)
val extensions : t -> Extension.t
end
(** Chain Validation. *)
module Validation : sig
(** 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 {{:https://tools.ietf.org/html/rfc5280#section-6}path
validation} algorithm for authenticating chains, but this does not handle
multiple possible paths. {{:https://tools.ietf.org/html/rfc4158}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).
*)
(** The type of signature verification errors. *)
type signature_error = [
| `Bad_signature of Distinguished_name.t * string
| `Bad_encoding of Distinguished_name.t * string * string
| `Hash_not_allowed of Distinguished_name.t * [ `MD5 | `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ]
| `Unsupported_keytype of Distinguished_name.t * Public_key.t
| `Unsupported_algorithm of Distinguished_name.t * string
| `Msg of string
]
(** [pp_signature_error ppf sige] pretty-prints the signature error [sige] on
[ppf]. *)
val pp_signature_error : signature_error Fmt.t
(** {1 Certificate Authorities} *)
(** The polymorphic variant of possible certificate authorities failures. *)
type ca_error = [
| signature_error
| `CAIssuerSubjectMismatch of Certificate.t
| `CAInvalidVersion of Certificate.t
| `CACertificateExpired of Certificate.t * Ptime.t option
| `CAInvalidExtensions of Certificate.t
]
(** [pp_ca_error ppf ca_error] pretty-prints the CA error [ca_error]. *)
val pp_ca_error : ca_error Fmt.t
(** [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_ca : ?allowed_hashes:Digestif.hash' list -> ?time:Ptime.t ->
Certificate.t -> (unit, [> ca_error ]) result
(** [valid_cas ~allowed_hashes ~time certificates] is [valid_certificates],
only those certificates which pass the {!valid_ca} check. *)
val valid_cas : ?allowed_hashes:Digestif.hash' list -> ?time:Ptime.t ->
Certificate.t list -> Certificate.t list
(** {1 Chain of trust verification} *)
(** The polymorphic variant of a leaf certificate validation error. *)
type leaf_validation_error = [
| `LeafCertificateExpired of Certificate.t * Ptime.t option
| `LeafInvalidIP of Certificate.t * Ipaddr.t option
| `LeafInvalidName of Certificate.t * [`host] Domain_name.t option
| `LeafInvalidVersion of Certificate.t
| `LeafInvalidExtensions of Certificate.t
]
(** The polymorphic variant of a chain validation error. *)
type chain_validation_error = [
| `IntermediateInvalidExtensions of Certificate.t
| `IntermediateCertificateExpired of Certificate.t * Ptime.t option
| `IntermediateInvalidVersion of Certificate.t
| `ChainIssuerSubjectMismatch of Certificate.t * Certificate.t
| `ChainAuthorityKeyIdSubjectKeyIdMismatch of Certificate.t * Certificate.t
| `ChainInvalidPathlen of Certificate.t * int
| `EmptyCertificateChain
| `NoTrustAnchor of Certificate.t
| `Revoked of Certificate.t
]
(** [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
{{:https://tools.ietf.org/html/rfc4158}RFC 4158}. *)
val build_paths : Certificate.t -> Certificate.t list -> Certificate.t list list
(** The polymorphic variant of a chain validation error: either the leaf
certificate is problematic, or the chain itself. *)
type chain_error = [
| signature_error
| leaf_validation_error
| chain_validation_error
]
(** [pp_chain_error ppf chain_error] pretty-prints the [chain_error]. *)
val pp_chain_error : chain_error Fmt.t
(** [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
{{:https://tools.ietf.org/html/rfc5280#section-6.1}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]. *)
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 ]) result
(** The polymorphic variant of a fingerprint validation error. *)
type fingerprint_validation_error = [
| `InvalidFingerprint of Certificate.t * string * string
]
(** The polymorphic variant of validation errors. *)
type validation_error = [
| signature_error
| leaf_validation_error
| fingerprint_validation_error
| `EmptyCertificateChain
| `InvalidChain
]
(** [pp_validation_error ppf validation_error] pretty-prints the
[validation_error]. *)
val pp_validation_error : validation_error Fmt.t
type r = ((Certificate.t list * Certificate.t) option, validation_error) result
(** [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. *)
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
(** {1 Fingerprint verification} *)
(** [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_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_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
{{!trust_key_fingerprint}public key pinning} has
{{:https://www.imperialviolet.org/2011/05/04/pinning.html} advantages}
over certificate pinning. *)
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
end
(** Certificate Signing request *)
(** A certificate authority (CA) deals with
{{:https://tools.ietf.org/html/rfc2986}PKCS 10 certificate signing requests},
their construction and encoding, and provisioning using a private key to
generate a certificate with a signature thereof. *)
module Signing_request : sig
(** The abstract type of a (self-signed) certification request. *)
type t
(** {1 Decoding and encoding in ASN.1 DER and PEM format} *)
(** [decode_der ~allowed_hashes octets] is [signing_request], the ASN.1
decoded [octets] or an error. The signature on the signing request
is validated, and its hash algorithm must be in [allowed_hashes] (by
default only SHA-2 is accepted). *)
val decode_der : ?allowed_hashes:Digestif.hash' list -> string ->
(t, [> `Msg of string ]) result
(** [encode_der sr] is [octets], the ASN.1 encoded representation of the [sr]. *)
val encode_der : t -> string
(** [decode_pem pem] is [t], where the single signing request of the [pem] is extracted *)
val decode_pem : string -> (t, [> `Msg of string ]) result
(** [encode_pem signing_request] is [pem], the pem encoded signing request. *)
val encode_pem : t -> string
(** {1 Construction of a signing request} *)
module Ext : sig
(** The GADT of certificate request extensions, as defined in
{{:http://tools.ietf.org/html/rfc2985}PKCS 9 (RFC 2985)}. *)
type _ k =
| Password : string k
| Name : string k
| Extensions : Extension.t k
include Gmap.S with type 'a key = 'a k
val pp : t Fmt.t
end
(** The raw request info of a
{{:https://tools.ietf.org/html/rfc2986#section-4}PKCS 10 certification request info}. *)
type request_info = {
subject : Distinguished_name.t ;
public_key : Public_key.t ;
extensions : Ext.t ;
}
(** [info signing_request] is {!request_info}, the information inside the
signing_request. *)
val info : t -> request_info
(** [signature_algorithm signing_request] is the algorithm used for the signature. *)
val signature_algorithm : t ->
(Key_type.signature_scheme * Digestif.hash') option
(** [hostnames signing_request] is the set of domain names this
[signing_request] is requesting. This is either the content of the DNS
entries of the SubjectAlternativeName extension, or the common name of the
[signing_request]. *)
val hostnames : t -> Host.Set.t
(** [create subject ~digest ~extensions private] creates [signing_request],
a certification request using the given [subject], [digest] (defaults to
[`SHA256]) and list of [extensions]. *)
val create : Distinguished_name.t -> ?digest:Digestif.hash' ->
?extensions:Ext.t -> Private_key.t -> (t, [> `Msg of string ]) result
(** {1 Provision a signing request to a certificate} *)
(** [sign signing_request ~valid_from ~valid_until ~allowed_hashes ~digest ~serial ~extensions ~subject private issuer]
creates [certificate], a signed certificate. Signing can fail if the
signature on the [signing_request] is invalid, or its hash algorithm does
not occur in [allowed_hashes] (default all SHA-2 algorithms). Public key
and subject are taken from the [signing_request] unless [subject] is
passed, the [extensions] are added to the X.509 certificate. The
[private] key is used to sign the certificate, the [issuer] is recorded
in the certificate. The digest defaults to [`SHA256]. The [serial]
defaults to a random value between 1 and 2^64. Certificate version is
always 3. Please note that the extensions in the [signing_request] are
ignored, you can pass them using:
{[match Ext.find Extensions (info csr).extensions with
| Ok ext -> ext
| Error _ -> Extension.empty
]} *)
val sign : t -> valid_from:Ptime.t -> valid_until:Ptime.t ->
?allowed_hashes:Digestif.hash' list ->
?digest:Digestif.hash' -> ?serial:string -> ?extensions:Extension.t ->
?subject:Distinguished_name.t ->
Private_key.t -> Distinguished_name.t ->
(Certificate.t, Validation.signature_error) result
end
(** X.509 Certificate Revocation Lists. *)
module CRL : sig
(** A certificate revocation list is a signed structure consisting of an
issuer, a timestamp, possibly a timestamp when to expect the next update,
and a list of revoked certificates (represented by a serial, a revocation
date, and extensions (e.g. reason) - see
{{:https://tools.ietf.org/html/rfc5280#section-5.2}RFC 5280 section 5.2}
for a list of available extensions (not enforced)). It also may contain
any extensions, e.g. a CRL number and whether it is partial or complete. *)
(** The type of a revocation list, kept abstract. *)
type t
(** {1 Encoding and decoding in ASN.1 DER format} *)
(** [encode_der crl] is [buffer], the ASN.1 DER encoding of the given
certificate revocation list. *)
val encode_der : t -> string
(** [decode_der buffer] is [crl], the certificate revocation list of the
ASN.1 encoded buffer. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** {1 Operations on CRLs} *)
(** [issuer c] is the issuer of the revocation list. *)
val issuer : t -> Distinguished_name.t
(** [this_update t] is the timestamp of the revocation list. *)
val this_update : t -> Ptime.t
(** [next_update t] is either [None] or [Some ts], the timestamp of the next
update. *)
val next_update : t -> Ptime.t option
(** The type of a revoked certificate, which consists of a serial number, the
revocation date, and possibly extensions. See
{{:https://tools.ietf.org/html/rfc5280#section-5.3}RFC 5280 section 5.3}
for allowed extensions (not enforced). *)
type revoked_cert = {
serial : string ;
date : Ptime.t ;
extensions : Extension.t
}
(** [reason revoked] extracts the [Reason] extension from [revoked] if
present. *)
val reason : revoked_cert -> Extension.reason option
(** [revoked_certificates t] is the list of revoked certificates of the
revocation list. *)
val revoked_certificates : t -> revoked_cert list
(** [extensions t] is the list of extensions, see RFC 5280 section 5.2 for
possible values. *)
val extensions : t -> Extension.t
(** [crl_number t] is the number of the CRL. *)
val crl_number : t -> int option
(** [signature_algorithm t] is the algorithm used for the signature. *)
val signature_algorithm : t ->
(Key_type.signature_scheme * Digestif.hash') option
(** {1 Validation and verification of CRLs} *)
(** [validate t ~allowed_hashes pk] validates the digital signature of the
revocation list. The [allowed_hashes] defaults to SHA-2. *)
val validate : t -> ?allowed_hashes:Digestif.hash' list ->
Public_key.t -> (unit, [> Validation.signature_error ]) result
(** The type of CRL verification errors. *)
type verification_error = [
| Validation.signature_error
| `Issuer_subject_mismatch of Distinguished_name.t * Distinguished_name.t
| `Not_yet_valid of Distinguished_name.t * Ptime.t * Ptime.t
| `Next_update_scheduled of Distinguished_name.t * Ptime.t * Ptime.t
]
(** [pp_verification_error ppf vere] pretty-prints the CRL verification error
[vere] on [ppf]. *)
val pp_verification_error : verification_error Fmt.t
(** [verify t ~allowed_hashes ~time cert] verifies that the issuer of [t]
matches the subject of [cert], and validates the digital signature of the
revocation list. The used hash algorithm must be in the [allowed_hashes]
(defaults to SHA-2). If [time] is provided, it must be after [this_update]
and before [next_update] of [t]. *)
val verify : t -> ?allowed_hashes:Digestif.hash' list ->
?time:Ptime.t -> Certificate.t -> (unit, [> verification_error ]) result
(** [is_revoked ~allowed_hashes ~issuer ~cert crls] is [true] if there exists
a revocation of [cert] in [crls] which is signed by the [issuer]. The
subject of [issuer] must match the issuer of the crl. The hash algorithm
used for signing must be in the [allowed_hashes] (defaults to SHA-2). *)
val is_revoked : ?allowed_hashes:Digestif.hash' list ->
issuer:Certificate.t -> cert:Certificate.t -> t list -> bool
(** {1 Construction and signing of CRLs} *)
(** [revoked ~digest ~issuer ~this_update ~next_update ~extensions certs priv]
constructs a revocation list with the given parameters. *)
val revoke : ?digest:Digestif.hash' ->
issuer:Distinguished_name.t ->
this_update:Ptime.t -> ?next_update:Ptime.t ->
?extensions:Extension.t ->
revoked_cert list -> Private_key.t -> (t, [> `Msg of string ]) result
(** [revoke_certificate cert ~this_update ~next_update t priv] adds [cert] to
the revocation list, increments its counter, adjusts [this_update] and
[next_update] timestamps, and digitally signs it using [priv]. *)
val revoke_certificate : revoked_cert ->
this_update:Ptime.t -> ?next_update:Ptime.t -> t -> Private_key.t ->
(t, [> `Msg of string ]) result
(** [revoke_certificates certs ~this_update ~next_update t priv] adds [certs]
to the revocation list, increments its counter, adjusts [this_update] and
[next_update] timestamps, and digitally signs it using [priv]. *)
val revoke_certificates : revoked_cert list ->
this_update:Ptime.t -> ?next_update:Ptime.t -> t -> Private_key.t ->
(t, [> `Msg of string ]) result
end
(** Certificate chain authenticators *)
module Authenticator : sig
(** An authenticator [t] is a function type which takes optionally an IP
address, a hostname and a certificate stack to an authentication decision
{!Validation.r}. If [ip] is specified, it needs to be present in the
SubjectAlternativeName extension of the server certificate. *)
type t = ?ip:Ipaddr.t -> host:[`host] Domain_name.t option ->
Certificate.t list -> Validation.r
(** [chain_of_trust ~time ~crls ~allowed_hashes trust_anchors] is
[authenticator], which uses the given [time] and list of [trust_anchors]
to verify the certificate chain. All signatures must use a hash algorithm
specified in [allowed_hashes], defaults to SHA-2. Signatures on revocation
lists [crls] must also use a hash algorithm in [allowed_hashes]. This is
an implementation of the algorithm described in
{{:https://tools.ietf.org/html/rfc5280#section-6.1}RFC 5280}, using
{!Validation.verify_chain_of_trust}. The given trust anchors are not
validated, you can filter them with {!Validation.valid_cas} if desired. *)
val chain_of_trust : time:(unit -> Ptime.t option) -> ?crls:CRL.t list ->
?allowed_hashes:Digestif.hash' list -> Certificate.t list -> t
(** [key_fingerprint ~time hash fingerprint] is an [authenticator]
that uses the given [time] and [fingerprint] to verify that the
fingerprint of the first element of the certificate chain matches the
given fingerprint, using {!Validation.trust_key_fingerprint}. *)
val key_fingerprint : time:(unit -> Ptime.t option) ->
hash:Digestif.hash' -> fingerprint:string -> t
(** [cert_fingerprint ~time hash fingerprint] is an [authenticator]
that uses the given [time] and [fingerprint] to verify the first
element of the certificate chain, using
{!Validation.trust_cert_fingerprint}. Note that
{{!key_fingerprint}public key pinning} has
{{:https://www.imperialviolet.org/2011/05/04/pinning.html} advantages}
over certificate pinning. *)
val cert_fingerprint : time:(unit -> Ptime.t option) ->
hash:Digestif.hash' -> fingerprint:string -> t
(** [of_string str] tries to parse the given [str] to an
{!type:Authenticator.t}. The format of it is:
- [none] no authentication,
- [key-fp(:<hash>?):<base64-encoded fingerprint>] to authenticate a peer via
its key fingerprint (hash is optional and defaults to SHA256),
- [cert-fp(:<hash>?):<base64-encoded fingerprint>] to authenticate a peer via
its certificate fingerprint (hash is optional and defaults to SHA256),
- [trust-anchor(:<base64-encoded DER certificate>)+] to authenticate a
peer from a list of certificates (certificate must be in PEM format
without header and footer (----BEGIN CERTIFICATE-----) and without
newlines).
If decoding is successful, the returned value expects a function which
outputs the current timestamp ([unit -> Ptime.t option]) and is then
an authenticator. If decoding fails, and error is returned. *)
val of_string : string ->
((unit -> Ptime.t option) -> t, [> `Msg of string ]) result
end
(** PKCS12 archive files *)
module PKCS12 : sig
(** A PKCS12 encoded archive file, *)
type t
(** [decode_der buffer] is [t], the PKCS12 archive of [buffer]. *)
val decode_der : string -> (t, [> `Msg of string ]) result
(** [encode_der t] is [buf], the PKCS12 encoded archive of [t]. *)
val encode_der : t -> string
(** [verify password t] verifies and decrypts the PKCS12 archive [t]. The
result is the contents of the archive. *)
val verify : string -> t ->
([ `Certificate of Certificate.t | `Crl of CRL.t
| `Private_key of Private_key.t | `Decrypted_private_key of Private_key.t ]
list, [> `Msg of string ]) result
(** [create ~mac ~algorithm ~iterations password certificates private_key]
constructs a PKCS12 archive with [certificates] and [private_key]. They
are encrypted with [algorithm] (using PBES2, PKCS5v2) and integrity
protected using [mac]. A [local key id] is always embedded in the private
key and matching certificate. *)
val create : ?mac:[`SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ] ->
?algorithm:[ `AES128_CBC | `AES192_CBC | `AES256_CBC ] ->
?iterations:int ->
string -> Certificate.t list -> Private_key.t ->
t
end
(** OCSP (Online Certificate Status Protocol) as described in
{{:https://tools.ietf.org/html/rfc6960}RFC 6960}. *)
module OCSP : sig
(** type for CertID to distinguish requested certs *)
type cert_id
(** [create_cert_id issuer serial] creates cert_id for this serial *)
val create_cert_id : ?hash:[ `MD5 | `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ] -> Certificate.t -> string ->
cert_id
(** [cert_id_serial certid] is serial number of this certid *)
val cert_id_serial : cert_id -> string
(** [pp_cert_id ppf cert_id] pretty prints cert_id *)
val pp_cert_id : cert_id Fmt.t
(** Module for encoding and decoding OCSP requests. *)
module Request : sig
(** type for Request *)
type t
(** [pp ppf request] pretty prints request *)
val pp : t Fmt.t
(** [create ~certs ~digest ~requestor_name ~key certids] creates request
for given [certids] and, if [key] is provided, signs it using [digest].
[requestorName] may be used by responder to distinguish requesters.
[certs] may be used by responder to check requestor authority. *)
val create : ?certs:Certificate.t list -> ?digest:Digestif.hash' ->
?requestor_name:General_name.b -> ?key:Private_key.t -> cert_id list ->
(t, [> `Msg of string ]) result
(** [validate request key] validates the signature of [request]
with the pulic [key]. *)
val validate : t -> ?allowed_hashes:Digestif.hash' list ->
Public_key.t ->
(unit, [> Validation.signature_error | `No_signature ]) result
(** [requestor_name request] is requestorName from this request *)
val requestor_name : t -> General_name.b option
(** [cert_ids request] is cert ids from this request *)
val cert_ids : t -> cert_id list
(** [decode_der buffer] decodes request in buffer *)
val decode_der : string -> (t, Asn.error) result
(** [encode_der request] encodes request into buffer *)
val encode_der : t -> string
end
(** Module for encoding and decoding OCSP responses. *)
module Response : sig
(** type for OCSPResponseStatus *)
type status = [
| `InternalError
| `MalformedRequest
| `SigRequired
| `Successful
| `TryLater
| `Unauthorized
]
(** [pp_status ppf status] pretty prints status *)
val pp_status : status Fmt.t
(** type for CertStatus *)
type cert_status = [
| `Good
| `Revoked of Ptime.t * Extension.reason option
| `Unknown
]
(** [pp_cert_status ppf status] pretty prints cert status *)
val pp_cert_status : cert_status Fmt.t
(** type for SingleResponse *)
type single_response
(** [create_single_response ~next_update ~single_extension cert_id
cert_status this_update] creates response info for one cert,
[this_update] should be current time. *)
val create_single_response : ?next_update:Ptime.t ->
?single_extensions:Extension.t -> cert_id -> cert_status -> Ptime.t ->
single_response
(** [pp_single_response ppf response] pretty prints single [response] *)
val pp_single_response : single_response Fmt.t
(** [single_response_cert_id response] is cert_id in this single [response] *)
val single_response_cert_id : single_response -> cert_id
(** [single_response_cert_id response] is cert_status in this single
[response] *)
val single_response_status : single_response -> cert_status
(** type for ResponderID *)
type responder_id = [
| `ByKey of string
| `ByName of Distinguished_name.t
]
(** [create_responder_id pubkey] creates responderID identified by this key.
Note: octets here contains SHA1 hash of public key, not itself. *)
val create_responder_id : Public_key.t -> responder_id
(** [pp_responder_id ppf responderID] pretty prints [responderID] *)
val pp_responder_id : responder_id Fmt.t
(** type for OCSPResponse *)
type t
(** [create_success ~digest ~certs ~response_extensions priv_key
responderID producedAt responses] creates response and signs it with
[priv_key]. [producedAt] should be current timestamp. *)
val create_success :
?digest:Digestif.hash' ->
?certs:Certificate.t list ->
?response_extensions:Extension.t ->
Private_key.t ->
responder_id ->
Ptime.t ->
single_response list -> (t, [> `Msg of string ]) result
(** [create status] creates error response. Successful status is not
allowed here because it requires responseBytes. *)
val create : [
| `MalformedRequest
| `InternalError
| `TryLater
| `SigRequired
| `Unauthorized
] -> t
(** [pp ppf response] pretty prints response *)
val pp : t Fmt.t
(** [status response] is response status *)
val status : t -> status
(** [responder_id request] is responder id from response *)
val responder_id : t -> (responder_id, [> `Msg of string ]) result
(** [responses response] is a list of responses (status per certificate). *)
val responses : t -> (single_response list, [> `Msg of string ]) result
(** [decode_der buffer] decodes response in buffer *)
val decode_der : string -> (t, Asn.error) result
(** [encode_der request] encodes response into buffer *)
val encode_der : t -> string
(** [validate response key] validates the signature of [response]
with the pulic [key]. *)
val validate : t -> ?allowed_hashes:Digestif.hash' list ->
?now:Ptime.t -> Public_key.t ->
(unit, [> Validation.signature_error | `No_signature | `Time_invalid ]) result
end
end