Ceci est une ancienne révision du document !


EJBCA - Création de certificats TLS

CréationNicolas THOREZ 2024/03/20 17:03

Grâce à nos autorités de certification, nous avons la possibilité de créer des certificats TLS pour nos serveurs webs. Ces certificats et le fait que les autorités sont reconnues par les postes clients permettent de se passer des certificats auto-signés et par extensions, suppriment les messages d'erreurs lors de l'accès à des sites auto-signés :

On va donc voir comment créer un modèle de certificat TLS et les détails de la création de certificats pour des serveurs webs.

Réf. : EJBCA

  • Sur l'interface d'administration, dans la section CA Functions, on va dans Certificate Profiles et on clone le profil par défaut SERVER :
  • On nomme le modèle et on le créé en cliquant sur Create from template :
  • On édite le modèle fraichement créé via son bouton Edit :
  • On commence par définir :
    • Available Key Algorithms : ECDSA
    • Available ECDSA curves : P-256 / prime256v1 / secp256r1
    • Signature Algorithm : Inherit from Issuing CA
    • Validity or end date of the certificate : 1y
    • Expriration Restriction : On coche la cae Use… puis dans la section suivante, on coche les cases :
      • Monday
      • Friday
      • Saturday
      • Sunday
    • Profile Description : Optionnel, permet de mieux identifier le modèle.
  • Dans la section X.509v3 extensions, on décoche :
    • Basic Constraints
  • Dans la section X.509v3 extensions - Usages, on coche :
    • Key Usage
    • Digital Signature
    • Key encipherment
    • Dans Extended Key Usage, on choisira :
      • Server Authentication
  • Dans la section X.509v3 extensions - Names, :
    • on coche Subject Alternative Name
    • on décoche Issuer Alternative Name
  • Dans la section X.509v3 extensions - Validation data, on coche :
    • CRL Distribution Points
    • Use CA defined CRL Distribution Point
    • Authority Information Access :
    • Use CA defined OCSP locator
    • Use CA defined CA issuer
  • Dans la section Other Data :
    • on décoche LDAP DN order
    • dans la partie Available CAs, on sélectionne notre autorité intermédiaire.
    • on sauvegarde le tout en cliquant sur Save
  • On va désormais dans la menu RA Functions, End Entity Profiles et dans la section Add End Entity Profile, on nomme notre modèle et on l'ajoute ajoute en cliquant sur Add Profile :
  • On sélectionne notre nouveau profil dans la liste et on l'édite :
  • On commence par décocher End Entity E-mail, on peut aussi ajouter si on le souhaite une description :
  • Dans la section Subject DN Attributes, on sélectionnes dans le menu déroulant et on ajoute à la suite de CN, Common name déjà présent avec le bouton Add :
    • O, Organization : on indiquera le nom de notre organisation et on cochera Required uniquement
    • C, Country (ISO 3166) : on indiquera le code du pays de l'organisation et on cochera Required uniquement
  • Pour CN, Common name, on cochera Required et Modifiable
  • Dans la section Other Subject Attributes, pour l'attribut Subject Alternative Name, on ajoutera DNS Name et on cochera Use entity CN field :
  • Dans la section Main Certificate Data, on choisira :
    • pour Default Certificate Profile : le profil TLS précédemment créé
    • pour Default CAs : l'autorité intermédiaire précédemment créée
    • pour Default Token : User Generated
    • pour Available Tokens : User Generated et PEM file
  • Puis on sauvegarde le tout avec le bouton Save en bas de la page :

Opérationnel

Voilà, notre autorité de certification est configurée et opérationnelle pour la délivrance de certificat TLS pour nos serveurs webs internes. Il ne reste plus qu'à créer ces fameux certificats. Pour cela, nous avons deux possibilités :
  • Via l'interface web
  • Via des appels API
  • On commence en ligne de commande par créer une configuration pour openssl :

nano /tmp/openssl.conf

[ req ]
default_md = sha256
prompt = no
distinguished_name = dn

[ dn ]
CN = <FQDN du serveur pour lequel on souhaite créer un certificat>
O = <Mon organisation>
C = <Pays de mon organisation>
  • On crée un clé privée :

openssl ecparam -genkey -name prime256v1 -out <fqdn>.key

  • On crée une requête de certification :

