Eigene Certificate Authority, inkl. OCSP-Responder

Mit OpenSSL eine fully-featured PKI selbst hosten

In Zeiten von Let’s Encrypt kann man sich natürlich fragen, wozu man eine eigene Public-Key-Infrastruktur braucht. Nun, ich würde da natürlich, wie immer, den Lerneffekt und #ownyourdata nennen. Aber vor allem: Warum sollte man für nicht-öffentliche interne Dienste Zertifikate verwenden, die in öffentlichen Listen (Certificate Transparency Logs) auftauchen?

Übersicht

Um Abhängigkeiten zu Kapseln, habe ich alles in einem eigenen LXC Container unter Proxmox eingerichtet. Ressourcenmäßig reichen 128 MB RAM völlig aus. Die einzige Abhängigkeit ist OpenSSL. Daneben wird noch ein Webserver benötigt, ich benutze nginx.

Zur Verwaltung der Zertifikate mit OpenSSL habe ich schon vor längerer Zeit caman gefunden. Ein wirklich tolles Shell-Script, das ich zwischenzeitlich ein wenig angepasst und erweitert habe, u.a. ein OCSP-Responder hat gefehlt.

Einrichtung

Folgendes Beispiel ist die echte Konfiguration von pki.berrnd.net. Ich habe dabei eine Root- sowie eine Intermediate-CA eingerichtet. Es sind somit eigtl. gleich zwei Zertifizierungsstellen. Der Hintergrund ist, dass nur die oberste (Root-)CA besonders geschützt werden muss. Zertifikate werden ausschließlich von der Zwischenzertifizierungsstelle signiert, heißt im schlimmsten Fall wird nur diese kompromittiert und man erstellt einfach eine neue unterhalb der weiterhin vorhandenen Root-CA.

Als erstes das Repository klonen und die Default-Konfigurationsdateien kopieren.

git clone https://github.com/berrnd/caman.git root-ca
cd root-ca
cp ca/caconfig.cnf.default ca/caconfig.cnf
cp ca/host.cnf.default ca/host.cnf
cp ca/ocspsigncert.cnf.default ca/ocspsigncert.cnf

Das gleiche brauchen wir dann nochmal für die Zwischenzertifizierungsstelle, also am besten jetzt den kompletten Ordner kopieren.

cd ..
cp -r ./root-ca ./signing-ca-2017

Als nächstes müssen die oben kopierten OpenSSL-Konfigurationsdateien angepasst werden. Die caconfig.cnf ist das Template für eine CA, die host.cnf die Vorlage für ein Host-Zertifikat und die ocspsigncert.cnf wird verwendet, um ein spezielles nur für den OCSP-Responder notwendiges Zertifikat zu erzeugen.

Nachfolgend meine Konfigurationen:

