X.509 Certificates and CRLs

A certificate is a binding between some identifying information (called a subject) and a public key. This binding is asserted by a signature on the certificate, which is placed there by some authority (the issuer) that at least claims that it knows the subject named in the certificate really “owns” the private key corresponding to the public key in the certificate.

The major certificate format in use today is X.509v3, used for instance in the Transport Layer Security (TLS) protocol. A X.509 certificate is represented by the class X509_Certificate. The data of an X.509 certificate is stored as a shared_ptr to a structure containing the decoded information. So copying X509_Certificate objects is quite cheap.

class X509_Certificate
X509_Certificate(const std::string &filename)

Load a certificate from a file. PEM or DER is accepted.

X509_Certificate(const std::vector<uint8_t> &in)

Load a certificate from a byte string.

X509_Certificate(DataSource &source)

Load a certificate from an abstract DataSource.

X509_DN subject_dn() const

Returns the distinguished name (DN) of the certificate’s subject. This is the primary place where information about the subject of the certificate is stored. However “modern” information that doesn’t fit in the X.500 framework, such as DNS name, email, IP address, or XMPP address, appears instead in the subject alternative name.

X509_DN issuer_dn() const

Returns the distinguished name (DN) of the certificate’s issuer, ie the CA that issued this certificate.

const AlternativeName &subject_alt_name() const

Return the subjects alternative name. This is used to store values like associated URIs, DNS addresses, and email addresses.

const AlternativeName &issuer_alt_name() const

Return alternative names for the issuer.

std::unique_ptr<Public_Key> load_subject_public_key() const

Deserialize the stored public key and return a new object. This might throw, if it happens that the public key object stored in the certificate is malformed in some way, or in the case that the public key algorithm used is not supported by the library.

See Serializing Public Keys for more information about what to do with the returned object. It may be any type of key, in principle, though RSA and ECDSA are most common.

std::vector<uint8_t> subject_public_key_bits() const

Return the binary encoding of the subject public key. This value (or a hash of it) is used in various protocols, eg for public key pinning.

AlgorithmIdentifier subject_public_key_algo() const

Return an algorithm identifier that identifies the algorithm used in the subject’s public key.

std::vector<uint8_t> serial_number() const

Return the certificates serial number. The tuple of issuer DN and serial number should be unique.

std::vector<uint8> raw_subject_dn() const

Return the binary encoding of the subject DN.

std::vector<uint8> raw_issuer_dn() const

Return the binary encoding of the issuer DN.

X509_Time not_before() const

Returns the point in time the certificate becomes valid

X509_Time not_after() const

Returns the point in time the certificate expires

const Extensions &v3_extensions() const

Returns all extensions of this certificate. You can use this to examine any extension data associated with the certificate, including custom extensions the library doesn’t know about.

std::vector<uint8_t> authority_key_id() const

Return the authority key id, if set. This is an arbitrary string; in the issuing certificate this will be the subject key id.

std::vector<uint8_t> subject_key_id() const

Return the subject key id, if set.

bool allowed_extended_usage(const OID &usage) const

Return true if and only if the usage OID appears in the extended key usage extension. Also will return true if the extended key usage extension is not used in the current certificate.

std::vector<OID> extended_key_usage() const

Return the list of extended key usages. May be empty.

std::string fingerprint(const std::string &hash_fn = "SHA-1") const

Return a fingerprint for the certificate, which is basically just a hash of the binary contents. Normally SHA-1 or SHA-256 is used, but any hash function is allowed.

Key_Constraints constraints() const

Returns a basic list of constraints which govern usage of the key embedded in this certificate.

The Key_Constraints is a class that behaves somewhat like an enum. The easiest way to use it is with its includes method. For example:

constraints().includes(Key_Constraints::DigitalSignature)

checks if the certificate key is valid for generating digital signatures.

bool matches_dns_name(const std::string &name) const

Check if the certificate’s subject alternative name DNS fields match name. This function also handles wildcard certificates.

std::string to_string() const

Returns a free-form human readable string describing the certificate.

