Esta guía explica cómo configurar un servidor web Apache con mod_ssl para autenticación cliente con TLS y la jerarquía de certificados para personas físicas o jurídicas, incluyendo comprobación de estado de revocación de los certificados (OCSP).
Existen muchos servidores HTTPS y terminadores TLS en general que permiten autenticación con certificado cliente. En esta guía se presenta mod_ssl de Apache HTTPd en particular.
Obtención e instalación de cadena de certificados de confianza
Para el caso presentado, se hace uso del comando openssl para convertir todos los certificados a formato PEM, ya que mod_ssl solamente trabaja con este formato en los certificados cliente. Para ello, se puede ejecutar:
wget https://www.firmadigital.go.cr/repositorio/CA%20RAIZ%20NACIONAL%20-%20COSTA%20RICA%20v2.crt
wget https://www.firmadigital.go.cr/repositorio/CA%20POLITICA%20PERSONA%20FISICA%20-%20COSTA%20RICA%20v2.crt
wget http://fdi.sinpe.fi.cr/repositorio/CA%20SINPE%20-%20PERSONA%20FISICA%20v2.crt
wget "http://fdi.sinpe.fi.cr/repositorio/CA%20SINPE%20-%20PERSONA%20FISICA%20v2(1).crt"
wget "http://fdi.sinpe.fi.cr/repositorio/CA%20SINPE%20-%20PERSONA%20FISICA%20v2(2).crt"
openssl x509 -inform der -in CA\ RAIZ\ NACIONAL\ -\ COSTA\ RICA\ v2.crt -outform pem -out CA\ RAIZ\ NACIONAL\ -\ COSTA\ RICA\ v2.pem
openssl x509 -inform der -in CA\ POLITICA\ PERSONA\ FISICA\ -\ COSTA\ RICA\ v2.crt -outform pem -out CA\ POLITICA\ PERSONA\ FISICA\ -\ COSTA\ RICA\ v2.pem
Subir al servidor los ficheros en una ruta que luego se le indicará a Apache HTTPd:
CA RAIZ NACIONAL - COSTA RICA v2.pem
CA POLITICA PERSONA FISICA - COSTA RICA v2.pem
CA SINPE - PERSONA FISICA v2.crt
CA SINPE - PERSONA FISICA v2(1).crt
CA SINPE - PERSONA FISICA v2(2).crt
Existen dos formas de suministrar a mod_ssl los certificados de la jerarquía de confianza. Combinarlos en un único fichero o bien renombrar o crear enlaces simbólicos cuyo nombre sea un hash. En esta guía se mostrará esta segunda opción. En el caso de los certificados con el mismo hash, se aumenta el consecutivo del número que hay final del nombre. Se pueden generar así:
ln -s CA\ RAIZ\ NACIONAL\ -\ COSTA\ RICA\ v2.pem $(openssl x509 -noout -hash -in CA\ RAIZ\ NACIONAL\ -\ COSTA\ RICA\ v2.pem).0
ln -s CA\ POLITICA\ PERSONA\ FISICA\ -\ COSTA\ RICA\ v2.pem $(openssl x509 -noout -hash -in CA\ POLITICA\ PERSONA\ FISICA\ -\ COSTA\ RICA\ v2.pem).0
ln -s CA\ SINPE\ -\ PERSONA\ FISICA\ v2.crt $(openssl x509 -noout -hash -in CA\ SINPE\ -\ PERSONA\ FISICA\ v2.crt).0
ln -s CA\ SINPE\ -\ PERSONA\ FISICA\ v2\(1\).crt $(openssl x509 -noout -hash -in CA\ SINPE\ -\ PERSONA\ FISICA\ v2\(1\).crt).1
ln -s CA\ SINPE\ -\ PERSONA\ FISICA\ v2\(2\).crt $(openssl x509 -noout -hash -in CA\ SINPE\ -\ PERSONA\ FISICA\ v2\(2\).crt).2
Lo anterior creará los siguientes enlaces simbólicos:
e1f996fa.0
c07e7fb7.0
395f14c9.0
395f14c9.1
395f14c9.2
Al enlazar a los nombres originales, es un poco más fácil saber a qué certificado se refiere cada uno, ya que los nombres usados por mod_ssl no son amigables.
Configuración de Apache HTTPd y mod_ssl
Debido a que algunos navegadores no son compatibles con autenticación con certificado cliente usando TLS 1.3 y HTTP/2, así como debido a que mod_ssl es poco intuitivo, conviene indicar que el parámetro de configuración SSLProtocol puede estar definido globalmente, es decir, fuera de un VirtualHost, y tiene prioridad sobre ellos. Si se quiere configurar un valor predeterminado pero sobrescribible por otros VirtualHost en particular, entonces el primer VirtualHost será el predeterminado.
Debido a la limitación en ciertos navegadores, si se utiliza TLS 1.3 en el primer VirtualHost para uso predeterminado, tener un VirtualHost separado para la autenticación con TLS 1.2. Aunque Firefox soporta autenticación cliente con TLS 1.3 y HTTP/2, no es el caso de otros navegadores que pueden mostrar errores, por lo que se sugiere lo siguiente para el caso del VirtualHost
<VirtualHost *:443>
# En caso de usar HTTP/2 (h2) se puede bajar a HTTP/1.1
Protocols http/1.1
# En caso de SSLProtocol en el primer VirtualHost con TLS 1.3
SSLProtocol -TLSv1.3
ServerName auth.example.org
DocumentRoot /var/www/auth.example.org
# No hay OCSP en toda la jerarquía excepto en la hoja
SSLOCSPEnable leaf
# Ruta a los certificados (puede ser cualquier ubicación del sistema)
SSLCACertificatePath /var/www/auth.example.org/certificados
# 3 es suficiente para la jerarquía nacional
SSLVerifyDepth 3
# Para acceder a datos desde un script
SSLOptions +StdEnvVars +ExportCertData
# En caso de querer controlar resultado de validación desde script
SSLVerifyClient optional
# (...)
</VirtualHost>
En esa ruta un script puede leer los encabezados que mod_ssl devuelve a lo interno. En el caso de PHP se pueden leer en el arreglo superglobal $_SERVER:
<?php
echo '<!doctype html>
<html lang=es-cr>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset=utf-8>
<title>Prueba de autenticación con certificado cliente TLS</title>
<h1>Prueba de autenticación con certificado cliente TLS</h1>';
if ($_SERVER['SSL_CLIENT_VERIFY'] === 'NONE') exit('<p>No se seleccionó ningún certificado.');
if ($_SERVER['SSL_CLIENT_VERIFY'] === 'SUCCESS') {
echo '<p>Hola, ', ucwords(strtolower(htmlspecialchars($_SERVER['SSL_CLIENT_S_DN_G']))), ":\n";
echo '<p>Con toda certeza, tu nombre completo es ', htmlspecialchars($_SERVER['SSL_CLIENT_S_DN_G']), ' ', htmlspecialchars($_SERVER['SSL_CLIENT_S_DN_S']), ".\n";
parse_str(str_replace(',', '&', htmlspecialchars($_SERVER['SSL_CLIENT_S_DN'])), $dn);
echo '<p>Identificación: ', $dn['serialNumber'];
echo '<p>Tu certificado vencerá dentro de ', htmlspecialchars($_SERVER['SSL_CLIENT_V_REMAIN']), " días.\n";
echo '<p>Fecha de emisión: ', htmlspecialchars($_SERVER['SSL_CLIENT_V_START']), ".\n";
echo '<p>Fecha de expiración: ', htmlspecialchars($_SERVER['SSL_CLIENT_V_END']), ".\n";
} else echo '<p>Parece que falló la autenticación.';
Queda a discreción del usuario el manejo de la autenticación al gusto, teniendo en cuenta el valor SUCCESS en SSL_VERIFY_CLIENT y lo que considere realizar en base a los datos como el serialNumber.
Existen muchas alternativas, como mod_gnutls en el mismo Apache HTTPd, o en otros servidores como nginx, lighttpd, haproxy, hitch, etc. donde también es posible configurar autenticación con certificado cliente, así como el tratamiento desde otros lenguajes de programación de la información de autenticación.
Hola Fran. Muy interesante tu publicación. Sin embargo te quería consultar si has tenido oportunidad de realizar algo como esto: https://www.youtube.com/watch?v=1uGwC8d6mBc&t=235s
Pero utilizando la tarjeta lectora y la firma digital que obtenemos a través del banco central, misma que usamos para firmar documentos PDF usando Adobe Acrobat Reader.
Hola Andres, con gusto. Si se quiere realizar una firma digital certificada dentro de la jerarquía de certificados del Micitt, sí es posible generar firmas de forma automatizada, sea utilizando certificados de persona física (solo disponible en tarjetas) como las de persona jurídica (sello electrónico).
Las llaves privadas de las tarjetas de persona física no se pueden extraer de la tarjeta, sin embargo en caso de ser persona jurídica, es posible generar certificados en fichero de llave privada y pública (pem/der o un llavero p12) para tal efecto, o generar llaves privadas en hardware para mayor seguridad, aunque no es obligatorio.
El firmador libre que está en https://firmador.libre.cr permite firmar documentos por línea de comandos, por lo que se podría invocar por sitio web. También se puede firmar por parte de cada usuario con tarjeta de firma digital si tienen instalado Java para lanzar el firmador desde web, donde se puede ver una demostración en https://firmador.libre.cr/demo-firma-web/ . Puedo contestarle por correo electrónico si necesita más información.
Saludos.