Crear certificado autofirmado con fecha de finalización en el pasado

24

Me gustaría crear certificados autofirmados sobre la marcha con fechas de inicio y finalización arbitrarias, incluidas las fechas de finalización en el pasado . Preferiría usar herramientas estándar, por ejemplo, OpenSSL, pero cualquier cosa que haga el trabajo sería genial.

La pregunta de desbordamiento de pila ¿ Cómo generar un certificado openssl con vencimiento inferior a un día? hace una pregunta similar, pero quiero que mi certificado sea autofirmado.

En caso de que se lo pregunte, los certificados son necesarios para las pruebas automatizadas.

rlandster
fuente

Respuestas:

32

Tiene dos formas de crear certificados en el pasado. Fingiendo el tiempo (1) (2) o definiendo el intervalo de tiempo al firmar el certificado (3).

1) En primer lugar, sobre fingir el tiempo: para hacer que un programa piense que está en una fecha diferente del sistema, eche un vistazo libfaketimeyfaketime

Para instalarlo en Debian:

sudo apt-get install faketime

Entonces lo usarías faketimeantes del opensslcomando.

Para ejemplos de uso:

$faketime 'last friday 5 pm' /bin/date
Fri Apr 14 17:00:00 WEST 2017
$faketime '2008-12-24 08:15:42' /bin/date
Wed Dec 24 08:15:42 WET 2008

De man faketime:

Se engañará al comando dado para que crea que la hora actual del sistema es la especificada en la marca de tiempo. El reloj de pared continuará ejecutándose a partir de esta fecha y hora a menos que se especifique lo contrario (ver opciones avanzadas). En realidad, faketime es un contenedor simple para libfaketime, que utiliza el mecanismo LD_PRELOAD para cargar una pequeña biblioteca que intercepta las llamadas del sistema a funciones como time (2) y fstat (2).

Entonces, por ejemplo, en su caso, puede definir muy bien una fecha de 2008 y crear un certificado con una validez de 2 años hasta 2010.

faketime '2008-12-24 08:15:42' openssl ... 

Como nota al margen, esta utilidad se puede utilizar en varias versiones de Unix, incluido MacOS, como una envoltura para cualquier tipo de programa (no exclusivo de la línea de comandos).

Como aclaración, solo los binarios cargados con este método (y sus hijos) tienen su hora cambiada, y la hora falsa no afecta la hora actual del resto del sistema.

2) Como dice @Wyzard, también tiene el datefudgepaquete que es muy similar en uso faketime.

Como diferencias, datefudgeno influye fstat(es decir, no cambia la creación del tiempo del archivo). También tiene su propia biblioteca, datefudge.so, que se carga usando LD_PRELOAD.

También tiene un lugar -s static timedonde siempre se devuelve el tiempo referenciado a pesar de cuántos segundos adicionales han pasado.

$ datefudge --static "2007-04-01 10:23" sh -c "sleep 3; date -R"
Sun, 01 Apr 2007 10:23:00 +0100

3) Además de fingir el tiempo, y aún más simplemente, también puede definir el punto de inicio y el punto final de validez del certificado al firmar el certificado en OpenSSL.

La idea errónea de la pregunta a la que se vincula en su pregunta es que la validez del certificado no se define en el momento de la solicitud (en la solicitud de CSR), sino al firmarla.

Al usar openssl capara crear el certificado autofirmado, agregue las opciones -startdatey -enddate.

El formato de fecha en esas dos opciones, de acuerdo con las fuentes de openssl en openssl/crypto/x509/x509_vfy.c, es ASN1_TIME, también conocido como ASN1UTCTime: el formato debe ser YYMMDDHHMMSSZ o YYYYMMDDHHMMSSZ.

Citando openssl/crypto/x509/x509_vfy.c:

int X509_cmp_time(const ASN1_TIME *ctm, time_t *cmp_time)
{
    static const size_t utctime_length = sizeof("YYMMDDHHMMSSZ") - 1;
    static const size_t generalizedtime_length = sizeof("YYYYMMDDHHMMSSZ") - 1;
    ASN1_TIME *asn1_cmp_time = NULL;
    int i, day, sec, ret = 0;

    /*
     * Note that ASN.1 allows much more slack in the time format than RFC5280.
     * In RFC5280, the representation is fixed:
     * UTCTime: YYMMDDHHMMSSZ
     * GeneralizedTime: YYYYMMDDHHMMSSZ
     *
     * We do NOT currently enforce the following RFC 5280 requirement:
     * "CAs conforming to this profile MUST always encode certificate
     *  validity dates through the year 2049 as UTCTime; certificate validity
     *  dates in 2050 or later MUST be encoded as GeneralizedTime."
     */

Y del registro CHANGE (error 2038): este registro de cambios es solo una nota al pie adicional, ya que solo concierne a aquellos que usan directamente la API.

Cambios entre 1.1.0e y 1.1.1 [xx XXX xxxx]

*) Agregue los tipos ASN.1 INT32, UINT32, INT64, UINT64 y las variantes con el prefijo Z. Estos están destinados a reemplazar LONG y ZLONG y tener un tamaño seguro. Se desaconseja el uso de LONG y ZLONG y está programado para su desuso en OpenSSL 1.2.0.