std::string PEM_encode() const

Returns the PEM encoding of the certificate

std::vector<uint8_t> BER_encode() const

Returns the DER/BER encoding of the certificate

X.509 Distinguished Names

class X509_DN
bool has_field(const std::string &attr) const

Returns true if get_attribute or get_first_attribute will return a value.

std::vector<std::string> get_attribute(const std::string &attr) const

Return all attributes associated with a certain attribute type.

std::string get_first_attribute(const std::string &attr) const

Like get_attribute but returns just the first attribute, or empty if the DN has no attribute of the specified type.

std::multimap<OID, std::string> get_attributes() const

Get all attributes of the DN. The OID maps to a DN component such as 2.5.4.10 (“Organization”), and the strings are UTF-8 encoded.

std::multimap<std::string, std::string> contents() const

Similar to get_attributes, but the OIDs are decoded to strings.

void add_attribute(const std::string &key, const std::string &val)

Add an attribute to a DN.

void add_attribute(const OID &oid, const std::string &val)

Add an attribute to a DN using an OID instead of string-valued attribute type.

The X509_DN type also supports iostream extraction and insertion operators, for formatted input and output.

X.509v3 Extensions

X.509v3 specifies a large number of possible extensions. Botan supports some, but by no means all of them. The following listing lists which X.509v3 extensions are supported and notes areas where there may be problems with the handling.

  • Key Usage and Extended Key Usage: No problems known.

  • Basic Constraints: No problems known. A self-signed v1 certificate is assumed to be a CA, while a v3 certificate is marked as a CA if and only if the basic constraints extension is present and set for a CA cert.

  • Subject Alternative Names: Only the “rfc822Name”, “dNSName”, and “uniformResourceIdentifier” and raw IPv4 fields will be stored; all others are ignored.

  • Issuer Alternative Names: Same restrictions as the Subject Alternative Names extension. New certificates generated by Botan never include the issuer alternative name.

  • Authority Key Identifier: Only the version using KeyIdentifier is supported. If the GeneralNames version is used and the extension is critical, an exception is thrown. If both the KeyIdentifier and GeneralNames versions are present, then the KeyIdentifier will be used, and the GeneralNames ignored.

  • Subject Key Identifier: No problems known.

  • Name Constraints: No problems known (though encoding is not supported).

Any unknown critical extension in a certificate will lead to an exception during path validation.

Extensions are handled by a special class taking care of encoding and decoding. It also supports encoding and decoding of custom extensions. To do this, it internally keeps two lists of extensions. Different lookup functions are provided to search them.

Note

Validation of custom extensions during path validation is currently not supported.

class Extensions
void add(Certificate_Extension *extn, bool critical = false)

Adds a new extension to the extensions object. If an extension of the same type already exists, extn will replace it. If critical is true the extension will be marked as critical in the encoding.

bool add_new(Certificate_Extension *extn, bool critical = false)

Like add but an existing extension will not be replaced. Returns true if the extension was used, false if an extension of the same type was already in place.

void replace(Certificate_Extension *extn, bool critical = false)

Adds an extension to the list or replaces it, if the same extension was already added

std::unique_ptr<Certificate_Extension> get(const OID &oid) const

Searches for an extension by OID and returns the result

template<typename T>
std::unique_ptr<T> get_raw(const OID &oid)

Searches for an extension by OID and returns the result. Only the unknown extensions, that is, extensions types that are not listed above, are searched for by this function.

std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const

Returns the list of extensions together with the corresponding criticality flag. Only contains the supported extension types listed above.

std::map<OID, std::pair<std::vector<uint8_t>, bool>> extensions_raw() const

Returns the list of extensions as raw, encoded bytes together with the corresponding criticality flag. Contains all extensions, known as well as unknown extensions.

Certificate Revocation Lists

It will occasionally happen that a certificate must be revoked before its expiration date. Examples of this happening include the private key being compromised, or the user to which it has been assigned leaving an organization. Certificate revocation lists are an answer to this problem (though online certificate validation techniques are starting to become somewhat more popular). Every once in a while the CA will release a new CRL, listing all certificates that have been revoked. Also included is various pieces of information like what time a particular certificate was revoked, and for what reason. In most systems, it is wise to support some form of certificate revocation, and CRLs handle this easily.

