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 $generated = '@' . 'generated'; 42my $inputfh = *STDIN; 43my $debug = 0; 44my $infile; 45my $outputdir; 46my %labels; 47my %certs; 48my %trusts; 49 50$debug++ 51 if defined $ENV{'WITH_DEBUG'} 52 and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/; 53 54GetOptions ( 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 60if ($infile) { 61 open($inputfh, "<", $infile) or die "Failed to open $infile"; 62} 63 64sub 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## It contains a certificate trusted for server authentication. 79## 80## Extracted from nss 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## It contains certificates trusted for server authentication. 95## 96## Extracted from nss 97## 98## $generated 99## 100EOH 101 } 102} 103 104# returns a string like YYMMDDhhmmssZ of current time in GMT zone 105sub timenow() 106{ 107 my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = gmtime(time); 108 return sprintf "%02d%02d%02d%02d%02d%02dZ", $year-100, $mon+1, $mday, $hour, $min, $sec; 109} 110 111sub printcert($$$) 112{ 113 my ($fh, $label, $certdata) = @_; 114 return unless $certdata; 115 open(OUT, "|openssl x509 -text -inform DER -fingerprint") 116 or die "could not pipe to openssl x509"; 117 print OUT $certdata; 118 close(OUT) or die "openssl x509 failed with exit code $?"; 119} 120 121# converts a datastream that is to be \177-style octal constants 122# from <> to a (binary) string and returns it 123sub graboct($) 124{ 125 my $ifh = shift; 126 my $data; 127 128 while (<$ifh>) { 129 last if /^END/; 130 my (undef,@oct) = split /\\/; 131 my @bin = map(chr(oct), @oct); 132 $data .= join('', @bin); 133 } 134 135 return $data; 136} 137 138sub grabcert($) 139{ 140 my $ifh = shift; 141 my $certdata; 142 my $cka_label = ''; 143 my $serial = 0; 144 my $distrust = 0; 145 146 while (<$ifh>) { 147 chomp; 148 last if ($_ eq ''); 149 150 if (/^CKA_LABEL UTF8 "([^"]+)"/) { 151 $cka_label = $1; 152 } 153 154 if (/^CKA_VALUE MULTILINE_OCTAL/) { 155 $certdata = graboct($ifh); 156 } 157 158 if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) { 159 $serial = graboct($ifh); 160 } 161 162 if (/^CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL/) 163 { 164 my $distrust_after = graboct($ifh); 165 my $time_now = timenow(); 166 if ($time_now >= $distrust_after) { $distrust = 1; } 167 if ($debug) { 168 printf STDERR "line $.: $cka_label ser #%d: distrust after %s, now: %s -> distrust $distrust\n", $serial, $distrust_after, timenow(); 169 } 170 if ($distrust) { 171 return undef; 172 } 173 } 174 } 175 return ($serial, $cka_label, $certdata); 176} 177 178sub grabtrust($) { 179 my $ifh = shift; 180 my $cka_label; 181 my $serial; 182 my $maytrust = 0; 183 my $distrust = 0; 184 185 while (<$ifh>) { 186 chomp; 187 last if ($_ eq ''); 188 189 if (/^CKA_LABEL UTF8 "([^"]+)"/) { 190 $cka_label = $1; 191 } 192 193 if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) { 194 $serial = graboct($ifh); 195 } 196 197 if (/^CKA_TRUST_SERVER_AUTH CK_TRUST (\S+)$/) 198 { 199 if ($1 eq 'CKT_NSS_NOT_TRUSTED') { 200 $distrust = 1; 201 } elsif ($1 eq 'CKT_NSS_TRUSTED_DELEGATOR') { 202 $maytrust = 1; 203 } elsif ($1 ne 'CKT_NSS_MUST_VERIFY_TRUST') { 204 confess "Unknown trust setting on line $.:\n" 205 . "$_\n" 206 . "Script must be updated:"; 207 } 208 } 209 } 210 211 if (!$maytrust && !$distrust && $debug) { 212 print STDERR "line $.: no explicit trust/distrust found for $cka_label\n"; 213 } 214 215 my $trust = ($maytrust and not $distrust); 216 return ($serial, $cka_label, $trust); 217} 218 219if (!$outputdir) { 220 print_header(*STDOUT, ""); 221} 222 223my $untrusted = 0; 224 225while (<$inputfh>) { 226 if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) { 227 my ($serial, $label, $certdata) = grabcert($inputfh); 228 if (defined $certs{$label."\0".$serial}) { 229 warn "Certificate $label duplicated!\n"; 230 } 231 if (defined $certdata) { 232 $certs{$label."\0".$serial} = $certdata; 233 # We store the label in a separate hash because truncating the key 234 # with \0 was causing garbage data after the end of the text. 235 $labels{$label."\0".$serial} = $label; 236 } else { # $certdata undefined? distrust_after in effect 237 $untrusted ++; 238 } 239 } elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) { 240 my ($serial, $label, $trust) = grabtrust($inputfh); 241 if (defined $trusts{$label."\0".$serial}) { 242 warn "Trust for $label duplicated!\n"; 243 } 244 $trusts{$label."\0".$serial} = $trust; 245 $labels{$label."\0".$serial} = $label; 246 } elsif (/^CVS_ID.*Revision: ([^ ]*).*/) { 247 print "## Source: \"certdata.txt\" CVS revision $1\n##\n\n"; 248 } 249} 250 251sub label_to_filename(@) { 252 my @res = @_; 253 map { s/\0.*//; s/[^[:alnum:]\-]/_/g; $_ = "$_.pem"; } @res; 254 return wantarray ? @res : $res[0]; 255} 256 257# weed out untrusted certificates 258foreach my $it (keys %trusts) { 259 if (!$trusts{$it}) { 260 if (!exists($certs{$it})) { 261 warn "Found trust for nonexistent certificate $labels{$it}\n" if $debug; 262 } else { 263 delete $certs{$it}; 264 warn "Skipping untrusted $labels{$it}\n" if $debug; 265 $untrusted++; 266 } 267 } 268} 269 270if (!$outputdir) { 271 print "## Untrusted certificates omitted from this bundle: $untrusted\n\n"; 272} 273print STDERR "## Untrusted certificates omitted from this bundle: $untrusted\n"; 274 275my $certcount = 0; 276foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) { 277 my $fh = *STDOUT; 278 my $filename; 279 if (!exists($trusts{$it})) { 280 die "Found certificate without trust block,\naborting"; 281 } 282 if ($outputdir) { 283 $filename = label_to_filename($labels{$it}); 284 open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $filename"; 285 print_header($fh, $labels{$it}); 286 } 287 printcert($fh, $labels{$it}, $certs{$it}); 288 if ($outputdir) { 289 close($fh) or die "Unable to close: $filename"; 290 } else { 291 print $fh "\n\n\n"; 292 } 293 $certcount++; 294 print STDERR "Trusting $certcount: $labels{$it}\n" if $debug; 295} 296 297if ($certcount < 25) { 298 die "Certificate count of $certcount is implausibly low.\nAbort"; 299} 300 301if (!$outputdir) { 302 print "## Number of certificates: $certcount\n"; 303 print "## End of file.\n"; 304} 305print STDERR "## Number of certificates: $certcount\n"; 306