Introduction Link to heading
Dans ce post j’initialiserai localement des CA racine et intermédiaire en utilisant cfssl
ainsi que les bases de
données nécessaires à la sauvegarde de l’état des PKI, qui seront aussi utilisées plus tard par les APIs.
La clef privée de la CA racine devrait rester hors ligne alors que nous utiliserons la CA intermédiaire pour les signatures classiques de certificats. Ainsi si la CA intermédiaire était compromise, il serait possible de la révoquer et d’en réinitialiser une autre rapidement. Ce montage permettrait aussi de créer différentes CA intermédiaires qui pourraient être distribuées entre différentes entités d’une organisation afin de permettre à chacune de celles-ci de signer à leur tour des certificats. Si l’une d’entre elles venait à être compromise, la révoquer n’empêcherait pas les autres CA intermédiaires de fonctionner.
cfssl
, le toolkit que j’ai utilisé, est un outil de gestion de PKI créé par CloudFlare et écrit en Go. J’ai
trouvé peu pratique son approche minimaliste de la documentation, mais heureusement, le code source de l’application
était suffisamment clair pour qu’en gardant le nez dedans, l’utilisation de l’outil reste facile.
Au moment où j’écris cet article, la dernière version de cfssl
est la
v1.6.4
.
Je n’écrirai pas ici à propos de l’installation de cfssl
que vous retrouverez probablement dans les dépôts de
votre distribution linux ou que vous pourrez compiler depuis ses sources facilement.
Configuration Link to heading
Base de données Link to heading
Comme dit plus haut, cfssl
peut sauvegarder son état dans une base de données. Trois types de bases sont
supportés : mysql, pgsql et SQLite. J’utiliserai dans cet article des bases SQLite par facilité, mais l’une des deux
autres devrait plutôt être utilisée en environnement de production.
L’utilisation d’une base de donnée permet l’utilisation des commandes liées à OCSP (Online Certificate Status Protocol) et tous les certificats signés par la CA seront aussi stockés en base.
Le fichier cloudflare/cfssl/certdb/README.md
informe que l’initialisation des bases de données se fait avec un outil appelé goose
. Ce projet n’a pas été mis à
jour depuis 2015 et plusieurs forks en ont été faits. Le fork principal, sur GitHub, n’est pas compatible avec les
scripts fournis dans le dépôt de cfssl
.
Vous devrez donc, pour initialiser la base, soit continuer avec
liamstask’s goose
( PKGBUILD) ou le
lâcher complètement. Pour cet article, j’ai choisi de ne pas l’utiliser après pourtant l’avoir utilisé lors de mes
premiers tests avec cfssl
.
La configuration utilisée pour initialiser la base peut être trouvée dans
cloudflare/cfssl/certdb/sqlite
.
J’ai arrangé les deux scripts SQL en un seul appelé ici 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)
);
Créer les bases nécessaires pour chaque CA est l’affaire de deux petites commandes :
sqlite3 db/root-certstore.db < db/definition.sql
sqlite3 db/intermediate-certstore.db < db/definition.sql
cfssl Link to heading
Accès à la base de données Link to heading
cfssl
a besoin d’un peut de configuration pour fonctionner.
Pour commencer, je dois créer les fichiers de configuration lui indiquant comment accéder aux bases. Il s’agit de deux fichiers JSON très simples :
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
Profils de signature Link to heading
Viennent ensuite les profils que mes CA vont utiliser pour signer des certificats. Il y a quelques paramètres que je dois définir maintenant même s’ils ne seront vraiment utiles que lorsque les CA seront finalement hébergées :
- À quelle adresse je pourrai récupérer mes CA ;
- Où leurs CRL seront distribuées ;
- Quelles seront les adresses auxquelles les répondeurs OCSP seront servis.
J’ai choisi de service le PKI sous le domaine pki.valhall.local
. Les URI que j’utiliserai seront :
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
Ces informations seront plus tard disponibles comme extensions dans les certificats. Par exemple, si on prend le
certificat de 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
Ces extensions font parties du système de vérification de la validité des certificats.
Les profils de signature sont donc définis dans un fichier JSON. Ils permettent de renseigner des paramètres par défaut pour certains types de certificats. Par exemple, la CA racine qui ne signera que des certificats intermédiaires utilisera la configuration suivante :
{
"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"
}
}
}
}
Dans la configuration ci-dessus, j’ai défini deux profils : intermediate
qui sera utilisé pour signer d’autres CA
et ocsp
qui sera utilisé pour signer le certificat utilisé pour signer l’OCSP. Le bloc .signing.default
est
utilisé pour renseigner des paramètres partagés entre les profils.
La CA intermédiaire sera utilisée principalement pour signer des certificats à destination de serveurs ou pour de l’authentification client. Étant donné que cette CA sera plus tard utilisée dans un mécanisme de renouvellement automatique, à l’image des CA de Let’s Encrypt, les certificats serveurs pourraient avec une période de validité courte :
{
"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"
}
}
}
}
Ces profils seront sauvegardés respectivement sous root/config/profiles.json
et intermediate/config/profiles.json
.
Définition des CA Link to heading
La structure de l’objet Certificat attendu par cfssl
est définie dans
cloudflare/cfssl/csr/csr.go#L138.
Les deux définitions que j’utiliserai, respectivement root/config/init.json
et intermediate/config/init.json
sont :
{
"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"
}]
}
La création des clefs privées et des certificats est ensuite plutôt facile. La commande genkey
créée une clef
privée, un CSR puis l’auto-signe.
cfssl genkey -initca root/config/init.json | cfssljson -bare root/ca
cfssl genkey
retourne un JSON comprenant trois paramètres : cert
, csr
et key
. cfssljson
, un autre exécutable
faisant partie du toolkit pourra créer les fichiers automatiquement depuis ce JSON.
La création de la CA intermédiaire suit le même processus. Il faudra par contre supprimer le certificat généré et réutiliser le CSR pour la faire signer par la CA racine :
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
La structure du dossier courant devrait maintenant ressembler à ça :
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
Et une requête SQL dans la base utilisée par la CA racine permet de voir que le certificat de la CA intermédiaire y a bien été stocké.
$ sqlite3 db/root-certstore.db "SELECT common_name FROM certificates;"
Valhall Intermediate CA Certificate
Clef et certificat pour OCSP Link to heading
Il ne reste plus qu’à générer les certificats et clefs pour les répondeurs OCSP de chaque CA et à générer les CRL.
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
Les CRL (Certificate Revocation Lists) sont des fichiers qui devraient (probablement ?) être mis à jour régulièrement. Je ne suis pas certain de savoir comment les clients SSL implémente le mécanisme de cache pour cette fonctionnalité étant donné que les CRL comportent deux dates : la date de la dernière mise à jour et la date de la prochaine mise à jour. Ce qui laisse supposer que si le client décide de cacher le CRL jusqu’à la prochaine mise à jour, ils pourraient manquer pendant cette période des mises à jour concernant la révocation de certificats, sujet critique. Les CRL sont aussi signées avec la clef privée de la CA, ce qui peut être gênant quand on souhaite garder la clef privée de la CA racine hors ligne. J’ai pû rencontrer différentes approches à ce sujet : CRL avec une date d’expiration similaire à celle de la CA et mise à jour seulement si une CA intermédiaire est compromise ou encore CRL hebdomadaires, ce qui est d’ailleurs le choix par défaut pour cfssl :
$ cfssl crl -h
# ...
# -expiry=168h0m0s: time from now after which the CRL will expire (default: one week)
J’ai choisi de correspondre à ce deuxième cas et comme je ne peux pas imaginer générer des CRL toutes les semaines sans automatiser le processus, la clef privée de ma CA racine sera stockée dans un coffre-fort et je m’arrangerai pour que la tâche de mise à jour aille la récupérer, ce qui est un risque acceptable pour mon home-lab.
cfssl crl
retourne un CRL au format PEM sans header/footer ni retours à la ligne, il faudra donc les ajouter :
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
La CRL peut être lue avec openssl crl
:
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:
# ...
Validation Link to heading
Tous les prérequis pour pouvoir signer, révoquer, etc. des certificats devraient être là. Il n’y a plus qu’à démarrer l’api pour tester.
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
Vous remarquerez un avertissement à propos de l’API authsign
qui est désactivée : j’aborderai ce sujet dans l’article
sur l’hébergement de l’API dans kubernetes. Pour faire simple, c’est désactivé parce que le fichier de configuration
renseignant les profils ne contient pas d’information sur des méthodes d’authentification. Si vous souhaitez servir
l’API en l’état, faites bien attention à qui pourrait y avoir accès. Parmi Les autres routes de l’API, certaines ne
serviront pas et nous verrons aussi comment les désactiver pour éviter de les rendre accessibles.
Dans un deuxième terminal, je vais utiliser cfssl
pour créer une clef privée, un CSR et faire signer ce dernier
via l’API servie depuis le premier terminal.
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
Les logs de l’api montrent bien que le certificat a été signé :
# 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
J’aurais aussi pu générer une clef privée et un CSR avec openssl req -newkey rsa:2048 -nodes -keyout test-key.pem -out test.csr
puis d’utiliser cfssl sign
avec la même option -remote
.
openssl x509
permet de consulter les informations du certificat :
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-----
Le profil server
a bien été utilisé : la période de validité est bien de 3 mois et les URIs concernant l’OCSP, les
CA et CRL sont tels que configurés.
Pour tester la suite, le répondeur OCSP devra être lancé :
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
Le certificat test.pem
pourra être verifié auprès du répondeur avec la commande openssl suivante :
openssl ocsp \
-issuer <(cat intermediate/ca.pem) \
-CAfile <(cat root/ca.pem) \
-cert test.pem \
-url http://localhost:8889
Si vous exécutez cette commande maintenant, vous devriez recevoir une erreur unauthorized
. C’est parce que cfssl
utilise des réponses OCSP cachées en base et pré-signées, ce qui veut dire qu’à chaque fois que vous révoquerez ou
signerez un nouveau certificat il faudra la mettre à jour.
# Responder Error: unauthorized (6)
Après avoir rafraîchi la réponse OCSP, openssl ocsp
retourne :
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
Vous pouvez désactiver l’alerte concernant le nonce manquant avec le paramètre -no_nonce
. Comme dit précédemment,
cfssl
utilise des réponses OCSP pré-signées, ce qui ne permet pas l’envoi de nonces (comportement compatible avec la
RFC 5019, section 4). Le projet cfssl
ne prévoie d’ailleurs pas
de gérer l’envoi de nonce dans le futur, cf.
cloudflare/cfssl/ocsp/responder.go#L336.
Il nous reste à valider le comportement à la révocation d’un certificat. L’API de révocation attend trois paramètres :
serial
: l’identifiant unique du certificat, (étrangement) en décimal ;authority_key_id
: l’identifiant de la CA, en minuscules et en hexadécimal, sans séparateurs ;reason
: la raison de la révocation.
Les raisons possibles lors d’une révocation sont listées dans la
RFC 5280, section 6.3.2. Leurs syntaxes pour l’api de cfssl
sont écrites dans
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,
}
Les valeurs des deux autres paramètres peuvent être déduites de la description du certificat de test avec openssl x509
affichée plus haut.
- Le numéro de série
54:f4:ca:61:42:01:e9:0d:8a:ae:93:65:42:b1:66:37:5d:91:8b:32
devient485014236354530765875959101436276396320072239922
; - L’identifiant de la CA
C8:80:42:BF:8B:0D:C9:9F:55:78:DD:56:E2:8B:1A:AE:57:49:37:1B
devientc88042bf8b0dc99f5578dd56e28b1aae5749371b
.
curl -d '{
"serial": "485014236354530765875959101436276396320072239922",
"authority_key_id": "c88042bf8b0dc99f5578dd56e28b1aae5749371b",
"reason": "cessationofoperation"
}' http://localhost:8888/api/v1/cfssl/revoke
# {"success":true,"result":{},"errors":[],"messages":[]}
Comme vous pouvez le voir, il n’y a pas d’authentification disponible pour cette API, au contraire de l’api sign
qui est aussi disponible avec au moins de l’authentification HMAC derrière la route authsign
. Ce n’est donc pas
une bonne idée d’exposer la route de révocation en l’état.
Après avoir rafraîchi la réponse OCSP, la commande openssl ocsp
utilisée précédemment répond avec :
# 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
Et après avoir régénéré le fichier CRL, openssl crl
retourne :
# 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
Nous savons donc depuis ces deux méthodes que le certificat est bien révoqué.
Conclusion Link to heading
Dans ce billet, j’ai créé une CA racine et une CA intermédiaire. J’ai été capable de signer puis de révoquer un certificat et de le tester dans ces différentes situations avec le répondeur OCSP et contre la CRL. Tout semble en ordre pour la suite.
Les futurs articles de cette série aborderont l’hébergement dans kubernetes (dans lequel j’automatiserai le rafraîchissement des réponses OCSP et des CRL) et l’utilisation de ce service pour fournir automatiquement des certificats pour d’autres services auto-hébergés.