For most users, processing a CRL is quite easy. All you have to do is call the constructor, which will take a filename (or a DataSource&). The CRLs can either be in raw BER/DER, or in PEM format; the constructor will figure out which format without any extra information. For example:

X509_CRL crl1("crl1.der");

DataSource_Stream in("crl2.pem");
X509_CRL crl2(in);

After that, pass the X509_CRL object to a Certificate_Store object with

void Certificate_Store::add_crl(const X509_CRL &crl)

and all future verifications will take into account the provided CRL.

Certificate Stores

An object of type Certificate_Store is a generalized interface to an external source for certificates (and CRLs). Examples of such a store would be one that looked up the certificates in a SQL database, or by contacting a CGI script running on a HTTP server. There are currently three mechanisms for looking up a certificate, and one for retrieving CRLs. By default, most of these mechanisms will return an empty std::optional of X509_Certificate. This storage mechanism is only queried when doing certificate validation: it allows you to distribute only the root key with an application, and let some online method handle getting all the other certificates that are needed to validate an end entity certificate. In particular, the search routines will not attempt to access the external database.

The certificate lookup methods are find_cert (by Subject Distinguished Name and optional Subject Key Identifier) and find_cert_by_pubkey_sha1 (by SHA-1 hash of the certificate’s public key). The Subject Distinguished Name is given as a X509_DN, while the SKID parameter takes a std::vector<uint8_t> containing the subject key identifier in raw binary. Both lookup methods are mandatory to implement.

Finally, there is a method for finding a CRL, called find_crl_for, that takes an X509_Certificate object, and returns a std::optional of X509_CRL. The std::optional return type makes it easy to return no CRLs by returning nullopt (eg, if the certificate store doesn’t support retrieving CRLs). Implementing the function is optional, and by default will return nullopt.

Certificate stores are used in the Transport Layer Security (TLS) module to store a list of trusted certificate authorities.

Note

In the 2.x library, the certificate store interface relied on shared_ptr<X509_Certificate> to avoid copies. However since 2.4.0, the X509_Certificate was internally shared, and thus the outer shared_ptr was just a cause of needless runtime overhead and API complexity. Starting in version 3.0, the certificate store interface is defined in terms of plain X509_Certificate.

In Memory Certificate Store

The in memory certificate store keeps all objects in memory only. Certificates can be loaded from disk initially, but also added later.

class Certificate_Store_In_Memory
Certificate_Store_In_Memory(const std::string &dir)

Attempt to parse all files in dir (including subdirectories) as certificates. Ignores errors.

Certificate_Store_In_Memory(const X509_Certificate &cert)

Adds given certificate to the store

Certificate_Store_In_Memory()

Create an empty store

void add_certificate(const X509_Certificate &cert)

Add a certificate to the store

void add_crl(const X509_CRL &crl)

Add a certificate revocation list (CRL) to the store.

System Certificate Stores

An interface to use the system provided certificate stores is available for Unix, macOS and Windows systems, System_Certificate_Store

Flatfile Certificate Stores

Flatfile_Certificate_Store is an implementation of certificate store that reads certificates as files from a directory. This is also used as the implementation of the Unix/Linux system certificate store.

The constructor takes a path to the directory to read, along with an optional boolean indicating if non-CA certificates should be ignored.

SQL-backed Certificate Stores

The SQL-backed certificate stores store all objects in an SQL database. They also additionally provide private key storage and revocation of individual certificates.

class Certificate_Store_In_SQL
Certificate_Store_In_SQL(const std::shared_ptr<SQL_Database> db, const std::string &passwd, RandomNumberGenerator &rng, const std::string &table_prefix = "")

Create or open an existing certificate store from an SQL database. The password in passwd will be used to encrypt private keys.

bool insert_cert(const X509_Certificate &cert)

