24

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.

32

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.

  • 1
    +1. I knew someone would come along with something better than what I wrote :) – Celada Apr 15 '17 at 19:41
  • 2
    There's also a similar program called datefudge. – Wyzard --Stop Harming Monica-- Apr 15 '17 at 21:59
  • @Wyzard Thanks, indeed I found it in Debian; interestingly enough, the manual states that while it also changes system calls to functions such as time(2), it does not influence fstat(2). – Rui F Ribeiro Apr 15 '17 at 22:03
  • 1
    Both faketime and datefudge work beautifully on my Debian jessie system. – rlandster Apr 15 '17 at 22:55
  • 1
    OTOH: +5 for finding out where to set those dates! – guntbert Apr 16 '17 at 17:17
8

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.

  • Well, to be fair, I had no idea you could use negative days. – Rui F Ribeiro Apr 15 '17 at 19:46
  • Can you not specify start date? – FreeSoftwareServers Apr 15 '17 at 21:43
  • @FreeSoftwareServers In the CSR you cannot; see the last part of my answer. – Rui F Ribeiro Apr 15 '17 at 21:51
  • More interstingly yet, wont the code barf finding such certificate? btw, I expanded my answer – Rui F Ribeiro Apr 16 '17 at 3:36
3

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.