]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
apps: ca,req,x509: Add explicit start and end dates options
authorStephan Wurm <atomisirsi@gsklan.de>
Wed, 9 Aug 2023 07:07:46 +0000 (09:07 +0200)
committerTomas Mraz <tomas@openssl.org>
Tue, 9 Apr 2024 18:13:31 +0000 (20:13 +0200)
- Added options `-not_before` (start date) and `-not-after` (end date)
  for explicit setting of the validity period of a certificate in the
  apps `ca`, `req` and `x509`
- The new options accept time strings or "today"
- In app `ca`, use the new options as aliases of the already existing
  options `-startdate` and `-enddate`
- When used in apps `req` and `x509`, the end date must be >= the start
  date, in app `ca` end date < start date is also accepted
- In any case, `-not-after` overrides the `-days` option
- Added helper function `check_cert_time_string` to validate given
  certificate time strings
- Use the new helper function in apps `ca`, `req` and `x509`
- Moved redundant code for time string checking into `set_cert_times`
  helper function.
- Added tests for explicit start and end dates in apps `req` and `x509`
- test: Added auxiliary functions for parsing fields from `-text`
  formatted output to `tconversion.pl`
- CHANGES: Added to new section 3.4

Signed-off-by: Stephan Wurm <atomisirsi@gsklan.de>
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21716)

12 files changed:
CHANGES.md
apps/ca.c
apps/include/apps.h
apps/lib/apps.c
apps/req.c
apps/x509.c
doc/man1/openssl-ca.pod.in
doc/man1/openssl-req.pod.in
doc/man1/openssl-x509.pod.in
test/recipes/25-test_req.t
test/recipes/25-test_x509.t
test/recipes/tconversion.pl

index 5bbc34549e889104be618d20a0e63bc8da887052..a15321dda97147e8c091a67d2e33a6c009995eaf 100644 (file)
@@ -12,6 +12,7 @@ appropriate release branch.
 OpenSSL Releases
 ----------------
 