Inserts cert into the store. Returns false if the certificate is already known and true if insertion was successful.

remove_cert(const X509_Certificate &cert)

Removes cert from the store. Returns false if the certificate could not be found and true if removal was successful.

std::shared_ptr<const Private_Key> find_key(const X509_Certificate&) const

Returns the private key for “cert” or an empty shared_ptr if none was found

std::vector<X509_Certificate> find_certs_for_key(const Private_Key &key) const

Returns all certificates for private key key

bool insert_key(const X509_Certificate &cert, const Private_Key &key)

Inserts key for cert into the store, returns false if the key is already known and true if insertion was successful.

void remove_key(const Private_Key &key)

Removes key from the store

void revoke_cert(const X509_Certificate&, CRL_Code, const X509_Time &time = X509_Time())

Marks cert as revoked starting from time

void affirm_cert(const X509_Certificate&)

Reverses the revocation for cert

std::vector<X509_CRL> generate_crls() const

Generates CRLs for all certificates marked as revoked. A CRL is returned for each unique issuer DN.

The Certificate_Store_In_SQL class operates on an abstract SQL_Database object. If support for sqlite3 was enabled at build time, Botan includes an implementation of this interface for sqlite3, and a subclass of Certificate_Store_In_SQL which creates or opens a sqlite3 database.

class Certificate_Store_In_SQLite
Certificate_Store_In_SQLite(const std::string &db_path, const std::string &passwd, RandomNumberGenerator &rng, const std::string &table_prefix = "")

Create or open an existing certificate store from an sqlite database file. The password in passwd will be used to encrypt private keys.

Path Validation

The process of validating a certificate chain up to a trusted root is called path validation, and in botan that operation is handled by a set of functions in x509path.h named x509_path_validate:

Path_Validation_Result x509_path_validate(const X509_Certificate &end_cert, const Path_Validation_Restrictions &restrictions, const Certificate_Store &store, const std::string &hostname = "", Usage_Type usage = Usage_Type::UNSPECIFIED, std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0), const std::vector<std::optional<OCSP::Response>> &ocsp_resp = std::vector<std::optional<OCSP::Response>>())

The last five parameters are optional. hostname specifies a hostname which is matched against the subject DN in end_cert according to RFC 6125. An empty hostname disables hostname validation. usage specifies key usage restrictions that are compared to the key usage fields in end_cert according to RFC 5280, if not set to UNSPECIFIED. validation_time allows setting the time point at which all certificates are validated. This is really only useful for testing. The default is the current system clock’s current time. ocsp_timeout sets the timeout for OCSP requests. The default of 0 disables OCSP checks completely. ocsp_resp allows adding additional OCSP responses retrieved from outside of the path validation. Note that OCSP online checks are done only as long as the http_util module was compiled in. Availability of online OCSP checks can be checked using the macro BOTAN_HAS_ONLINE_REVOCATION_CHECKS.

For the different flavors of x509_path_validate, check x509path.h.

The result of the validation is returned as a class:

class Path_Validation_Result

Specifies the result of the validation

bool successful_validation() const

Returns true if a certificate path from end_cert to a trusted root was found and all path validation checks passed.

std::string result_string() const

Returns a descriptive string of the validation status (for instance “Verified”, “Certificate is not yet valid”, or “Signature error”). This is the string value of the result function below.

const X509_Certificate &trust_root() const

If the validation was successful, returns the certificate which is acting as the trust root for end_cert.

const std::vector<X509_Certificate> &cert_path() const

Returns the full certificate path starting with the end entity certificate and ending in the trust root.

Certificate_Status_Code result() const

Returns the ‘worst’ error that occurred during validation. For instance, we do not want an expired certificate with an invalid signature to be reported to the user as being simply expired (a relatively innocuous and common error) when the signature isn’t even valid.

const std::vector<std::set<Certificate_Status_Code>> &all_statuses() const

For each certificate in the chain, returns a set of status which indicate all errors which occurred during validation. This is primarily useful for diagnostic purposes.

std::set<std::string> trusted_hashes() const