root-ca caconfig.cnf
# # Certificate authority configuration file for OpenSSL # dir = ./ca #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/ca.crt serial = $dir/serial crl = $dir/ca.crl private_key = $dir/ca.key RANDFILE = $dir/private/.rand x509_extensions = usr_cert name_opt = ca_default cert_opt = ca_default # V2 CRLs crl_extensions = crl_ext crlnumber = $dir/crlnumber # Default expiration and encryption policies for certificates # 30 days for CRL, 100 years for certs default_crl_days = 30 default_days = 36500 default_md = sha256 preserve = no policy = policy_match # Needed to copy SANs from CSR to cert copy_extensions = copy #################################################################### # Default policy to use when generating server certificates # The following fields must be defined in the server certificate [ policy_match ] countryName = supplied stateOrProvinceName = supplied organizationName = supplied organizationalUnitName = supplied commonName = supplied emailAddress = supplied #################################################################### # The default root certificate generation policy [ req ] default_bits = 4096 default_keyfile = $dir/ca.key default_md = sha256 prompt = no distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = utf8only #################################################################### # Root Certificate Authority distinguished name # Change these fields to match your local environment [ req_distinguished_name ] # >> Change the following 6 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI commonName = pki.berrnd.net Root CA emailAddress = pki@berrnd.net # << End changes #################################################################### # Extensions to use when generating server certificates [ usr_cert ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer # >> Change the following URL crlDistributionPoints = URI:http://pki.berrnd.net/root-ca.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/root-ca.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/root-ca # << End changes #################################################################### # Extensions for a CA [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = CA:true # >> Change the following URL nsComment = "Private certificate authority by Bernd Bestel (https://berrnd.de), trust me by adding the CA certificate (http://pki.berrnd.net/root-ca.crt) to your trusted root certificates." crlDistributionPoints = URI:http://pki.berrnd.net/root-ca.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/root-ca.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/root-ca # << End changes #################################################################### # CRL extensions. [ crl_ext ] authorityKeyIdentifier = keyid:always
root-ca host.cnf
# # Host configuration # [ ca ] default_ca = local_ca [ local_ca ] # Default expiration and encryption policies for certificates # 10 years for certs default_days = 3650 [ req ] prompt = no distinguished_name = host_distinguished_name req_extensions = v3_req [ host_distinguished_name ] # >> Change the following 4 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI emailAddress = pki@berrnd.net crlDistributionPoints = URI:http://pki.berrnd.net/root-ca.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/root-ca.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/root-ca # << End changes commonName = <> [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment <>
root-ca ocspsigncert.cnf
# # Host configuration # [ ca ] default_ca = local_ca [ local_ca ] # Default expiration and encryption policies for certificates # 10 years for certs default_days = 3650 [ req ] prompt = no distinguished_name = host_distinguished_name req_extensions = v3_req [ host_distinguished_name ] # >> Change the following 4 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI emailAddress = pki@berrnd.net crlDistributionPoints = URI:http://pki.berrnd.net/signing-ca-2017.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/signing-ca-2017.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/signing-ca-2017 # << End changes commonName = <> [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = OCSPSigning
signing-ca-2017 caconfig.cnf
# # Certificate authority configuration file for OpenSSL # dir = ./ca #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/ca.crt serial = $dir/serial crl = $dir/ca.crl private_key = $dir/ca.key RANDFILE = $dir/private/.rand x509_extensions = usr_cert name_opt = ca_default cert_opt = ca_default # V2 CRLs crl_extensions = crl_ext crlnumber = $dir/crlnumber # Default expiration and encryption policies for certificates # 30 days for CRL, 100 years for certs default_crl_days = 30 default_days = 36500 default_md = sha256 preserve = no policy = policy_match # Needed to copy SANs from CSR to cert copy_extensions = copy #################################################################### # Default policy to use when generating server certificates # The following fields must be defined in the server certificate [ policy_match ] countryName = supplied stateOrProvinceName = supplied organizationName = supplied organizationalUnitName = supplied commonName = supplied emailAddress = supplied #################################################################### # The default root certificate generation policy [ req ] default_bits = 4096 default_keyfile = $dir/ca.key default_md = sha256 prompt = no distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = utf8only #################################################################### # Root Certificate Authority distinguished name # Change these fields to match your local environment [ req_distinguished_name ] # >> Change the following 6 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI commonName = pki.berrnd.net Signing CA 2017 emailAddress = pki@berrnd.net # << End changes #################################################################### # Extensions to use when generating server certificates [ usr_cert ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer # >> Change the following URL crlDistributionPoints = URI:http://pki.berrnd.net/signing-ca-2017.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/signing-ca-2017.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/signing-ca-2017 # << End changes #################################################################### # Extensions for a CA [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = CA:true # >> Change the following URL nsComment = "Private certificate authority by Bernd Bestel (https://berrnd.de), trust me by adding the CA certificate (http://pki.berrnd.net/root-ca.crt) to your trusted root certificates." crlDistributionPoints = URI:http://pki.berrnd.net/signing-ca-2017.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/signing-ca-2017.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/signing-ca-2017 # << End changes #################################################################### # CRL extensions. [ crl_ext ] authorityKeyIdentifier = keyid:always
signing-ca-2017 host.cnf
# # Host configuration # [ ca ] default_ca = local_ca [ local_ca ] # Default expiration and encryption policies for certificates # 10 years for certs default_days = 3650 [ req ] prompt = no distinguished_name = host_distinguished_name req_extensions = v3_req [ host_distinguished_name ] # >> Change the following 4 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI emailAddress = pki@berrnd.net crlDistributionPoints = URI:http://pki.berrnd.net/signing-ca-2017.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/signing-ca-2017.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/signing-ca-2017 # << End changes commonName = <> [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment <>
signing-ca-2017 ocspsigncert.cnf
# # Host configuration # [ ca ] default_ca = local_ca [ local_ca ] # Default expiration and encryption policies for certificates # 10 years for certs default_days = 3650 [ req ] prompt = no distinguished_name = host_distinguished_name req_extensions = v3_req [ host_distinguished_name ] # >> Change the following 4 variables: # countryName must be 2 character character code countryName = DE stateOrProvinceName = BY organizationName = berrnd.net organizationalUnitName = PKI emailAddress = pki@berrnd.net crlDistributionPoints = URI:http://pki.berrnd.net/signing-ca-2017.crl authorityInfoAccess = caIssuers;URI:http://pki.berrnd.net/signing-ca-2017.crt authorityInfoAccess = OCSP;URI:http://pki.berrnd.net/ocsp/signing-ca-2017 # << End changes commonName = <> [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = OCSPSigning

