Info
The context of this post is described in the first post of the series of article about setting up a PKI. Please see links here

Introduction Link to heading

In this post, I will locally initialize a root and an intermediate CA using cfssl as well as the database needed to save the current PKI state and run the api from.

The root CA key would later have to stay offline while we’d use the intermediate CA for day to day certificate signing. This way, if the intermediate CA was compromised, we could revoke it and start fresh. This would also allow us to create multiple intermediate CA that we could give to different entities of an organization without revoking the sole issuer if one intermediate is compromised.

cfssl is a PKI toolkit created by Cloudflare and written in Go. I found its minimalistic approach to documentation inconvenient but thankfully the clarity of its source code makes up for it.

As I write this post, cfssl latest version is v1.6.4. I won’t cover cfssl installation: you will probably find it in your distribution’s repositories. You can also build it from sources easily.

Configuration Link to heading

Database Link to heading

cfssl can save it’s state in a database. Three database provider are supported: mysql, pgsql and sqlite. For convenience, I’ll be using sqlite here, but you should probably be using one of the other two in a production environment. Using a database lets us use OCSP related commands and everything related to certificate signature will also store the certificate.

The cloudflare/cfssl/certdb/README.md file informs to initialize the database with a tool called goose. This project was not updated since early 2015 and multiple forks were made. The main one on GitHub is not compatible with init scripts from the cfssl repository.

You should either stick with liamstask’s goose ( PKGBUILD) or drop goose altogether. For this article I chose to drop goose although I did use it when I first started to play with cfssl.

The configuration used to initialize the database can be found in cloudflare/cfssl/certdb/sqlite.

I factored the two initialization scripts in the same file db/definition.sql:

CREATE TABLE certificates (
  serial_number            blob NOT NULL,
  authority_key_identifier blob NOT NULL,
  ca_label                 blob,
  status                   blob NOT NULL,
  reason                   int,
  expiry                   timestamp,
  revoked_at               timestamp,
  pem                      blob NOT NULL,
  issued_at                timestamp,
  not_before               timestamp,
  metadata                 text,
  sans                     text,
  common_name              text,
  PRIMARY KEY(serial_number, authority_key_identifier)
);

CREATE TABLE ocsp_responses (
  serial_number            blob NOT NULL,
  authority_key_identifier blob NOT NULL,
  body                     blob NOT NULL,
  expiry                   timestamp,
  PRIMARY KEY(serial_number, authority_key_identifier),
  FOREIGN KEY(serial_number, authority_key_identifier) REFERENCES certificates(serial_number, authority_key_identifier)
);

I then created a database for the two CAs:

sqlite3 db/root-certstore.db < db/definition.sql
sqlite3 db/intermediate-certstore.db < db/definition.sql

cfssl Link to heading

Database access Link to heading

cfssl needs a bit of configuration to work.

First I have to create the configuration file giving him access to the database I just created:

mkdir -p {root,intermediate}/config
cat <<EOF > root/config/db.json
{
    "driver": "sqlite3",
    "data_source": "db/root-certstore.db"
}
EOF
cat <<EOF > intermediate/config/db.json
{
    "driver": "sqlite3",
    "data_source": "db/intermediate-certstore.db"
}
EOF

Signing profiles Link to heading

Then I have to define the profiles my CAs will use. There are a few things to consider now even if it won’t be used until I can host the PKI:

  • where my CA will be hosted;
  • where the CRL will be hosted;
  • what uri will serve the OCSP responder.

I chose to serve the PKI under the domain pki.valhall.local. Here is a list of the uri I’ll be using:

  • http://pki.valhall.local/root/ca
  • http://pki.valhall.local/root/crl
  • http://pki.valhall.local/root/ocsp
  • http://pki.valhall.local/intermediate/ca
  • http://pki.valhall.local/intermediate/crl
  • http://pki.valhall.local/intermediate/ocsp

Those information will later be available as extensions inside the signed certificate. Let’s take a look at the certificate behind google.com:

$ echo | \
  openssl s_client \
    -showcerts \
    -servername google.com \
    -connect google.com:443 2>/dev/null | \
  openssl x509 \
    -inform pem \
    -noout -text