Returns the set of all cryptographic hash functions which are implicitly trusted for this validation to be correct.

A Path_Validation_Restrictions is passed to the path validator and specifies restrictions and options for the validation step. The two constructors are:

Path_Validation_Restrictions(bool require_rev, size_t minimum_key_strength, bool ocsp_all_intermediates, const std::set<std::string> &trusted_hashes)

If require_rev is true, then any path without revocation information (CRL or OCSP check) is rejected with the code NO_REVOCATION_DATA. The minimum_key_strength parameter specifies the minimum strength of public key signature we will accept is. The set of hash names trusted_hashes indicates which hash functions we’ll accept for cryptographic signatures. Any untrusted hash will cause the error case UNTRUSTED_HASH.

Path_Validation_Restrictions(bool require_rev = false, size_t minimum_key_strength = 80, bool ocsp_all_intermediates = false)

A variant of the above with some convenient defaults. The current default minimum_key_strength of 80 roughly corresponds to 1024 bit RSA. The set of trusted hashes is set to all SHA-2 variants, and, if minimum_key_strength is less than or equal to 80, then SHA-1 signatures will also be accepted.

Code Example

For sheer demonstrative purposes, the following code verifies an end entity certificate against a trusted Root CA certificate.

#include <botan/certstor_system.h>
#include <botan/x509cert.h>
#include <botan/x509path.h>

int main() {
   // Create a certificate store and add a locally trusted CA certificate
   Botan::Certificate_Store_In_Memory customStore;
   customStore.add_certificate(Botan::X509_Certificate("root.crt"));

   // Additionally trust all system-specific CA certificates
   Botan::System_Certificate_Store systemStore;
   std::vector<Botan::Certificate_Store*> trusted_roots{&customStore, &systemStore};

   // Load the end entity certificate and two untrusted intermediate CAs from file
   std::vector<Botan::X509_Certificate> end_certs;
   end_certs.emplace_back(Botan::X509_Certificate("ee.crt"));    // The end-entity certificate, must come first
   end_certs.emplace_back(Botan::X509_Certificate("int2.crt"));  // intermediate 2
   end_certs.emplace_back(Botan::X509_Certificate("int1.crt"));  // intermediate 1

   // Optional: Set up restrictions, e.g. min. key strength, maximum age of OCSP responses
   Botan::Path_Validation_Restrictions restrictions;

   // Optional: Specify usage type, compared against the key usage in end_certs[0]
   Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;

   // Optional: Specify hostname, if not empty, compared against the DNS name in end_certs[0]
   std::string hostname;

   Botan::Path_Validation_Result validationResult =
      Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage);

   if(!validationResult.successful_validation()) {
      // call validationResult.result() to get the overall status code
      return -1;
   }

   return 0;  // Verification succeeded
}

Creating New Certificates

