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