31

I would like to create self-signed certificates on the fly with arbitrary start- and end-dates, including end-dates in the past. I would prefer to use standard tools, e.g., OpenSSL, but anything that gets the job done would be great.

The Stack Overflow question How to generate openssl certificate with expiry less than one day? asks a similar question, but I want my certificate to be self-signed.

In case you were wondering, the certificates are needed for automated testing.

4 Answers 4

43

You have two ways of creating certificates in the past. Either faking the time (1)(2), or defining the time interval when signing the certificate (3).

1) Firstly, about faking the time: to make one program think it is in a different date from the system, have a look at libfaketime and faketime

To install it in Debian:

sudo apt-get install faketime

You would then use faketime before the openssl command.

For examples of use:

$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

From man faketime:

The given command will be tricked into believing that the current system time is the one specified in the timestamp. The wall clock will continue to run from this date and time unless specified otherwise (see advanced options). Actually, faketime is a simple wrapper for libfaketime, which uses the LD_PRELOAD mechanism to load a small library which intercepts system calls to functions such as time(2) and fstat(2).

So for instance, in your case, you can very well define a date of 2008, and create then a certificate with the validity of 2 years up to 2010.

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

As a side note, this utility can be used in several Unix versions, including MacOS, as an wrapper to any kind of programs (not exclusive to the command line).

As a clarification, only the binaries loaded with this method (and their children) have their time changed, and the fake time does not affect the current time of the rest of the system.

2) As @Wyzard states, you also have the datefudge package which is very similar in use to faketime.

As differences, datefudge does not influence fstat (i.e. does not change file time creation). It also has it´s own library, datefudge.so, that it loads using LD_PRELOAD.

It also has a -s static time where the time referenced is always returned despite how many extra seconds have passed.

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

3) Besides faking the time, and even more simply, you can also define the starting point and ending point of validity of the certificate when signing the certificate in OpenSSL.

The misconception of the question you link to in your question, is that certificate validity is not defined at request time (at the CSR request), but when signing it.

When using openssl ca to create the self-signed certificate, add the options -startdate and -enddate.

The date format in those two options, according to openssl sources at openssl/crypto/x509/x509_vfy.c, is ASN1_TIME aka ASN1UTCTime: the format must be either YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ.

Quoting 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."
     */

And from the CHANGE log (2038 bug?) - This change log is just as an additional footnote, as it only concerns those using directly the API.

Changes between 1.1.0e and 1.1.1 [xx XXX xxxx]

*) Add the ASN.1 types INT32, UINT32, INT64, UINT64 and variants prefixed with Z. These are meant to replace LONG and ZLONG and to be size safe. The use of LONG and ZLONG is discouraged and scheduled for deprecation in OpenSSL 1.2.0.

So, creating a certificate from the 1st of January 2008 to the 1st of January of 2010, can be done as:

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

or

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

-startdate and -enddate do appear in the openssl sources and CHANGE log; as @guntbert noted, while they do not appear in the main man openssl page, they also appear in 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).

Quoting openssl/CHANGE:

Changes between 0.9.3a and 0.9.4 [09 Aug 1999]

*) Fix -startdate and -enddate (which was missing) arguments to 'ca' program.

P.S. As for the chosen answer of the question you reference from StackExchange: it is generally a bad idea to change the system time, especially in production systems; and with the methods in this answer you do not need root privileges when using them.

10
  • 1
    +1. I knew someone would come along with something better than what I wrote :)
    – Celada
    Apr 15, 2017 at 19:41
  • 2
    There's also a similar program called datefudge.
    – Wyzard
    Apr 15, 2017 at 21:59
  • 1
    Both faketime and datefudge work beautifully on my Debian jessie system.
    – rlandster
    Apr 15, 2017 at 22:55
  • 1
    OTOH: +5 for finding out where to set those dates!
    – guntbert
    Apr 16, 2017 at 17:17
  • 1
    For self-signed certs faketime works so much better than the complex alternatives. It's just two openssl commands and you're done. I had never heard of faketime, thanks!
    – CR.
    Mar 24, 2022 at 5:08
10

I'm almost surprised to find that the obvious thing works: whereas openssl takes as an argument the number of days for which the certificate should be valid, just supply a negative number!

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

Note that this actually results in something very strange: a certificate whose expiry timestamp precedes its start-of-validity timestamp. I don't actually recommend that you use this for your automated testing, since it's weird. You probably want a way to back-date the start-of-validity timestamp as well.

6
  • Well, to be fair, I had no idea you could use negative days. Apr 15, 2017 at 19:46
  • Can you not specify start date? Apr 15, 2017 at 21:43
  • @FreeSoftwareServers In the CSR you cannot; see the last part of my answer. Apr 15, 2017 at 21:51
  • More interstingly yet, wont the code barf finding such certificate? btw, I expanded my answer Apr 16, 2017 at 3:36
  • The start date is still the current date in the certificate. I made it using unix.stackexchange.com/a/588249/157973
    – Velu
    May 22, 2020 at 4:07
5

Or you could use something like this short python program ... (caveats apply)

It creates a key (test.key) and a certificate (test.crt) with inception time 10 years in the past (-10*365*24*60*60 seconds is -10 years) and expiration time 5 years in the past (-5*365*24*60*60).

Please note that it's a minimal demonstration program, so it does not bother to set any extensions, (e.g. basicConstraints) and uses fixed serial.

#!/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))
2
  • the code seem to be missing essential X.509 standard fields. Apr 16, 2017 at 3:41
  • 3
    This is very helpful. It gives me easier programmatic control over the certificate creation.
    – rlandster
    Apr 16, 2017 at 4:40
0

Step-1. Install faketime

sudo apt-get install faketime

Step-2. Generate expired certificate a day before currentdate.

faketime 'last friday 5 pm' /bin/bash -c 'openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 6 -nodes'

Step-3 Verify the certificate validity date

openssl x509 -noout -text -in cert.pem

Certificate Validity dates - Screenshot

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .