]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - secure/caroot/MAca-bundle.pl
[1/3] Initial infrastructure for SSL root bundle in base
[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 $inputfh = *STDIN;
43 my $debug = 0;
44 my $infile;
45 my $outputdir;
46 my %labels;
47 my %certs;
48 my %trusts;
49
50 $debug++
51     if defined $ENV{'WITH_DEBUG'}
52         and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/;
53
54 GetOptions (
55         "debug+" => \$debug,
56         "infile:s" => \$infile,
57         "outputdir:s" => \$outputdir)
58   or die("Error in command line arguments\n$0 [-d] [-i input-file] [-o output-dir]\n");
59
60 if ($infile) {
61     open($inputfh, "<", $infile) or die "Failed to open $infile";
62 }
63
64 sub print_header($$)
65 {
66     my $dstfile = shift;
67     my $label = shift;
68
69     if ($outputdir) {
70         print $dstfile <<EOFH;
71 ##
72 ##  $label
73 ##
74 ##  This is a single X.509 certificate for a public Certificate
75 ##  Authority (CA). It was automatically extracted from Mozilla's
76 ##  root CA list (the file `certdata.txt' in security/nss).
77 ##
78 ##  Extracted from nss
79 ##  with $VERSION
80 ##
81 EOFH
82     } else {
83         print $dstfile <<EOH;
84 ##
85 ##  ca-root-nss.crt -- Bundle of CA Root Certificates
86 ##
87 ##  This is a bundle of X.509 certificates of public Certificate
88 ##  Authorities (CA). These were automatically extracted from Mozilla's
89 ##  root CA list (the file `certdata.txt').
90 ##
91 ##  Extracted from nss
92 ##  with $VERSION
93 ##
94 EOH
95     }
96 }
97
98 sub printcert($$$)
99 {
100     my ($fh, $label, $certdata) = @_;
101     return unless $certdata;
102     open(OUT, "|openssl x509 -text -inform DER -fingerprint")
103             or die "could not pipe to openssl x509";
104     print OUT $certdata;
105     close(OUT) or die "openssl x509 failed with exit code $?";
106 }
107
108 sub graboct($)
109 {
110     my $ifh = shift;
111     my $data;
112
113     while (<$ifh>) {
114         last if /^END/;
115         my (undef,@oct) = split /\\/;
116         my @bin = map(chr(oct), @oct);
117         $data .= join('', @bin);
118     }
119
120     return $data;
121 }
122
123
124 sub grabcert($)
125 {
126     my $ifh = shift;
127     my $certdata;
128     my $cka_label;
129     my $serial;
130
131     while (<$ifh>) {
132         chomp;
133         last if ($_ eq '');
134
135         if (/^CKA_LABEL UTF8 "([^"]+)"/) {
136             $cka_label = $1;
137         }
138
139         if (/^CKA_VALUE MULTILINE_OCTAL/) {
140             $certdata = graboct($ifh);
141         }
142
143         if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
144             $serial = graboct($ifh);
145         }
146     }
147     return ($serial, $cka_label, $certdata);
148 }
149
150 sub grabtrust($) {
151     my $ifh = shift;
152     my $cka_label;
153     my $serial;
154     my $maytrust = 0;
155     my $distrust = 0;
156
157     while (<$ifh>) {
158         chomp;
159         last if ($_ eq '');
160
161         if (/^CKA_LABEL UTF8 "([^"]+)"/) {
162             $cka_label = $1;
163         }
164
165         if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
166             $serial = graboct($ifh);
167         }
168
169         if (/^CKA_TRUST_(SERVER_AUTH|EMAIL_PROTECTION|CODE_SIGNING) CK_TRUST (\S+)$/)
170         {
171             if ($2 eq      'CKT_NSS_NOT_TRUSTED') {
172                 $distrust = 1;
173             } elsif ($2 eq 'CKT_NSS_TRUSTED_DELEGATOR') {
174                 $maytrust = 1;
175             } elsif ($2 ne 'CKT_NSS_MUST_VERIFY_TRUST') {
176                 confess "Unknown trust setting on line $.:\n"
177                 . "$_\n"
178                 . "Script must be updated:";
179             }
180         }
181     }
182
183     if (!$maytrust && !$distrust && $debug) {
184         print STDERR "line $.: no explicit trust/distrust found for $cka_label\n";
185     }
186
187     my $trust = ($maytrust and not $distrust);
188     return ($serial, $cka_label, $trust);
189 }
190
191 if (!$outputdir) {
192         print_header(*STDOUT, "");
193 }
194
195 while (<$inputfh>) {
196     if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
197         my ($serial, $label, $certdata) = grabcert($inputfh);
198         if (defined $certs{$label."\0".$serial}) {
199             warn "Certificate $label duplicated!\n";
200         }
201         $certs{$label."\0".$serial} = $certdata;
202         # We store the label in a separate hash because truncating the key
203         # with \0 was causing garbage data after the end of the text.
204         $labels{$label."\0".$serial} = $label;
205     } elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) {
206         my ($serial, $label, $trust) = grabtrust($inputfh);
207         if (defined $trusts{$label."\0".$serial}) {
208             warn "Trust for $label duplicated!\n";
209         }
210         $trusts{$label."\0".$serial} = $trust;
211         $labels{$label."\0".$serial} = $label;
212     } elsif (/^CVS_ID.*Revision: ([^ ]*).*/) {
213         print "##  Source: \"certdata.txt\" CVS revision $1\n##\n\n";
214     }
215 }
216
217 sub label_to_filename(@) {
218     my @res = @_;
219     map { s/\0.*//; s/[^[:alnum:]\-]/_/g; $_ = "$_.pem"; } @res;
220     return wantarray ? @res : $res[0];
221 }
222
223 # weed out untrusted certificates
224 my $untrusted = 0;
225 foreach my $it (keys %trusts) {
226     if (!$trusts{$it}) {
227         if (!exists($certs{$it})) {
228             warn "Found trust for nonexistent certificate $labels{$it}\n" if $debug;
229         } else {
230             delete $certs{$it};
231             warn "Skipping untrusted $labels{$it}\n" if $debug;
232             $untrusted++;
233         }
234     }
235 }
236
237 if (!$outputdir) {
238     print               "##  Untrusted certificates omitted from this bundle: $untrusted\n\n";
239 }
240 print STDERR    "##  Untrusted certificates omitted from this bundle: $untrusted\n";
241
242 my $certcount = 0;
243 foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) {
244     my $fh = *STDOUT;
245     my $filename;
246     if (!exists($trusts{$it})) {
247         die "Found certificate without trust block,\naborting";
248     }
249     if ($outputdir) {
250         $filename = label_to_filename($labels{$it});
251         open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $filename";
252         print_header($fh, $labels{$it});
253     }
254     printcert($fh, $labels{$it}, $certs{$it});
255     if ($outputdir) {
256         close($fh) or die "Unable to close: $filename";
257     } else {
258         print $fh "\n\n\n";
259     }
260     $certcount++;
261     print STDERR "Trusting $certcount: $labels{$it}\n" if $debug;
262 }
263
264 if ($certcount < 25) {
265     die "Certificate count of $certcount is implausibly low.\nAbort";
266 }
267
268 if (!$outputdir) {
269     print "##  Number of certificates: $certcount\n";
270     print "##  End of file.\n";
271 }
272 print STDERR    "##  Number of certificates: $certcount\n";