A CA is represented by the type X509_CA, which can be found in x509_ca.h. A CA always needs its own certificate, which can either be a self-signed certificate (see below on how to create one) or one issued by another CA (see the section on PKCS #10 requests). Creating a CA object is done by the following constructor:

X509_CA::X509_CA(const X509_Certificate &cert, const Private_Key &key, const std::string &hash_fn, RandomNumberGenerator &rng)

The private key is the private key corresponding to the public key in the CA’s certificate. hash_fn is the name of the hash function to use for signing, e.g., SHA-256. rng is queried for random during signing.

There is an alternative constructor that lets you set additional options, namely the padding scheme that will be used by the X509_CA object to sign certificates and certificate revocation lists. If the padding is not set explicitly, the CA will use some default. The only time you need this alternate interface is for creating RSA-PSS certificates.

X509_CA::X509_CA(const X509_Certificate &cert, const Private_Key &key, const std::string &hash_fn, const std::string &padding_fn, RandomNumberGenerator &rng)

Requests for new certificates are supplied to a CA in the form of PKCS #10 certificate requests (called a PKCS10_Request object in Botan). These are decoded in a similar manner to certificates/CRLs/etc. A request is vetted by humans (who somehow verify that the name in the request corresponds to the name of the entity who requested it), and then signed by a CA key, generating a new certificate:

X509_Certificate X509_CA::sign_request(const PKCS10_Request &req, RandomNumberGenerator &rng, const X509_Time &not_before, const X509_Time &not_after)

If you need more control over the signing process, you can use the methods

static X509_Certificate X509_CA::make_cert(PK_Signer &signer, RandomNumberGenerator &rng, const BigInt &serial_number, const AlgorithmIdentifier &sig_algo, const std::vector<uint8_t> &pub_key, const X509_Time &not_before, const X509_Time &not_after, const X509_DN &issuer_dn, const X509_DN &subject_dn, const Extensions &extensions)
static Extensions X509_CA::choose_extensions(const PKCS10_Request &req, const X509_Certificate &ca_certificate, const std::string &hash_fn)

Returns the extensions that would be created by sign_request if it was used. You can call this and then modify the extensions list before invoking X509_CA::make_cert

Generating CRLs

As mentioned previously, the ability to process CRLs is highly important in many PKI systems. In fact, according to strict X.509 rules, you must not validate any certificate if the appropriate CRLs are not available (though hardly any systems are that strict). In any case, a CA should have a valid CRL available at all times.

Of course, you might be wondering what to do if no certificates have been revoked. Never fear; empty CRLs, which revoke nothing at all, can be issued. To generate a new, empty CRL, just call

X509_CRL X509_CA::new_crl(RandomNumberGenerator &rng, uint32_t next_update = 0)

This function will return a new, empty CRL. The next_update parameter is the number of seconds before the CRL expires. If it is set to the (default) value of zero, then a reasonable default (currently 7 days) will be used.

On the other hand, you may have issued a CRL before. In that case, you will want to issue a new CRL that contains all previously revoked certificates, along with any new ones. This is done by calling

X509_CRL X509_CA::update_crl(const X509_CRL &last_crl, std::vector<CRL_Entry> new_entries, RandomNumberGenerator &rng, size_t next_update = 0)

Where last_crl is the last CRL this CA issued, and new_entries is a list of any newly revoked certificates. The function returns a new X509_CRL to make available for clients.

The CRL_Entry type is a structure that contains, at a minimum, the serial number of the revoked certificate. As serial numbers are never repeated, the pairing of an issuer and a serial number (should) distinctly identify any certificate. In this case, we represent the serial number as a secure_vector<uint8_t> called serial. There are two additional (optional) values, an enumeration called CRL_Code that specifies the reason for revocation (reason), and an object that represents the time that the certificate became invalid (if this information is known).

If you wish to remove an old entry from the CRL, insert a new entry for the same cert, with a reason code of REMOVE_FROM_CRL. For example, if a revoked certificate has expired ‘normally’, there is no reason to continue to explicitly revoke it, since clients will reject the cert as expired in any case.

Self-Signed Certificates

Generating a new self-signed certificate can often be useful, for example when setting up a new root CA, or for use in specialized protocols. The library provides a utility function for this:

X509_Certificate create_self_signed_cert(const X509_Cert_Options &opts, const Private_Key &key, const std::string &hash_fn, RandomNumberGenerator &rng)

Where key is the private key you wish to use (the public key, used in the certificate itself is extracted from the private key), and opts is an structure that has various bits of information that will be used in creating the certificate (this structure, and its use, is discussed below).

Creating PKCS #10 Requests

Also in x509self.h, there is a function for generating new PKCS #10 certificate requests:

PKCS10_Request create_cert_req(const X509_Cert_Options &opts, const Private_Key &key, const std::string &hash_fn, RandomNumberGenerator &rng)

This function acts quite similarly to create_self_signed_cert, except it instead returns a PKCS #10 certificate request. After creating it, one would typically transmit it to a CA, who signs it and returns a freshly minted X.509 certificate.

PKCS10_Request PKCS10_Request::create(const Private_Key &key, const X509_DN &subject_dn, const Extensions &extensions, const std::string &hash_fn, RandomNumberGenerator &rng, const std::string &padding_scheme = "", const std::string &challenge = "")

This function (added in 2.5) is similar to create_cert_req but allows specifying all the parameters directly. In fact create_cert_req just creates the DN and extensions from the options, then uses this call to actually create the PKCS10_Request object.

Certificate Options

What is this X509_Cert_Options thing we’ve been passing around? It’s a class representing a bunch of information that will end up being stored into the certificate. This information comes in 3 major flavors: information about the subject (CA or end-user), the validity period of the certificate, and restrictions on the usage of the certificate. For special cases, you can also add custom X.509v3 extensions.

First and foremost is a number of std::string members, which contains various bits of information about the user: common_name, serial_number, country, organization, org_unit, locality, state, email, dns_name, and uri. As many of these as possible should be filled it (especially an email address), though the only required ones are common_name and country.

Additionally there are a small selection of std::vector<std::string> members, which allow space for repeating elements: more_org_units and more_dns.

There is another value that is only useful when creating a PKCS #10 request, which is called challenge. This is a challenge password, which you can later use to request certificate revocation (if the CA supports doing revocations in this manner).

Then there is the validity period; these are set with not_before and not_after. Both of these functions also take a std::string, which specifies when the certificate should start being valid, and when it should stop being valid. If you don’t set the starting validity period, it will automatically choose the current time. If you don’t set the ending time, it will choose the starting time plus a default time period. The arguments to these functions specify the time in the following format: “2002/11/27 1:50:14”. The time is in 24-hour format, and the date is encoded as year/month/day. The date must be specified, but you can omit the time or trailing parts of it, for example “2002/11/27 1:50” or “2002/11/27”.

Third, you can set constraints on a key. The one you’re mostly likely to want to use is to create (or request) a CA certificate, which can be done by calling the member function CA_key. This should only be used when needed.

Moreover, you can specify the padding scheme to be used when digital signatures are computed by calling function set_padding_scheme with a string representing the padding scheme. This way, you can control the padding scheme for self-signed certificates and PKCS #10 requests. The padding scheme used by a CA when building a certificate or a certificate revocation list can be set in the X509_CA constructor. The supported padding schemes can be found in src/lib/pubkey/padding.cpp. Some alternative names for the padding schemes are understood, as well.

Other constraints can be set by calling the member functions add_constraints and add_ex_constraints. The first takes a Key_Constraints value, and replaces any previously set value. If no value is set, then the certificate key is marked as being valid for any usage. You can set it to any of the following (for more than one usage, OR them together): DigitalSignature, NonRepudiation, KeyEncipherment, DataEncipherment, KeyAgreement, KeyCertSign, CrlSign, EncipherOnly, or DecipherOnly. Many of these have quite special semantics, so you should either consult the appropriate standards document (such as RFC 5280), or just not call add_constraints, in which case the appropriate values will be chosen for you based on the key type.

The second function, add_ex_constraints, allows you to specify an OID that has some meaning with regards to restricting the key to particular usages. You can, if you wish, specify any OID you like, but there is a set of standard ones that other applications will be able to understand. These are the ones specified by the PKIX standard, and are named “PKIX.ServerAuth” (for TLS server authentication), “PKIX.ClientAuth” (for TLS client authentication), “PKIX.CodeSigning”, “PKIX.EmailProtection” (most likely for use with S/MIME), “PKIX.IPsecUser”, “PKIX.IPsecTunnel”, “PKIX.IPsecEndSystem”, and “PKIX.TimeStamping”. You can call “add_ex_constraints” any number of times - each new OID will be added to the list to include in the certificate.

Lastly, you can add any X.509v3 extensions in the extensions member, which is useful if you want to encode a custom extension, or encode an extension in a way differently from how Botan defaults.

OCSP Requests

A client makes an OCSP request to what is termed an ‘OCSP responder’. This responder returns a signed response attesting that the certificate in question has not been revoked. The most recent OCSP specification is as of this writing RFC 6960.

Normally OCSP validation happens automatically as part of X.509 certificate validation, as long as OCSP is enabled (by setting a non-zero ocsp_timeout in the call to x509_path_validate, or for TLS by implementing the related tls_verify_cert_chain_ocsp_timeout callback and returning a non-zero value from that). So most applications should not need to directly manipulate OCSP request and response objects.

For those that do, the primary ocsp interface is in ocsp.h. First a request must be formed, using information contained in the subject certificate and in the subject’s issuing certificate.

class OCSP::Request
OCSP::Request(const X509_Certificate &issuer_cert, const BigInt &subject_serial)

Create a new OCSP request

OCSP::Request(const X509_Certificate &issuer_cert, const X509_Certificate &subject_cert)

Variant of the above, using serial number from subject_cert.

std::vector<uint8_t> BER_encode() const

Encode the current OCSP request as a binary string.

std::string base64_encode() const

Encode the current OCSP request as a base64 string.

Then the response is parsed and validated, and if valid, can be consulted for certificate status information.

class OCSP::Response
OCSP::Response(const uint8_t response_bits[], size_t response_bits_len)

Attempts to parse response_bits as an OCSP response. Throws an exception if parsing fails. Note that this does not verify that the OCSP response is valid (ie that the signature is correct), merely that the ASN.1 structure matches an OCSP response.

Certificate_Status_Code check_signature(const std::vector<Certificate_Store*> &trust_roots, const std::vector<X509_Certificate> &cert_path = const std::vector<X509_Certificate>()) const

Find the issuing certificate of the OCSP response, and check the signature.

If possible, pass the full certificate path being validated in the optional cert_path argument: this additional information helps locate the OCSP signer’s certificate in some cases. If this does not return Certificate_Status_Code::OCSP_SIGNATURE_OK, then the request must not be be used further.

Certificate_Status_Code verify_signature(const X509_Certificate &issuing_cert) const

If the certificate that issued the OCSP response is already known (eg, because in some specific application all the OCSP responses will always be signed by a single trusted issuer whose cert is baked into the code) this provides an alternate version of check_signature.

Certificate_Status_Code status_for(const X509_Certificate &issuer, const X509_Certificate &subject, std::chrono::system_clock::time_point ref_time = std::chrono::system_clock::now()) const

Assuming the signature is valid, returns the status for the subject certificate. Make sure to get the ordering of the issuer and subject certificates correct.

The ref_time is normally just the system clock, but can be used if validation against some other reference time is desired (such as for testing, to verify an old previously valid OCSP response, or to use an alternate time source such as the Roughtime protocol instead of the local client system clock).

const X509_Time &produced_at() const

Return the time this OCSP response was (claimed to be) produced at.

const X509_DN &signer_name() const

Return the distinguished name of the signer. This is used to help find the issuing certificate.

This field is optional in OCSP responses, and may not be set.

const std::vector<uint8_t> &signer_key_hash() const

Return the SHA-1 hash of the public key of the signer. This is used to help find the issuing certificate. The Certificate_Store API find_cert_by_pubkey_sha1 can search on this value.

This field is optional in OCSP responses, and may not be set.

const std::vector<uint8_t> &raw_bits() const

Return the entire raw ASN.1 blob (for debugging or specialized decoding needs)

One common way of making OCSP requests is via HTTP, see RFC 2560 Appendix A for details. A basic implementation of this is the function online_check, which is available as long as the http_util module was compiled in; check by testing for the macro BOTAN_HAS_HTTP_UTIL.

OCSP::Response online_check(const X509_Certificate &issuer, const BigInt &subject_serial, const std::string &ocsp_responder, const Certificate_Store *trusted_roots)

Assemble a OCSP request for serial number subject_serial and attempt to request it to responder at URI ocsp_responder over a new HTTP socket, parses and returns the response. If trusted_roots is not null, then the response is additionally validated using OCSP response API check_signature. Otherwise, this call must be performed later by the application.

OCSP::Response online_check(const X509_Certificate &issuer, const X509_Certificate &subject, const Certificate_Store *trusted_roots)

Variant of the above but uses serial number and OCSP responder URI from subject.