Por lo tanto, la creación de un certificado del 1 de enero de 2008 al 1 de enero de 2010 se puede hacer de la siguiente manera:

openssl ca -config /path/to/myca.conf -in req.csr -out ourdomain.pem \
-startdate 200801010000Z -enddate 201001010000Z

o

openssl ca -config /path/to/myca.conf -in req.csr -out ourdomain.pem \
-startdate 0801010000Z -enddate 1001010000Z

-startdatey -enddateaparecen en el opensslregistro de fuentes y CAMBIO; como señaló @guntbert, aunque no aparecen en la man opensslpágina principal , también aparecen en man ca:

-startdate date
       this allows the start date to be explicitly set. The format of the date is
       YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure).

   -enddate date
       this allows the expiry date to be explicitly set. The format of the date is
       YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure).

Citando openssl/CHANGE:

Cambios entre 0.9.3a y 0.9.4 [09 de agosto de 1999]

*) Repara los argumentos -startdate y -enddate (que faltaban) en el programa 'ca'.

PD: En cuanto a la respuesta elegida de la pregunta a la que hace referencia en StackExchange: generalmente es una mala idea cambiar la hora del sistema, especialmente en los sistemas de producción; y con los métodos en esta respuesta no necesita privilegios de root cuando los usa.

Rui F Ribeiro
fuente
1
+1. Sabía que alguien vendría con algo mejor que lo que escribí :)
Celada
2
También hay un programa similar llamado datefudge.
Wyzard --Detener Dañar a Mónica--
@Wyzard Gracias, de hecho lo encontré en Debian; Curiosamente, el manual establece que si bien también cambia las llamadas del sistema a funciones como el tiempo (2), no influye en fstat (2).
Rui F Ribeiro
1
Ambos faketimey datefudgefuncionan maravillosamente en mi sistema Debian jessie.
rlandster
1
OTOH: +5 para averiguar dónde establecer esas fechas!
guntbert
8

Casi me sorprende descubrir que lo obvio funciona: mientras que openssltoma como argumento el número de días durante los cuales el certificado debe ser válido, ¡simplemente proporcione un número negativo!

openssl req -x509 -newkey rsa:4096 \
    -keyout key.pem -out cert.pem -days -365

Tenga en cuenta que esto realmente resulta en algo muy extraño: un certificado cuya marca de tiempo de vencimiento precede a su marca de tiempo de inicio de validez. En realidad, no recomiendo que uses esto para tus pruebas automatizadas, ya que es extraño. Probablemente también desee una forma de retroceder la fecha de inicio de validez.

Celada
fuente
Bueno, para ser justos, no tenía idea de que podría usar días negativos.
Rui F Ribeiro
¿No puedes especificar la fecha de inicio?
FreeSoftwareServers
@FreeSoftwareServers En la CSR no puede; Vea la última parte de mi respuesta.
Rui F Ribeiro
Más interesante aún, ¿el código no quiere encontrar ese certificado? por cierto, amplié mi respuesta
Rui F Ribeiro
3

O podría usar algo como este breve programa de Python ... (se aplican advertencias)

Crea una clave (test.key) y un certificado (test.crt) con un tiempo de inicio de 10 años en el pasado (-10 * 365 * 24 * 60 * 60 segundos es -10 años) y un tiempo de vencimiento de 5 años en el pasado (-5 * 365 * 24 * 60 * 60).

Tenga en cuenta que es un programa de demostración mínimo, por lo que no se molesta en establecer extensiones (por ejemplo, basicConstraints) y utiliza una serie fija.

#!/usr/bin/env python

from OpenSSL import crypto

key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
cert = crypto.X509()
cert.get_subject().CN = "Test"
cert.set_serial_number(666)
cert.gmtime_adj_notBefore(-10*365*24*60*60)
cert.gmtime_adj_notAfter(-5*365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(key)
cert.sign(key, 'sha384')

open("test.crt", "wb").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
open("test.key", "wb").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
Edheldil
fuente
Parece que faltan campos esenciales en el código X.509 estándar estándar.
Rui F Ribeiro
2
Esto es muy útil. Me da un control programático más fácil sobre la creación del certificado.
rlandster