Generación de certificados X.509 autofirmados con "Subject Alternative Name"

Los certificados X.509 usados en TLS requieren una firma digital que les de validez. Para que un navegador web los reconozca como válidos, hoy en día, es necesario que la firma la realice una autoridad de certificación. Espero que eso cambie en el futuro con iniciativas como DANE pero ahora mismo es lo que hay.

En ciertos casos (intranets, servicios para grupos cerrados de usuarios...) obtener esa firma puede ser complicado o inconveniente. De hecho existen entornos donde confirmar en una autoridad de certificación externa puede minar la seguridad del sistema.

En situaciones así hay dos prácticas habituales. La primera consiste en crear una autoridad de certificación interna y privada para la organización. La otra opción es emplear certificados autofirmados.

Internet está repleto de tutoriales sobre cómo hace las dos cosas empleando OpenSSL. Es rutina. Pero hace unas semanas me encontré con este warning al actualizar uno de mis productos:

/usr/local/lib/python3.3/site-packages/requests/packages/urllib3/connection.py:251: SecurityWarning: Certificate has no subjectAltName, falling back to check for a commonName for now. This feature is being removed by major browsers and deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 for details.) SecurityWarning

Resumamos el problema: tradicionalmente los certificados X.509 tienen un campo crítico llamado CN (Common Name). Este campo es el que emplean los navegadores para confirmar que un certificado X.509 efectivamente pertenece al dominio al que nos estamos conectando. El problema es que la sintaxis de dicho campo es muy limitada y no permite cosas como, por ejemplo, que un mismo certificado X.509 sea válido para diferentes dominios.

Así que se introdujo un nuevo campo opcional llamado SubjectAltName. Este campo permite especificar varias identidades y estas pueden ser de ámbitos diversos: direcciones IP, direcciones de correo electrónico, nombres de dominio... La regla es que si un certificado X.509 contiene campos SubjectAltName el verificador (típicamente tu navegador web) debe ignorar el CN y emplear el SubjectAltName en su lugar.

El warning nos indica que el campo CN no debe emplearse y el uso de SubjectAltName es ya prácticamente obligatorio.

El problema es que generar certificados X.509 autofirmados con campos SubjectAltName no es trivial y, de hecho, dar con la receta apropiada me ha llevado bastante tiempo. Lo documento aquí para la próxima vez que lo necesite, esperando que le sirva a alguien más.

El primer paso es olvidarnos del entorno interactivo de OpenSSL. Lástima. La verdad es que OpenSSL dista mucho de ser amigable.

Creamos un fichero de configuración OpenSSL con este contenido:

 [req]
 distinguished_name = req_distinguished_name
 x509_extensions = v3_req
 prompt = no

 [req_distinguished_name]
 C = ES
 ST = Madrid
 L = Madrid
 O = JCEA
 OU = JCEA
 CN = XXXXX.jcea.es

 [v3_req]
 #basicConstraints = CA:true
 #keyUsage = keyEncipherment, dataEncipherment
 extendedKeyUsage = serverAuth
 subjectAltName = @alt_names

 [alt_names]
 DNS.1 = XXXXX.jcea.es

Este fichero de configuración sirve para generar un certificado X.509 autofirmado concreto. Naturalmente hay que cambiarlo para generar otros certificados.

Vemos en la línea 12 que se define una entrada CN, pero la parte clave son las líneas 17-21. Ahí se indica a OpenSSL que emplee SubjectAltName y su contenido (se pueden añadir varios dominios, emails, IPs, etc).

Podemos generar un certificado X.509 autofirmado con un solo comando OpenSSL:

$ openssl req -x509 -config XXXXX.jcea.es.conf \
     -newkey rsa:2048 -sha256 -days 3650 -nodes \
     -keyout XXXXX.jcea.es.key -out XXXXX.jcea.es.cert \

Aquí empleamos el fichero de configuración anterior para crear un certificado RSA de 2048 bits usando un hash SHA256. Estas son las buenas prácticas actuales. Le doy una validez de diez años para el uso concreto que le voy a dar, en tu caso variará.

El siguiente paso será proteger convenientemente (permisos 0400 en entornos Unix, por ejemplo) el fichero XXXXX.jcea.es.key. Contiene la clave privada y nadie debería tener acceso a ella.

El certificado así generado contiene lo siguiente:

 $ openssl x509 -noout -text -in XXXXX.jcea.es.cert
 Certificate:
     Data:
         Version: 3 (0x2)
         Serial Number:
             cd:23:58:ba:f4:c2:3d:99
     Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=ES, ST=Madrid, L=Madrid, O=JCEA, OU=JCEA, CN=XXXXX.jcea.es
         Validity
             Not Before: Mar 18 14:43:53 2015 GMT
             Not After : Mar 15 14:43:53 2025 GMT
         Subject: C=ES, ST=Madrid, L=Madrid, O=JCEA, OU=JCEA, CN=XXXXX.jcea.es
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 Public-Key: (2048 bit)
                 Modulus:
                     [...]
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Extended Key Usage:
                 TLS Web Server Authentication
             X509v3 Subject Alternative Name:
                 DNS:XXXXX.jcea.es
     Signature Algorithm: sha256WithRSAEncryption
          [...]

Lo importante son las líneas 19-23. Obsérvese que NO hay un campo:

X509v3 Basic Constraints:
  CA:TRUE

Es algo que siempre me ha puesto nervioso con otras recetas que hay por Internet.

Podemos aplicar la misma técnica para generar un CSR con SubjectAltName que luego firmamos con una autoridad de certificación interna.