openssl req -new -key <fqdn>.key -config /tmp/openssl.conf

  • On copie cette requête et on va sur l'interface de gestion, soit via l'interface d'administration en cliquant sur le menu RA Web (tout en bas des menus), soit directement via l'adresse https://<ejbca-address>/ejbca/ra.
  • Une fois sur l'interface, on clique sur Make New Request :
  • On sélectionne :
    • Notre profil de certificat TLS
    • Notre profil de certification pour du TLS
    • On choisit Provided by user
    • On colle notre requête créée en ligne commande dans Upload CSR
    • On clique sur Upload CSR
  • On trouve alors le résumé de notre requête. On ajoutera dans le champs Username de la section Provide User Credentials, le FQDN principale pour ce certificat puis on pourra le télécharger en cliquant sur un des boutons de téléchargement situé en bas de page, chacun correspondant à un type précis de certificat (selon les besoin de votre serveur web donc) :
  • Pour faciliter la création/interrogation/révocation par appel API, j'ai créé un script manage-certificate.sh :

#!/bin/bash

#########################################
#                                       #
#   Script de gestion des certificats   #
#                                       #
#########################################

#===========#
# Variables #
#===========#

# Dossier de travail
WorkingDirectory=/usr/local/batch/certmgr

# Authentification pour le script
ScriptAuthentication="$WorkingDirectory/script.p12:123456"

# Adresse de l'autorité de certification
AuthorityAddress="localhost"

# Profil de certificat
CertificateProfileName="TLS-SERVER"

# Profil de serveur
EndEntityProfileName="TlsServer"

# Nom de l'autorité de certification
CertificationAuthorityName="SubCa"

# Fichier de configuration pour openssl
OpensslConfig="/tmp/cert.conf"

# Modèle de requête API
ApiTemplate='{"certificate_request":$ApiCsr, "certificate_profile_name":$ApiCp, "end_entity_profile_name":$ApiEep, "certificate_authority_name":$ApiCa, "username":$ApiUser, "password":$ApiPwd}'

# Dossier racine pour les certificats
CertificateRoorDirectory="$WorkingDirectory/issued"

# Utilisateur pour l'enregistremet
EnrollmentUser="exploit"

# Mot de passe pour l'enregistrement
EnrollmentPassword="123456"

# Fichier de réponse de l'API
ApiResponse="/tmp/api.json"

# Certificat racine
RootCertificate="$WorkingDirectory/issued/RootCa.pem"

# Certificat intermédiaire
SubCertificate="$WorkingDirectory/issued/SubCa.pem"

# Fichier de log
ManageLog="/var/log/manage-certificate.log"

# Raison de révocation par défaut
RevokeReason="CESSATION_OF_OPERATION"

# Organisation
Organization="ShyrkaSystem"

# Pays
Country="FR"

#===========#
# Fonctions #
#===========#

# Demande d'aide
display-help() {
        echo "Script de gestion des certificats"
        echo "---------------------------------"
        echo ""
        echo "Usage : ./manage-certificate.sh [add|new|create] <fqdn>"
        echo "                                [renew] <fqdn>"
        echo "                                [revoke|delete] <fqdn> <reason>"
        echo "                                [status] <fqdn>"
        echo "                                [help]"
        echo ""
        echo "Arguments :"
        echo ""
        echo "add                   Crée un certificat pour le domaine <fqdn>."
        echo "create                Identique à add."
        echo "delete                Révoque et supprime le certificat pour le domaine <fqdn>."
        echo "help                  Affiche l'aide."
        echo "new                   Identique à add."
        echo "renew                 Renouvelle un certificat existant (création si inexistant)."
        echo "revoke                Identique à delete."
        echo "status                Affiche le status du certificat pour le domaine <fqdn>."
        echo ""
        echo "<fqdn>                Obligatoire pour toute action et sous la forme d'un FQDN (ex : www.google.fr)."
        echo "<reason>              Facultatif. Indique la raison de la révocation du certificat."
        echo "                      Par défaut, la raison sera CESSATION_OF_OPERATION."
        echo "                      Les raisons disponibles sont :"
        echo "                        - KEY_COMPROMISE"
        echo "                        - CA_COMPROMISE"
        echo "                        - AFFILIATION_CHANGED"
        echo "                        - SUPERSEDED"
        echo "                        - CESSATION_OF_OPERATION"
        echo "                        - CERTIFICATE_HOLD"
        echo "                        - PRIVILEGES_WITHDRAW"
        echo "                        - AA_COMPROMISE"
        echo ""
}

# Identifiant d'un certificat
get-id() {
        openssl x509 -noout -serial -in $CertificateRoorDirectory/$1/fullchain.pem | cut -d'=' -f2 | sed 's/://g'
}

