Use Cloudflare’s PKI and TLS toolkit to quickly generate self-signed certificates.

Installation

Inspect golang-cfssl package.

$ apt info golang-cfssl
Package: golang-cfssl
Version: 1.2.0+git20160825.89.7fb22c8-3.1
Built-Using: golang-1.15 (= 1.15.4-1ubuntu1), golang-github-cloudflare-go-metrics (= 0.0~git20151117.0.6a9aea3-3), golang-github-cloudflare-redoctober (= 0.0~git20161017.0.78e9720-5), golang-github-daaku-go.zipexe (= 1.0.1-1), golang-github-geertjohan-go.rice (= 1.0.0+git20191102.d954009-1build2), golang-github-go-sql-driver-mysql (= 1.5.0-1), golang-github-google-certificate-transparency (= 0.0~git20160709.0.0f6e3d1~ds1-3), golang-github-jmhodges-clock (= 1.0-3), golang-github-jmoiron-sqlx (= 1.1+git20160206.61.398dd58-2), golang-github-kisielk-sqlstruct (= 0.0~git20150917.0.0b86a3e-2), golang-github-kisom-goutils (= 0.0~git20161101.0.858c9cb-2), golang-github-lib-pq (= 1.3.0-1), golang-github-mattn-go-sqlite3 (= 1.6.0~ds1-2), golang-go.crypto (= 1:0.0~git20201221.eec23a3-1), golang-golang-x-net (= 1:0.0+git20201031.ff519b6+dfsg-1)
Priority: extra
Section: universe/devel
Source: golang-github-cloudflare-cfssl
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian Go Packaging Team <pkg-go-maintainers@lists.alioth.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 44.7 MB
Depends: libc6 (>= 2.32), libsqlite3-0 (>= 3.7.15)
Homepage: https://github.com/cloudflare/cfssl
Download-Size: 8725 kB
APT-Sources: http://pl.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages
Description: CloudFlare's PKI and TLS toolkit
 CFSSL is CloudFlare's PKI/TLS swiss army knife. It is both a command
 line tool and an HTTP API server for signing, verifying, and bundling
 TLS certificates. CFSSL can be used to build custom TLS PKI tools as
 well as create and manipulate traditional certificate structures
 using code or on the command line.
 .
 This package contains the command line executables.

Install golang-cfssl package.

$ sudo apt install golang-cfssl 
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  golang-cfssl
0 upgraded, 1 newly installed, 0 to remove and 5 not upgraded.
Need to get 8725 kB of archives.
After this operation, 44.7 MB of additional disk space will be used.
Get:1 http://pl.archive.ubuntu.com/ubuntu jammy/universe amd64 golang-cfssl amd64 1.2.0+git20160825.89.7fb22c8-3.1 [8725 kB]
Fetched 8725 kB in 2s (4525 kB/s)       
Selecting previously unselected package golang-cfssl.
(Reading database ... 303188 files and directories currently installed.)
Preparing to unpack .../golang-cfssl_1.2.0+git20160825.89.7fb22c8-3.1_amd64.deb ...
Unpacking golang-cfssl (1.2.0+git20160825.89.7fb22c8-3.1) ...
Setting up golang-cfssl (1.2.0+git20160825.89.7fb22c8-3.1) ...

Inspect cfssl version.

$ cfssl version 
Version: 1.2.0
Revision: dev
Runtime: go1.15.4

Basic usage

Store default cofiguration.

$ cfssl print-defaults config | tee config.json
{
    "signing": {
        "default": {
            "expiry": "168h"
        },
        "profiles": {
            "www": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth"
                ]
            },
            "client": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            }
        }
    }
}

Customize defined profiles.

$ cat config.json 
{
    "signing": {
        "default": {
            "expiry": "168h"
        },
        "profiles": {
            "intermediate": {
                "expiry": "8760h",
                "usages": [
                    "cert sign",
                    "crl sign"
                ],
                "ca_constraint": {
                   "is_ca": true,
                   "max_path_len":1
                }
            },
            "server": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth"
                ]
            },
            "client": {
                "expiry": "8760h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            }
        }
    }
}

Create ca directory for certificate authority.

$ mkdir ca

Generate default certificate signing request.