# Certificate:
#     Data:
#         ...
#         X509v3 extensions:
#             ...
#             Authority Information Access:
#                 OCSP - URI:http://ocsp.pki.goog/gts1c3
#                 CA Issuers - URI:http://pki.goog/repo/certs/gts1c3.der
#             X509v3 CRL Distribution Points:
#                 Full Name:
#                   URI:http://crls.pki.goog/gts1c3/moVDfISia2k.crl

These extensions, embedded in the certificate, are part of the verification process.

cfssl uses a json file defining the signing profiles, among other things. Signing profiles are predefined sets of parameters used to sign a kind of certificate. I wrote earlier about the root CA that will only sign intermediate CAs. Its profiles file would be:

{
  "signing": {
    "default": {
      "crl_url": "http://pki.valhall.local/root/crl",
      "ocsp_url": "http://pki.valhall.local/root/ocsp",
      "issuer_urls": [
        "http://pki.valhall.local/root/ca"
      ],
      "expiry": "8760h"
    },
    "profiles": {
      "intermediate": {
        "usages": [
          "signing",
          "digital signature",
          "key encipherment",
          "cert sign",
          "crl sign",
          "server auth",
          "client auth"
        ],
        "ca_constraint": {
          "is_ca": true,
          "max_path_len": 0,
          "max_path_len_zero": true
        },
        "expiry": "87600h"
      },
      "ocsp": {
        "usages": [
          "digital signature",
          "ocsp signing"
        ],
        "expiry": "26280h"
      }
    }
  }
}

In the above json configuration I defined two profiles, intermediate that will be used to sign other CA certificates and ocsp that will be used to sign the certificate used by the OCSP responder. The .signing.default object is used to set parameters shared between the profiles.

The intermediate CA will mainly be used to sign certificates for servers and for client authentications. Since I’ll later use this intermediate CA to sign certificates within an automatic renewal process, I chose to make the certificate signed with the server profile short-lived:

{
  "signing": {
    "default": {
      "crl_url": "http://pki.valhall.local/intermediate/crl",
      "ocsp_url": "http://pki.valhall.local/intermediate/ocsp",
      "issuer_urls": [
        "http://pki.valhall.local/intermediate/ca"
      ],
      "expiry": "8760h"
    },
    "profiles": {
      "client": {
        "usages": [
          "signing",
          "digital signing",
          "key encipherment",
          "client auth"
        ],
        "expiry": "8760h"
      },
      "server": {
        "usages": [
          "signing",
          "digital signing",
          "key encipherment",
          "server auth"
        ],
        "expiry": "2190h"
      },
      "ocsp": {
        "usages": [
          "digital signature",
          "ocsp signing"
        ],
        "expiry": "26280h"
      }
    }
  }
}

These profile files will be saved in root/config/profiles.json and intermediate/config/profiles.json.

CA certificate definition Link to heading

The structure of a certificate request is defined at cloudflare/cfssl/csr/csr.go#L138.

Here are the two definitions I’ll use, respectfully in root/config/init.json and intermediate/config/init.json:

{
  "CN": "Valhall Root CA Certificate",
  "CA": {
    "expiry": "87600h"
  },
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [{
    "C":  "FR",
    "ST": "Pays de la Loire",
    "L":  "Nantes",
    "O":  "Valhall"
  }]
}
{
  "CN": "Valhall Intermediate CA Certificate",
  "CA": {
    "expiry": "87600h"
  },
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [{
    "C":  "FR",
    "ST": "Pays de la Loire",
    "L":  "Nantes",
    "O":  "Valhall"
  }]
}

The creation of a private key and a certificate is quite easy.

The genkey command of cfssl toolkit will create a private key, a signing request and will self-sign it.

cfssl genkey -initca root/config/init.json | cfssljson -bare root/ca

cfssl genkey returns a JSON with three keys: cert, csr and key. cfssljson will create the three files.

Creating the intermediate CA follows the same process. I’ll just have to discard the certificate and sign the CSR with the root CA instead:

