Browse Source

apps/ca: allow CRL lastUpdate/nextUpdate fields to be specified

When generating a CRL using the "ca" utility, allow values for the
lastUpdate and nextUpdate fields to be specified using the command line
options -crl_lastupdate and -crl_nextupdate respectively.

Reviewed-by: Tomas Mraz <tmraz@fedoraproject.org>
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/12784)
master
Chris Novakovic 2 years ago
committed by Dmitry Belyavskiy
parent
commit
64713cb10d
7 changed files with 331 additions and 16 deletions
  1. +6
    -0
      CHANGES.md
  2. +24
    -13
      apps/ca.c
  3. +3
    -0
      apps/include/apps.h
  4. +51
    -0
      apps/lib/apps.c
  5. +19
    -1
      doc/man1/openssl-ca.pod.in
  6. +201
    -2
      test/recipes/80-test_ca.t
  7. +27
    -0
      test/recipes/80-test_ca_data/revoked.key

+ 6
- 0
CHANGES.md View File

@ -1158,6 +1158,12 @@ OpenSSL 3.0
*Richard Levitte*
* Added the options `-crl_lastupdate` and `-crl_nextupdate` to `openssl ca`,
allowing the `lastUpdate` and `nextUpdate` fields in the generated CRL to
be set explicitly.
*Chris Novakovic*
* Added support for Linux Kernel TLS data-path. The Linux Kernel data-path
improves application performance by removing data copies and providing
applications with zero-copy system calls such as sendfile and splice.


+ 24
- 13
apps/ca.c View File