+ - [OpenSSL 3.4](#openssl-34)
  - [OpenSSL 3.3](#openssl-33)
  - [OpenSSL 3.2](#openssl-32)
  - [OpenSSL 3.1](#openssl-31)
@@ -28,7 +29,12 @@ OpenSSL 3.4
 
 ### Changes between 3.3 and 3.4 [xx XXX xxxx]
 
- * None yet
+ * Added options `-not_before` and `-not_after` for explicit setting
+   start and end dates of certificates created with the `req` and `x509`
+   apps. Added the same options also to `ca` app as alias for
+   `-startdate` and `-enddate` options.
+
+   *Stephan Wurm*
 
 OpenSSL 3.3
 -----------
index e12a8c2370cd431bfd7aef64cb3f3d9231113114..d6dd26a4139a835973df74b68b352c3c14f94718 100644 (file)
--- a/apps/ca.c
+++ b/apps/ca.c
@@ -150,7 +150,7 @@ typedef enum OPTION_choice {
     OPT_IN, OPT_INFORM, OPT_OUT, OPT_DATEOPT, OPT_OUTDIR, OPT_VFYOPT,
     OPT_SIGOPT, OPT_NOTEXT, OPT_BATCH, OPT_PRESERVEDN, OPT_NOEMAILDN,
     OPT_GENCRL, OPT_MSIE_HACK, OPT_CRL_LASTUPDATE, OPT_CRL_NEXTUPDATE,
-    OPT_CRLDAYS, OPT_CRLHOURS, OPT_CRLSEC,
+    OPT_CRLDAYS, OPT_CRLHOURS, OPT_CRLSEC, OPT_NOT_BEFORE, OPT_NOT_AFTER,
     OPT_INFILES, OPT_SS_CERT, OPT_SPKAC, OPT_REVOKE, OPT_VALID,
     OPT_EXTENSIONS, OPT_EXTFILE, OPT_STATUS, OPT_UPDATEDB, OPT_CRLEXTS,
     OPT_RAND_SERIAL, OPT_QUIET,
@@ -199,10 +199,13 @@ const OPTIONS ca_options[] = {
      "Always create a random serial; do not store it"},
     {"multivalue-rdn", OPT_MULTIVALUE_RDN, '-',
      "Deprecated; multi-valued RDNs support is always on."},
-    {"startdate", OPT_STARTDATE, 's', "Cert notBefore, YYMMDDHHMMSSZ"},
+    {"startdate", OPT_STARTDATE, 's',
+     "[CC]YYMMDDHHMMSSZ value for notBefore certificate field"},
+    {"not_before", OPT_NOT_BEFORE, 's', "An alias for -startdate"},
     {"enddate", OPT_ENDDATE, 's',
-     "YYMMDDHHMMSSZ cert notAfter (overrides -days)"},
-    {"days", OPT_DAYS, 'p', "Number of days to certify the cert for"},
+     "[CC]YYMMDDHHMMSSZ value for notAfter certificate field, overrides -days"},
+    {"not_after", OPT_NOT_AFTER, 's', "An alias for -enddate"},
+    {"days", OPT_DAYS, 'p', "Number of days from today to certify the cert for"},
     {"extensions", OPT_EXTENSIONS, 's',
      "Extension section (override value in config file)"},
     {"extfile", OPT_EXTFILE, '<',
@@ -359,9 +362,11 @@ opthelp:
             /* obsolete */
             break;
         case OPT_STARTDATE:
+        case OPT_NOT_BEFORE:
             startdate = opt_arg();
             break;
         case OPT_ENDDATE:
+        case OPT_NOT_AFTER:
             enddate = opt_arg();
             break;
         case OPT_DAYS:
@@ -874,22 +879,8 @@ end_of_options:
         if (startdate == NULL)
             startdate =
                 app_conf_try_string(conf, section, ENV_DEFAULT_STARTDATE);
-        if (startdate != NULL && !ASN1_TIME_set_string_X509(NULL, startdate)) {
-            BIO_printf(bio_err,
-                       "start date is invalid, it should be YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ\n");
-            goto end;
-        }
-        if (startdate == NULL)
-            startdate = "today";
-
         if (enddate == NULL)
             enddate = app_conf_try_string(conf, section, ENV_DEFAULT_ENDDATE);
-        if (enddate != NULL && !ASN1_TIME_set_string_X509(NULL, enddate)) {
-            BIO_printf(bio_err,
-                       "end date is invalid, it should be YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ\n");
-            goto end;
-        }
-
         if (days == 0) {
             if (!app_conf_try_number(conf, section, ENV_DEFAULT_DAYS, &days))
                 days = 0;
@@ -898,6 +889,9 @@ end_of_options:
             BIO_printf(bio_err, "cannot lookup how many days to certify for\n");
             goto end;
         }
+        if (days != 0 && enddate != NULL)
+            BIO_printf(bio_err,
+                       "Warning: -enddate or -not_after option overriding -days option\n");
 
         if (rand_ser) {
             if ((serial = BN_new()) == NULL || !rand_serial(serial, NULL)) {
@@ -1671,7 +1665,7 @@ static int do_body(X509 **xret, EVP_PKEY *pkey, X509 *x509,
             goto end;
     }
 
-    if (!set_cert_times(ret, startdate, enddate, days))
+    if (!set_cert_times(ret, startdate, enddate, days, 0))
         goto end;
 
     if (enddate != NULL) {
index a1b2cbbdc3d6ee4142e0c8a6da8528da271378fb..f1d5fccc416cbd4e7272c21c440fed96812a7b94 100644 (file)
@@ -82,8 +82,12 @@ int has_stdin_waiting(void);
 # endif
 
 void corrupt_signature(const ASN1_STRING *signature);
+
+/* Helpers for setting X509v3 certificate fields notBefore and notAfter */
+int check_cert_time_string(const char *time, const char *desc);
 int set_cert_times(X509 *x, const char *startdate, const char *enddate,
-                   int days);
+                   int days, int strict_compare_times);
+
 int set_crl_lastupdate(X509_CRL *crl, const char *lastupdate);
 int set_crl_nextupdate(X509_CRL *crl, const char *nextupdate,
                        long days, long hours, long secs);
index e04530ff4400692b0295785df6e1e409af3f1a26..c87f0f02f1f1d6f3087fb44542e2afd624d5d5f0 100644 (file)
@@ -3275,23 +3275,54 @@ void corrupt_signature(const ASN1_STRING *signature)
     s[signature->length - 1] ^= 0x1;
 }
 
+int check_cert_time_string(const char *time, const char *desc)
+{
+    if (time == NULL || strcmp(time, "today") == 0
+            || ASN1_TIME_set_string_X509(NULL, time))
+        return 1;
+    BIO_printf(bio_err,
+               "%s is invalid, it should be \"today\" or have format [CC]YYMMDDHHMMSSZ\n",
+               desc);
+    return 0;
+}
+
 int set_cert_times(X509 *x, const char *startdate, const char *enddate,
-                   int days)
+                   int days, int strict_compare_times)
 {
+    if (!check_cert_time_string(startdate, "start date"))
+        return 0;
+    if (!check_cert_time_string(enddate, "end date"))
+        return 0;
     if (startdate == NULL || strcmp(startdate, "today") == 0) {
-        if (X509_gmtime_adj(X509_getm_notBefore(x), 0) == NULL)
+        if (X509_gmtime_adj(X509_getm_notBefore(x), 0) == NULL) {
+            BIO_printf(bio_err, "Error setting notBefore certificate field\n");
             return 0;
+        }
     } else {
-        if (!ASN1_TIME_set_string_X509(X509_getm_notBefore(x), startdate))
+        if (!ASN1_TIME_set_string_X509(X509_getm_notBefore(x), startdate)) {
+            BIO_printf(bio_err, "Error setting notBefore certificate field\n");
             return 0;
+        }
+    }
+    if (enddate != NULL && strcmp(enddate, "today") == 0) {
+        enddate = NULL;
+        days = 0;
     }
     if (enddate == NULL) {
-        if (X509_time_adj_ex(X509_getm_notAfter(x), days, 0, NULL)
-            == NULL)
+        if (X509_time_adj_ex(X509_getm_notAfter(x), days, 0, NULL) == NULL) {
+            BIO_printf(bio_err, "Error setting notAfter certificate field\n");
             return 0;
+        }
     } else if (!ASN1_TIME_set_string_X509(X509_getm_notAfter(x), enddate)) {
+        BIO_printf(bio_err, "Error setting notAfter certificate field\n");
         return 0;
     }
+    if (ASN1_TIME_compare(X509_get0_notAfter(x), X509_get0_notBefore(x)) < 0) {
+        BIO_printf(bio_err, "%s: end date before start date\n",
+                   strict_compare_times ? "Error" : "Warning");
+        if (strict_compare_times)
+            return 0;
+    }
     return 1;
 }
 
index b54a4897227df0c95f591323ce55fe937635cc54..dfa8319156e6389653ab481f6e2fd1836ef25152 100644 (file)
@@ -43,7 +43,7 @@
 
 #define DEFAULT_KEY_LENGTH 2048
 #define MIN_KEY_LENGTH     512
-#define DEFAULT_DAYS       30 /* default cert validity period in days */
+#define DEFAULT_DAYS       30 /* default certificate validity period in days */
 #define UNSET_DAYS         -2 /* -1 may be used for testing expiration checks */
 #define EXT_COPY_UNSET     -1
 
@@ -87,7 +87,7 @@ typedef enum OPTION_choice {
     OPT_VERIFY, OPT_NOENC, OPT_NODES, OPT_NOOUT, OPT_VERBOSE, OPT_UTF8,
     OPT_NAMEOPT, OPT_REQOPT, OPT_SUBJ, OPT_SUBJECT, OPT_TEXT,
     OPT_X509, OPT_X509V1, OPT_CA, OPT_CAKEY,
-    OPT_MULTIVALUE_RDN, OPT_DAYS, OPT_SET_SERIAL,
+    OPT_MULTIVALUE_RDN, OPT_NOT_BEFORE, OPT_NOT_AFTER, OPT_DAYS, OPT_SET_SERIAL,
     OPT_COPY_EXTENSIONS, OPT_EXTENSIONS, OPT_REQEXTS, OPT_ADDEXT,
     OPT_PRECERT, OPT_MD,
     OPT_SECTION, OPT_QUIET,
@@ -127,7 +127,11 @@ const OPTIONS req_options[] = {
      "Print the subject of the output request or cert"},
     {"multivalue-rdn", OPT_MULTIVALUE_RDN, '-',
      "Deprecated; multi-valued RDNs support is always on."},
-    {"days", OPT_DAYS, 'p', "Number of days cert is valid for"},
+    {"not_before", OPT_NOT_BEFORE, 's',
+     "[CC]YYMMDDHHMMSSZ value for notBefore certificate field"},
+    {"not_after", OPT_NOT_AFTER, 's',
+     "[CC]YYMMDDHHMMSSZ value for notAfter certificate field, overrides -days"},
+    {"days", OPT_DAYS, 'p', "Number of days certificate is valid for"},
     {"set_serial", OPT_SET_SERIAL, 's', "Serial number to use"},
     {"copy_extensions", OPT_COPY_EXTENSIONS, 's',
      "copy extensions from request when using -x509"},
@@ -259,6 +263,7 @@ int req_main(int argc, char **argv)
     char *template = default_config_file, *keyout = NULL;
     const char *keyalg = NULL;
     OPTION_CHOICE o;
+    char *not_before = NULL, *not_after = NULL;
     int days = UNSET_DAYS;
     int ret = 1, gen_x509 = 0, i = 0, newreq = 0, verbose = 0, progress = 1;
     int informat = FORMAT_UNDEF, outformat = FORMAT_PEM, keyform = FORMAT_UNDEF;
@@ -423,9 +428,15 @@ int req_main(int argc, char **argv)
         case OPT_CAKEY:
             CAkeyfile = opt_arg();
             break;
+        case OPT_NOT_BEFORE:
+            not_before = opt_arg();
+            break;
+        case OPT_NOT_AFTER:
+            not_after = opt_arg();
+            break;
         case OPT_DAYS:
             days = atoi(opt_arg());
-            if (days < -1) {
+            if (days <= UNSET_DAYS) {
                 BIO_printf(bio_err, "%s: -days parameter arg must be >= -1\n",
                            prog);
                 goto end;
@@ -494,9 +505,13 @@ int req_main(int argc, char **argv)
 
     if (!gen_x509) {
         if (days != UNSET_DAYS)
-            BIO_printf(bio_err, "Ignoring -days without -x509; not generating a certificate\n");
+            BIO_printf(bio_err, "Warning: Ignoring -days without -x509; not generating a certificate\n");
+        if (not_before != NULL)
+            BIO_printf(bio_err, "Warning: Ignoring -not_before without -x509; not generating a certificate\n");
+        if (not_after != NULL)
+            BIO_printf(bio_err, "Warning: Ignoring -not_after without -x509; not generating a certificate\n");
         if (ext_copy == EXT_COPY_NONE)
-            BIO_printf(bio_err, "Ignoring -copy_extensions 'none' when -x509 is not given\n");
+            BIO_printf(bio_err, "Warning: Ignoring -copy_extensions 'none' when -x509 is not given\n");
     }
     if (infile == NULL) {
         if (gen_x509)
@@ -802,10 +817,11 @@ int req_main(int argc, char **argv)
 
             if (!X509_set_issuer_name(new_x509, issuer))
                 goto end;
-            if (days == UNSET_DAYS) {
+            if (days == UNSET_DAYS)
                 days = DEFAULT_DAYS;
-            }
-            if (!set_cert_times(new_x509, NULL, NULL, days))
+            else if (not_after != NULL)
+                BIO_printf(bio_err,"Warning: -not_after option overriding -days option\n");
+            if (!set_cert_times(new_x509, not_before, not_after, days, 1))
                 goto end;
             if (!X509_set_subject_name(new_x509, n_subj))
                 goto end;
index d96e7819b27d0231e31cfadf02f8f20d7fb65d1c..cd5b7bf796bfeb2b8b5811c3db6583dc849f9db8 100644 (file)
@@ -29,8 +29,8 @@
 
 #undef POSTFIX
 #define POSTFIX ".srl"
-#define DEFAULT_DAYS    30 /* default cert validity period in days */
-#define UNSET_DAYS      -2 /* -1 is used for testing expiration checks */
+#define DEFAULT_DAYS       30 /* default certificate validity period in days */
+#define UNSET_DAYS         -2 /* -1 may be used for testing expiration checks */
 #define EXT_COPY_UNSET     -1
 
 static int callb(int ok, X509_STORE_CTX *ctx);
@@ -54,6 +54,7 @@ typedef enum OPTION_choice {
     OPT_CLRREJECT, OPT_ALIAS, OPT_CACREATESERIAL, OPT_CLREXT, OPT_OCSPID,
     OPT_SUBJECT_HASH_OLD, OPT_ISSUER_HASH_OLD, OPT_COPY_EXTENSIONS,
     OPT_BADSIG, OPT_MD, OPT_ENGINE, OPT_NOCERT, OPT_PRESERVE_DATES,
+    OPT_NOT_BEFORE, OPT_NOT_AFTER,
     OPT_R_ENUM, OPT_PROV_ENUM, OPT_EXT
 } OPTION_CHOICE;
 
@@ -135,6 +136,10 @@ const OPTIONS x509_options[] = {
      "Serial number to use, overrides -CAserial"},
     {"next_serial", OPT_NEXT_SERIAL, '-',
      "Increment current certificate serial number"},
+    {"not_before", OPT_NOT_BEFORE, 's',
+     "[CC]YYMMDDHHMMSSZ value for notBefore certificate field"},
+    {"not_after", OPT_NOT_AFTER, 's',
+     "[CC]YYMMDDHHMMSSZ value for notAfter certificate field, overrides -days"},
     {"days", OPT_DAYS, 'n',
      "Number of days until newly generated certificate expires - default 30"},
     {"preserve_dates", OPT_PRESERVE_DATES, '-',
@@ -279,7 +284,7 @@ int x509_main(int argc, char **argv)
     char *ext_names = NULL;
     char *extsect = NULL, *extfile = NULL, *passin = NULL, *passinarg = NULL;
     char *infile = NULL, *outfile = NULL, *privkeyfile = NULL, *CAfile = NULL;
-    char *prog;
+    char *prog, *not_before = NULL, *not_after = NULL;
     int days = UNSET_DAYS; /* not explicitly set */
     int x509toreq = 0, modulus = 0, print_pubkey = 0, pprint = 0;
     int CAformat = FORMAT_UNDEF, CAkeyformat = FORMAT_UNDEF;
@@ -376,9 +381,15 @@ int x509_main(int argc, char **argv)
             if (!vfyopts || !sk_OPENSSL_STRING_push(vfyopts, opt_arg()))
                 goto opthelp;
             break;
+        case OPT_NOT_BEFORE:
+            not_before = opt_arg();
+            break;
+        case OPT_NOT_AFTER:
+            not_after = opt_arg();
+            break;
         case OPT_DAYS:
             days = atoi(opt_arg());
-            if (days < -1) {
+            if (days <= UNSET_DAYS) {
                 BIO_printf(bio_err, "%s: -days parameter arg must be >= -1\n",
                            prog);
                 goto err;
@@ -610,12 +621,22 @@ int x509_main(int argc, char **argv)
     if (!opt_check_md(digest))
         goto opthelp;
 
+    if (preserve_dates && not_before != NULL) {
+        BIO_printf(bio_err, "Cannot use -preserve_dates with -not_before option\n");
+        goto err;
+    }
+    if (preserve_dates && not_after != NULL) {
+        BIO_printf(bio_err, "Cannot use -preserve_dates with -not_after option\n");
+        goto err;
+    }
     if (preserve_dates && days != UNSET_DAYS) {
         BIO_printf(bio_err, "Cannot use -preserve_dates with -days option\n");
         goto err;
     }
     if (days == UNSET_DAYS)
         days = DEFAULT_DAYS;
+    else if (not_after != NULL)
+        BIO_printf(bio_err, "Warning: -not_after option overriding -days option\n");
 
     if (!app_passwd(passinarg, NULL, &passin, NULL)) {
         BIO_printf(bio_err, "Error getting password\n");
@@ -837,7 +858,7 @@ int x509_main(int argc, char **argv)
         goto end;
 
     if (reqfile || newcert || privkey != NULL || CAfile != NULL) {
-        if (!preserve_dates && !set_cert_times(x, NULL, NULL, days))
+        if (!preserve_dates && !set_cert_times(x, not_before, not_after, days, 1))
             goto end;
         if (fissu != NULL) {
             if (!X509_set_issuer_name(x, fissu))
index fe09f85c2c1cad93b23180d7fce1a848358d5cc6..2bb7258237f0f4cc38473ab84532f9454caee296 100644 (file)
@@ -30,7 +30,9 @@ B<openssl> B<ca>
 [B<-crlsec> I<seconds>]
 [B<-crlexts> I<section>]
 [B<-startdate> I<date>]
+[B<-not_before> I<date>]
 [B<-enddate> I<date>]
+[B<-not_after> I<date>]
 [B<-days> I<arg>]
 [B<-md> I<arg>]
 [B<-policy> I<arg>]
@@ -226,23 +228,32 @@ Don't output the text form of a certificate to the output file.
 Specify the date output format. Values are: rfc_822 and iso_8601.
 Defaults to rfc_822.
 
-=item B<-startdate> I<date>
+=item B<-startdate> I<date>, B<-not_before> I<date>
 
 This allows the start date to be explicitly set. The format of the
 date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure), or
 YYYYMMDDHHMMSSZ (the same as an ASN1 GeneralizedTime structure). In
 both formats, seconds SS and timezone Z must be present.
+Alternatively, you can also use "today".
 
-=item B<-enddate> I<date>
+=item B<-enddate> I<date>, B<-not_after> I<date>
 
 This allows the expiry date to be explicitly set. The format of the
 date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure), or
 YYYYMMDDHHMMSSZ (the same as an ASN1 GeneralizedTime structure). In
 both formats, seconds SS and timezone Z must be present.
+Alternatively, you can also use "today".
+
+This overrides the B<-days> option.
 
 =item B<-days> I<arg>
 
-The number of days to certify the certificate for.
+The number of days from today to certify the certificate for.
+
+Regardless of the option B<-not_before>, the days are always counted from
+today.
+When used together with the option B<-not_after>/B<-startdate>, the explicit
+expiry date takes precedence.
 
 =item B<-md> I<alg>
 
@@ -502,7 +513,7 @@ not necessary anymore, see the L</HISTORY> section.
 
 =item B<default_days>
 
-The same as the B<-days> option. The number of days to certify
+The same as the B<-days> option. The number of days from today to certify
 a certificate for.
 
 =item B<default_startdate>
index 77a1dab025cbad29a5f1f42d43875d393b7827d7..808801348fd59546f22954bc91ce67ebdafdff32 100644 (file)
@@ -36,6 +36,8 @@ B<openssl> B<req>
 [B<-x509v1>]
 [B<-CA> I<filename>|I<uri>]
 [B<-CAkey> I<filename>|I<uri>]
+[B<-not_before> I<date>]
+[B<-not_after> I<date>]
 [B<-days> I<n>]
 [B<-set_serial> I<n>]
 [B<-newhdr>]
@@ -327,12 +329,37 @@ Sets the "CA" private key to sign a certificate with.
 The private key must match the public key of the certificate given with B<-CA>.
 If this option is not provided then the key must be present in the B<-CA> input.
 
+=item B<-not_before> I<date>
+
+When B<-x509> is in use this allows the start date to be explicitly set,
+otherwise it is ignored. The format of I<date> is YYMMDDHHMMSSZ (the
+same as an ASN1 UTCTime structure), or YYYYMMDDHHMMSSZ (the same as an
+ASN1 GeneralizedTime structure). In both formats, seconds SS and
+timezone Z must be present.
+Alternatively, you can also use "today".
+
+=item B<-not_after> I<date>
+
+When B<-x509> is in use this allows the expiry date to be explicitly
+set, otherwise it is ignored. The format of I<date> is YYMMDDHHMMSSZ
+(the same as an ASN1 UTCTime structure), or YYYYMMDDHHMMSSZ (the same as
+an ASN1 GeneralizedTime structure). In both formats, seconds SS and
+timezone Z must be present.
+Alternatively, you can also use "today".
+
+This overrides the B<-days> option.
+
 =item B<-days> I<n>
 
-When B<-x509> is in use this specifies the number of
-days to certify the certificate for, otherwise it is ignored. I<n> should
+When B<-x509> is in use this specifies the number of days from today to
+certify the certificate for, otherwise it is ignored. I<n> should
 be a positive integer. The default is 30 days.
 
+Regardless of the option B<-not_before>, the days are always counted from
+today.
+When used together with the option B<-not_after>, the explicit expiry
+date takes precedence.
+
 =item B<-set_serial> I<n>
 
 Serial number to use when outputting a self-signed certificate.
index c22f0d681d496ab25dc011b28a997b48a14ab1f5..b72f898f8bc9cc3a60e8902549e7416cc9887c6d 100644 (file)
@@ -54,6 +54,8 @@ B<openssl> B<x509>
 [B<-checkip> I<ipaddr>]
 [B<-set_serial> I<n>]
 [B<-next_serial>]
+[B<-not_before> I<date>]
+[B<-not_after> I<date>]
 [B<-days> I<arg>]
 [B<-preserve_dates>]
 [B<-set_issuer> I<arg>]
@@ -183,6 +185,8 @@ It sets the issuer name to the subject name (i.e., makes it self-issued).
 Unless the B<-preserve_dates> option is supplied,
 it sets the validity start date to the current time
 and the end date to a value determined by the B<-days> option.
+Start date and end date can also be explicitly supplied with options
+B<-not_before> and B<-not_after>.
 
 =item B<-signkey> I<filename>|I<uri>
 
@@ -376,17 +380,40 @@ The serial number can be decimal or hex (if preceded by C<0x>).
 
 Set the serial to be one more than the number in the certificate.
 
+=item B<-not_before> I<date>
+
+This allows the start date to be explicitly set. The format of the
+date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure), or
+YYYYMMDDHHMMSSZ (the same as an ASN1 GeneralizedTime structure). In
+both formats, seconds SS and timezone Z must be present.
+Alternatively, you can also use "today".
+
+Cannot be used together with the B<-preserve_dates> option.
+
+=item B<-not_after> I<date>
+
+This allows the expiry date to be explicitly set. The format of the
+date is YYMMDDHHMMSSZ (the same as an ASN1 UTCTime structure), or
+YYYYMMDDHHMMSSZ (the same as an ASN1 GeneralizedTime structure). In
+both formats, seconds SS and timezone Z must be present.
+Alternatively, you can also use "today".
+
+Cannot be used together with the B<-preserve_dates> option.
+This overrides the option B<-days>.
+
 =item B<-days> I<arg>
 
-Specifies the number of days until a newly generated certificate expires.
+Specifies the number of days from today until a newly generated certificate expires.
 The default is 30.
-Cannot be used together with the B<-preserve_dates> option.
+
+Cannot be used together with the option B<-preserve_dates>.
+If option B<-not_after> is set, the explicit expiry date takes precedence.
 
 =item B<-preserve_dates>
 
 When signing a certificate, preserve "notBefore" and "notAfter" dates of any
 input certificate instead of adjusting them to current time and duration.
-Cannot be used together with the B<-days> option.
+Cannot be used together with the options B<-days>, B<-not_before> and B<-not_after>.
 
 =item B<-set_issuer> I<arg>
 
index 8c168b50f398ceea03c6c5d337896450b606a8e3..f68f443fe455227c4e3680d0b71d21bfcc3e4770 100644 (file)
@@ -15,7 +15,7 @@ use OpenSSL::Test qw/:DEFAULT srctop_file/;
 
 setup("test_req");
 
-plan tests => 108;
+plan tests => 109;
 
 require_ok(srctop_file('test', 'recipes', 'tconversion.pl'));
 
@@ -607,3 +607,15 @@ ok(run(app(["openssl", "req", "-x509", "-new", "-days", "365",
 # Verify cert
 ok(run(app(["openssl", "x509", "-in", "testreq-cert.pem",
             "-noout", "-text"])), "cert verification");
+
+# Generate cert with explicit start and end dates
+my $today = strftime("%Y-%m-%d", localtime);
+my $cert = "self-signed_explicit_date.pem";
+ok(run(app(["openssl", "req", "-x509", "-new", "-text",
+            "-config", srctop_file('test', 'test.cnf'),
+            "-key", srctop_file("test", "testrsa.pem"),
+            "-not_before", "today",
+            "-not_after", "today",
+            "-out", $cert]))
+&& get_not_before_date($cert) eq $today
+&& get_not_after_date($cert) eq $today, "explicit start and end dates");
index 739ac746ba743e150fe645ebedda7d9cd0a0297f..f2b818b73c565d15190f9bb98311b53ed5331d6f 100644 (file)
@@ -16,7 +16,7 @@ use OpenSSL::Test qw/:DEFAULT srctop_file/;
 
 setup("test_x509");
 
-plan tests => 46;
+plan tests => 51;
 
 # Prevent MSys2 filename munging for arguments that look like file paths but
 # aren't
@@ -187,20 +187,6 @@ ok(!run(app(["openssl", "x509", "-noout", "-dates", "-dateopt", "invalid_format"
             "-in", srctop_file("test/certs", "ca-cert.pem")])),
    "Run with invalid -dateopt format");
 
-# extracts issuer from a -text formatted-output
-sub get_issuer {
-    my $f = shift(@_);
-    my $issuer = "";
-    open my $fh, $f or die;
-    while (my $line = <$fh>) {
-        if ($line =~ /Issuer:/) {
-            $issuer = $line;
-        }
-    }
-    close $fh;
-    return $issuer;
-}
-
 # Tests for signing certs (broken in 1.1.1o)
 my $a_key = "a-key.pem";
 my $a_cert = "a-cert.pem";
@@ -224,7 +210,7 @@ ok(run(app(["openssl", "x509", "-in", $a_cert, "-CA", $ca_cert,
             "-CAkey", $ca_key, "-set_serial", "1234567890",
             "-preserve_dates", "-sha256", "-text", "-out", $a2_cert])));
 # verify issuer is CA
-ok (get_issuer($a2_cert) =~ /CN=ca.example.com/);
+ok(get_issuer($a2_cert) =~ /CN=ca.example.com/);
 
 my $in_csr = srctop_file('test', 'certs', 'x509-check.csr');
 my $in_key = srctop_file('test', 'certs', 'x509-check-key.pem');
@@ -268,6 +254,51 @@ ok(run(app(["openssl", "x509", "-req", "-text", "-CAcreateserial",
             "-in", $b_csr])));
 ok(-e $ca_serial_dot_in_dir);
 
+# Tests for explict start and end dates of certificates
+my $today;
+my $enddate;
+$today = strftime("%Y-%m-%d", localtime);
+ok(run(app(["openssl", "x509", "-req", "-text",
+           "-key", $b_key,
+           "-not_before", "20231031000000Z",
+           "-not_after", "today",
+            "-in", $b_csr, "-out", $b_cert]))
+&& get_not_before($b_cert) =~ /Oct 31 00:00:00 2023 GMT/
+&& get_not_after_date($b_cert) eq $today);
+# explicit start and end dates
+ok(run(app(["openssl", "x509", "-req", "-text",
+           "-key", $b_key,
+           "-not_before", "20231031000000Z",
+           "-not_after", "20231231000000Z",
+           "-days", "99",
+            "-in", $b_csr, "-out", $b_cert]))
+&& get_not_before($b_cert) =~ /Oct 31 00:00:00 2023 GMT/
+&& get_not_after($b_cert) =~ /Dec 31 00:00:00 2023 GMT/);
+# start date today and days
+$today = strftime("%Y-%m-%d", localtime);
+$enddate = strftime("%Y-%m-%d", localtime(time + 99 * 24 * 60 * 60));
+ok(run(app(["openssl", "x509", "-req", "-text",
+           "-key", $b_key,
+           "-not_before", "today",
+           "-days", "99",
+            "-in", $b_csr, "-out", $b_cert]))
+&& get_not_before_date($b_cert) eq $today
+&& get_not_after_date($b_cert) eq $enddate);
+# end date before start date
+ok(!run(app(["openssl", "x509", "-req", "-text",
+             "-key", $b_key,
+             "-not_before", "today",
+             "-not_after", "20231031000000Z",
+              "-in", $b_csr, "-out", $b_cert])));
+# default days option
+$today = strftime("%Y-%m-%d", localtime);
+$enddate = strftime("%Y-%m-%d", localtime(time + 30 * 24 * 60 * 60));
+ok(run(app(["openssl", "x509", "-req", "-text",
+           "-key", $b_key,
+            "-in", $b_csr, "-out", $b_cert]))
+&& get_not_before_date($b_cert) eq $today
+&& get_not_after_date($b_cert) eq $enddate);
+
 SKIP: {
     skip "EC is not supported by this OpenSSL build", 1
         if disabled("ec");
index 6f10758f29accfd9e3e309d9e0d46328655bfa88..a2548eca7d03b326f0a31aa687d26ecff546d66b 100644 (file)
@@ -13,6 +13,8 @@ use warnings;
 use File::Compare qw/compare_text/;
 use File::Copy;
 use OpenSSL::Test qw/:DEFAULT/;
+use Time::Piece;
+use POSIX qw(strftime);
 
 my %conversionforms = (
     # Default conversion forms.  Other series may be added with
@@ -176,4 +178,44 @@ sub cert_ext_has_n_different_lines {
     # not unlinking $out
 }
 
+# extracts string value of certificate field from a -text formatted-output
+sub get_field {
+    my ($f, $field) = @_;
+    my $string = "";
+    open my $fh, $f or die;
+    while (my $line = <$fh>) {
+        if ($line =~ /$field:\s+(.*)/) {
+            $string = $1;
+        }
+    }
+    close $fh;
+    return $string;
+}
+
+sub get_issuer {
+    return get_field(@_, "Issuer");
+}
+
+sub get_not_before {
+    return get_field(@_, "Not Before");
+}
+
+# Date as yyyy-mm-dd
+sub get_not_before_date {
+    return Time::Piece->strptime(
+        get_not_before(@_),
+        "%b %d %T %Y %Z")->date;
+}
+
+sub get_not_after {
+    return get_field(@_, "Not After ");
+}
+
+# Date as yyyy-mm-dd
+sub get_not_after_date {
+    return Time::Piece->strptime(
+        get_not_after(@_),
+        "%b %d %T %Y %Z")->date;
+}
+
 1;