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