Anschließend erzeugen wir zuerst die Root-CA und auch gleich das nötige Zertifikat für den OCSP-Responder. Danach das Gleiche für die Signing-CA, wobei diese schon von der Root-CA selbst signiert wird:

root-ca/caman init
root-ca/caman newocspsigncert ocsp-signcert-20171204
signing-ca-2017/caman init ca:./root-ca
signing-ca-2017/caman newocspsigncert ocsp-signcert-20171204

Host-Zertifikate werden nun ausschließlich von der Signing-CA ausgestellt, das funktioniert recht einfach:

signing-ca-2017/new certtest.berrnd.org
signing-ca-2017/sign certtest.berrnd.org

Anschließend liegen unter signing-ca-2017/store/certtest.berrnd.org die erzeugten Zertifikat-Dateien.

Zertifikat für "certtest.berrnd.org" Zertifikats-Kette für "certtest.berrnd.org"

Die CA-Zertifikate sowie die Sperrlisten müssen dann noch über den Webserver veröffentlicht werden, sodass diese unter den in der Konfiguration angegebenen URLs erreichbar sind.

Auch der OCSP-Responder muss natürlich unter der angegebenen URL erreichbar sein, gestartet werden kann dieser mit:

root-ca/caman ocsp ocsp-signcert-20171204
signing-ca-2017/caman ocsp ocsp-signcert-20171204

Am besten lässt man diesen als Dienst laufen, der Port muss ggf. direkt oben im caman Shell-Script angepasst werden, wobei ich auch das über den Webserver (Reverse-Proxy) laufen lasse.

Der Vollständigkeit halber hier noch meine nginx Konfiguration sowie ein gutes altes init.d-Script für den OCSP-Responder Daemon:

nginx site config
server { server_name pki.berrnd.net; root /var/www; location /root-ca.crt { alias /opt/caman/root-ca/ca/ca.crt; } location /root-ca.crl { alias /opt/caman/root-ca/ca/ca.crl; } location /signing-ca-2017.crt { alias /opt/caman/signing-ca-2017/ca/ca.crt; } location /signing-ca-2017.crl { alias /opt/caman/signing-ca-2017/ca/ca.crl; } location /ocsp/root-ca { proxy_pass http://127.0.0.1:8089/; } location /ocsp/signing-ca-2017 { proxy_pass http://127.0.0.1:8088/; } }
init.d ocsp-responder-root-ca
#!/bin/sh ### BEGIN INIT INFO # Provides: ocsp-responder-root-ca # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO dir="/opt/caman/root-ca" cmd="./caman ocsp ocsp-signcert-20171204" user="root" name=`basename $0` pid_file="/var/run/$name.pid" stdout_log="/var/log/$name.log" stderr_log="/var/log/$name.err" get_pid() { cat "$pid_file" } is_running() { [ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1 } case "$1" in start) if is_running; then echo "Already started" else echo "Starting $name" cd "$dir" if [ -z "$user" ]; then sudo $cmd >> "$stdout_log" 2>> "$stderr_log" & else sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" & fi echo $! > "$pid_file" if ! is_running; then echo "Unable to start, see $stdout_log and $stderr_log" exit 1 fi fi ;; stop) if is_running; then echo -n "Stopping $name.." kill `get_pid` for i in {1..10} do if ! is_running; then break fi echo -n "." sleep 1 done echo if is_running; then echo "Not stopped; may still be shutting down or shutdown may have failed" exit 1 else echo "Stopped" if [ -f "$pid_file" ]; then rm "$pid_file" fi fi else echo "Not running" fi ;; restart) $0 stop if is_running; then echo "Unable to stop, will not attempt to start" exit 1 fi $0 start ;; status) if is_running; then echo "Running" else echo "Stopped" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0
Du möchtest mir hierzu Feedback hinterlassen? Dann schreib mir gerne eine Nachricht.