]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - secure/caroot/MAca-bundle.pl
OpenSSL: Cleanup record length checks for KTLS
[FreeBSD/FreeBSD.git] / secure / caroot / MAca-bundle.pl
1 #!/usr/bin/env perl
2 ##
3 ##  MAca-bundle.pl -- Regenerate ca-root-nss.crt from the Mozilla certdata.txt
4 ##
5 ##  Rewritten in September 2011 by Matthias Andree to heed untrust
6 ##
7
8 ##  Copyright (c) 2011, 2013 Matthias Andree <mandree@FreeBSD.org>
9 ##  All rights reserved.
10 ##  Copyright (c) 2018, Allan Jude <allanjude@FreeBSD.org>
11 ##
12 ##  Redistribution and use in source and binary forms, with or without
13 ##  modification, are permitted provided that the following conditions are
14 ##  met:
15 ##
16 ##  * Redistributions of source code must retain the above copyright
17 ##  notice, this list of conditions and the following disclaimer.
18 ##
19 ##  * Redistributions in binary form must reproduce the above copyright
20 ##  notice, this list of conditions and the following disclaimer in the
21 ##  documentation and/or other materials provided with the distribution.
22 ##
23 ##  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 ##  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 ##  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 ##  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 ##  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 ##  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 ##  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 ##  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 ##  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 ##  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 ##  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 ##  POSSIBILITY OF SUCH DAMAGE.
35
36 use strict;
37 use Carp;
38 use MIME::Base64;
39 use Getopt::Long;
40
41 my $VERSION = '$FreeBSD$';
42 my $generated = '@' . 'generated';
43 my $inputfh = *STDIN;
44 my $debug = 0;
45 my $infile;
46 my $outputdir;
47 my %labels;
48 my %certs;
49 my %trusts;
50
51 $debug++
52     if defined $ENV{'WITH_DEBUG'}
53         and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/;
54
55 GetOptions (
56         "debug+" => \$debug,
57         "infile:s" => \$infile,
58         "outputdir:s" => \$outputdir)
59   or die("Error in command line arguments\n$0 [-d] [-i input-file] [-o output-dir]\n");
60
61 if ($infile) {
62     open($inputfh, "<", $infile) or die "Failed to open $infile";
63 }
64
65 sub print_header($$)
66 {
67     my $dstfile = shift;
68     my $label = shift;
69
70     if ($outputdir) {
71         print $dstfile <<EOFH;
72 ##
73 ##  $label
74 ##
75 ##  This is a single X.509 certificate for a public Certificate
76 ##  Authority (CA). It was automatically extracted from Mozilla's
77 ##  root CA list (the file `certdata.txt' in security/nss).
78 ##
79 ##  It contains a certificate trusted for server authentication.
80 ##
81 ##  Extracted from nss
82 ##  with $VERSION
83 ##
84 ##  $generated
85 ##
86 EOFH
87     } else {
88         print $dstfile <<EOH;
89 ##
90 ##  ca-root-nss.crt -- Bundle of CA Root Certificates
91 ##
92 ##  This is a bundle of X.509 certificates of public Certificate
93 ##  Authorities (CA). These were automatically extracted from Mozilla's
94 ##  root CA list (the file `certdata.txt').
95 ##
96 ##  It contains certificates trusted for server authentication.
97 ##
98 ##  Extracted from nss
99 ##  with $VERSION
100 ##
101 ##  $generated
102 ##
103 EOH
104     }
105 }
106
107 # returns a string like YYMMDDhhmmssZ of current time in GMT zone
108 sub timenow()
109 {
110         my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = gmtime(time);
111         return sprintf "%02d%02d%02d%02d%02d%02dZ", $year-100, $mon+1, $mday, $hour, $min, $sec;
112 }
113
114 sub printcert($$$)
115 {
116     my ($fh, $label, $certdata) = @_;
117     return unless $certdata;
118     open(OUT, "|openssl x509 -text -inform DER -fingerprint")
119             or die "could not pipe to openssl x509";
120     print OUT $certdata;
121     close(OUT) or die "openssl x509 failed with exit code $?";
122 }
123
124 # converts a datastream that is to be \177-style octal constants
125 # from <> to a (binary) string and returns it
126 sub graboct($)
127 {
128     my $ifh = shift;
129     my $data;
130
131     while (<$ifh>) {
132         last if /^END/;
133         my (undef,@oct) = split /\\/;
134         my @bin = map(chr(oct), @oct);
135         $data .= join('', @bin);
136     }
137
138     return $data;
139 }
140
141 sub grabcert($)
142 {
143     my $ifh = shift;
144     my $certdata;
145     my $cka_label = '';
146     my $serial = 0;
147     my $distrust = 0;
148
149     while (<$ifh>) {
150         chomp;
151         last if ($_ eq '');
152
153         if (/^CKA_LABEL UTF8 "([^"]+)"/) {
154             $cka_label = $1;
155         }
156
157         if (/^CKA_VALUE MULTILINE_OCTAL/) {
158             $certdata = graboct($ifh);
159         }
160
161         if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
162             $serial = graboct($ifh);
163         }
164
165         if (/^CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL/)
166         {
167             my $distrust_after = graboct($ifh);
168             my $time_now = timenow();
169             if ($time_now >= $distrust_after) { $distrust = 1; }
170             if ($debug) {
171                 printf STDERR "line $.: $cka_label ser #%d: distrust after %s, now: %s -> distrust $distrust\n", $serial, $distrust_after, timenow();
172             }
173             if ($distrust) {
174                 return undef;
175             }
176         }
177     }
178     return ($serial, $cka_label, $certdata);
179 }
180
181 sub grabtrust($) {
182     my $ifh = shift;
183     my $cka_label;
184     my $serial;
185     my $maytrust = 0;
186     my $distrust = 0;
187
188     while (<$ifh>) {
189         chomp;
190         last if ($_ eq '');
191
192         if (/^CKA_LABEL UTF8 "([^"]+)"/) {
193             $cka_label = $1;
194         }
195
196         if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
197             $serial = graboct($ifh);
198         }
199
200         if (/^CKA_TRUST_SERVER_AUTH CK_TRUST (\S+)$/)
201         {
202             if ($1 eq      'CKT_NSS_NOT_TRUSTED') {
203                 $distrust = 1;
204             } elsif ($1 eq 'CKT_NSS_TRUSTED_DELEGATOR') {
205                 $maytrust = 1;
206             } elsif ($1 ne 'CKT_NSS_MUST_VERIFY_TRUST') {
207                 confess "Unknown trust setting on line $.:\n"
208                 . "$_\n"
209                 . "Script must be updated:";
210             }
211         }
212     }
213
214     if (!$maytrust && !$distrust && $debug) {
215         print STDERR "line $.: no explicit trust/distrust found for $cka_label\n";
216     }
217
218     my $trust = ($maytrust and not $distrust);
219     return ($serial, $cka_label, $trust);
220 }
221
222 if (!$outputdir) {
223         print_header(*STDOUT, "");
224 }
225
226 my $untrusted = 0;
227
228 while (<$inputfh>) {
229     if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
230         my ($serial, $label, $certdata) = grabcert($inputfh);
231         if (defined $certs{$label."\0".$serial}) {
232             warn "Certificate $label duplicated!\n";
233         }
234         if (defined $certdata) {
235                 $certs{$label."\0".$serial} = $certdata;
236                 # We store the label in a separate hash because truncating the key
237                 # with \0 was causing garbage data after the end of the text.
238                 $labels{$label."\0".$serial} = $label;
239         } else { # $certdata undefined? distrust_after in effect
240                 $untrusted ++;
241         }
242     } elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) {
243         my ($serial, $label, $trust) = grabtrust($inputfh);
244         if (defined $trusts{$label."\0".$serial}) {
245             warn "Trust for $label duplicated!\n";
246         }
247         $trusts{$label."\0".$serial} = $trust;
248         $labels{$label."\0".$serial} = $label;
249     } elsif (/^CVS_ID.*Revision: ([^ ]*).*/) {
250         print "##  Source: \"certdata.txt\" CVS revision $1\n##\n\n";
251     }
252 }
253
254 sub label_to_filename(@) {
255     my @res = @_;
256     map { s/\0.*//; s/[^[:alnum:]\-]/_/g; $_ = "$_.pem"; } @res;
257     return wantarray ? @res : $res[0];
258 }
259
260 # weed out untrusted certificates
261 foreach my $it (keys %trusts) {
262     if (!$trusts{$it}) {
263         if (!exists($certs{$it})) {
264             warn "Found trust for nonexistent certificate $labels{$it}\n" if $debug;
265         } else {
266             delete $certs{$it};
267             warn "Skipping untrusted $labels{$it}\n" if $debug;
268             $untrusted++;
269         }
270     }
271 }
272
273 if (!$outputdir) {
274     print               "##  Untrusted certificates omitted from this bundle: $untrusted\n\n";
275 }
276 print STDERR    "##  Untrusted certificates omitted from this bundle: $untrusted\n";
277
278 my $certcount = 0;
279 foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) {
280     my $fh = *STDOUT;
281     my $filename;
282     if (!exists($trusts{$it})) {
283         die "Found certificate without trust block,\naborting";
284     }
285     if ($outputdir) {
286         $filename = label_to_filename($labels{$it});
287         open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $filename";
288         print_header($fh, $labels{$it});
289     }
290     printcert($fh, $labels{$it}, $certs{$it});
291     if ($outputdir) {
292         close($fh) or die "Unable to close: $filename";
293     } else {
294         print $fh "\n\n\n";
295     }
296     $certcount++;
297     print STDERR "Trusting $certcount: $labels{$it}\n" if $debug;
298 }
299
300 if ($certcount < 25) {
301     die "Certificate count of $certcount is implausibly low.\nAbort";
302 }
303
304 if (!$outputdir) {
305     print "##  Number of certificates: $certcount\n";
306     print "##  End of file.\n";
307 }
308 print STDERR    "##  Number of certificates: $certcount\n";