Wenn man Applikation entwickelt, die sich gegenseitig über mTLS authentifizieren müssen, benötigt man ein Tripel von x509-Zertifikatsdateien: Eine Root-CA, Client-Zertifikate und Server-Zertifikate und die entsprechenden Schlüsseldateien. Eine Möglichkeit, diese Dateien zu erstellen, ist die Verwendung von openssl, aber das ist ziemlich mühsam, da man eine Menge konfigurieren muss. Eine elegantere Lösung ist die Verwendung des CloudFlare PKI/TLS toolkit (kurz CFSSL).

In diesem Post erkläre ich wie man alle Zertifikate erstellt, die zum Testen einer mTLS-Verbindung benötigt werden.

Setup

Um CFSSL zu installieren, kann man entweder brew verwenden oder es direkt mit der go Toolchain installieren.

Installation mit brew:

brew install cfssl

Für die Installation von CFSSL mit der go Toolchain sei auf offizielle Dokumentation verwiesen.

Root CA

Zunächst benötigt man eine Root CA (Certificate Authority), von der alle anderen Zertifikate abgeleitet werden.
CFSSL unterstützt die Konfiguration von Variablen für Zertifikate in einem JSON-Format. Also erstellen wir zunächst eine JSON-Datei mit Variablen für unsere CA:

Datei: configs/ca.json

{
  "CN": "www.radile.net",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C":  "DE",
      "L":  "Cologne",
      "O":  "Martin Radile",
      "OU": "cfssldemo",
      "ST": "NRW"
    }
  ]
}

Um die CA-Dateien zu erstellen, führt man den folgenden Befehl aus:

mkdir -p certs
cfssl gencert -initca \
    configs/ca.json | cfssljson -bare certs/ca

Dadurch wird eine neue CA mit den in configs/ca.json konfigurierten Variablen erstellt. Um den Inhalt des CA-Zertifikats anzuzeigen, kann man openssl verwenden:

openssl x509 -in certs/ca.pem -text

Die Ausgabe wird in etwa so aussehen:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            01:02:c7:ec:cc:d9:20:af:c5:f9:7c:34:79:eb:f6:6c:74:30:12:ca
    Signature Algorithm: sha512WithRSAEncryption
        Issuer: C=DE, ST=NRW, L=Cologne, O=Martin Radile, OU=cfssldemo, CN=www.radile.net
        Validity
            Not Before: Jan 31 20:42:00 2022 GMT
            Not After : Jan 30 20:42:00 2027 GMT
        Subject: C=DE, ST=NRW, L=Cologne, O=Martin Radile, OU=cfssldemo, CN=www.radile.net
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
...

Profile

Mit der neuen CA kann man jetzt die Zertifikate für die Client- und Serveranwendungen erstellen. Wir erstellen wieder eine neue Konfigurationsdatei, um Profile für die Zertifikatserstellung zu definieren. Auf diese Profile wird später bei der Erstellung der Client- und Serverzertifikate verwiesen.

Datei: configs/ca-config.json

{
  "signing": {
    "profiles": {
      "client": {
        "usages": ["client auth"],
        "expiry": "8760h"
      },
      "server": {
        "usages": ["server auth"],
        "expiry": "8760h"
      }
    }
  }
}

Die Konfiguration enthält zwei Profile mit den Namen client und server. Die Namen können beliebig gewählt werden. Das client Profil bestimmt, dass die erstellten Zertfikate für die Authentifizierung als Client verwendet werden können. Mit dem server Profil kann CFSSL Zertifikate für eine Serveranwendung erstellen. Beide Profile sind so konfiguriert, dass die Zertifikate eine Gültigkeit von einem Jahr haben.

Client Zertifikate

Wir brauchen wieder eine Konfigurationsdatei mit Parametern für unsere Zertifikate:

Datei: configs/client.json

{
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C":  "DE",
      "L":  "Cologne",
      "O":  "Martin Radile",
      "OU": "client cfssl demo",
      "ST": "NRW"
    }
  ]
}

Mit dem folgenden Befehl werden die Clientzertifikate erstellt:

cfssl gencert \
    -ca=certs/ca.pem \
    -ca-key=certs/ca-key.pem \
    -config=configs/ca-config.json \
    -profile=client \
    configs/client.json | cfssljson -bare certs/client

Wir verweisen auf die CA-Dateien und unser zuvor erstelltes client Profil. Der letzte Parameter für cfssljson certs/client ist das Ausgabeverzeichnis und das Präfix der Dateinamen für die Zertifikate.

Server Zertifikate

Die Konfiguration für die Serverzertifikate sieht fast identisch aus. Der einzige Unterschied ist das Feld hosts. Hier können die Hosts hinzufügt werden, für die das generierte Zertifikat gültig sein soll.

Wenn in der TLS Konfiguration der Client-Anwendung eine Option für strict host checking aktiviert ist, würde der Verbindungsaufbau abgelehnt werden wenn die Hostnamen nicht passen. Der konfigurierte Hostname sollte also passend zum Hostnamen sein unter dem der Server später zu erreichen ist. Es ist auch möglich IP Adressen und Wildcard Hosts zu konfigurieren wie z.B. *.cfssldemo.radile.net.

Datei: configs/server.json

{
  "hosts": [
    "server.cfssldemo.radile.net",
    "127.0.0.1",
    "localhost"
  ],
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C":  "DE",
      "L":  "Cologne",
      "O":  "Martin Radile",
      "OU": "server cfssl demo",
      "ST": "NRW"
    }
  ]
}

Mit dem folgenden Befehl werden die Serverzertifikate erstellt:

cfssl gencert \
    -ca=certs/ca.pem \
    -ca-key=certs/ca-key.pem \
    -config=configs/ca-config.json \
    -profile=server \
    configs/server.json | cfssljson -bare certs/server

Die Hostnamen werden in das SAN-Feld (Subject Alternative Name) des Zertifikats aufgenommen:

openssl x509 -in certs/server.pem -text 
...
    X509v3 extensions:
        X509v3 Subject Alternative Name: 
            DNS:server.cfssldemo.radile.net, DNS:localhost, IP Address:127.0.0.1
...

Dateiübersicht

Nach dem alle Schritte ausgeführt wurden sollten die folgenden Dateien vorhanden sein:

├── certs
│   ├── ca-key.pem
│   ├── ca.csr
│   ├── ca.pem
│   ├── client-key.pem
│   ├── client.csr
│   ├── client.pem
│   ├── server-key.pem
│   ├── server.csr
│   └── server.pem
└── configs
    ├── ca-config.json
    ├── ca.json
    ├── client.json
    └── server.json

Der letzte Schritt ist das Verteilen der Dateien auf die Client- und Serveranwendungen sowie die Einbindung in beide Anwendungen:

  • client
    • certs/ca.pem
    • certs/client.pem
    • certs/client-key.pem
  • server
    • certs/ca.pem
    • certs/server.pem
    • certs/server-key.pem

Vollständiger Code

Den vollständigen Code mit einem Makefile findet ihr hier github.com/mradile/cfssl-mtls-demo.