cfssl genkey -initca intermediate/config/init.json | cfssljson -bare intermediate/ca
rm intermediate/ca.pem
cfssl sign \
    -ca root/ca.pem \
    -ca-key root/ca-key.pem \
    -config root/config/profiles.json \
    -profile intermediate \
    -db-config root/config/db.json \
    intermediate/ca.csr | cfssljson -bare intermediate/ca

You should now have the following files in your current directory:

tree

# .
# โ”œโ”€โ”€ db
# โ”‚ย ย  โ”œโ”€โ”€ definition.sql
# โ”‚ย ย  โ”œโ”€โ”€ intermediate-certstore.db
# โ”‚ย ย  โ””โ”€โ”€ root-certstore.db
# โ”œโ”€โ”€ intermediate
# โ”‚ย ย  โ”œโ”€โ”€ ca.csr
# โ”‚ย ย  โ”œโ”€โ”€ ca-key.pem
# โ”‚ย ย  โ”œโ”€โ”€ ca.pem
# โ”‚ย ย  โ””โ”€โ”€ config
# โ”‚ย ย      โ”œโ”€โ”€ db.json
# โ”‚ย ย      โ”œโ”€โ”€ init.json
# โ”‚ย ย      โ””โ”€โ”€ profiles.json
# โ””โ”€โ”€ root
#     โ”œโ”€โ”€ ca.csr
#     โ”œโ”€โ”€ ca-key.pem
#     โ”œโ”€โ”€ ca.pem
#     โ””โ”€โ”€ config
#         โ”œโ”€โ”€ db.json
#         โ”œโ”€โ”€ init.json
#         โ””โ”€โ”€ profiles.json
#
# 6 directories, 15 files

A SQL request in the root database shows that the intermediate CA certificate was also stored.

$ sqlite3 db/root-certstore.db "SELECT common_name FROM certificates;"
Valhall Intermediate CA Certificate

OCSP key & certificate Link to heading

Almost done, the only remaining tasks are to generate the certificates and keys for both CA’s ocsp responder and to generate the CRLs.

cat <<EOF > root/config/ocsp.json
{
  "CN": "Valhall Root OCSP Certificate",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [{
    "C":  "FR",
    "ST": "Pays de la Loire",
    "L":  "Nantes",
    "O":  "Valhall"
  }]
}
EOF
cfssl gencert \
    -ca root/ca.pem \
    -ca-key root/ca-key.pem \
    -config root/config/profiles.json \
    -profile ocsp \
    -db-config root/config/db.json \
    root/config/ocsp.json | cfssljson -bare root/ocsp
cat <<EOF > intermediate/config/ocsp.json
{
  "CN": "Valhall Intermediate OCSP Certificate",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [{
    "C":  "FR",
    "ST": "Pays de la Loire",
    "L":  "Nantes",
    "O":  "Valhall"
  }]
}
EOF
cfssl gencert \
    -ca intermediate/ca.pem \
    -ca-key intermediate/ca-key.pem \
    -config intermediate/config/profiles.json \
    -profile ocsp \
    -db-config intermediate/config/db.json \
    intermediate/config/ocsp.json | cfssljson -bare intermediate/ocsp

CRL Link to heading

The Certificate Revocation Lists are files that should (probably?) be regularly updated. I’m not sure how clients implement the cache mechanism for this feature as the CRL advertises two dates: last update and next update. Meaning if they cache it until next update date, then they could miss a certificate being revoked until the cache ttl is reached. CRL are also signed with their CA’s key, meaning if you want to keep the root CA’s private key offline, this could be quite tricky. I’ve seen different approaches: some create CRL advertising a next update date when the CA will expire and only refresh it manually when needed and others create weekly CRL, which is cfssl’s default:

$ cfssl crl -h

# ...
#   -expiry=168h0m0s: time from now after which the CRL will expire (default: one week)

I chose to do the latter and since I can’t imagine creating weekly CRL and not having the task automated, I’ll later store the private keys in a Hashicorp Vault instance I manage, which is an acceptable risk for my home-lab.

cfssl crl outputs a PEM CRL without header/footer and without line feeds, so I’ll have to handle that:

