« Security/sevices/SSLCertificate/Apache » : différence entre les versions
(Page créée avec « == Préparation == Création du répertoire des certificats <syntaxhighlight lang="bash"> mkdir /etc/pki/httpd && cd /etc/pki/httpd </syntaxhighlight> === Création du fi... ») |
|||
| (78 versions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 1 : | Ligne 1 : | ||
== Introduction == | |||
Cette page explique les opérations nécessaires pour la configuration d'un serveur Apache v2 utilisant des certificats utilisateurs. Ces certificats pourront être révoqués facilement. | |||
== Pré-requis == | |||
*Le module ssl doit être activé dans la configuration d'Apache et la configuration ssl est incluse : | |||
:* Le module est chargé :<pre>LoadModule ssl_module modules/mod_ssl.so</pre> | |||
:* La configuration ssl est incluse :<pre>Include conf.d/ssl.conf</pre> | |||
* Le firewall est ouvert pour une connection '''https''' sur le port '''443''' en '''TCP'''<pre>iptables -I INPUT -p tcp --dport 443 -j ACCEPT</pre><pre>iptables -I OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT</pre> | |||
== Préparation == | == Préparation == | ||
| Ligne 12 : | Ligne 22 : | ||
<pre> | <pre> | ||
HOME = | HOME = /etc/pki/httpd | ||
RANDFILE = .rand | RANDFILE = /etc/pki/httpd/.rand | ||
[ca] | [ca] | ||
| Ligne 19 : | Ligne 29 : | ||
[ca_default] | [ca_default] | ||
dir = | dir = /etc/pki/httpd | ||
certs = $dir/certs | certs = $dir/certs | ||
crl_dir = $dir/crl | crl_dir = $dir/crl | ||
database = $dir/index.txt | database = $dir/index.txt | ||
new_certs_dir = $dir/newcerts | new_certs_dir = $dir/newcerts | ||
certificate = $dir/ | certificate = $dir/httpd_ca.crt | ||
private_key = $dir/private/ | private_key = $dir/private/httpd_ca.key | ||
serial = $dir/serial | serial = $dir/serial | ||
crl = $dir/crl | crl = $dir/ca.crl | ||
crlnumber = $dir/crlnumber | |||
crl_extensions = crl_ext | |||
x509_extensions = usr_cert | x509_extensions = usr_cert | ||
name_opt = ca_default | name_opt = ca_default | ||
cert_opt = ca_default | cert_opt = ca_default | ||
default_days = | default_days = 365 | ||
default_crl_days = 30 | default_crl_days = 30 | ||
default_md = md5 | default_md = md5 | ||
| Ligne 50 : | Ligne 62 : | ||
distinguished_name = req_distinguished_name | distinguished_name = req_distinguished_name | ||
attributes = req_attributes | attributes = req_attributes | ||
x509_extensions = v3_ca | x509_extensions = v3_ca | ||
string_mask = MASK:0x2002 | string_mask = MASK:0x2002 | ||
| Ligne 59 : | Ligne 71 : | ||
countryName_max = 2 | countryName_max = 2 | ||
stateOrProvinceName = State or Province Name (full name) | stateOrProvinceName = State or Province Name (full name) | ||
stateOrProvinceName_default = | stateOrProvinceName_default = Languedoc-Roussillon | ||
localityName = Locality Name (eg, city) | localityName = Locality Name (eg, city) | ||
localityName_default = | localityName_default = Beaucaire | ||
0.organizationName = Organization Name (eg, company) | 0.organizationName = Organization Name (eg, company) | ||
0.organizationName_default = | 0.organizationName_default = Home | ||
organizationalUnitName = Organizational Unit Name (eg, section) | organizationalUnitName = Organizational Unit Name (eg, section) | ||
commonName = Common Name (eg, your name or your server\'s hostname) | commonName = Common Name (eg, your name or your server\'s hostname) | ||
| Ligne 86 : | Ligne 98 : | ||
authorityKeyIdentifier = keyid:always,issuer:always | authorityKeyIdentifier = keyid:always,issuer:always | ||
basicConstraints = CA:true | basicConstraints = CA:true | ||
[crl_ext] | |||
authorityKeyIdentifier=keyid:always,issuer:always | |||
[OCSP] | |||
basicConstraints = CA:FALSE | |||
keyUsage = digitalSignature | |||
extendedKeyUsage = OCSPSigning | |||
issuerAltName = issuer:copy | |||
subjectKeyIdentifier = hash | |||
authorityKeyIdentifier = keyid:always,issuer:always | |||
authorityInfoAccess = OCSP;URI:http://didier.domicile.org/ | |||
[OCSP_SERVER] | |||
nsComment = "OpenSSL Generated Server Certificate" | |||
subjectKeyIdentifier = hash | |||
authorityKeyIdentifier = keyid,issuer:always | |||
issuerAltName = issuer:copy | |||
basicConstraints = critical,CA:FALSE | |||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment | |||
nsCertType = server | |||
extendedKeyUsage = serverAuth | |||
authorityInfoAccess = OCSP;URI:http://didier.domicile.org/ | |||
[OCSP_CLIENT] | |||
nsComment = "OpenSSL Generated Client Certificate" | |||
subjectKeyIdentifier = hash | |||
authorityKeyIdentifier = keyid,issuer:always | |||
issuerAltName = issuer:copy | |||
basicConstraints = critical,CA:FALSE | |||
keyUsage = digitalSignature, nonRepudiation | |||
nsCertType = client | |||
extendedKeyUsage = clientAuth | |||
authorityInfoAccess = OCSP;URI:http://didier.domicile.org/ | |||
</pre> | </pre> | ||
{{Admon/note|Modification du fichier ssl.cnf|Bien que cela ne soit pas obligatoire, il est recommandé d'éditer les champs suffixés par '''_default''' dans la section '''[req_distinguished_name]''', afin de coller aux informations du serveur en cours d'installation<br>Cela vous permettra d'accepter directement les valeurs proposées par défaut à la génération des certificats.<br>Les autres sections n'ont pas besoin d'être éditées.}} | {{Admon/note|Modification du fichier ssl.cnf|Bien que cela ne soit pas obligatoire, il est recommandé d'éditer les champs suffixés par '''_default''' dans la section '''[req_distinguished_name]''', afin de coller aux informations du serveur en cours d'installation<br>Cela vous permettra d'accepter directement les valeurs proposées par défaut à la génération des certificats.<br>Les autres sections n'ont pas besoin d'être éditées.}} | ||
=== Création des scripts de | === Création des scripts de gestion de certificats === | ||
Par simplicité, on va se servir de ces scripts pour générer les certificats des utilisateurs. | Par simplicité, on va se servir de ces scripts pour générer les certificats des utilisateurs. | ||
Script de génération des certificats | ==== Script de génération des certificats ==== | ||
Fichier <path>/etc/pki/httpd/generate-certificate.sh</path> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
#!/bin/bash | #!/bin/bash | ||
# user is equal to parameter one or the first argument when you actually run the script | # user is equal to parameter one or the first argument when you actually run the script | ||
| Ligne 104 : | Ligne 150 : | ||
if [[ "$user" == "" ]] | if [[ "$user" == "" ]] | ||
then | then | ||
echo "Usage: $(basename $0) USER" | |||
exit | |||
fi | fi | ||
| Ligne 116 : | Ligne 162 : | ||
openssl req -config ssl2.cnf -new -nodes -out certs/${user}.csr -key certs/${user}.key | openssl req -config ssl2.cnf -new -nodes -out certs/${user}.csr -key certs/${user}.key | ||
openssl ca -config ssl2.cnf | openssl ca -config ssl2.cnf \ | ||
-out certs/${user}.crt -outdir certs -infiles certs/${user}.csr | |||
cat certs/${user}.crt certs/${user}.key > ${user}.pem | cat certs/${user}.crt certs/${user}.key > pem/${user}.pem | ||
mv ssl2.cnf confs/${user}-ssl.cnf | mv ssl2.cnf confs/${user}-ssl.cnf | ||
| Ligne 125 : | Ligne 171 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Script de génération des certificats pour les navigateurs Web | ==== Script de génération des certificats pour les navigateurs Web ==== | ||
Fichier <path>/etc/pki/httpd/generate-web-certificate.sh</path> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
#!/bin/bash | #!/bin/bash | ||
# | # user is equal to parameter one or the first argument when you actually run the script | ||
user=$1 | |||
if [[ "$user" == "" ]] | |||
then | |||
echo "Usage: $(basename $0) USER" | |||
exit | |||
fi | |||
echo "Generate web certificates for \"${user}\"" | |||
openssl pkcs12 -export -inkey certs/${user}.key -in certs/${user}.crt \ | |||
-out certs/${user}_browser_cert.p12 | |||
</syntaxhighlight> | |||
==== Script de revocation de certificats ==== | |||
Fichier <path>/etc/pki/httpd/revoke-certificate.sh</path> | |||
<syntaxhighlight lang="bash"> | |||
#!/bin/bash | |||
# user is equal to parameter one or the first argument when you actually run the script | # user is equal to parameter one or the first argument when you actually run the script | ||
user=$1 | user=$1 | ||
if [[ "$user" == "" ]] | |||
then | |||
echo "Usage: $(basename $0) USER" | |||
exit | |||
fi | |||
echo "Revoke certificates for \"${user}\"" | |||
openssl ca -revoke certs/${user}.crt -config ssl.cnf | |||
# Save old certificate | |||
x=1 | |||
while [ -f "certs/${user}.revoked.$x.crt" ] | |||
do | |||
x=$(( $x + 1 )) | |||
done | |||
mv certs/${user}.crt certs/${user}.revoked.$x.crt | |||
# Regen CRL list | |||
openssl ca -gencrl -config ssl.cnf -out ca.crl | |||
</syntaxhighlight> | |||
==== Script de renouvellement de certificats ==== | |||
Fichier <path>/etc/pki/httpd/renew-certificate.sh</path> | |||
Il faut révoquer l'ancien certificat et signer la demande de certificat créée initialement, basée sur sa clef privée. | |||
L'ancien certificat se retrouve en cherchant dans le fichier index.txt le nom qualifié (DN) qui correspond à la requête. La procédure de révocation s'effectue ensuite avec le numéro de série <xx> et le fichier de certificat cert/<xx>.pem. | |||
La signature de la nouvelle requête peut être manuelle pour s'assurer que les dates de validité du certificat seront correctes. | |||
<syntaxhighlight lang="bash"> | |||
#!/bin/bash | |||
# user is equal to parameter one or the first argument when you actually run the script | |||
user=$1 | |||
if [[ "$user" == "" ]] | if [[ "$user" == "" ]] | ||
then | then | ||
echo "Usage: $(basename $0) USER" | |||
exit | |||
fi | fi | ||
echo "Renew certificates for \"${user}\"" | |||
./revoke-certificate.sh ${user} | |||
openssl ca -config confs/${user}-ssl.cnf \ | |||
-out certs/${user}.crt -outdir certs -infiles certs/${user}.csr | |||
cat certs/${user}.crt certs/${user}.key > ${user}.pem | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== Génération de l'autorité de certification == | == Mise en place == | ||
=== Génération de l'autorité de certification === | |||
Toujours depuis le répertoire /etc/pki/httpd, on initialise notre générateur et on génère l’autorité de certification. Celle-ci se compose d'une paire clé/certificat qui servira à signer tous les autres certificats. | Toujours depuis le répertoire /etc/pki/httpd, on initialise notre générateur et on génère l’autorité de certification. Celle-ci se compose d'une paire clé/certificat qui servira à signer tous les autres certificats. | ||
| Ligne 153 : | Ligne 260 : | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /etc/pki/httpd/ | cd /etc/pki/httpd/ | ||
mkdir {certs,private,confs} | mkdir {certs,newcerts,private,confs,crl,pem} | ||
touch index.txt | touch index.txt | ||
echo 01 > serial | echo 01 > serial | ||
openssl genrsa -out private/ | echo 01 > crlnumber | ||
openssl req -config ssl.cnf -new -x509 -days 3650 -key private/ | openssl genrsa -out private/httpd_ca.key 2048 | ||
-out | openssl req -config ssl.cnf -new -x509 -days 3650 -key private/httpd_ca.key \ | ||
-out httpd_ca.crt -extensions v3_ca | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | === Activation de la Liste des certificats révoqués : ''Certificate Revocation Lists'' (CRL) === | ||
On active la CRL en deux temps <ref>http://www.apacheweek.com/features/crl</ref> | |||
* création d'un lien en dur ( ou copie ) du fichier crl dans le répertoire crl | |||
* création d'un lien symbolique de celui-ci en le nommant avec un hash ( donné par openssl ) | |||
<syntaxhighlight lang="bash"> | |||
cd /etc/pki/httpd/ | |||
ln ca.crl crl/ | |||
cd crl | |||
ln -s ca.crl `openssl crl -hash -noout -in ca.crl`.r0 | |||
</syntaxhighlight> | |||
=== Génération du certificat pour Apache === | |||
Il faut créer un certificat valide pour le serveur | |||
<syntaxhighlight lang="bash"> | |||
cd /etc/pki/httpd/ | |||
./generate-certificate.sh httpd | |||
</syntaxhighlight> | |||
=== Configuration d'Apache === | |||
Fichier <path>/etc/httpd/conf.d/ssl.conf</path> | |||
<syntaxhighlight lang="apache"> | |||
LoadModule ssl_module modules/mod_ssl.so | |||
Listen 443 | |||
SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog | |||
SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) | |||
SSLSessionCacheTimeout 300 | |||
SSLMutex default | |||
SSLRandomSeed startup file:/dev/urandom 256 | |||
SSLRandomSeed connect builtin | |||
SSLCryptoDevice builtin | |||
<VirtualHost _default_:443> | |||
ErrorLog logs/ssl_error_log | |||
TransferLog logs/ssl_access_log | |||
LogLevel warn | |||
SSLEngine on | |||
SSLProtocol all -SSLv2 | |||
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL | |||
SSLHonorCipherOrder on | |||
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt | |||
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key | |||
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt | |||
SSLCARevocationPath /etc/pki/httpd/crl/ | |||
SSLVerifyClient require | |||
SSLVerifyDepth 2 | |||
<Files ~ "\.(cgi|shtml|phtml|php3?)$"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Files> | |||
<Directory "/var/www/cgi-bin"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Directory> | |||
SetEnvIf User-Agent ".*MSIE.*" \ | |||
nokeepalive ssl-unclean-shutdown \ | |||
downgrade-1.0 force-response-1.0 | |||
CustomLog logs/ssl_request_log \ | |||
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" | |||
</VirtualHost> | |||
</syntaxhighlight> | |||
{{Admon/important|Redémarrage du service web|Il faut redémarrer Apache pour que les nouveaux paramètres soient pris en compte. | |||
Avec '''sysVinit''' | |||
<pre>service httpd restart</pre> | |||
Avec '''systemctl''' | |||
<pre>systemctl restart httpd.service</pre>}} | |||
== Gérer les certificats des utilisateurs == | |||
Cette partie est la seule qui peut être exécuter plusieurs fois. Elle est très amplement facilitée par les scripts de gestion édités auparavant. | |||
=== Ajout d'un utilisateur === | |||
L'autorisation d'accès d'un utilisateur est conditionné par un certificat web qui doit être importer dans le navigateur de celui-ci. Ce certificat web est une simple traduction du certificat openssl à qui il est étroitement associé. Le certificat web ne peut exister sans lui. | |||
'''Généreration des certificats''' | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /etc/pki/httpd/ | cd /etc/pki/httpd/ | ||
| Ligne 170 : | Ligne 364 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | Il ne reste plus qu'à donner '''de manière sécurisée''' le certificat web à l'utilisateur le fichier <path>/etc/pki/httpd/certs/<USER>_browser_cert.p12</path>, ainsi que le mot de passe associé. | ||
Celui-ci devra l'importer dans son navigateur : | |||
* [[Security/sevices/SSLCertificate/ImportFirefox| Importer un certificat dans Firefox]] | |||
=== Révocation d'utilisateur === | |||
On révoque son certificat et celui-ci ne peut plus accéder au site. | |||
<syntaxhighlight lang="bash"> | |||
cd /etc/pki/httpd/ | |||
./revoke-certificate.sh <USER> | |||
</syntaxhighlight> | |||
== Virtualhosts == | |||
Apache ne supporte pas le multi virtualhost de but en blanc sur une connection https, mais cela est toutefois possible sous certaines conditions : | |||
* Les certificats et les clés sont partagées par tous les virtualhosts ( y compris le default ) et ceux-ci doivent être recopiés à chaque définition de ''virtualhost''. De plus celles-ci doivent comporter le port :<pre><VirtualHost 192.168.0.100:443></pre> | |||
* Utilisation de la directive NameVirtualHost avec spécification du port :<pre>NameVirtualHost 192.168.0.100:443</pre> | |||
* Les autres définitions des virtualhosts accessibles en http doivent mentionner le port ( httpd-vhosts.conf ) :<pre><VirtualHost 192.168.0.100:80></pre> | |||
Fichier <path>/etc/httpd/conf.d/ssl.conf</path> | |||
<syntaxhighlight lang="apache"> | |||
LoadModule ssl_module modules/mod_ssl.so | |||
Listen 443 | |||
SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog | |||
SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) | |||
SSLSessionCacheTimeout 300 | |||
SSLMutex default | |||
SSLRandomSeed startup file:/dev/urandom 256 | |||
SSLRandomSeed connect builtin | |||
SSLCryptoDevice builtin | |||
<VirtualHost _default_:443> | |||
ErrorLog logs/ssl_error_log | |||
TransferLog logs/ssl_access_log | |||
LogLevel warn | |||
SSLEngine on | |||
SSLProtocol all -SSLv2 | |||
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL | |||
SSLHonorCipherOrder on | |||
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt | |||
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key | |||
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt | |||
SSLCARevocationPath /etc/pki/httpd/crl/ | |||
SSLVerifyClient require | |||
SSLVerifyDepth 2 | |||
<Files ~ "\.(cgi|shtml|phtml|php3?)$"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Files> | |||
<Directory "/var/www/cgi-bin"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Directory> | |||
SetEnvIf User-Agent ".*MSIE.*" \ | |||
nokeepalive ssl-unclean-shutdown \ | |||
downgrade-1.0 force-response-1.0 | |||
CustomLog logs/ssl_request_log \ | |||
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" | |||
</VirtualHost> | |||
NameVirtualHost 192.168.0.100:443 | |||
<VirtualHost 192.168.0.100:443> | |||
SSLEngine on | |||
SSLProtocol all -SSLv2 | |||
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL | |||
SSLHonorCipherOrder on | |||
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt | |||
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key | |||
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt | |||
SSLCARevocationPath /etc/pki/httpd/crl/ | |||
SSLVerifyClient require | |||
SSLVerifyDepth 2 | |||
ServerAdmin root@localhost | |||
DocumentRoot "/home/ssl-vhost" | |||
ServerName ssl-vhost.localdomain | |||
<Directory "/home/ssl-vhost"> | |||
AllowOverride none | |||
Allow from all | |||
Order allow,deny | |||
</Directory> | |||
LogLevel warn | |||
ErrorLog "logs/ssl-vhost-error.log" | |||
CustomLog "logs/ssl-vhost-access.log" common | |||
TransferLog logs/ssl-vhost-access.log | |||
</VirtualHost> | |||
</syntaxhighlight> | |||
== Répartition de charge == | |||
Il sera de la responsabilité de l'administrateur de recopier ( ou de mettre en place une réplication Raid sur IP, une tâche {{app|cron}} ) le répertoire <path>/etc/pki/httpd</path> sur tous les noeuds du cluster à chaque modification ( ajout, révocation, renouvellement, etc... ) | |||
Exemple d'ajout d'une grappe à un cluster ( ipvsadm ) dont l'IP WAN est 1.2.3.4: | |||
ipvsadm -A -t 1.2.3.4:443 -s lc | |||
ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.1:443 -m | |||
ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.2:443 -m | |||
ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.3:443 -m | |||
Destruction du cluster | |||
ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.1:443 -m | |||
ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.2:443 -m | |||
ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.3:443 -m | |||
ipvsadm -D -t 1.2.3.4:443 -s lc | |||
== Utilisation avec PHP == | |||
Il faut activer la passation des paramètres SSL à PHP en rajoutant l'option '''''ExportCertData'''''<ref>http://cweiske.de/tagebuch/ssl-client-certificates.htm</ref> | |||
Fichier <path>/etc/httpd/conf.d/ssl.conf</path> | |||
<syntaxhighlight lang="apache"> | |||
<Files ~ "\.(cgi|shtml|phtml|php3?)$"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Files> | |||
<Directory "/var/www/cgi-bin"> | |||
SSLOptions +StdEnvVars +ExportCertData | |||
</Directory> | |||
</syntaxhighlight> | |||
Quand un certificat d'un client est disponible, la variable globale '''''$_SERVER''''' contient beaucoup plus d'information, toutes préfixées avec '''''SSL_CLIENT_'''''. | |||
* '''SSL_CLIENT_VERIFY''' est très importante et les information du certificat '''ne doivent pas être utilisées''' si cette variable '''n'est pas égale à SUCCESS'''. Cette variable est passée à '''''NONE''''' si il n'y a pas de certificat. | |||
* '''SSL_CLIENT_M_SERIAL''' contient le numéro de série qui identifie le certificat unique associé depuis l'autorité de certification. | |||
* Toutes les variables '''SSL_CLIENT_I_*''' sont à propos de l'autorité de certification. | |||
* Toutes les variables '''SSL_CLIENT_S_*''' sont à propos de l'utilisateur. | |||
Dans le cas qui nous intéresse, on vérifie le nom en lisant la varible '''SSL_CLIENT_S_DN_CN''' et l'email associé avec '''SSL_CLIENT_S_DN_Email'''. | |||
Exemple de script PHP affichant le tableau suivant | |||
{| class="wikitable" | |||
! Clé de $_SERVER | |||
! Valeur | |||
! Commentaires | |||
|- | |||
| SSL_CLIENT_VERIFY | |||
| SUCCESS | |||
| Client utilisant un certificat valide si égal à SUCCESS | |||
|- | |||
| SSL_CLIENT_S_DN_CN | |||
| didier | |||
| Utilisateur (CN) | |||
|- | |||
| SSL_CLIENT_S_DN_Email | |||
| didier@localhost | |||
| Email | |||
|- | |||
| SSL_SERVER_S_DN_C | |||
| FR | |||
| Code pays | |||
|- | |||
| SSL_SERVER_S_DN_ST | |||
| Languedoc-Roussillon | |||
| Etat ou région | |||
|- | |||
| SSL_SERVER_S_DN_O | |||
| Home | |||
| Organisation | |||
|} | |||
<syntaxhighlight lang="html4strict"> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title>Test</title> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | |||
<style> | |||
th, td, table { | |||
border:1px black solid; | |||
border-collapse:collapse; | |||
padding: 5px; | |||
text-align:left; | |||
} | |||
th { background-color:lightgrey;} | |||
</style> | |||
</head> | |||
<body> | |||
<table> | |||
<tr> | |||
<th>Clé de $_SERVER</th> | |||
<th>Valeur</th> | |||
<th>Commentaires</th> | |||
</tr> | |||
<tr> | |||
<td>SSL_CLIENT_VERIFY</td> | |||
<td><?php echo $_SERVER['SSL_CLIENT_VERIFY'];?></td> | |||
<td>Client utilisant un certificat valide si égal à SUCCESS</td> | |||
</tr> | |||
<tr> | |||
<td>SSL_CLIENT_S_DN_CN</td> | |||
<td><?php echo $_SERVER['SSL_CLIENT_S_DN_CN'];?></td> | |||
<td>Utilisateur (CN)</td> | |||
</tr> | |||
<tr> | |||
<td>SSL_CLIENT_S_DN_Email</td> | |||
<td><?php echo $_SERVER['SSL_CLIENT_S_DN_Email'];?></td> | |||
<td>Email</td> | |||
</tr> | |||
<tr> | |||
<td>SSL_SERVER_S_DN_C</td> | |||
<td><?php echo $_SERVER['SSL_SERVER_S_DN_C'];?></td> | |||
<td>Code pays</td> | |||
</tr> | |||
<tr> | |||
<td>SSL_SERVER_S_DN_ST</td> | |||
<td><?php echo $_SERVER['SSL_SERVER_S_DN_ST'];?></td> | |||
<td>Etat ou région</td> | |||
</tr> | |||
<tr> | |||
<td>SSL_SERVER_S_DN_O</td> | |||
<td><?php echo $_SERVER['SSL_SERVER_S_DN_O'];?></td> | |||
<td>Organisation</td> | |||
</tr> | |||
</table> | |||
</body> | |||
</html> | |||
</syntaxhighlight> | |||
== All in one script == | |||
[[Scripts/manageCA| manageCA Script]] | |||
{{:Scripts/manageCA}} | |||
== Références == | |||
<references/> | |||
Dernière version du 7 janvier 2013 à 17:52
Introduction
Cette page explique les opérations nécessaires pour la configuration d'un serveur Apache v2 utilisant des certificats utilisateurs. Ces certificats pourront être révoqués facilement.
Pré-requis
- Le module ssl doit être activé dans la configuration d'Apache et la configuration ssl est incluse :
- Le module est chargé :
LoadModule ssl_module modules/mod_ssl.so
- La configuration ssl est incluse :
Include conf.d/ssl.conf
- Le module est chargé :
- Le firewall est ouvert pour une connection https sur le port 443 en TCP
iptables -I INPUT -p tcp --dport 443 -j ACCEPT
iptables -I OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
Préparation
Création du répertoire des certificats
mkdir /etc/pki/httpd && cd /etc/pki/httpd
Création du fichier de configuration de nos futurs certificats
On créer le fichier <path>/etc/pki/httpd/ssl.cnf</path>
HOME = /etc/pki/httpd RANDFILE = /etc/pki/httpd/.rand [ca] default_ca = ca_default [ca_default] dir = /etc/pki/httpd certs = $dir/certs crl_dir = $dir/crl database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/httpd_ca.crt private_key = $dir/private/httpd_ca.key serial = $dir/serial crl = $dir/ca.crl crlnumber = $dir/crlnumber crl_extensions = crl_ext x509_extensions = usr_cert name_opt = ca_default cert_opt = ca_default default_days = 365 default_crl_days = 30 default_md = md5 preserve = no policy = policy_match [policy_match] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [req] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca string_mask = MASK:0x2002 [req_distinguished_name] countryName = Country Name (2 letter code) countryName_default = FR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Languedoc-Roussillon localityName = Locality Name (eg, city) localityName_default = Beaucaire 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Home organizationalUnitName = Organizational Unit Name (eg, section) commonName = Common Name (eg, your name or your server\'s hostname) commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 [req_attributes] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [usr_cert] basicConstraints = CA:FALSE nsComment = "OpenSSL Generated Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always [v3_ca] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always basicConstraints = CA:true [crl_ext] authorityKeyIdentifier=keyid:always,issuer:always [OCSP] basicConstraints = CA:FALSE keyUsage = digitalSignature extendedKeyUsage = OCSPSigning issuerAltName = issuer:copy subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always authorityInfoAccess = OCSP;URI:http://didier.domicile.org/ [OCSP_SERVER] nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always issuerAltName = issuer:copy basicConstraints = critical,CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment nsCertType = server extendedKeyUsage = serverAuth authorityInfoAccess = OCSP;URI:http://didier.domicile.org/ [OCSP_CLIENT] nsComment = "OpenSSL Generated Client Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always issuerAltName = issuer:copy basicConstraints = critical,CA:FALSE keyUsage = digitalSignature, nonRepudiation nsCertType = client extendedKeyUsage = clientAuth authorityInfoAccess = OCSP;URI:http://didier.domicile.org/
Création des scripts de gestion de certificats
Par simplicité, on va se servir de ces scripts pour générer les certificats des utilisateurs.
Script de génération des certificats
Fichier <path>/etc/pki/httpd/generate-certificate.sh</path>
#!/bin/bash
# user is equal to parameter one or the first argument when you actually run the script
user=$1
if [[ "$user" == "" ]]
then
echo "Usage: $(basename $0) USER"
exit
fi
echo "Generate certificates for \"${user}\""
openssl genrsa -out certs/${user}.key 2048
cat ssl.cnf | sed 's/insert_hostname/'${user}'/'> ssl2.cnf
openssl req -config ssl2.cnf -new -nodes -out certs/${user}.csr -key certs/${user}.key
openssl ca -config ssl2.cnf \
-out certs/${user}.crt -outdir certs -infiles certs/${user}.csr
cat certs/${user}.crt certs/${user}.key > pem/${user}.pem
mv ssl2.cnf confs/${user}-ssl.cnf
Fichier <path>/etc/pki/httpd/generate-web-certificate.sh</path>
#!/bin/bash
# user is equal to parameter one or the first argument when you actually run the script
user=$1
if [[ "$user" == "" ]]
then
echo "Usage: $(basename $0) USER"
exit
fi
echo "Generate web certificates for \"${user}\""
openssl pkcs12 -export -inkey certs/${user}.key -in certs/${user}.crt \
-out certs/${user}_browser_cert.p12
Script de revocation de certificats
Fichier <path>/etc/pki/httpd/revoke-certificate.sh</path>
#!/bin/bash
# user is equal to parameter one or the first argument when you actually run the script
user=$1
if [[ "$user" == "" ]]
then
echo "Usage: $(basename $0) USER"
exit
fi
echo "Revoke certificates for \"${user}\""
openssl ca -revoke certs/${user}.crt -config ssl.cnf
# Save old certificate
x=1
while [ -f "certs/${user}.revoked.$x.crt" ]
do
x=$(( $x + 1 ))
done
mv certs/${user}.crt certs/${user}.revoked.$x.crt
# Regen CRL list
openssl ca -gencrl -config ssl.cnf -out ca.crl
Script de renouvellement de certificats
Fichier <path>/etc/pki/httpd/renew-certificate.sh</path>
Il faut révoquer l'ancien certificat et signer la demande de certificat créée initialement, basée sur sa clef privée.
L'ancien certificat se retrouve en cherchant dans le fichier index.txt le nom qualifié (DN) qui correspond à la requête. La procédure de révocation s'effectue ensuite avec le numéro de série <xx> et le fichier de certificat cert/<xx>.pem.
La signature de la nouvelle requête peut être manuelle pour s'assurer que les dates de validité du certificat seront correctes.
#!/bin/bash
# user is equal to parameter one or the first argument when you actually run the script
user=$1
if [[ "$user" == "" ]]
then
echo "Usage: $(basename $0) USER"
exit
fi
echo "Renew certificates for \"${user}\""
./revoke-certificate.sh ${user}
openssl ca -config confs/${user}-ssl.cnf \
-out certs/${user}.crt -outdir certs -infiles certs/${user}.csr
cat certs/${user}.crt certs/${user}.key > ${user}.pem
Mise en place
Génération de l'autorité de certification
Toujours depuis le répertoire /etc/pki/httpd, on initialise notre générateur et on génère l’autorité de certification. Celle-ci se compose d'une paire clé/certificat qui servira à signer tous les autres certificats.
Ici, le CN sera obligatoirement le nom d'hôte du serveur (FQDN), les autres renseignements ne sont pas bloquants pour la suite.
cd /etc/pki/httpd/
mkdir {certs,newcerts,private,confs,crl,pem}
touch index.txt
echo 01 > serial
echo 01 > crlnumber
openssl genrsa -out private/httpd_ca.key 2048
openssl req -config ssl.cnf -new -x509 -days 3650 -key private/httpd_ca.key \
-out httpd_ca.crt -extensions v3_ca
Activation de la Liste des certificats révoqués : Certificate Revocation Lists (CRL)
On active la CRL en deux temps [1]
- création d'un lien en dur ( ou copie ) du fichier crl dans le répertoire crl
- création d'un lien symbolique de celui-ci en le nommant avec un hash ( donné par openssl )
cd /etc/pki/httpd/
ln ca.crl crl/
cd crl
ln -s ca.crl `openssl crl -hash -noout -in ca.crl`.r0
Génération du certificat pour Apache
Il faut créer un certificat valide pour le serveur
cd /etc/pki/httpd/
./generate-certificate.sh httpd
Configuration d'Apache
Fichier <path>/etc/httpd/conf.d/ssl.conf</path>
LoadModule ssl_module modules/mod_ssl.so
Listen 443
SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog
SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout 300
SSLMutex default
SSLRandomSeed startup file:/dev/urandom 256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin
<VirtualHost _default_:443>
ErrorLog logs/ssl_error_log
TransferLog logs/ssl_access_log
LogLevel warn
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL
SSLHonorCipherOrder on
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt
SSLCARevocationPath /etc/pki/httpd/crl/
SSLVerifyClient require
SSLVerifyDepth 2
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars +ExportCertData
</Files>
<Directory "/var/www/cgi-bin">
SSLOptions +StdEnvVars +ExportCertData
</Directory>
SetEnvIf User-Agent ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
CustomLog logs/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
Gérer les certificats des utilisateurs
Cette partie est la seule qui peut être exécuter plusieurs fois. Elle est très amplement facilitée par les scripts de gestion édités auparavant.
Ajout d'un utilisateur
L'autorisation d'accès d'un utilisateur est conditionné par un certificat web qui doit être importer dans le navigateur de celui-ci. Ce certificat web est une simple traduction du certificat openssl à qui il est étroitement associé. Le certificat web ne peut exister sans lui.
Généreration des certificats
cd /etc/pki/httpd/
./generate-certificate.sh <USER>
./generate-web-certificate.sh <USER>
Il ne reste plus qu'à donner de manière sécurisée le certificat web à l'utilisateur le fichier <path>/etc/pki/httpd/certs/<USER>_browser_cert.p12</path>, ainsi que le mot de passe associé.
Celui-ci devra l'importer dans son navigateur :
Révocation d'utilisateur
On révoque son certificat et celui-ci ne peut plus accéder au site.
cd /etc/pki/httpd/
./revoke-certificate.sh <USER>
Virtualhosts
Apache ne supporte pas le multi virtualhost de but en blanc sur une connection https, mais cela est toutefois possible sous certaines conditions :
- Les certificats et les clés sont partagées par tous les virtualhosts ( y compris le default ) et ceux-ci doivent être recopiés à chaque définition de virtualhost. De plus celles-ci doivent comporter le port :
<VirtualHost 192.168.0.100:443>
- Utilisation de la directive NameVirtualHost avec spécification du port :
NameVirtualHost 192.168.0.100:443
- Les autres définitions des virtualhosts accessibles en http doivent mentionner le port ( httpd-vhosts.conf ) :
<VirtualHost 192.168.0.100:80>
Fichier <path>/etc/httpd/conf.d/ssl.conf</path>
LoadModule ssl_module modules/mod_ssl.so
Listen 443
SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog
SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout 300
SSLMutex default
SSLRandomSeed startup file:/dev/urandom 256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin
<VirtualHost _default_:443>
ErrorLog logs/ssl_error_log
TransferLog logs/ssl_access_log
LogLevel warn
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL
SSLHonorCipherOrder on
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt
SSLCARevocationPath /etc/pki/httpd/crl/
SSLVerifyClient require
SSLVerifyDepth 2
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars +ExportCertData
</Files>
<Directory "/var/www/cgi-bin">
SSLOptions +StdEnvVars +ExportCertData
</Directory>
SetEnvIf User-Agent ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
CustomLog logs/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
NameVirtualHost 192.168.0.100:443
<VirtualHost 192.168.0.100:443>
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite RC4-SHA:AES128-SHA:ALL:!ADH:!EXP:!LOW:!MD5:!SSLV2:!NULL
SSLHonorCipherOrder on
SSLCertificateFile /etc/pki/httpd/certs/httpd.crt
SSLCertificateKeyFile /etc/pki/httpd/certs/httpd.key
SSLCACertificateFile /etc/pki/httpd/httpd_ca.crt
SSLCARevocationPath /etc/pki/httpd/crl/
SSLVerifyClient require
SSLVerifyDepth 2
ServerAdmin root@localhost
DocumentRoot "/home/ssl-vhost"
ServerName ssl-vhost.localdomain
<Directory "/home/ssl-vhost">
AllowOverride none
Allow from all
Order allow,deny
</Directory>
LogLevel warn
ErrorLog "logs/ssl-vhost-error.log"
CustomLog "logs/ssl-vhost-access.log" common
TransferLog logs/ssl-vhost-access.log
</VirtualHost>
Répartition de charge
Il sera de la responsabilité de l'administrateur de recopier ( ou de mettre en place une réplication Raid sur IP, une tâche cron ) le répertoire <path>/etc/pki/httpd</path> sur tous les noeuds du cluster à chaque modification ( ajout, révocation, renouvellement, etc... )
Exemple d'ajout d'une grappe à un cluster ( ipvsadm ) dont l'IP WAN est 1.2.3.4:
ipvsadm -A -t 1.2.3.4:443 -s lc ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.1:443 -m ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.2:443 -m ipvsadm -a -t 1.2.3.4:443 -r 192.168.0.3:443 -m
Destruction du cluster
ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.1:443 -m ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.2:443 -m ipvsadm -d -t 1.2.3.4:443 -r 192.168.0.3:443 -m ipvsadm -D -t 1.2.3.4:443 -s lc
Utilisation avec PHP
Il faut activer la passation des paramètres SSL à PHP en rajoutant l'option ExportCertData[2]
Fichier <path>/etc/httpd/conf.d/ssl.conf</path>
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars +ExportCertData
</Files>
<Directory "/var/www/cgi-bin">
SSLOptions +StdEnvVars +ExportCertData
</Directory>
Quand un certificat d'un client est disponible, la variable globale $_SERVER contient beaucoup plus d'information, toutes préfixées avec SSL_CLIENT_.
- SSL_CLIENT_VERIFY est très importante et les information du certificat ne doivent pas être utilisées si cette variable n'est pas égale à SUCCESS. Cette variable est passée à NONE si il n'y a pas de certificat.
- SSL_CLIENT_M_SERIAL contient le numéro de série qui identifie le certificat unique associé depuis l'autorité de certification.
- Toutes les variables SSL_CLIENT_I_* sont à propos de l'autorité de certification.
- Toutes les variables SSL_CLIENT_S_* sont à propos de l'utilisateur.
Dans le cas qui nous intéresse, on vérifie le nom en lisant la varible SSL_CLIENT_S_DN_CN et l'email associé avec SSL_CLIENT_S_DN_Email.
Exemple de script PHP affichant le tableau suivant
| Clé de $_SERVER | Valeur | Commentaires |
|---|---|---|
| SSL_CLIENT_VERIFY | SUCCESS | Client utilisant un certificat valide si égal à SUCCESS |
| SSL_CLIENT_S_DN_CN | didier | Utilisateur (CN) |
| SSL_CLIENT_S_DN_Email | didier@localhost | |
| SSL_SERVER_S_DN_C | FR | Code pays |
| SSL_SERVER_S_DN_ST | Languedoc-Roussillon | Etat ou région |
| SSL_SERVER_S_DN_O | Home | Organisation |
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
th, td, table {
border:1px black solid;
border-collapse:collapse;
padding: 5px;
text-align:left;
}
th { background-color:lightgrey;}
</style>
</head>
<body>
<table>
<tr>
<th>Clé de $_SERVER</th>
<th>Valeur</th>
<th>Commentaires</th>
</tr>
<tr>
<td>SSL_CLIENT_VERIFY</td>
<td><?php echo $_SERVER['SSL_CLIENT_VERIFY'];?></td>
<td>Client utilisant un certificat valide si égal à SUCCESS</td>
</tr>
<tr>
<td>SSL_CLIENT_S_DN_CN</td>
<td><?php echo $_SERVER['SSL_CLIENT_S_DN_CN'];?></td>
<td>Utilisateur (CN)</td>
</tr>
<tr>
<td>SSL_CLIENT_S_DN_Email</td>
<td><?php echo $_SERVER['SSL_CLIENT_S_DN_Email'];?></td>
<td>Email</td>
</tr>
<tr>
<td>SSL_SERVER_S_DN_C</td>
<td><?php echo $_SERVER['SSL_SERVER_S_DN_C'];?></td>
<td>Code pays</td>
</tr>
<tr>
<td>SSL_SERVER_S_DN_ST</td>
<td><?php echo $_SERVER['SSL_SERVER_S_DN_ST'];?></td>
<td>Etat ou région</td>
</tr>
<tr>
<td>SSL_SERVER_S_DN_O</td>
<td><?php echo $_SERVER['SSL_SERVER_S_DN_O'];?></td>
<td>Organisation</td>
</tr>
</table>
</body>
</html>
All in one script
Version potentiellement plus à jour sur https://github.com/didier13150/manageCA
#!/bin/bash
################################################################################
# Author: Didier Fabert
# Rev 0.4
################################################################################
COUNTRYNAME="FR"
STATE="Languedoc-Roussillon"
CITY="Beaucaire"
COMPANY="Home"
OCSP_URL="http://didier.domicile.org/"
PKI_PATH="/etc/pki"
NAME=""
CFG_FILE="/etc/manageCA.conf"
function printUsage() {
echo "$(basename $0)"
}
function printHelp() {
echo
echo "Options:"
echo -e "\t-c <NAME> Config File [${CFG_FILE}]"
echo -e "\t-p <PATH> Path for PKI [/etc/pki]"
echo -e "\t-n <NAME> CA Name [None]"
}
function printMenu() {
clear
echo "====================================================================="
echo " ${COMPANY} Certificate Management System"
echo "====================================================================="
echo
echo " 1) Create a Client/Server/OCSP certificate"
echo " 2) Create a Client Certificate for Web (PKCS#12)"
echo " 3) Renew a Certificate"
echo " 4) Revoke a Certificate"
echo " 5) List Certificates"
echo
echo " i) Initialize Root Certificate Authority (CA)"
echo " d) Delete CA"
echo " o) Show/Modify/Save CA Options"
echo " q) Quit"
echo
echo " Options available before init"
echo " p) Change PKI default path [${PKI_PATH}]"
echo " n) Change CA name [${NAME}]"
echo
}
function printSubMenu {
clear
echo "-----------------------------------------------------------------"
echo ${1}
echo "-----------------------------------------------------------------"
echo
}
function manageOptions() {
local buffer
while true;
do
printSubMenu "CA Options"
echo " 1) Country Name [${COUNTRYNAME}]"
echo " 2) State Name [${STATE}]"
echo " 3) City Name [${CITY}]"
echo " 4) Company Name [${COMPANY}]"
echo " 5) OCSP URL [${OCSP_URL}]"
echo
echo " s) Save Options"
echo " p) Previous menu"
echo
read -p " ==> Make your choice [none]: " -n 1 CHOICE
echo
echo
case ${CHOICE} in
1)
read -p " ==> New Country Name [${COUNTRYNAME}]: " buffer
[ ! -z ${buffer} ] && COUNTRYNAME=${buffer}
;;
2)
read -p " ==> New State Name [${STATE}]: " buffer
[ ! -z ${buffer} ] && STATE=${buffer}
;;
3)
read -p " ==> New City Name [${CITY}]: " buffer
[ ! -z ${buffer} ] && CITY=${buffer}
;;
4)
read -p " ==> New Company Name [${COMPANY}]: " buffer
[ ! -z ${buffer} ] && COMPANY=${buffer}
;;
5)
read -p " ==> New OCSP URL [${OCSP_URL}]: " buffer
[ ! -z ${buffer} ] && OCSP_URL=${buffer}
;;
s)
saveCfg
;;
p)
return
;;
esac
done
}
function saveCfg() {
local buffer=$1
if [ -z ${buffer} ]
then
echo
read -p " ==> File to save [${CFG_FILE}]: " buffer
[ ! -z ${buffer} ] && CFG_FILE=${buffer}
fi
touch ${CFG_FILE}
if [ -w ${CFG_FILE} ]
then
echo "## Configuration file for manageCA.sh script" > ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# Country Code for certificate" >> ${CFG_FILE}
echo "COUNTRYNAME=\"${COUNTRYNAME}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# State Name for certificate" >> ${CFG_FILE}
echo "STATE=\"${STATE}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# City Name for certificate" >> ${CFG_FILE}
echo "CITY=\"${CITY}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# Company Name for certificate" >> ${CFG_FILE}
echo "COMPANY=\"${COMPANY}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# OCSP URL for certificate" >> ${CFG_FILE}
echo "OCSP_URL=\"${OCSP_URL}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
echo "# PKI Default Path" >> ${CFG_FILE}
echo "PKI_PATH=\"${PKI_PATH}\"" >> ${CFG_FILE}
echo >> ${CFG_FILE}
else
echo
echo "Error: ${CFG_FILE} is not writable for you"
read -p "Press [enter] to continue" DUMMY
fi
}
function addUser() {
local user
local email
local usage="client"
local buffer
local userdata
printSubMenu "Create a client certificate"
read -p " ==> User name [NONE]: " user
if [[ "${user}" == "" ]]
then
return
fi
echo
read -p " ==> User email [NONE]: " email
if [[ "${email}" == "" ]]
then
return
fi
echo
read -p " ==> Select Usage Key (server, client or ocsp) [client]: " buffer
[ -z ${buffer} ] || usage=${buffer}
echo
if [[ "${usage}" == "ocsp" ]]
then
extension="-extensions OCSP"
else
read -p "Add OCSP Extension to Certificate ? [Y/n]: " buffer
[ -z ${buffer} ] && buffer="y"
if [[ "${buffer}" == "y" ]]
then
if [[ "${usage}" == "server" ]]
then
extension="-extensions OCSP_SERVER"
else
extension="-extensions OCSP_CLIENT"
fi
else
extension=""
fi
fi
openssl genrsa -out ${PKI_PATH}/${NAME}/certs/${user}.key 2048 \
1>/dev/null 2>&1
if [[ "${usage}" == "server" ]]
then
userdata="organizationalUnitName_default = User\n"
else
userdata="organizationalUnitName_default = Admin\n"
fi
userdata="${userdata}commonName_default = ${user}\n"
userdata="${userdata}emailAddress_default = ${email}"
cat ${PKI_PATH}/${NAME}/ssl.cnf | tr -d '#' | \
sed -e "s/@USERDATA@/${userdata}/" \
> ${PKI_PATH}/${NAME}/ssl2.cnf
openssl req -config ${PKI_PATH}/${NAME}/ssl2.cnf -new -nodes -batch \
-out ${PKI_PATH}/${NAME}/certs/${user}.csr \
-key ${PKI_PATH}/${NAME}/certs/${user}.key
openssl ca -config ${PKI_PATH}/${NAME}/ssl2.cnf \
-cert ${PKI_PATH}/${NAME}/${NAME}_ca.crt ${extension} \
-out ${PKI_PATH}/${NAME}/certs/${user}.crt \
-outdir ${PKI_PATH}/${NAME}/certs \
-infiles ${PKI_PATH}/${NAME}/certs/${user}.csr
cat ${PKI_PATH}/${NAME}/certs/${user}.crt \
${PKI_PATH}/${NAME}/certs/${user}.key \
> ${PKI_PATH}/${NAME}/pem/${user}.pem
mv ${PKI_PATH}/${NAME}/ssl2.cnf ${PKI_PATH}/${NAME}/confs/${user}-ssl.cnf
read -p "Press [enter] to continue" DUMMY
}
function webUser() {
local user
printSubMenu "Create a Client Certificate for Web"
printUserList
read -p " ==> User name [NONE]: " user
if [[ "${user}" == "" ]]
then
return
fi
echo
if [ -f ${PKI_PATH}/${NAME}/certs/${user}.crt ]
then
openssl pkcs12 -export -inkey ${PKI_PATH}/${NAME}/certs/${user}.key \
-in ${PKI_PATH}/${NAME}/certs/${user}.crt \
-CAfile ${PKI_PATH}/${NAME}/${NAME}_ca.crt \
-out ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12
fi
echo
[ -f ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12 ] \
&& echo "Web certificate: ${PKI_PATH}/${NAME}/certs/${user}_browser_cert.p12" \
|| echo "Error encoured"
echo
read -p "Press [enter] to continue" DUMMY
}
function renewUser() {
local user
printSubMenu "Renew a Server Certificate"
printUserList
read -p " ==> User name [NONE]: " user
if [[ "${user}" == "" ]]
then
echo "Error: Name cannot be empty"
read -p "Press [enter] to continue" DUMMY
return
fi
revokeUser ${user}
openssl ca -config ${PKI_PATH}/${NAME}/confs/${user}-ssl.cnf \
-out ${PKI_PATH}/${NAME}/certs/${user}.crt \
-outdir ${PKI_PATH}/${NAME}/certs \
-infiles ${PKI_PATH}/${NAME}/certs/${user}.csr
cat ${PKI_PATH}/${NAME}/certs/${user}.crt \
${PKI_PATH}/${NAME}/certs/${user}.key \
> ${PKI_PATH}/${NAME}/pem/${user}.pem
echo
read -p "Press [enter] to continue" DUMMY
}
function revokeUser() {
local user=$1
printSubMenu "Revoke a Client Certificate"
printUserList
[ -z ${user} ] && read -p " ==> User name [NONE]: " user
if [[ "${user}" == "" ]]
then
return
fi
openssl ca -revoke ${PKI_PATH}/${NAME}/certs/${user}.crt \
-config ${PKI_PATH}/${NAME}/ssl.cnf
# Save old certificate
x=1
while [ -f "${PKI_PATH}/${NAME}/certs/${user}.revoked.$x.crt" ]
do
x=$(( $x + 1 ))
done
mv ${PKI_PATH}/${NAME}/certs/${user}.crt ${PKI_PATH}/${NAME}/certs/${user}.revoked.$x.crt
x=1
while [ -f "${PKI_PATH}/${NAME}/pem/${user}.revoked.$x.pem" ]
do
x=$(( $x + 1 ))
done
mv ${PKI_PATH}/${NAME}/pem/${user}.pem ${PKI_PATH}/${NAME}/pem/${user}.revoked.$x.pem
# Regen CRL
openssl ca -gencrl -config ${PKI_PATH}/${NAME}/ssl.cnf \
-out ${PKI_PATH}/${NAME}/${NAME}_ca.crl
echo
echo "Don't forget to reload Apache"
echo
[ -z ${1} ] read -p "Press [enter] to continue" DUMMY
}
function listUser() {
printSubMenu "List Client Certificates"
while [ 1 ]
do
read LINE || break
LISTNUM=`echo ${LINE} | grep -v "^R" | awk '{ print $3 }'`
LISTCN=`echo ${LINE} | grep -v "^R" | awk -F CN= '{ print $2 }' | cut -d '/' -f1`
[ -z ${LISTNUM} ] || echo " ${LISTNUM} ${LISTCN}"
done < ${PKI_PATH}/${NAME}/index.txt
echo
read -p "Press [enter] to continue" DUMMY
}
function printUserList() {
while [ 1 ]
do
read LINE || break
LISTCN=`echo ${LINE} | grep -v "^R" | awk -F CN= '{ print $2 }' | cut -d '/' -f1`
[ -z ${LISTCN} ] || echo "- ${LISTCN}"
done < ${PKI_PATH}/${NAME}/index.txt
}
function changeDefaultPath() {
local buffer
read -p " ==> Select New path for CA [${PKI_PATH}]: " buffer
if [ ! -z ${buffer} ]; then PKI_PATH=${buffer} ; fi
}
function changeName() {
read -p " ==> Select New CA name [NONE]: " NAME
}
function initCA() {
printSubMenu "CA Initialisation"
if [ -f ${PKI_PATH}/${NAME}/ssl.cnf ]
then
read -p "!!! Already initalized !!!"
return
fi
read -p " ==> Fully qualified Hostname [NONE]: " hostname
if [[ "$hostname" == "" ]]
then
echo "Error: Fully qualified Hostname cannot be empty"
read -p "Press [enter] to continue" DUMMY
return
fi
echo
read -p " ==> Admin email [NONE]: " email
if [[ "$email" == "" ]]
then
echo "Error: email cannot be empty"
read -p "Press [enter] to continue" DUMMY
return
fi
mkdir -p ${PKI_PATH}/${NAME}/{certs,newcerts,private,confs,crl,pem}
initConfig
touch ${PKI_PATH}/${NAME}/index.txt
[ -f ${PKI_PATH}/${NAME}/serial ] || echo 01 > ${PKI_PATH}/${NAME}/serial
[ -f ${PKI_PATH}/${NAME}/crlnumber ] || echo 01 > ${PKI_PATH}/${NAME}/crlnumber
[ -f ${PKI_PATH}/${NAME}/private/${NAME}_ca.key ] || \
openssl genrsa -out ${PKI_PATH}/${NAME}/private/${NAME}_ca.key 2048 \
1>/dev/null 2>&1
local userdata="organizationalUnitName_default = Admin\n"
userdata="${userdata}commonName_default = ${hostname}\n"
userdata="${userdata}emailAddress_default = ${email}"
cat ${PKI_PATH}/${NAME}/ssl.cnf | tr -d '#' | \
sed -e "s/@USERDATA@/${userdata}/" \
> ${PKI_PATH}/${NAME}/ssl2.cnf
openssl req -config ${PKI_PATH}/${NAME}/ssl2.cnf -new -x509 -days 3650 -batch \
-key ${PKI_PATH}/${NAME}/private/${NAME}_ca.key \
-out ${PKI_PATH}/${NAME}/${NAME}_ca.crt -extensions v3_ca
mv ${PKI_PATH}/${NAME}/ssl2.cnf ${PKI_PATH}/${NAME}/confs/ca.cnf
[ -f ${PKI_PATH}/${NAME}/${NAME}_ca.crl ] || openssl ca -gencrl \
-config ${PKI_PATH}/${NAME}/ssl.cnf -out ${PKI_PATH}/${NAME}/${NAME}_ca.crl
ln ${PKI_PATH}/${NAME}/${NAME}_ca.crl ${PKI_PATH}/${NAME}/crl/
local hash=`openssl crl -hash -noout -in ${PKI_PATH}/${NAME}/crl/${NAME}_ca.crl`
ln -s ${PKI_PATH}/${NAME}/crl/${NAME}_ca.crl ${PKI_PATH}/${NAME}/crl/$hash.r0
echo
echo "CA initialized"
echo
read -p "Press [enter] to continue" DUMMY
}
function deleteCA() {
printSubMenu "Deleting CA"
read -p " ==> Are you sure ? Type uppercase YES to confirm: " CONFIRM
if [[ "${CONFIRM}" == "YES" ]]
then
rm -rf ${PKI_PATH}/${NAME}
echo
echo "CA completely deleted"
echo
read -p "Press [enter] to continue" DUMMY
fi
}
function initConfig() {
cat << 'EOF' > ${PKI_PATH}/${NAME}/ssl.cnf
HOME = @HOME@
RANDFILE = @HOME@/.rand
[ca]
default_ca = ca_default
[ca_default]
dir = @HOME@
certs = $dir/certs
crl_dir = $dir/crl
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/@NAME@_ca.crt
private_key = $dir/private/@NAME@_ca.key
serial = $dir/serial
crl = $dir/@NAME@_ca.crl
crlnumber = $dir/crlnumber
crl_extensions = crl_ext
x509_extensions = usr_cert
name_opt = ca_default
cert_opt = ca_default
default_days = 365
default_crl_days = 30
default_md = md5
preserve = no
policy = policy_match
[policy_match]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 1024
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
string_mask = MASK:0x2002
[req_distinguished_name]
countryName = Country Name (2 letter code)
countryName_default = @COUNTRYNAME@
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = @STATE@
localityName = Locality Name (eg, city)
localityName_default = @CITY@
0.organizationName = Organization Name (eg, company)
0.organizationName_default = @ORGANISATION@
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, your name or your server\'s hostname)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
##@USERDATA@
[req_attributes]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName = An optional company name
[usr_cert]
basicConstraints = CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
[v3_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = CA:true
[crl_ext]
authorityKeyIdentifier=keyid:always,issuer:always
[OCSP]
basicConstraints = CA:FALSE
keyUsage = digitalSignature
extendedKeyUsage = OCSPSigning
issuerAltName = issuer:copy
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
authorityInfoAccess = OCSP;URI:@OCSPURL@
[OCSP_SERVER]
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
issuerAltName = issuer:copy
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment
nsCertType = server
extendedKeyUsage = serverAuth
authorityInfoAccess = OCSP;URI:@OCSPURL@
[OCSP_CLIENT]
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
issuerAltName = issuer:copy
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, nonRepudiation
nsCertType = client
extendedKeyUsage = clientAuth
authorityInfoAccess = OCSP;URI:@OCSPURL@
EOF
sed -i \
-e "s#@HOME@#${PKI_PATH}/${NAME}#g" \
-e "s#@NAME@#${NAME}#g" \
-e "s#@COUNTRYNAME@#${COUNTRYNAME}#g" \
-e "s#@STATE@#${STATE}#g" \
-e "s#@CITY@#${CITY}#g" \
-e "s#@ORGANISATION@#${COMPANY}#g" \
-e "s#@OCSPURL@#${OCSP_URL}#g" \
${PKI_PATH}/${NAME}/ssl.cnf
}
#Main program
# process command line arguments
while getopts "?hup:n:c:" opt
do
case "${opt}" in
u)
printUsage
exit 0
;;
h|\?)
printUsage
printHelp
exit 0
;;
c)
CFG_FILE=$OPTARG
;;
p)
PKI_PATH=$OPTARG
;;
n)
NAME=$OPTARG
;;
esac
done
# Load config
[ -f ${CFG_FILE} ] && source ${CFG_FILE} || saveCfg ${CFG_FILE}
clear
[ -z ${NAME} ] && changeName
while true;
do
printMenu
# CheckOpenSSLConfig
read -p " ==> Make your choice [none]: " -n 1 CHOICE
case ${CHOICE} in
1)
addUser
;;
2)
webUser
;;
3)
renewUser
;;
4)
revokeUser
;;
5)
listUser
;;
I|i)
initCA
;;
Q|q)
echo
break
;;
P|p)
echo
changeDefaultPath
;;
N|n)
echo
changeName
;;
D|d)
deleteCA
;;
O|o)
manageOptions
;;
esac
done