@ -155,7 +155,8 @@ typedef enum OPTION_choice {
OPT_KEY, OPT_CERT, OPT_CERTFORM, OPT_SELFSIGN,
OPT_IN, OPT_INFORM, OPT_OUT, OPT_OUTDIR, OPT_VFYOPT,
OPT_SIGOPT, OPT_NOTEXT, OPT_BATCH, OPT_PRESERVEDN, OPT_NOEMAILDN,
OPT_GENCRL, OPT_MSIE_HACK, OPT_CRLDAYS, OPT_CRLHOURS, OPT_CRLSEC,
OPT_GENCRL, OPT_MSIE_HACK, OPT_CRL_LASTUPDATE, OPT_CRL_NEXTUPDATE,
OPT_CRLDAYS, OPT_CRLHOURS, OPT_CRLSEC,
OPT_INFILES, OPT_SS_CERT, OPT_SPKAC, OPT_REVOKE, OPT_VALID,
OPT_EXTENSIONS, OPT_EXTFILE, OPT_STATUS, OPT_UPDATEDB, OPT_CRLEXTS,
OPT_RAND_SERIAL,
@ -241,6 +242,10 @@ const OPTIONS ca_options[] = {
"sets compromise time to val and the revocation reason to keyCompromise"},
{"crl_CA_compromise", OPT_CRL_CA_COMPROMISE, 's',
"sets compromise time to val and the revocation reason to CACompromise"},
{"crl_lastupdate", OPT_CRL_LASTUPDATE, 's',
"Sets the CRL lastUpdate time to val (YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ)"},
{"crl_nextupdate", OPT_CRL_NEXTUPDATE, 's',
"Sets the CRL nextUpdate time to val (YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ)"},
{"crldays", OPT_CRLDAYS, 'p', "Days until the next CRL is due"},
{"crlhours", OPT_CRLHOURS, 'p', "Hours until the next CRL is due"},
{"crlsec", OPT_CRLSEC, 'p', "Seconds until the next CRL is due"},
@ -262,7 +267,6 @@ int ca_main(int argc, char **argv)
EVP_PKEY *pkey = NULL;
BIO *in = NULL, *out = NULL, *Sout = NULL;
ASN1_INTEGER *tmpser;
ASN1_TIME *tmptm;
CA_DB *db = NULL;
DB_ATTR db_attr;
STACK_OF(CONF_VALUE) *attribs = NULL;
@ -291,6 +295,7 @@ int ca_main(int argc, char **argv)
int keyformat = FORMAT_PEM, multirdn = 1, notext = 0, output_der = 0;
int ret = 1, email_dn = 1, req = 0, verbose = 0, gencrl = 0, dorevoke = 0;
int rand_ser = 0, i, j, selfsign = 0, def_nid, def_ret;
char *crl_lastupdate = NULL, *crl_nextupdate = NULL;
long crldays = 0, crlhours = 0, crlsec = 0, days = 0;
unsigned long chtype = MBSTRING_ASC, certopt = 0;
X509 *x509 = NULL, *x509p = NULL, *x = NULL;
@ -425,6 +430,12 @@ opthelp:
case OPT_MSIE_HACK:
msie_hack = 1;
break;
case OPT_CRL_LASTUPDATE:
crl_lastupdate = opt_arg();
break;
case OPT_CRL_NEXTUPDATE:
crl_nextupdate = opt_arg();
break;
case OPT_CRLDAYS:
crldays = atol(opt_arg());
break;
@ -1146,7 +1157,8 @@ end_of_options:
crlhours = 0;
ERR_clear_error();
}
if ((crldays == 0) && (crlhours == 0) && (crlsec == 0)) {
if ((crl_nextupdate == NULL) &&
(crldays == 0) && (crlhours == 0) && (crlsec == 0)) {
BIO_printf(bio_err,
"cannot lookup how long until the next CRL is issued\n");
goto end;
@ -1159,19 +1171,18 @@ end_of_options:
if (!X509_CRL_set_issuer_name(crl, X509_get_subject_name(x509)))
goto end;
tmptm = ASN1_TIME_new();
if (tmptm == NULL
|| X509_gmtime_adj(tmptm, 0) == NULL
|| !X509_CRL_set1_lastUpdate(crl, tmptm)
|| X509_time_adj_ex(tmptm, crldays, crlhours * 60 * 60 + crlsec,
NULL) == NULL) {
BIO_puts(bio_err, "error setting CRL nextUpdate\n");
ASN1_TIME_free(tmptm);
if (!set_crl_lastupdate(crl, crl_lastupdate)) {
BIO_puts(bio_err, "error setting CRL lastUpdate\n");
ret = 1;
goto end;
}
X509_CRL_set1_nextUpdate(crl, tmptm);
ASN1_TIME_free(tmptm);
if (!set_crl_nextupdate(crl, crl_nextupdate,
crldays, crlhours, crlsec)) {
BIO_puts(bio_err, "error setting CRL nextUpdate\n");
ret = 1;
goto end;
}
for (i = 0; i < sk_OPENSSL_PSTRING_num(db->db->data); i++) {
pp = sk_OPENSSL_PSTRING_value(db->db->data, i);


+ 3
- 0
apps/include/apps.h View File

@ -75,6 +75,9 @@ int has_stdin_waiting(void);
void corrupt_signature(const ASN1_STRING *signature);
int set_cert_times(X509 *x, const char *startdate, const char *enddate,
int days);
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);
typedef struct args_st {
int size;


+ 51
- 0
apps/lib/apps.c View File

@ -2704,6 +2704,57 @@ int set_cert_times(X509 *x, const char *startdate, const char *enddate,
return 1;
}
int set_crl_lastupdate(X509_CRL *crl, const char *lastupdate)
{
int ret = 0;
ASN1_TIME *tm = ASN1_TIME_new();
if (tm == NULL)
goto end;
if (lastupdate == NULL) {
if (X509_gmtime_adj(tm, 0) == NULL)
goto end;
} else {
if (!ASN1_TIME_set_string_X509(tm, lastupdate))
goto end;
}
if (!X509_CRL_set1_lastUpdate(crl, tm))
goto end;
ret = 1;
end:
ASN1_TIME_free(tm);
return ret;
}
int set_crl_nextupdate(X509_CRL *crl, const char *nextupdate,
long days, long hours, long secs)
{
int ret = 0;
ASN1_TIME *tm = ASN1_TIME_new();
if (tm == NULL)
goto end;
if (nextupdate == NULL) {
if (X509_time_adj_ex(tm, days, hours * 60 * 60 + secs, NULL) == NULL)
goto end;
} else {
if (!ASN1_TIME_set_string_X509(tm, nextupdate))
goto end;
}
if (!X509_CRL_set1_nextUpdate(crl, tm))
goto end;
ret = 1;
end:
ASN1_TIME_free(tm);
return ret;
}
void make_uppercase(char *string)
{
int i;


+ 19
- 1
doc/man1/openssl-ca.pod.in View File

@ -22,6 +22,8 @@ B<openssl> B<ca>
[B<-crl_hold> I<instruction>]
[B<-crl_compromise> I<time>]
[B<-crl_CA_compromise> I<time>]
[B<-crl_lastupdate> I<date>]
[B<-crl_nextupdate> I<date>]
[B<-crldays> I<days>]
[B<-crlhours> I<hours>]
[B<-crlsec> I<seconds>]
@ -337,6 +339,20 @@ This option has been deprecated and has no effect.
This option generates a CRL based on information in the index file.
=item B<-crl_lastupdate> I<time>
Allows the value of the CRL's lastUpdate field to be explicitly set; if
this option is not present, the current time is used. Accepts times in
YYMMDDHHMMSSZ format (the same as an ASN1 UTCTime structure) or
YYYYMMDDHHMMSSZ format (the same as an ASN1 GeneralizedTime structure).
=item B<-crl_nextupdate> I<time>
Allows the value of the CRL's nextUpdate field to be explicitly set; if
this option is present, any values given for B<-crldays>, B<-crlhours>
and B<-crlsec> are ignored. Accepts times in the same formats as
B<-crl_lastupdate>.
=item B<-crldays> I<num>
The number of days before the next CRL is due. That is the days from
@ -781,7 +797,9 @@ then even if a certificate is issued with CA:TRUE it will not be valid.
Since OpenSSL 1.1.1, the program follows RFC5280. Specifically,
certificate validity period (specified by any of B<-startdate>,
B<-enddate> and B<-days>) will be encoded as UTCTime if the dates are
B<-enddate> and B<-days>) and CRL last/next update time (specified by
any of B<-crl_lastupdate>, B<-crl_nextupdate>, B<-crldays>, B<-crlhours>
and B<-crlsec>) will be encoded as UTCTime if the dates are
earlier than year 2049 (included), and as GeneralizedTime if the dates
are in year 2050 or later.


+ 201
- 2
test/recipes/80-test_ca.t View File

@ -12,8 +12,9 @@ use warnings;
use POSIX;
use File::Path 2.00 qw/rmtree/;
use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file/;
use OpenSSL::Test qw/:DEFAULT cmdstr data_file srctop_file/;
use OpenSSL::Test::Utils;
use Time::Local qw/timegm/;
setup("test_ca");
@ -26,7 +27,7 @@ my $std_openssl_cnf = '"'
rmtree("demoCA", { safe => 0 });
plan tests => 6;
plan tests => 15;
SKIP: {
$ENV{OPENSSL_CONFIG} = '-config ' . $cnf;
skip "failed creating CA structure", 4
@ -71,6 +72,158 @@ SKIP: {
"Signing SM2 certificate request");
}
test_revoke('notimes', {
should_succeed => 1,
});
test_revoke('lastupdate_invalid', {
lastupdate => '1234567890',
should_succeed => 0,
});
test_revoke('lastupdate_utctime', {
lastupdate => '200901123456Z',
should_succeed => 1,
});
test_revoke('lastupdate_generalizedtime', {
lastupdate => '20990901123456Z',
should_succeed => 1,
});
test_revoke('nextupdate_invalid', {
nextupdate => '1234567890',
should_succeed => 0,
});
test_revoke('nextupdate_utctime', {
nextupdate => '200901123456Z',
should_succeed => 1,
});
test_revoke('nextupdate_generalizedtime', {
nextupdate => '20990901123456Z',
should_succeed => 1,
});
test_revoke('both_utctime', {
lastupdate => '200901123456Z',
nextupdate => '200908123456Z',
should_succeed => 1,
});
test_revoke('both_generalizedtime', {
lastupdate => '20990901123456Z',
nextupdate => '20990908123456Z',
should_succeed => 1,
});
sub test_revoke {
my ($filename, $opts) = @_;
# Before Perl 5.12.0, the range of times Perl could represent was limited by
# the size of time_t, so Time::Local was hamstrung by the Y2038 problem -
# Perl 5.12.0 onwards use an internal time implementation with a guaranteed
# >32-bit time range on all architectures, so the tests involving post-2038
# times won't fail provided we're running under that version or newer
if ($] < 5.012000) {
plan skip_all => 'Perl >= 5.12.0 required to run certificate revocation tests';
}
subtest "Revoke certificate and generate CRL: $filename" => sub {
$ENV{CN2} = $filename;
ok(
run(app(['openssl',
'req',
'-config', $cnf,
'-new',
'-key', data_file('revoked.key'),
'-out', "$filename-req.pem",
'-section', 'userreq',
])),
'Generate CSR'
);
delete $ENV{CN2};
ok(
run(app(['openssl',
'ca',
'-batch',
'-config', $cnf,
'-in', "$filename-req.pem",
'-out', "$filename-cert.pem",
])),
'Sign CSR'
);
ok(
run(app(['openssl',
'ca',
'-config', $cnf,
'-revoke', "$filename-cert.pem",
])),
'Revoke certificate'
);
my @gencrl_opts;
if (exists $opts->{lastupdate}) {
push @gencrl_opts, '-crl_lastupdate', $opts->{lastupdate};
}
if (exists $opts->{nextupdate}) {
push @gencrl_opts, '-crl_nextupdate', $opts->{nextupdate};
}
is(
run(app(['openssl',
'ca',
'-config', $cnf,
'-gencrl',
'-out', "$filename-crl.pem",
'-crlsec', '60',
@gencrl_opts,
])),
$opts->{should_succeed},
'Generate CRL'
);
my $crl_gentime = time;
# The following tests only need to run if the CRL was supposed to be
# generated:
return unless $opts->{should_succeed};
my $crl_lastupdate = crl_field("$filename-crl.pem", 'lastUpdate');
if (exists $opts->{lastupdate}) {
is(
$crl_lastupdate,
rfc5280_time($opts->{lastupdate}),
'CRL lastUpdate field has expected value'
);
} else {
diag("CRL lastUpdate: $crl_lastupdate");
diag("openssl run time: $crl_gentime");
ok(
# Is the CRL's lastUpdate time within a second of the time that
# `openssl ca -gencrl` was executed?
$crl_gentime - 1 <= $crl_lastupdate && $crl_lastupdate <= $crl_gentime + 1,
'CRL lastUpdate field has (roughly) expected value'
);
}
my $crl_nextupdate = crl_field("$filename-crl.pem", 'nextUpdate');
if (exists $opts->{nextupdate}) {
is(
$crl_nextupdate,
rfc5280_time($opts->{nextupdate}),
'CRL nextUpdate field has expected value'
);
} else {
diag("CRL nextUpdate: $crl_nextupdate");
diag("openssl run time: $crl_gentime");
ok(
# Is the CRL's lastUpdate time within a second of the time that
# `openssl ca -gencrl` was executed, taking into account the use
# of '-crlsec 60'?
$crl_gentime + 59 <= $crl_nextupdate && $crl_nextupdate <= $crl_gentime + 61,
'CRL nextUpdate field has (roughly) expected value'
);
}
};
}
sub yes {
my $cntr = 10;
open(PIPE, "|-", join(" ",@_));
@ -80,3 +233,49 @@ sub yes {
return 0;
}
# Get the value of the lastUpdate or nextUpdate field from a CRL
sub crl_field {
my ($crl_path, $field_name) = @_;
my @out = run(
app(['openssl',
'crl',
'-in', $crl_path,
'-noout',
'-' . lc($field_name),
]),
capture => 1,
statusvar => \my $exit,
);
ok($exit, "CRL $field_name field retrieved");
diag("CRL $field_name: $out[0]");
$out[0] =~ s/^\Q$field_name\E=//;
$out[0] =~ s/\n?//;
my $time = human_time($out[0]);
return $time;
}
# Converts human-readable ASN1_TIME_print() output to Unix time
sub human_time {
my ($human) = @_;
my ($mo, $d, $h, $m, $s, $y) = $human =~ /^([A-Za-z]{3})\s+(\d+) (\d{2}):(\d{2}):(\d{2}) (\d{4})/;
my %months = (
Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5,
Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11,
);
return timegm($s, $m, $h, $d, $months{$mo}, $y);
}
# Converts an RFC 5280 timestamp to Unix time
sub rfc5280_time {
my ($asn1) = @_;
my ($y, $mo, $d, $h, $m, $s) = $asn1 =~ /^(\d{2,4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
return timegm($s, $m, $h, $d, $mo - 1, $y);
}

+ 27
- 0
test/recipes/80-test_ca_data/revoked.key View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvpWG+nFMaupRmFBLmaEsZhXzYPPh/2n7zkgBHf7hLDiw472P
S0infjrGc+Orv+Habpb1/sPktKitvFY01JYldCfDENs1ECUcjkuBm/tCBHxvPeI4
bA7kLo4qGG0wzWcqI2s3zwWfzLdZWH+6xAo/jKSty5icwPKPaWcPwU4zz/yFXrte
z5Owz9n0UnmHsfhNFWhl1bcZCGN/XrE0rQWPWvDk4Y8l8lxcYZWwVi0xKwhleeWx
O50+uVJLrF3EWGaFjYGzBCj0Py+skgFi1f2OC9oCDHrHbgkcrByyTNdUuCYzsl8U
87H5PWJNQJlrtueJWJfW1E3qS2hS8zyosTWI2wIDAQABAoIBAAqWkhYt3zn9ZKUa
qOiTHL1bMNdNFVw0TioKtA0vkOD9EU7FxEVBdxS7PTVJC8ywRotoVahex8EFsglJ
nMvGv7PxVZQFKbCI70o0hbHdxrArHZ8Jh2rxdNnlSg9rWY+/0IaBOWuF+3fLI+qX
xg+IJjmxGHq8MtFBaJhJgoWRy60dWNpbIDmp4Yk8E3MqtNCZQU+UMXesVpC3UZ2Z
k7V+zJaC3clvQaE3vNRl7d2si94HyxHIhA0cdWBDc8+vwzrsUJhQYHZw3QH5yX4g
3CDVe1wwljts2Jdz6YN1JaqHP1R6Cw8mihnkGEoF10mIM0YFm9pGQrFscVQFOuQQ
WK0aiwECgYEA4qFjAdorYqnAPM6YnHyJtwheTKEbq5duF9JdSnpkV63usWB9a5B4
d+QADhmaq6YNh2rjodNwn3oxCqlceV6PhE/sTWLkjXD8+r6wPiR06BFJszBt8BKi
CjW1IjaMGAEpOa5f9vqhv0xgahpNBjWKfFIj4hHX6ha3N3RScwPep4ECgYEA10hH
qKCbDiiErluUbCLveKxBx++ldxHoO64iLwbKadsHqhi5w+lIONp7P7B8MfNAHbgi
NCKZ/Z9llyHUuNKjgFFFwEXXP5Vm1PyQeZRh5Mn7pG0qTNMbEanBRSi4pmkg3ha6
MfsoroH6aaCIqojTHFxvP2GQUHe6iQTE5KXe/lsCgYAGd3Bpxx3hzNItaf+0x2Du
lhCKwzYU6Vc82sXKsD9YuR/Mc+JgRVkKorjVrilZqH1OVeB056GZC1WG5lo7JFWY
AufNNXssBgNR7Er/Gu8zB/tAX6tjZES3YdzvQvswXCge+zjFxVUELlu561IzUSfI
cFeRxcyRY2CK9oi7u2qJAQKBgQCbwbfW0RxeCgK1A51G/5+y9y5AsapNk68qPrqM
u6UHIWlSL8F3dPjD6Y7ybYXtvcjNt8NHZSF01jZyOg/mCMAyvppwmhc58aYMww7k
Z+7L0Tc3p6PLIZGcHe2vU98Ex1r4VAky0DyGxZOfiH5Yo9XZ1ybF/JilH3reV4z8
wOWtsQKBgBD7oJRC7V+DEiDN7/7SxfOBBDI66LGpOlsnuSFXM++G+Q9yhg4vvpW1
pnNLQoMazqrub7Iemz2YVp/XBh8R07ISvSMDu0QMzSprxs254UYxpi1SfhUDf5Vt
HPCAQTzvjohzWfPQVWy/GQxPWtttW8DHRYmcIel3O6XvlH4SYSj9
-----END RSA PRIVATE KEY-----

Loading…
Cancel
Save