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