# Status d'un certificat
get-status() {
        curl -X GET -k -s \
                --cert-type P12 \
                --cert "$ScriptAuthentication" \
                --header "accept: application/json" \
                "https://${AuthorityAddress}/ejbca/ejbca-rest-api/v1/certificate/CN%3D${1}%2CO%3D${2}%2CC%3DFR/${3}/revocationstatus" \
                | jq .
}

# Révocation d'un certificat
revoke-certificate() {
        curl -X PUT -k -s \
                --cert-type P12 \
                --cert "$ScriptAuthentication" \
                --header "accept: application/json" \
                "https://${AuthorityAddress}/ejbca/ejbca-rest-api/v1/certificate/CN%3D${1}%2CO%3D${2}%2CC%3DFR/${3}/revoke?reason=${4}" \
                | jq .
}

# Création du certificat
create-certificate() {
        # Création du JSON pour la requête API
        Json=$(jq -n \
                --arg ApiCsr "$1" \
                --arg ApiCp "$2" \
                --arg ApiEep "$3" \
                --arg ApiCa "$4" \
                --arg ApiUser "$5" \
                --arg ApiPwd "$6" \
                "$7")

        # Envoi de la requête
        curl -X POST -s -k \
                --cert-type P12 \
                --cert "$ScriptAuthentication" \
                --header 'Content-Type: application/json' \
                --data "$Json" \
                "https://${AuthorityAddress}/ejbca/ejbca-rest-api/v1/certificate/pkcs10enroll" \
                | jq .
}

#===========#
# Arguments #
#===========#

# Initialisation
action="null"
fqdn="null"

case $1 in
        "help")
                display-help
                exit 0
                ;;
        "add"|"new"|"create"|"revoke"|"delete"|"status"|"renew")
                action="$1"
                fqdn="$2"
                reason="$3"
                ;;
        *)
                echo "Erreur : Argument $1 inconnu"
                echo ""
                display-help
                exit 2
                ;;
esac

# Vérification
if [ "$action" == "null" ]; then
        # Aucune action, affichage de l'aide
        display-help
        exit 1
else
        # Vérification du fqdn
        if [ "$fqdn" == "null" ]; then
                echo "$(date '+%F %X %Z') - WARN - un nom de domaine est obligatoire." | tee -a $ManageLog
                echo ""
                display-help
                exit 1
        elif [[ $fqdn =~ ^([a-z0-9]+([a-z0-9]|\-)*\.)+[a-z]{2,} ]]; then
                true
        else
                echo "$(date '+%F %X %Z') - CRIT - le domaine $fqdn n'est pas valide." | tee -a $ManageLog
                exit 2
        fi
fi

# Log
echo "$(date '+%F %X %Z') - INFO - Démarrage (Action: $action | Domaine: $fqdn)" | tee -a $ManageLog

#============#
# Traitement #
#============#