$ cfssl print-defaults csr | tee ca/ca.json
{
    "CN": "example.net",
    "hosts": [
        "example.net",
        "www.example.net"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "US",
            "ST": "CA",
            "L": "San Francisco"
        }
    ]
}

Update certificate signing request for certificate authority.

$ cat ca/ca.json
{
    "CN": "CA",
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

Generate certificate authority.

$ cfssl genkey -config config.json -initca ca/ca.json | cfssljson -bare ca/ca
2022/08/09 23:08:12 [INFO] generate received request
2022/08/09 23:08:12 [INFO] received CSR
2022/08/09 23:08:12 [INFO] generating key: ecdsa-256
2022/08/09 23:08:12 [INFO] encoded CSR
2022/08/09 23:08:12 [INFO] signed certificate with serial number 522304223956609469872673890562035558319492295545

Inspect created files.

$ cat ca/ca-key.pem 
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJn50YBLgd2nea57yHJ8SC9S/xBNLiIvLzrCAn9jQVA2oAoGCCqGSM49
AwEHoUQDQgAEQuG+aoYeiTwwTHbrksYYX7SlHCf8fgZxtravHsphC7y2frTOPkqk
5Npl209BjAv7KkgR9SXgfIm2jyHt55AfYw==
-----END EC PRIVATE KEY-----
$ cat ca/ca.pem     
-----BEGIN CERTIFICATE-----
MIIBXjCCAQSgAwIBAgIUW3zuca3VICpWoTQd9wniw5zt63kwCgYIKoZIzj0EAwIw
DTELMAkGA1UEAxMCQ0EwHhcNMjIwODA5MjEwMzAwWhcNMjcwODA4MjEwMzAwWjAN
MQswCQYDVQQDEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABELhvmqGHok8
MEx265LGGF+0pRwn/H4Gcba2rx7KYQu8tn60zj5KpOTaZdtPQYwL+ypIEfUl4HyJ
to8h7eeQH2OjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G
A1UdDgQWBBQHGnkYcbGiDHgjO7fkqPTu2ijAqDAKBggqhkjOPQQDAgNIADBFAiAi
lFQVyYdCiCAFJIShCU6R6ICdAPZOv0TDlaePialABgIhALqjP1/EFMgidw+lghPd
yBqWCh/3YfXT1UPK7mhhkLWX
-----END CERTIFICATE-----
$ cat ca/ca.csr 
-----BEGIN CERTIFICATE REQUEST-----
MIHGMG8CAQAwDTELMAkGA1UEAxMCQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AARC4b5qhh6JPDBMduuSxhhftKUcJ/x+BnG2tq8eymELvLZ+tM4+SqTk2mXbT0GM
C/sqSBH1JeB8ibaPIe3nkB9joAAwCgYIKoZIzj0EAwIDRwAwRAIgLjX6tD3CZbC8
OUfslt56CigtOwnX8OlghzfvEM5BZY0CIFLg2iWsoipyDP6HCledhwu9VxYoSZxS
gpy9YeHYA5cx
-----END CERTIFICATE REQUEST-----

Display certificate signing request details.

$ cfssl certinfo -csr ca/ca.csr 
{
  "Raw": "MIHGMG8CAQAwDTELMAkGA1UEAxMCQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARC4b5qhh6JPDBMduuSxhhftKUcJ/x+BnG2tq8eymELvLZ+tM4+SqTk2mXbT0GMC/sqSBH1JeB8ibaPIe3nkB9joAAwCgYIKoZIzj0EAwIDRwAwRAIgLjX6tD3CZbC8OUfslt56CigtOwnX8OlghzfvEM5BZY0CIFLg2iWsoipyDP6HCledhwu9VxYoSZxSgpy9YeHYA5cx",
  "RawTBSCertificateRequest": "MG8CAQAwDTELMAkGA1UEAxMCQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARC4b5qhh6JPDBMduuSxhhftKUcJ/x+BnG2tq8eymELvLZ+tM4+SqTk2mXbT0GMC/sqSBH1JeB8ibaPIe3nkB9joAA=",
  "RawSubjectPublicKeyInfo": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQuG+aoYeiTwwTHbrksYYX7SlHCf8fgZxtravHsphC7y2frTOPkqk5Npl209BjAv7KkgR9SXgfIm2jyHt55AfYw==",
  "RawSubject": "MA0xCzAJBgNVBAMTAkNB",
  "Version": 0,
  "Signature": "MEQCIC41+rQ9wmWwvDlH7JbeegooLTsJ1/DpYIc37xDOQWWNAiBS4NolrKIqcgz+hwpXnYcLvVcWKEmcUoKcvWHh2AOXMQ==",
  "SignatureAlgorithm": 10,
  "PublicKeyAlgorithm": 3,
  "PublicKey": {
    "Curve": {
      "P": 115792089210356248762697446949407573530086143415290314195533631308867097853951,
      "N": 115792089210356248762697446949407573529996955224135760342422259061068512044369,
      "B": 41058363725152142129326129780047268409114441015993725554835256314039467401291,
      "Gx": 48439561293906451759052585252797914202762949526041747995844080717082404635286,
      "Gy": 36134250956749795798585127919587881956611106672985015071877198253568414405109,
      "BitSize": 256,
      "Name": "P-256"
    },
    "X": 30251502799755363927972087438779104029159549838866690506724075781343993203644,
    "Y": 82544809046968063736120571004889741174050166087270672368870795144509531365219
  },
  "Subject": {
    "Country": null,
    "Organization": null,
    "OrganizationalUnit": null,
    "Locality": null,
    "Province": null,
    "StreetAddress": null,
    "PostalCode": null,
    "SerialNumber": "",
    "CommonName": "CA",
    "Names": [
      {
        "Type": [
          2,
          5,
          4,
          3
        ],
        "Value": "CA"
      }
    ],
    "ExtraNames": null
  },
  "Attributes": null,
  "Extensions": null,
  "ExtraExtensions": null,
  "DNSNames": null,
  "EmailAddresses": null,
  "IPAddresses": null,
  "URIs": null
}

Display certificate authority details.

$ cfssl certinfo -cert ca/ca.pem 
{
  "subject": {
    "common_name": "CA",
    "names": [
      "CA"
    ]
  },
  "issuer": {
    "common_name": "CA",
    "names": [
      "CA"
    ]
  },
  "serial_number": "522304223956609469872673890562035558319492295545",
  "not_before": "2022-08-09T21:03:00Z",
  "not_after": "2027-08-08T21:03:00Z",
  "sigalg": "ECDSAWithSHA256",
  "authority_key_id": "",
  "subject_key_id": "7:1A:79:18:71:B1:A2:C:78:23:3B:B7:E4:A8:F4:EE:DA:28:C0:A8",
  "pem": "-----BEGIN CERTIFICATE-----\nMIIBXjCCAQSgAwIBAgIUW3zuca3VICpWoTQd9wniw5zt63kwCgYIKoZIzj0EAwIw\nDTELMAkGA1UEAxMCQ0EwHhcNMjIwODA5MjEwMzAwWhcNMjcwODA4MjEwMzAwWjAN\nMQswCQYDVQQDEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABELhvmqGHok8\nMEx265LGGF+0pRwn/H4Gcba2rx7KYQu8tn60zj5KpOTaZdtPQYwL+ypIEfUl4HyJ\nto8h7eeQH2OjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBQHGnkYcbGiDHgjO7fkqPTu2ijAqDAKBggqhkjOPQQDAgNIADBFAiAi\nlFQVyYdCiCAFJIShCU6R6ICdAPZOv0TDlaePialABgIhALqjP1/EFMgidw+lghPd\nyBqWCh/3YfXT1UPK7mhhkLWX\n-----END CERTIFICATE-----\n"
}

Create certificate signing request for intermediate certificate.

$ cat ca/intermediate.json
{
    "CN": "Intermediate CA",
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

Generate intermediate certificate.

$ cfssl gencert -config config.json -ca ca/ca.pem -ca-key ca/ca-key.pem -profile intermediate ca/intermediate.json | cfssljson -bare ca/intermediate
2022/08/09 23:46:44 [INFO] generate received request
2022/08/09 23:46:44 [INFO] received CSR
2022/08/09 23:46:44 [INFO] generating key: ecdsa-256
2022/08/09 23:46:44 [INFO] encoded CSR
2022/08/09 23:46:44 [INFO] signed certificate with serial number 722720822862120708336239517806371808811307146170
2022/08/09 23:46:44 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

Display generated files.

$ cat ca/intermediate.csr 
-----BEGIN CERTIFICATE REQUEST-----
MIHVMHwCAQAwGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEZgGvEBQ/FlPEUunzr1X+Qd/xm88mLjEahMQfmk0Dss6O
zX53rutP1yetdrxAbHEZ6b7mGVOFYJk104MVweI7eKAAMAoGCCqGSM49BAMCA0kA
MEYCIQCdmciYbhTfTA3rrrWvBgRuWl/DJZKBRBaefXZmaMc16QIhAP7yNyaJ2Dxw
8DHM5OLA5hueKDi0bGoEzllvxnrvD5NM
-----END CERTIFICATE REQUEST-----
$ cat ca/intermediate.pem  
-----BEGIN CERTIFICATE-----
MIIBkDCCATWgAwIBAgIUfpfsKz16JaERgfN9yTA5k0dKm7owCgYIKoZIzj0EAwIw
DTELMAkGA1UEAxMCQ0EwHhcNMjIwODA5MjE0MjAwWhcNMjMwODA5MjE0MjAwWjAa
MRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAARmAa8QFD8WU8RS6fOvVf5B3/GbzyYuMRqExB+aTQOyzo7Nfneu60/XJ612
vEBscRnpvuYZU4VgmTXTgxXB4jt4o2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUtZwWK3tXBTfrcr74kyxX8V1GoWYwHwYD
VR0jBBgwFoAUBxp5GHGxogx4Izu35Kj07toowKgwCgYIKoZIzj0EAwIDSQAwRgIh
AP/Ebo5OvshsBN2xG+4s48lX55HrDrjY+NppuNSouFsNAiEArVa2amMKyKH4lJ9G
UZsn8hugqXk7DxnDUJ7eCP5lDNQ=
-----END CERTIFICATE-----
$ cat ca/intermediate-key.pem 
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIYyB3OtHKjBg5OGzNggIaivh6nmydtvH2bI+8uHNTt9oAoGCCqGSM49
AwEHoUQDQgAEZgGvEBQ/FlPEUunzr1X+Qd/xm88mLjEahMQfmk0Dss6OzX53rutP
1yetdrxAbHEZ6b7mGVOFYJk104MVweI7eA==
-----END EC PRIVATE KEY-----

Display certificate signing request details.

$ cfssl certinfo -csr ca/intermediate.csr 
{
  "Raw": "MIHVMHwCAQAwGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZgGvEBQ/FlPEUunzr1X+Qd/xm88mLjEahMQfmk0Dss6OzX53rutP1yetdrxAbHEZ6b7mGVOFYJk104MVweI7eKAAMAoGCCqGSM49BAMCA0kAMEYCIQCdmciYbhTfTA3rrrWvBgRuWl/DJZKBRBaefXZmaMc16QIhAP7yNyaJ2Dxw8DHM5OLA5hueKDi0bGoEzllvxnrvD5NM",
  "RawTBSCertificateRequest": "MHwCAQAwGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZgGvEBQ/FlPEUunzr1X+Qd/xm88mLjEahMQfmk0Dss6OzX53rutP1yetdrxAbHEZ6b7mGVOFYJk104MVweI7eKAA",
  "RawSubjectPublicKeyInfo": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZgGvEBQ/FlPEUunzr1X+Qd/xm88mLjEahMQfmk0Dss6OzX53rutP1yetdrxAbHEZ6b7mGVOFYJk104MVweI7eA==",
  "RawSubject": "MBoxGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBDQQ==",
  "Version": 0,
  "Signature": "MEYCIQCdmciYbhTfTA3rrrWvBgRuWl/DJZKBRBaefXZmaMc16QIhAP7yNyaJ2Dxw8DHM5OLA5hueKDi0bGoEzllvxnrvD5NM",
  "SignatureAlgorithm": 10,
  "PublicKeyAlgorithm": 3,
  "PublicKey": {
    "Curve": {
      "P": 115792089210356248762697446949407573530086143415290314195533631308867097853951,
      "N": 115792089210356248762697446949407573529996955224135760342422259061068512044369,
      "B": 41058363725152142129326129780047268409114441015993725554835256314039467401291,
      "Gx": 48439561293906451759052585252797914202762949526041747995844080717082404635286,
      "Gy": 36134250956749795798585127919587881956611106672985015071877198253568414405109,
      "BitSize": 256,
      "Name": "P-256"
    },
    "X": 46138885641659983430080903485448755337896834096028214294563990680908322812622,
    "Y": 64591500993797885380266791737762276863124256044322382082031650206363506850680
  },
  "Subject": {
    "Country": null,
    "Organization": null,
    "OrganizationalUnit": null,
    "Locality": null,
    "Province": null,
    "StreetAddress": null,
    "PostalCode": null,
    "SerialNumber": "",
    "CommonName": "Intermediate CA",
    "Names": [
      {
        "Type": [
          2,
          5,
          4,
          3
        ],
        "Value": "Intermediate CA"
      }
    ],
    "ExtraNames": null
  },
  "Attributes": null,
  "Extensions": null,
  "ExtraExtensions": null,
  "DNSNames": null,
  "EmailAddresses": null,
  "IPAddresses": null,
  "URIs": null
}

Display certificate details.

$ cfssl certinfo -cert ca/intermediate.pem 
{
  "subject": {
    "common_name": "Intermediate CA",
    "names": [
      "Intermediate CA"
    ]
  },
  "issuer": {
    "common_name": "CA",
    "names": [
      "CA"
    ]
  },
  "serial_number": "722720822862120708336239517806371808811307146170",
  "not_before": "2022-08-09T21:42:00Z",
  "not_after": "2023-08-09T21:42:00Z",
  "sigalg": "ECDSAWithSHA256",
  "authority_key_id": "7:1A:79:18:71:B1:A2:C:78:23:3B:B7:E4:A8:F4:EE:DA:28:C0:A8",
  "subject_key_id": "B5:9C:16:2B:7B:57:5:37:EB:72:BE:F8:93:2C:57:F1:5D:46:A1:66",
  "pem": "-----BEGIN CERTIFICATE-----\nMIIBkDCCATWgAwIBAgIUfpfsKz16JaERgfN9yTA5k0dKm7owCgYIKoZIzj0EAwIw\nDTELMAkGA1UEAxMCQ0EwHhcNMjIwODA5MjE0MjAwWhcNMjMwODA5MjE0MjAwWjAa\nMRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB\nBwNCAARmAa8QFD8WU8RS6fOvVf5B3/GbzyYuMRqExB+aTQOyzo7Nfneu60/XJ612\nvEBscRnpvuYZU4VgmTXTgxXB4jt4o2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T\nAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUtZwWK3tXBTfrcr74kyxX8V1GoWYwHwYD\nVR0jBBgwFoAUBxp5GHGxogx4Izu35Kj07toowKgwCgYIKoZIzj0EAwIDSQAwRgIh\nAP/Ebo5OvshsBN2xG+4s48lX55HrDrjY+NppuNSouFsNAiEArVa2amMKyKH4lJ9G\nUZsn8hugqXk7DxnDUJ7eCP5lDNQ=\n-----END CERTIFICATE-----\n"
}

Create servers directory.

$ mkdir servers

Create certificate signing request for specific server.

$ cat  servers/octocat.cloud.json
{
    "CN": "octocat.cloud",
    "hosts": [
        "octocat.cloud",
        "*.octocat.cloud"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

Generate server certificate.

$ cfssl gencert -config config.json -ca ca/intermediate.pem -ca-key ca/intermediate-key.pem -profile server servers/octocat.cloud.json | cfssljson -bare servers/octocat.cloud
2022/08/09 23:48:16 [INFO] generate received request
2022/08/09 23:48:16 [INFO] received CSR
2022/08/09 23:48:16 [INFO] generating key: ecdsa-256
2022/08/09 23:48:16 [INFO] encoded CSR
2022/08/09 23:48:16 [INFO] signed certificate with serial number 598061817172767418647975386187038228727111508845

Display certificate signing request details.

$ cat servers/octocat.cloud.csr 
-----BEGIN CERTIFICATE REQUEST-----
MIIBDjCBtgIBADAYMRYwFAYDVQQDEw1vY3RvY2F0LmNsb3VkMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEgrcIzcmAD8evqNt6o78A/exJDJNVdfJFw8i0fQMQjHEa
AnSUV648v2HdOby3QaH16w5NCu4ajc82fxqMH4RIbqA8MDoGCSqGSIb3DQEJDjEt
MCswKQYDVR0RBCIwIIINb2N0b2NhdC5jbG91ZIIPKi5vY3RvY2F0LmNsb3VkMAoG
CCqGSM49BAMCA0cAMEQCICif3KFMw1fzt46r8lOcJMtKi1BALD+lfNzJmJNlaz5C
AiBAIcFpMRkodiGfeJ9aZzE7guhVNNjgVgslDY6i1odRjA==
-----END CERTIFICATE REQUEST-----
$ cat servers/octocat.cloud.pem 
-----BEGIN CERTIFICATE-----
MIIB1jCCAXygAwIBAgIUaMIE9tKft5MLlZR/G5keER3+Y20wCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMB4XDTIyMDgwOTIxNDMwMFoXDTIz
MDgwOTIxNDMwMFowGDEWMBQGA1UEAxMNb2N0b2NhdC5jbG91ZDBZMBMGByqGSM49
AgEGCCqGSM49AwEHA0IABIK3CM3JgA/Hr6jbeqO/AP3sSQyTVXXyRcPItH0DEIxx
GgJ0lFeuPL9h3Tm8t0Gh9esOTQruGo3PNn8ajB+ESG6jgaEwgZ4wDgYDVR0PAQH/
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0O
BBYEFDyUtB4NxOtH7PD21zW+7KjRhJRfMB8GA1UdIwQYMBaAFLWcFit7VwU363K+
+JMsV/FdRqFmMCkGA1UdEQQiMCCCDW9jdG9jYXQuY2xvdWSCDyoub2N0b2NhdC5j
bG91ZDAKBggqhkjOPQQDAgNIADBFAiEAoxH057bLSqSzgLPZaU/sySih88cWsiVu
pzaBHAtQ8lICICTVp6wBAO5iARU6e0fh69jOSjDQkhKtm9IT7aZJrw/u
-----END CERTIFICATE-----
$ cat servers/octocat.cloud-key.pem 
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJ5670BazTK53CrRuR38C0m/Ju5XKktZ9RlNFTd4gMLQoAoGCCqGSM49
AwEHoUQDQgAEgrcIzcmAD8evqNt6o78A/exJDJNVdfJFw8i0fQMQjHEaAnSUV648
v2HdOby3QaH16w5NCu4ajc82fxqMH4RIbg==
-----END EC PRIVATE KEY-----

Decode certificate signing request.

$ cfssl certinfo -csr servers/octocat.cloud.csr 
{
  "Raw": "MIIBDjCBtgIBADAYMRYwFAYDVQQDEw1vY3RvY2F0LmNsb3VkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgrcIzcmAD8evqNt6o78A/exJDJNVdfJFw8i0fQMQjHEaAnSUV648v2HdOby3QaH16w5NCu4ajc82fxqMH4RIbqA8MDoGCSqGSIb3DQEJDjEtMCswKQYDVR0RBCIwIIINb2N0b2NhdC5jbG91ZIIPKi5vY3RvY2F0LmNsb3VkMAoGCCqGSM49BAMCA0cAMEQCICif3KFMw1fzt46r8lOcJMtKi1BALD+lfNzJmJNlaz5CAiBAIcFpMRkodiGfeJ9aZzE7guhVNNjgVgslDY6i1odRjA==",
  "RawTBSCertificateRequest": "MIG2AgEAMBgxFjAUBgNVBAMTDW9jdG9jYXQuY2xvdWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASCtwjNyYAPx6+o23qjvwD97EkMk1V18kXDyLR9AxCMcRoCdJRXrjy/Yd05vLdBofXrDk0K7hqNzzZ/GowfhEhuoDwwOgYJKoZIhvcNAQkOMS0wKzApBgNVHREEIjAggg1vY3RvY2F0LmNsb3Vkgg8qLm9jdG9jYXQuY2xvdWQ=",
  "RawSubjectPublicKeyInfo": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgrcIzcmAD8evqNt6o78A/exJDJNVdfJFw8i0fQMQjHEaAnSUV648v2HdOby3QaH16w5NCu4ajc82fxqMH4RIbg==",
  "RawSubject": "MBgxFjAUBgNVBAMTDW9jdG9jYXQuY2xvdWQ=",
  "Version": 0,
  "Signature": "MEQCICif3KFMw1fzt46r8lOcJMtKi1BALD+lfNzJmJNlaz5CAiBAIcFpMRkodiGfeJ9aZzE7guhVNNjgVgslDY6i1odRjA==",
  "SignatureAlgorithm": 10,
  "PublicKeyAlgorithm": 3,
  "PublicKey": {
    "Curve": {
      "P": 115792089210356248762697446949407573530086143415290314195533631308867097853951,
      "N": 115792089210356248762697446949407573529996955224135760342422259061068512044369,
      "B": 41058363725152142129326129780047268409114441015993725554835256314039467401291,
      "Gx": 48439561293906451759052585252797914202762949526041747995844080717082404635286,
      "Gy": 36134250956749795798585127919587881956611106672985015071877198253568414405109,
      "BitSize": 256,
      "Name": "P-256"
    },
    "X": 59124064090659368039972684833048761796286291427904476664394940453950382443633,
    "Y": 11764472359176663751076283782292254787550789644624181651413856393211328874606
  },
  "Subject": {
    "Country": null,
    "Organization": null,
    "OrganizationalUnit": null,
    "Locality": null,
    "Province": null,
    "StreetAddress": null,
    "PostalCode": null,
    "SerialNumber": "",
    "CommonName": "octocat.cloud",
    "Names": [
      {
        "Type": [
          2,
          5,
          4,
          3
        ],
        "Value": "octocat.cloud"
      }
    ],
    "ExtraNames": null
  },
  "Attributes": [
    {
      "Type": [
        1,
        2,
        840,
        113549,
        1,
        9,
        14
      ],
      "Value": [
        [
          {
            "Type": [
              2,
              5,
              29,
              17
            ],
            "Value": "MCCCDW9jdG9jYXQuY2xvdWSCDyoub2N0b2NhdC5jbG91ZA=="
          }
        ]
      ]
    }
  ],
  "Extensions": [
    {
      "Id": [
        2,
        5,
        29,
        17
      ],
      "Critical": false,
      "Value": "MCCCDW9jdG9jYXQuY2xvdWSCDyoub2N0b2NhdC5jbG91ZA=="
    }
  ],
  "ExtraExtensions": null,
  "DNSNames": [
    "octocat.cloud",
    "*.octocat.cloud"
  ],
  "EmailAddresses": null,
  "IPAddresses": null,
  "URIs": null
}

Display certificate details.

$ cfssl certinfo -cert servers/octocat.cloud.pem 
{
  "subject": {
    "common_name": "octocat.cloud",
    "names": [
      "octocat.cloud"
    ]
  },
  "issuer": {
    "common_name": "Intermediate CA",
    "names": [
      "Intermediate CA"
    ]
  },
  "serial_number": "598061817172767418647975386187038228727111508845",
  "sans": [
    "octocat.cloud",
    "*.octocat.cloud"
  ],
  "not_before": "2022-08-09T21:43:00Z",
  "not_after": "2023-08-09T21:43:00Z",
  "sigalg": "ECDSAWithSHA256",
  "authority_key_id": "B5:9C:16:2B:7B:57:5:37:EB:72:BE:F8:93:2C:57:F1:5D:46:A1:66",
  "subject_key_id": "3C:94:B4:1E:D:C4:EB:47:EC:F0:F6:D7:35:BE:EC:A8:D1:84:94:5F",
  "pem": "-----BEGIN CERTIFICATE-----\nMIIB1jCCAXygAwIBAgIUaMIE9tKft5MLlZR/G5keER3+Y20wCgYIKoZIzj0EAwIw\nGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMB4XDTIyMDgwOTIxNDMwMFoXDTIz\nMDgwOTIxNDMwMFowGDEWMBQGA1UEAxMNb2N0b2NhdC5jbG91ZDBZMBMGByqGSM49\nAgEGCCqGSM49AwEHA0IABIK3CM3JgA/Hr6jbeqO/AP3sSQyTVXXyRcPItH0DEIxx\nGgJ0lFeuPL9h3Tm8t0Gh9esOTQruGo3PNn8ajB+ESG6jgaEwgZ4wDgYDVR0PAQH/\nBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0O\nBBYEFDyUtB4NxOtH7PD21zW+7KjRhJRfMB8GA1UdIwQYMBaAFLWcFit7VwU363K+\n+JMsV/FdRqFmMCkGA1UdEQQiMCCCDW9jdG9jYXQuY2xvdWSCDyoub2N0b2NhdC5j\nbG91ZDAKBggqhkjOPQQDAgNIADBFAiEAoxH057bLSqSzgLPZaU/sySih88cWsiVu\npzaBHAtQ8lICICTVp6wBAO5iARU6e0fh69jOSjDQkhKtm9IT7aZJrw/u\n-----END CERTIFICATE-----\n"
}

Create bundle directory.

$ mkdir bundle

Create certificate bundle.

$ cfssl bundle -config config.json -cert servers/octocat.cloud.pem -key servers/octocat.cloud-key.pem -ca-bundle ca/ca.pem  -int-bundle ca/intermediate.pem | cfssljson -bare bundle/octocat.cloud
2022/08/10 00:55:59 [INFO] bundling certificate for CN=octocat.cloud
2022/08/10 00:55:59 [WARNING] No metadata, Ubiquitous falls back to Optimal.
2022/08/10 00:55:59 [INFO] the anchoring root is CN=CA

Inspect created files.

$  cat bundle/octocat.cloud-bundle.pem 
-----BEGIN CERTIFICATE-----
MIIB1jCCAXygAwIBAgIUaMIE9tKft5MLlZR/G5keER3+Y20wCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMB4XDTIyMDgwOTIxNDMwMFoXDTIz
MDgwOTIxNDMwMFowGDEWMBQGA1UEAxMNb2N0b2NhdC5jbG91ZDBZMBMGByqGSM49
AgEGCCqGSM49AwEHA0IABIK3CM3JgA/Hr6jbeqO/AP3sSQyTVXXyRcPItH0DEIxx
GgJ0lFeuPL9h3Tm8t0Gh9esOTQruGo3PNn8ajB+ESG6jgaEwgZ4wDgYDVR0PAQH/
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0O
BBYEFDyUtB4NxOtH7PD21zW+7KjRhJRfMB8GA1UdIwQYMBaAFLWcFit7VwU363K+
+JMsV/FdRqFmMCkGA1UdEQQiMCCCDW9jdG9jYXQuY2xvdWSCDyoub2N0b2NhdC5j
bG91ZDAKBggqhkjOPQQDAgNIADBFAiEAoxH057bLSqSzgLPZaU/sySih88cWsiVu
pzaBHAtQ8lICICTVp6wBAO5iARU6e0fh69jOSjDQkhKtm9IT7aZJrw/u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBkDCCATWgAwIBAgIUfpfsKz16JaERgfN9yTA5k0dKm7owCgYIKoZIzj0EAwIw
DTELMAkGA1UEAxMCQ0EwHhcNMjIwODA5MjE0MjAwWhcNMjMwODA5MjE0MjAwWjAa
MRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAARmAa8QFD8WU8RS6fOvVf5B3/GbzyYuMRqExB+aTQOyzo7Nfneu60/XJ612
vEBscRnpvuYZU4VgmTXTgxXB4jt4o2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUtZwWK3tXBTfrcr74kyxX8V1GoWYwHwYD
VR0jBBgwFoAUBxp5GHGxogx4Izu35Kj07toowKgwCgYIKoZIzj0EAwIDSQAwRgIh
AP/Ebo5OvshsBN2xG+4s48lX55HrDrjY+NppuNSouFsNAiEArVa2amMKyKH4lJ9G
UZsn8hugqXk7DxnDUJ7eCP5lDNQ=
-----END CERTIFICATE-----
$ cat bundle/octocat.cloud-key.pem    
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJ5670BazTK53CrRuR38C0m/Ju5XKktZ9RlNFTd4gMLQoAoGCCqGSM49
AwEHoUQDQgAEgrcIzcmAD8evqNt6o78A/exJDJNVdfJFw8i0fQMQjHEaAnSUV648
v2HdOby3QaH16w5NCu4ajc82fxqMH4RIbg==
-----END EC PRIVATE KEY-----

Additional notes

The way to generate client certificates should be obvious at this moment.

You can also use sqlite database, HTTP server handles CF SSL requests and OCSP.

ko-fi