echo "-----BEGIN X509 CRL-----" > root/crl.pem
cfssl crl \
    -ca root/ca.pem \
    -ca-key root/ca-key.pem \
    -db-config root/config/db.json | fold -w 64 >> root/crl.pem
echo "-----END X509 CRL-----" >> root/crl.pem
echo "-----BEGIN X509 CRL-----" > intermediate/crl.pem
cfssl crl \
    -ca intermediate/ca.pem \
    -ca-key intermediate/ca-key.pem \
    -db-config intermediate/config/db.json | fold -w 64 >> intermediate/crl.pem
echo "-----END X509 CRL-----" >> intermediate/crl.pem

You can check the CRL with the openssl command:

openssl crl -inform PEM -text -noout -in root/crl.pem
# Certificate Revocation List (CRL):
#         Version 2 (0x1)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C = FR, ST = Ile de France, L = Nantes, O = Valhall, CN = Valhall Root CA Certificate
#         Last Update: Jun 17 08:39:31 2023 GMT
#         Next Update: Jun 24 08:39:31 2023 GMT
#         CRL extensions:
#             X509v3 Authority Key Identifier:
#                 D0:33:EF:44:95:BD:B2:0B:61:6D:B8:E0:19:95:6D:80:90:AA:3F:A6
# No Revoked Certificates.
#     Signature Algorithm: sha256WithRSAEncryption
#     Signature Value:
#         7b:91:89:00:41:d4:80:72:0b:af:db:7d:e5:19:cd:d0:29:3b:
#         ...

Testing Link to heading

I should now have everything needed to try and sign certificates, revoke them, etc. let’s start the API:

cfssl serve \
      -ca=intermediate/ca.pem \
      -ca-key=intermediate/ca-key.pem \
      -responder=intermediate/ocsp.pem \
      -responder-key=intermediate/ocsp-key.pem \
      -db-config=intermediate/config/db.json \
      -config=intermediate/config/profiles.json
# 2023/06/17 10:55:12 [INFO] Initializing signer
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/newcert' is enabled
# 2023/06/17 10:55:12 [INFO] setting up key / CSR generator
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/newkey' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/ocspsign' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/info' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/gencrl' is enabled
# 2023/06/17 10:55:12 [INFO] bundler API ready
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/bundle' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/scan' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/revoke' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/certadd' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/sign' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/init_ca' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/scaninfo' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/certinfo' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/' is enabled
# 2023/06/17 10:55:12 [WARNING] endpoint 'authsign' is disabled: {"code":5200,"message":"Invalid or unknown policy"}
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/crl' is enabled
# 2023/06/17 10:55:12 [INFO] endpoint '/api/v1/cfssl/health' is enabled
# 2023/06/17 10:55:12 [INFO] Handler set up complete.
# 2023/06/17 10:55:12 [INFO] Now listening on 127.0.0.1:8888

You will notice a warning about authsign API endpoint being disabled: I’ll cover this in the article about serving the API in kubernetes. It’s only disabled since I did not set any authentication method in the configuration file. If you want to serve the API as is, you should consider carefully who will be able to access it. Other endpoints won’t all be used and there is also a method to disable those you won’t want to use nor expose.

In another terminal, I’ll use cfssl to create a key, CSR and ask the CA behind the API to sign the CSR.