if [ "$action" == "add" -o "$action" == "create" -o "$action" == "new" -o "$action" == "renew" ]; then

        # Vérification de l'état actuel
        if [ "$action" == "renew" ]; then
                # Renouvellement demandé
                if [ ! -d $CertificateRoorDirectory/$fqdn ]; then
                        # Pas de certificat existant, création du dossier et de la clé privée
                        echo "$(date '+%F %X %Z') - WARN - Certificat inexistant. Création du certificat au lieu du renouvellement." | tee -a $ManageLog
                        mkdir -p $CertificateRoorDirectory/$fqdn
                        echo "$(date '+%F %X %Z') - INFO - Création du dossier $CertificateRoorDirectory/$fqdn" | tee -a $ManageLog
                        openssl ecparam -genkey -name prime256v1 -out $CertificateRoorDirectory/$fqdn/private.key
                        echo "$(date '+%F %X %Z') - INFO - Création de la clé privée $CertificateRoorDirectory/$fqdn/private.key" | tee -a $ManageLog
                elif [ ! -f $CertificateRoorDirectory/$fqdn/private.key ]; then
                        # Pas de clé privée à utiliser
                        echo "$(date '+%F %X %Z') - WARN - Clé privée absente. Création d'une nouvelle clé." | tee -a $ManageLog
                        openssl ecparam -genkey -name prime256v1 -out $CertificateRoorDirectory/$fqdn/private.key
                else
                        # Vérification de la clé privée
                        CertificateChecksum=$(openssl x509 -noout -pubkey -in $CertificateRoorDirectory/$fqdn/fullchain.pem 2>&1 | sha256sum | awk '{print $1}')
                        PrivateKeyChecksum=$(openssl pkey -pubout -in $CertificateRoorDirectory/$fqdn/private.key 2>&1 | sha256sum | awk '{print $1}')
                        if [ "$CertificateChecksum" == "$PrivateKeyChecksum" ]; then
                                # Clé valide
                                echo "$(date '+%F %X %Z') - INFO - Clé privée valide." | tee -a $ManageLog
                        else
                                # Clé invalide
                                echo "$(date '+%F %X %Z') - WARN - Clé privée invalide. Création d'une nouvelle clé." | tee -a $ManageLog
                                openssl ecparam -genkey -name prime256v1 -out $CertificateRoorDirectory/$fqdn/private.key
                        fi
                fi
        else
                # Création demandée
                if [ -f $CertificateRoorDirectory/$fqdn/fullchain.pem ]; then
                        # Certificat déjà existant
                        echo "$(date '+%F %X %Z') - WARN - Un certificat pour $fqdn existe déjà." | tee -a $ManageLog
                        echo "$(date '+%F %X %Z') - INFO - Vous pouvez le renouveller à la place sinon il faudra le révoquer au préalable." | tee -a $ManageLog
                        echo "$(date '+%F %X %Z') - INFO - Si vous êtes sûr que ce certificat est déjà révoqué, il est nécessaire de supprimer le dossier $CertificateRoorDirectory/$fqdn." | tee -a $ManageLog
                        exit 1
                elif [ ! -d $CertificateRoorDirectory/$fqdn ]; then
                        # Création du répertoire pour le certificat
                        mkdir -p $CertificateRoorDirectory/$fqdn
                        echo "$(date '+%F %X %Z') - INFO - Création du dossier $CertificateRoorDirectory/$fqdn" | tee -a $ManageLog

                        # Création de la clé privée
                        openssl ecparam -genkey -name prime256v1 -out $CertificateRoorDirectory/$fqdn/private.key
                        echo "$(date '+%F %X %Z') - INFO - Création de la clé privée $CertificateRoorDirectory/$fqdn/private.key" | tee -a $ManageLog
                else
                        # Création de la clé privée
                        openssl ecparam -genkey -name prime256v1 -out $CertificateRoorDirectory/$fqdn/private.key
                        echo "$(date '+%F %X %Z') - INFO - Création de la clé privée $CertificateRoorDirectory/$fqdn/private.key" | tee -a $ManageLog
                fi
        fi

        # Création de la configuration pour openssl
        if [ -e $OpensslConfig  ]; then
                rm -f $OpensslConfig
        fi

        echo "[ req ]" > $OpensslConfig
        echo "default_md = sha256" >> $OpensslConfig
        echo "prompt = no" >> $OpensslConfig
        echo "distinguished_name = dn" >> $OpensslConfig
        echo "" >> $OpensslConfig
        echo "[ dn ]" >> $OpensslConfig
        echo "CN = $fqdn" >> $OpensslConfig
        echo "O = $Organization" >> $OpensslConfig
        echo "C = $Country" >> $OpensslConfig

        # Création de la requête
        CsrContent=$(openssl req -new -key $CertificateRoorDirectory/$fqdn/private.key -config $OpensslConfig)

        # Envoi de la requête
        CreateRequest=$(create-certificate "$CsrContent" "$CertificateProfileName" "$EndEntityProfileName" "$CertificationAuthorityName" "$EnrollmentUser" "$EnrollmentPassword" "$ApiTemplate")

        # Extraction du certificat
        echo $CreateRequest | jq -r .certificate > $CertificateRoorDirectory/$fqdn/cert.b64

        # Conversion du DER en binaire
        openssl base64 -d -A -in $CertificateRoorDirectory/$fqdn/cert.b64 -out $CertificateRoorDirectory/$fqdn/cert.bin

        # Conversion du binaire en PEM
        openssl x509 -inform der -in $CertificateRoorDirectory/$fqdn/cert.bin -out $CertificateRoorDirectory/$fqdn/fullchain.pem

        # Ajout des certificats racines et intermédiaires
        cat $SubCertificate >> $CertificateRoorDirectory/$fqdn/fullchain.pem
        cat $RootCertificate >> $CertificateRoorDirectory/$fqdn/fullchain.pem

        # Log
        echo "$(date '+%F %X %Z') - INFO - Création du certificat $CertificateRoorDirectory/$fqdn/fullchain.pem" | tee -a $ManageLog
        exit 0

