Création — Nicolas 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
CA Functions, on va dans Certificate Profiles et on clone le profil par défaut SERVER :
Create from template :
Edit :
ECDSAP-256 / prime256v1 / secp256r1Inherit from Issuing CA1yUse… puis dans la section suivante, on coche les cases :
X.509v3 extensions, on décoche :
X.509v3 extensions - Usages, on coche :
X.509v3 extensions - Names, :
X.509v3 extensions - Validation data, on coche :
Other Data :Save
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 :


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 :Required uniquementRequired uniquementRequired et Modifiable
Other Subject Attributes, pour l'attribut Subject Alternative Name, on ajoutera DNS Name et on cochera Use entity CN field :
Main Certificate Data, on choisira :User GeneratedUser Generated et PEM fileSave en bas de la page :
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>
openssl ecparam -genkey -name prime256v1 -out <fqdn>.key
openssl req -new -key <fqdn>.key -config /tmp/openssl.conf
RA Web (tout en bas des menus), soit directement via l'adresse https://<ejbca-address>/ejbca/ra.Make New Request :
Provided by userUpload CSRUpload CSR
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) :
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//[^[:alnum:]]/}%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//[^[:alnum:]]/}%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
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.EnrollmentUser : Un nom de compte à associer aux certificats créés. Si il n'existe pas, il sera créé automatiquement.EnrollmentPassword : Le mot de passe du compte précédent.Organization : Le nom de votre organisation.Country : Votre pays.$WorkingDirectory et les certificats racine et intermédiaire au format PEM dans $WorkingDirectory/issued/.