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