elif [ "$action" == "status" ]; then

        # Recherche du n° de série du dernier certificat
        CertificateSerialNumber=$(get-id $fqdn)

        # Demande du status
        CertificateStatusRequest=$(get-status $CertificationAuthorityName $Organization $CertificateSerialNumber)

        # Extraction des données
        CertificateExtractedStatus=$(echo $CertificateStatusRequest | jq .revoked)
        if [ "$CertificateExtractedStatus" == "false" ]; then
                CertificateStatus="actif"
        else
                CertificateStatus="révoqué"
        fi
        CertificateDetails=$(echo $CertificateStatusRequest | jq .revocation_reason)

        # Log
        echo "$(date '+%F %X %Z') - INFO - Certificat $fqdn (S/N: $CertificateSerialNumber) en état : $CertificateStatus (Détails: $CertificateDetails)" | tee -a $ManageLog
        exit 0

elif [ "$action" == "delete" -o "$action" == "revoke" ]; then

        # Validation de la raison de la révocation
        if [ -z "$reason" ]; then
                echo "$(date '+%F %X %Z') - INFO - Aucune raison de révocation indiqué, utilisation de la raison par défaut ($RevokeReason)." | tee -a $ManageLog
        else
                case ${reason^^} in
                        "KEY_COMPROMISE"|"CA_COMPROMISE"|"AFFILIATION_CHANGED"|"SUPERSEDED"|"CESSATION_OF_OPERATION"|"CERTIFICATE_HOLD"|"PRIVILEGES_WITHDRAW"|"AA_COMPROMISE")
                                RevokeReason=${reason^^}
                                echo "$(date '+%F %X %Z') - INFO - Raison de la révocation : $RevokeReason" | tee -a $ManageLog
                                ;;
                        *)
                                echo "$(date '+%F %X %Z') - WARN - Raison de révocation indiquée ($reason) indisponible, utilisation de la raison par défaut ($RevokeReason)." | tee -a $ManageLog
                                ;;
                esac
        fi

        # Recherche du n° de série du dernier certificat
        CertificateSerialNumber=$(get-id $fqdn)

        # Demande du status
        CertificateStatusRequest=$(get-status $CertificationAuthorityName $Organization $CertificateSerialNumber)

        # Extraction des données
        CertificateExtractedStatus=$(echo $CertificateStatusRequest | jq .revoked)

        # Vérification
        if [ "$CertificateExtractedStatus" == "false" ]; then

                # Révocation
                RevokeRequest=$(revoke-certificate $CertificationAuthorityName $Organization $CertificateSerialNumber $RevokeReason)

                # Extraction des données
                RevokeStatus=$(echo $RevokeRequest | jq .revoked)
                RevokeMessage=$(echo $RevokeRequest | jq .message)

                # Log
                if [ "$RevokeStatus" == "true" ]; then
                        /usr/bin/rm -rf $CertificateRoorDirectory/$fqdn
                        echo "$(date '+%F %X %Z') - INFO - Certificat $fqdn révoqué et supprimé (API Message: $RevokeMessage)" | tee -a $ManageLog
                        exit 0
                else
                        echo "$(date '+%F %X %Z') - CRIT - Echec de la révocation pour $fqdn (API Message: $RevokeMessage)" | tee -a $ManageLog
                        echo "$(date '+%F %X %Z') - INFO - Fichiers conservés dans $CertificateRoorDirectory/$fqdn"
                        exit 2
                fi
        else
                # Log
                echo "$(date '+%F %X %Z') - WARN - Certificat $fqdn déjà révoqué)" | tee -a $ManageLog
                exit 1
        fi
else
        # Action inconnue
        echo "$(date '+%F %X %Z') - UNKN - L'action $action n'est pas prévue" | tee -a $ManageLog
        exit 3
fi

  • Il suffit de modifier les variables :
    • WorkingDirectory : Le dossier dans lesquels sont stockés les certificats créés, le certificat du script, etc…
    • AuthorityAddress : L'adresse de votre autorité de certification.
    • CertificateProfileName : Le nom de votre profil de certificat.
    • EndEntityProfileName : Le nom de votre profil de serveur.
    • CertificationAuthorityName : Le nom de votre autorité de certification.
    • Organization : Le nom de votre organisation.
    • Country : Votre pays.
  • On place le certificat du script dans $WorkingDirectory et les certificats racine et intermédiaire au format PEM dans $WorkingDirectory/issued/.
  • Il ne reste plus qu'à lancer le script.
Entrer votre commentaire. La syntaxe wiki est autorisée:
 
  • ejbca_tls.1711030829.txt.gz
  • Dernière modification : 2024/03/21 15:20
  • de nekan