VMS: Redefine _XOPEN_SOURCE_EXTENDED with the value 1
[openssl.git] / test / recipes / 80-test_ca.t
1 #! /usr/bin/env perl
2 # Copyright 2015-2020 The OpenSSL Project Authors. All Rights Reserved.
3 #
4 # Licensed under the Apache License 2.0 (the "License").  You may not use
5 # this file except in compliance with the License.  You can obtain a copy
6 # in the file LICENSE in the source distribution or at
7 # https://www.openssl.org/source/license.html
8
9
10 use strict;
11 use warnings;
12
13 use POSIX;
14 use File::Path 2.00 qw/rmtree/;
15 use OpenSSL::Test qw/:DEFAULT cmdstr data_file srctop_file/;
16 use OpenSSL::Test::Utils;
17 use Time::Local qw/timegm/;
18
19 setup("test_ca");
20
21 $ENV{OPENSSL} = cmdstr(app(["openssl"]), display => 1);
22
23 my $cnf = '"' . srctop_file("test","ca-and-certs.cnf") . '"';;
24 my $std_openssl_cnf = '"'
25     . srctop_file("apps", $^O eq "VMS" ? "openssl-vms.cnf" : "openssl.cnf")
26     . '"';
27
28 rmtree("demoCA", { safe => 0 });
29
30 plan tests => 15;
31  SKIP: {
32      $ENV{OPENSSL_CONFIG} = '-config ' . $cnf;
33      skip "failed creating CA structure", 4
34          if !ok(run(perlapp(["CA.pl","-newca"], stdin => undef)),
35                 'creating CA structure');
36
37      $ENV{OPENSSL_CONFIG} = '-config ' . $cnf;
38      skip "failed creating new certificate request", 3
39          if !ok(run(perlapp(["CA.pl","-newreq",
40                              '-extra-req', '-outform DER -section userreq'])),
41                 'creating certificate request');
42      $ENV{OPENSSL_CONFIG} = '-rand_serial -inform DER -config '.$std_openssl_cnf;
43      skip "failed to sign certificate request", 2
44          if !is(yes(cmdstr(perlapp(["CA.pl", "-sign"]))), 0,
45                 'signing certificate request');
46
47      ok(run(perlapp(["CA.pl", "-verify", "newcert.pem"])),
48         'verifying new certificate');
49
50      skip "CT not configured, can't use -precert", 1
51          if disabled("ct");
52
53      $ENV{OPENSSL_CONFIG} = '-config ' . $cnf;
54      ok(run(perlapp(["CA.pl", "-precert", '-extra-req', '-section userreq'], stderr => undef)),
55         'creating new pre-certificate');
56 }
57
58 SKIP: {
59     skip "SM2 is not supported by this OpenSSL build", 1
60               if disabled("sm2");
61
62     is(yes(cmdstr(app(["openssl", "ca", "-config",
63                        $cnf,
64                        "-in", srctop_file("test", "certs", "sm2-csr.pem"),
65                        "-out", "sm2-test.crt",
66                        "-sigopt", "distid:1234567812345678",
67                        "-vfyopt", "distid:1234567812345678",
68                        "-md", "sm3",
69                        "-cert", srctop_file("test", "certs", "sm2-root.crt"),
70                        "-keyfile", srctop_file("test", "certs", "sm2-root.key")]))),
71        0,
72        "Signing SM2 certificate request");
73 }
74
75 test_revoke('notimes', {
76     should_succeed => 1,
77 });
78 test_revoke('lastupdate_invalid', {
79     lastupdate     => '1234567890',
80     should_succeed => 0,
81 });
82 test_revoke('lastupdate_utctime', {
83     lastupdate     => '200901123456Z',
84     should_succeed => 1,
85 });
86 test_revoke('lastupdate_generalizedtime', {
87     lastupdate     => '20990901123456Z',
88     should_succeed => 1,
89 });
90 test_revoke('nextupdate_invalid', {
91     nextupdate     => '1234567890',
92     should_succeed => 0,
93 });
94 test_revoke('nextupdate_utctime', {
95     nextupdate     => '200901123456Z',
96     should_succeed => 1,
97 });
98 test_revoke('nextupdate_generalizedtime', {
99     nextupdate     => '20990901123456Z',
100     should_succeed => 1,
101 });
102 test_revoke('both_utctime', {
103     lastupdate     => '200901123456Z',
104     nextupdate     => '200908123456Z',
105     should_succeed => 1,
106 });
107 test_revoke('both_generalizedtime', {
108     lastupdate     => '20990901123456Z',
109     nextupdate     => '20990908123456Z',
110     should_succeed => 1,
111 });
112
113 sub test_revoke {
114     my ($filename, $opts) = @_;
115
116     subtest "Revoke certificate and generate CRL: $filename" => sub {
117         # Before Perl 5.12.0, the range of times Perl could represent was
118         # limited by the size of time_t, so Time::Local was hamstrung by the
119         # Y2038 problem
120         # Perl 5.12.0 onwards use an internal time implementation with a
121         # guaranteed >32-bit time range on all architectures, so the tests
122         # involving post-2038 times won't fail provided we're running under
123         # that version or newer
124         plan skip_all =>
125             'Perl >= 5.12.0 required to run certificate revocation tests'
126             if $] < 5.012000;
127
128         $ENV{CN2} = $filename;
129         ok(
130             run(app(['openssl',
131                      'req',
132                      '-config',  $cnf,
133                      '-new',
134                      '-key',     data_file('revoked.key'),
135                      '-out',     "$filename-req.pem",
136                      '-section', 'userreq',
137             ])),
138             'Generate CSR'
139         );
140         delete $ENV{CN2};
141
142         ok(
143             run(app(['openssl',
144                      'ca',
145                      '-batch',
146                      '-config',  $cnf,
147                      '-in',      "$filename-req.pem",
148                      '-out',     "$filename-cert.pem",
149             ])),
150             'Sign CSR'
151         );
152
153         ok(
154             run(app(['openssl',
155                      'ca',
156                      '-config', $cnf,
157                      '-revoke', "$filename-cert.pem",
158             ])),
159             'Revoke certificate'
160         );
161
162         my @gencrl_opts;
163
164         if (exists $opts->{lastupdate}) {
165             push @gencrl_opts, '-crl_lastupdate', $opts->{lastupdate};
166         }
167
168         if (exists $opts->{nextupdate}) {
169             push @gencrl_opts, '-crl_nextupdate', $opts->{nextupdate};
170         }
171
172         is(
173             run(app(['openssl',
174                      'ca',
175                      '-config', $cnf,
176                      '-gencrl',
177                      '-out',    "$filename-crl.pem",
178                      '-crlsec', '60',
179                      @gencrl_opts,
180             ])),
181             $opts->{should_succeed},
182             'Generate CRL'
183         );
184         my $crl_gentime = time;
185
186         # The following tests only need to run if the CRL was supposed to be
187         # generated:
188         return unless $opts->{should_succeed};
189
190         my $crl_lastupdate = crl_field("$filename-crl.pem", 'lastUpdate');
191         if (exists $opts->{lastupdate}) {
192             is(
193                 $crl_lastupdate,
194                 rfc5280_time($opts->{lastupdate}),
195                 'CRL lastUpdate field has expected value'
196             );
197         } else {
198             diag("CRL lastUpdate:   $crl_lastupdate");
199             diag("openssl run time: $crl_gentime");
200             ok(
201                 # Is the CRL's lastUpdate time within a second of the time that
202                 # `openssl ca -gencrl` was executed?
203                 $crl_gentime - 1 <= $crl_lastupdate && $crl_lastupdate <= $crl_gentime + 1,
204                 'CRL lastUpdate field has (roughly) expected value'
205             );
206         }
207
208         my $crl_nextupdate = crl_field("$filename-crl.pem", 'nextUpdate');
209         if (exists $opts->{nextupdate}) {
210             is(
211                 $crl_nextupdate,
212                 rfc5280_time($opts->{nextupdate}),
213                 'CRL nextUpdate field has expected value'
214             );
215         } else {
216             diag("CRL nextUpdate:   $crl_nextupdate");
217             diag("openssl run time: $crl_gentime");
218             ok(
219                 # Is the CRL's lastUpdate time within a second of the time that
220                 # `openssl ca -gencrl` was executed, taking into account the use
221                 # of '-crlsec 60'?
222                 $crl_gentime + 59 <= $crl_nextupdate && $crl_nextupdate <= $crl_gentime + 61,
223                 'CRL nextUpdate field has (roughly) expected value'
224             );
225         }
226     };
227 }
228
229 sub yes {
230     my $cntr = 10;
231     open(PIPE, "|-", join(" ",@_));
232     local $SIG{PIPE} = "IGNORE";
233     1 while $cntr-- > 0 && print PIPE "y\n";
234     close PIPE;
235     return 0;
236 }
237
238 # Get the value of the lastUpdate or nextUpdate field from a CRL
239 sub crl_field {
240     my ($crl_path, $field_name) = @_;
241
242     my @out = run(
243         app(['openssl',
244              'crl',
245              '-in', $crl_path,
246              '-noout',
247              '-' . lc($field_name),
248         ]),
249         capture => 1,
250         statusvar => \my $exit,
251     );
252     ok($exit, "CRL $field_name field retrieved");
253     diag("CRL $field_name: $out[0]");
254
255     $out[0] =~ s/^\Q$field_name\E=//;
256     $out[0] =~ s/\n?//;
257     my $time = human_time($out[0]);
258
259     return $time;
260 }
261
262 # Converts human-readable ASN1_TIME_print() output to Unix time
263 sub human_time {
264     my ($human) = @_;
265
266     my ($mo, $d, $h, $m, $s, $y) = $human =~ /^([A-Za-z]{3})\s+(\d+) (\d{2}):(\d{2}):(\d{2}) (\d{4})/;
267
268     my %months = (
269         Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4,  Jun => 5,
270         Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11,
271     );
272
273     return timegm($s, $m, $h, $d, $months{$mo}, $y);
274 }
275
276 # Converts an RFC 5280 timestamp to Unix time
277 sub rfc5280_time {
278     my ($asn1) = @_;
279
280     my ($y, $mo, $d, $h, $m, $s) = $asn1 =~ /^(\d{2,4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
281
282     return timegm($s, $m, $h, $d, $mo - 1, $y);
283 }