cfssl gencert \
      -remote="localhost:8888" \
      -config=intermediate/config/profiles.json \
      -profile server \
      <(echo '
{
  "CN": "Test",
  "hosts": [
    "test.valhall.local"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [{
    "C":  "FR",
    "ST": "Pays de la Loire",
    "L":  "Nantes",
    "O":  "Valhall"
  }]
}') | \
    cfssljson -bare test
# 2023/06/17 11:17:28 [INFO] generate received request
# 2023/06/17 11:17:28 [INFO] received CSR
# 2023/06/17 11:17:28 [INFO] generating key: rsa-2048
# 2023/06/17 11:17:28 [INFO] encoded CSR

You can see new logs on the server side:

# 2023/06/17 11:17:28 [INFO] signature request received
# 2023/06/17 11:17:28 [INFO] signed certificate with serial number 485014236354530765875959101436276396320072239922
# 2023/06/17 11:17:28 [INFO] wrote response
# 2023/06/17 11:17:28 [INFO] 127.0.0.1:46402 - "POST /api/v1/cfssl/sign" 200

You can also use the cfssl sign command to sign an existing CSR created directly with openssl.

Let’s see if all the configuration I made earlier paid of:

openssl x509 -text -in test.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             54:f4:ca:61:42:01:e9:0d:8a:ae:93:65:42:b1:66:37:5d:91:8b:32
#         Signature Algorithm: sha512WithRSAEncryption
#         Issuer: C = FR, ST = Ile de France, L = Nantes, O = Valhall, CN = Valhall Intermediate CA Certificate
#         Validity
#             Not Before: Jun 17 09:12:00 2023 GMT
#             Not After : Sep 16 15:12:00 2023 GMT
#         Subject: C = FR, ST = Pays de la Loire, L = Nantes, O = Valhall, CN = Test
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:c3:6e:f3:0a:21:ff:fa:be:10:11:48:63:60:1a:
#                     ...
#                     cf:f1
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Key Encipherment
#             X509v3 Extended Key Usage:
#                 TLS Web Server Authentication
#             X509v3 Basic Constraints: critical
#                 CA:FALSE
#             X509v3 Subject Key Identifier:
#                 33:29:48:E7:3A:B6:4E:90:92:1E:F7:F2:33:E6:1F:99:F2:42:5E:EF
#             X509v3 Authority Key Identifier:
#                 C8:80:42:BF:8B:0D:C9:9F:55:78:DD:56:E2:8B:1A:AE:57:49:37:1B
#             Authority Information Access:
#                 OCSP - URI:http://pki.valhall.local/intermediate/ocsp
#                 CA Issuers - URI:http://pki.valhall.local/intermediate/ca
#             X509v3 Subject Alternative Name:
#                 DNS:test.valhall.local
#             X509v3 CRL Distribution Points:
#                 Full Name:
#                   URI:http://pki.valhall.local/intermediate/crl
#     Signature Algorithm: sha512WithRSAEncryption
#     Signature Value:
#         a2:e2:9a:dd:83:57:ff:4e:3c:92:b3:cc:78:1b:4c:0e:f0:da:
#         ...
#         0e:3c:54:81:ef:04:9b:af
# -----BEGIN CERTIFICATE-----
# MIIFsDCCA5igAwIBAgIUVPTKYUIB6Q2KrpNlQrFmN12RizIwDQYJKoZIhvcNAQEN
# ...
# qVBNU7XEsx7X+n4rDjxUge8Em68=
# -----END CERTIFICATE-----

The server profile was correctly used: validity time is three months and OCSP, CA and CRL endpoint are correct.

Let’s start the OCSP responder:

cfssl ocspserve -db-config intermediate/config/db.json -port 8889
# 2023/06/18 10:23:06 [INFO] Registering OCSP responder handler
# 2023/06/18 10:23:06 [INFO] Now listening on 127.0.0.1:8889

The test.pem certificate can be verified with the following openssl command:

openssl ocsp \
    -issuer <(cat intermediate/ca.pem) \
    -CAfile <(cat root/ca.pem) \
    -cert test.pem \
    -url http://localhost:8889

If you ran this command right away, you should have received the error unauthorized. It’s because cfssl uses pre-signed ocsp responses, meaning it has to sign a new response each time I sign or revoke a certificate.

# Responder Error: unauthorized (6)

Let’s refresh the OCSP response - it will be stored in the database - then retry the openssl ocsp command:

cfssl ocsprefresh \
    -ca intermediate/ca.pem \
    -responder intermediate/ocsp.pem \
    -responder-key intermediate/ocsp-key.pem \
    -db-config intermediate/config/db.json
# WARNING: no nonce in response
# Response verify OK
# test.pem: good
#   This Update: Jun 18 08:00:00 2023 GMT
#   Next Update: Jun 22 08:00:00 2023 GMT

You can disable the warning about the nonce not being present with the parameter -no_nonce. As you saw, cfssl uses pre-signed ocsp responses, and therefore they cannot include nonces (this is compatible with RFC 5019, section 4). The cfssl project does not intend to support nonces as written at cloudflare/cfssl/ocsp/responder.go#L336.

Let’s now revoke the certificate. The revoke API takes three parameters:

  • serial: the certificate serial number in decimal (strangely);
  • authority_key_id: the authority key identifier, in lowercase hexadecimal without separators;
  • reason: a reason for the revocation.

Possible reasons for revocation are listed in RFC 5280, section 6.3.2. Their syntax for cfssl api are written in cloudflare/cfssl/ocsp/ocsp.go#L26:

// revocationReasonCodes is a map between string reason codes
// to integers as defined in RFC 5280
var revocationReasonCodes = map[string]int{
    "unspecified":          ocsp.Unspecified,
    "keycompromise":        ocsp.KeyCompromise,
    "cacompromise":         ocsp.CACompromise,
    "affiliationchanged":   ocsp.AffiliationChanged,
    "superseded":           ocsp.Superseded,
    "cessationofoperation": ocsp.CessationOfOperation,
    "certificatehold":      ocsp.CertificateHold,
    "removefromcrl":        ocsp.RemoveFromCRL,
    "privilegewithdrawn":   ocsp.PrivilegeWithdrawn,
    "aacompromise":         ocsp.AACompromise,
}

In the earlier certificate description with openssl x509 I could see the value for the two other parameters:

  • The serial number 54:f4:ca:61:42:01:e9:0d:8a:ae:93:65:42:b1:66:37:5d:91:8b:32 becomes 485014236354530765875959101436276396320072239922;
  • The authority key identifier C8:80:42:BF:8B:0D:C9:9F:55:78:DD:56:E2:8B:1A:AE:57:49:37:1B becomes c88042bf8b0dc99f5578dd56e28b1aae5749371b.
curl -d '{
  "serial": "485014236354530765875959101436276396320072239922",
  "authority_key_id": "c88042bf8b0dc99f5578dd56e28b1aae5749371b",
  "reason": "cessationofoperation"
}' http://localhost:8888/api/v1/cfssl/revoke
# {"success":true,"result":{},"errors":[],"messages":[]}

As you can see, there is no authentication whatsoever provided for this endpoint, contrary to the sign endpoint also being available with at least HMAC authentication as authsign so it’s not a good idea to expose it as is.

After refreshing the cached OCSP response, the openssl ocsp command answers with:

# Response verify OK
# test.pem: revoked
#   This Update: Jun 19 08:00:00 2023 GMT
#   Next Update: Jun 23 08:00:00 2023 GMT
#   Reason: cessationOfOperation
#   Revocation Time: Jun 19 08:25:56 2023 GMT

And after regenerating the CRL file, openssl crl outputs:

# Certificate Revocation List (CRL):
#         Version 2 (0x1)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C = FR, ST = Ile de France, L = Nantes, O = Valhall, CN = Valhall Intermediate CA Certificate
#         Last Update: Jun 19 08:46:10 2023 GMT
#         Next Update: Jun 26 08:46:10 2023 GMT
#         CRL extensions:
#             X509v3 Authority Key Identifier:
#                 C8:80:42:BF:8B:0D:C9:9F:55:78:DD:56:E2:8B:1A:AE:57:49:37:1B
# Revoked Certificates:
#     Serial Number: 54F4CA614201E90D8AAE936542B166375D918B32
#         Revocation Date: Jun 19 08:25:56 2023 GMT
#     Signature Algorithm: sha256WithRSAEncryption
#     Signature Value:
#         d6:3d:18:16:6c:6a:db:07:99:41:02:76:aa:4b:16:b5:da:bd:
#         ...
#         42:df:ef:c2:6f:17:6b:3c

We know from both methods that the certificate is indeed revoked.

Conclusion Link to heading

In this post I created a root and an intermediate CA, was able to sign then revoke a certificate and test it against the OCSP responder and the CRL file. Everything is ready to go further.

Future posts will be about hosting our new PKI in kubernetes (also providing all the CRL and OCSP refreshing mechanism) and use it to automatically issue certificates to other hosted services.