1*0886019bSDag-Erling Smørgrav#!/usr/bin/env perl 2*0886019bSDag-Erling Smørgrav#- 3*0886019bSDag-Erling Smørgrav# SPDX-License-Identifier: BSD-2-Clause 4*0886019bSDag-Erling Smørgrav# 5*0886019bSDag-Erling Smørgrav# Copyright (c) 2011, 2013 Matthias Andree <mandree@FreeBSD.org> 6*0886019bSDag-Erling Smørgrav# Copyright (c) 2018 Allan Jude <allanjude@FreeBSD.org> 7*0886019bSDag-Erling Smørgrav# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org> 8*0886019bSDag-Erling Smørgrav# 9*0886019bSDag-Erling Smørgrav# Redistribution and use in source and binary forms, with or without 10*0886019bSDag-Erling Smørgrav# modification, are permitted provided that the following conditions 11*0886019bSDag-Erling Smørgrav# are met: 12*0886019bSDag-Erling Smørgrav# 1. Redistributions of source code must retain the above copyright 13*0886019bSDag-Erling Smørgrav# notice, this list of conditions and the following disclaimer. 14*0886019bSDag-Erling Smørgrav# 2. Redistributions in binary form must reproduce the above copyright 15*0886019bSDag-Erling Smørgrav# notice, this list of conditions and the following disclaimer in the 16*0886019bSDag-Erling Smørgrav# documentation and/or other materials provided with the distribution. 17*0886019bSDag-Erling Smørgrav# 18*0886019bSDag-Erling Smørgrav# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19*0886019bSDag-Erling Smørgrav# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20*0886019bSDag-Erling Smørgrav# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21*0886019bSDag-Erling Smørgrav# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22*0886019bSDag-Erling Smørgrav# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23*0886019bSDag-Erling Smørgrav# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24*0886019bSDag-Erling Smørgrav# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25*0886019bSDag-Erling Smørgrav# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26*0886019bSDag-Erling Smørgrav# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27*0886019bSDag-Erling Smørgrav# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28*0886019bSDag-Erling Smørgrav# SUCH DAMAGE. 29*0886019bSDag-Erling Smørgrav# 30*0886019bSDag-Erling Smørgrav# 31*0886019bSDag-Erling Smørgrav# ca-extract.pl -- Extract trusted and untrusted certificates from 32*0886019bSDag-Erling Smørgrav# Mozilla's certdata.txt. 33*0886019bSDag-Erling Smørgrav# 34*0886019bSDag-Erling Smørgrav# Rewritten in September 2011 by Matthias Andree to heed untrust 35*0886019bSDag-Erling Smørgrav# 36*0886019bSDag-Erling Smørgrav 37*0886019bSDag-Erling Smørgravuse strict; 38*0886019bSDag-Erling Smørgravuse warnings; 39*0886019bSDag-Erling Smørgravuse Carp; 40*0886019bSDag-Erling Smørgravuse MIME::Base64; 41*0886019bSDag-Erling Smørgravuse Getopt::Long; 42*0886019bSDag-Erling Smørgravuse Time::Local qw( timegm_posix ); 43*0886019bSDag-Erling Smørgravuse POSIX qw( strftime ); 44*0886019bSDag-Erling Smørgrav 45*0886019bSDag-Erling Smørgravmy $generated = '@' . 'generated'; 46*0886019bSDag-Erling Smørgravmy $inputfh = *STDIN; 47*0886019bSDag-Erling Smørgravmy $debug = 0; 48*0886019bSDag-Erling Smørgravmy $infile; 49*0886019bSDag-Erling Smørgravmy $trustdir = "trusted"; 50*0886019bSDag-Erling Smørgravmy $untrustdir = "untrusted"; 51*0886019bSDag-Erling Smørgravmy %labels; 52*0886019bSDag-Erling Smørgravmy %certs; 53*0886019bSDag-Erling Smørgravmy %trusts; 54*0886019bSDag-Erling Smørgravmy %expires; 55*0886019bSDag-Erling Smørgrav 56*0886019bSDag-Erling Smørgrav$debug++ 57*0886019bSDag-Erling Smørgrav if defined $ENV{'WITH_DEBUG'} 58*0886019bSDag-Erling Smørgrav and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/; 59*0886019bSDag-Erling Smørgrav 60*0886019bSDag-Erling SmørgravGetOptions ( 61*0886019bSDag-Erling Smørgrav "debug+" => \$debug, 62*0886019bSDag-Erling Smørgrav "infile:s" => \$infile, 63*0886019bSDag-Erling Smørgrav "trustdir:s" => \$trustdir, 64*0886019bSDag-Erling Smørgrav "untrustdir:s" => \$untrustdir) 65*0886019bSDag-Erling Smørgrav or die("Error in command line arguments\n$0 [-d] [-i input-file] [-t trust-dir] [-u untrust-dir]\n"); 66*0886019bSDag-Erling Smørgrav 67*0886019bSDag-Erling Smørgravif ($infile) { 68*0886019bSDag-Erling Smørgrav open($inputfh, "<", $infile) or die "Failed to open $infile"; 69*0886019bSDag-Erling Smørgrav} 70*0886019bSDag-Erling Smørgrav 71*0886019bSDag-Erling Smørgravsub print_header($$) 72*0886019bSDag-Erling Smørgrav{ 73*0886019bSDag-Erling Smørgrav my $dstfile = shift; 74*0886019bSDag-Erling Smørgrav my $label = shift; 75*0886019bSDag-Erling Smørgrav 76*0886019bSDag-Erling Smørgrav print $dstfile <<EOFH; 77*0886019bSDag-Erling Smørgrav## 78*0886019bSDag-Erling Smørgrav## $label 79*0886019bSDag-Erling Smørgrav## 80*0886019bSDag-Erling Smørgrav## This is a single X.509 certificate for a public Certificate 81*0886019bSDag-Erling Smørgrav## Authority (CA). It was automatically extracted from Mozilla's 82*0886019bSDag-Erling Smørgrav## root CA list (the file `certdata.txt' in security/nss). 83*0886019bSDag-Erling Smørgrav## 84*0886019bSDag-Erling Smørgrav## $generated 85*0886019bSDag-Erling Smørgrav## 86*0886019bSDag-Erling SmørgravEOFH 87*0886019bSDag-Erling Smørgrav} 88*0886019bSDag-Erling Smørgrav 89*0886019bSDag-Erling Smørgravsub printcert($$$) 90*0886019bSDag-Erling Smørgrav{ 91*0886019bSDag-Erling Smørgrav my ($fh, $label, $certdata) = @_; 92*0886019bSDag-Erling Smørgrav return unless $certdata; 93*0886019bSDag-Erling Smørgrav open(OUT, "|-", qw(openssl x509 -text -inform DER -fingerprint)) 94*0886019bSDag-Erling Smørgrav or die "could not pipe to openssl x509"; 95*0886019bSDag-Erling Smørgrav print OUT $certdata; 96*0886019bSDag-Erling Smørgrav close(OUT) or die "openssl x509 failed with exit code $?"; 97*0886019bSDag-Erling Smørgrav} 98*0886019bSDag-Erling Smørgrav 99*0886019bSDag-Erling Smørgrav# converts a datastream that is to be \177-style octal constants 100*0886019bSDag-Erling Smørgrav# from <> to a (binary) string and returns it 101*0886019bSDag-Erling Smørgravsub graboct($) 102*0886019bSDag-Erling Smørgrav{ 103*0886019bSDag-Erling Smørgrav my $ifh = shift; 104*0886019bSDag-Erling Smørgrav my $data = ""; 105*0886019bSDag-Erling Smørgrav 106*0886019bSDag-Erling Smørgrav while (<$ifh>) { 107*0886019bSDag-Erling Smørgrav last if /^END/; 108*0886019bSDag-Erling Smørgrav $data .= join('', map { chr(oct($_)) } m/\\([0-7]{3})/g); 109*0886019bSDag-Erling Smørgrav } 110*0886019bSDag-Erling Smørgrav 111*0886019bSDag-Erling Smørgrav return $data; 112*0886019bSDag-Erling Smørgrav} 113*0886019bSDag-Erling Smørgrav 114*0886019bSDag-Erling Smørgravsub grabcert($) 115*0886019bSDag-Erling Smørgrav{ 116*0886019bSDag-Erling Smørgrav my $ifh = shift; 117*0886019bSDag-Erling Smørgrav my $certdata; 118*0886019bSDag-Erling Smørgrav my $cka_label = ''; 119*0886019bSDag-Erling Smørgrav my $serial = 0; 120*0886019bSDag-Erling Smørgrav my $distrust = 0; 121*0886019bSDag-Erling Smørgrav 122*0886019bSDag-Erling Smørgrav while (<$ifh>) { 123*0886019bSDag-Erling Smørgrav chomp; 124*0886019bSDag-Erling Smørgrav last if ($_ eq ''); 125*0886019bSDag-Erling Smørgrav 126*0886019bSDag-Erling Smørgrav if (/^CKA_LABEL UTF8 "([^"]+)"/) { 127*0886019bSDag-Erling Smørgrav $cka_label = $1; 128*0886019bSDag-Erling Smørgrav } 129*0886019bSDag-Erling Smørgrav 130*0886019bSDag-Erling Smørgrav if (/^CKA_VALUE MULTILINE_OCTAL/) { 131*0886019bSDag-Erling Smørgrav $certdata = graboct($ifh); 132*0886019bSDag-Erling Smørgrav } 133*0886019bSDag-Erling Smørgrav 134*0886019bSDag-Erling Smørgrav if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) { 135*0886019bSDag-Erling Smørgrav $serial = graboct($ifh); 136*0886019bSDag-Erling Smørgrav } 137*0886019bSDag-Erling Smørgrav 138*0886019bSDag-Erling Smørgrav if (/^CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL/) 139*0886019bSDag-Erling Smørgrav { 140*0886019bSDag-Erling Smørgrav my $distrust_after = graboct($ifh); 141*0886019bSDag-Erling Smørgrav my ($year, $mon, $mday, $hour, $min, $sec) = unpack "A2A2A2A2A2A2", $distrust_after; 142*0886019bSDag-Erling Smørgrav $distrust_after = timegm_posix($sec, $min, $hour, $mday, $mon - 1, $year + 100); 143*0886019bSDag-Erling Smørgrav $expires{$cka_label."\0".$serial} = $distrust_after; 144*0886019bSDag-Erling Smørgrav } 145*0886019bSDag-Erling Smørgrav } 146*0886019bSDag-Erling Smørgrav return ($serial, $cka_label, $certdata); 147*0886019bSDag-Erling Smørgrav} 148*0886019bSDag-Erling Smørgrav 149*0886019bSDag-Erling Smørgravsub grabtrust($) { 150*0886019bSDag-Erling Smørgrav my $ifh = shift; 151*0886019bSDag-Erling Smørgrav my $cka_label; 152*0886019bSDag-Erling Smørgrav my $serial; 153*0886019bSDag-Erling Smørgrav my $maytrust = 0; 154*0886019bSDag-Erling Smørgrav my $distrust = 0; 155*0886019bSDag-Erling Smørgrav 156*0886019bSDag-Erling Smørgrav while (<$ifh>) { 157*0886019bSDag-Erling Smørgrav chomp; 158*0886019bSDag-Erling Smørgrav last if ($_ eq ''); 159*0886019bSDag-Erling Smørgrav 160*0886019bSDag-Erling Smørgrav if (/^CKA_LABEL UTF8 "([^"]+)"/) { 161*0886019bSDag-Erling Smørgrav $cka_label = $1; 162*0886019bSDag-Erling Smørgrav } 163*0886019bSDag-Erling Smørgrav 164*0886019bSDag-Erling Smørgrav if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) { 165*0886019bSDag-Erling Smørgrav $serial = graboct($ifh); 166*0886019bSDag-Erling Smørgrav } 167*0886019bSDag-Erling Smørgrav 168*0886019bSDag-Erling Smørgrav if (/^CKA_TRUST_SERVER_AUTH CK_TRUST (\S+)$/) { 169*0886019bSDag-Erling Smørgrav if ($1 eq 'CKT_NSS_NOT_TRUSTED') { 170*0886019bSDag-Erling Smørgrav $distrust = 1; 171*0886019bSDag-Erling Smørgrav } elsif ($1 eq 'CKT_NSS_TRUSTED_DELEGATOR') { 172*0886019bSDag-Erling Smørgrav $maytrust = 1; 173*0886019bSDag-Erling Smørgrav } elsif ($1 ne 'CKT_NSS_MUST_VERIFY_TRUST') { 174*0886019bSDag-Erling Smørgrav confess "Unknown trust setting on line $.:\n" 175*0886019bSDag-Erling Smørgrav . "$_\n" 176*0886019bSDag-Erling Smørgrav . "Script must be updated:"; 177*0886019bSDag-Erling Smørgrav } 178*0886019bSDag-Erling Smørgrav } 179*0886019bSDag-Erling Smørgrav } 180*0886019bSDag-Erling Smørgrav 181*0886019bSDag-Erling Smørgrav if (!$maytrust && !$distrust && $debug) { 182*0886019bSDag-Erling Smørgrav print STDERR "line $.: no explicit trust/distrust found for $cka_label\n"; 183*0886019bSDag-Erling Smørgrav } 184*0886019bSDag-Erling Smørgrav 185*0886019bSDag-Erling Smørgrav my $trust = ($maytrust and not $distrust); 186*0886019bSDag-Erling Smørgrav return ($serial, $cka_label, $trust); 187*0886019bSDag-Erling Smørgrav} 188*0886019bSDag-Erling Smørgrav 189*0886019bSDag-Erling Smørgravwhile (<$inputfh>) { 190*0886019bSDag-Erling Smørgrav if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) { 191*0886019bSDag-Erling Smørgrav my ($serial, $label, $certdata) = grabcert($inputfh); 192*0886019bSDag-Erling Smørgrav if (defined $certs{$label."\0".$serial}) { 193*0886019bSDag-Erling Smørgrav warn "Certificate $label duplicated!\n"; 194*0886019bSDag-Erling Smørgrav } 195*0886019bSDag-Erling Smørgrav if (defined $certdata) { 196*0886019bSDag-Erling Smørgrav $certs{$label."\0".$serial} = $certdata; 197*0886019bSDag-Erling Smørgrav # We store the label in a separate hash because truncating the key 198*0886019bSDag-Erling Smørgrav # with \0 was causing garbage data after the end of the text. 199*0886019bSDag-Erling Smørgrav $labels{$label."\0".$serial} = $label; 200*0886019bSDag-Erling Smørgrav } 201*0886019bSDag-Erling Smørgrav } elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) { 202*0886019bSDag-Erling Smørgrav my ($serial, $label, $trust) = grabtrust($inputfh); 203*0886019bSDag-Erling Smørgrav if (defined $trusts{$label."\0".$serial}) { 204*0886019bSDag-Erling Smørgrav warn "Trust for $label duplicated!\n"; 205*0886019bSDag-Erling Smørgrav } 206*0886019bSDag-Erling Smørgrav $trusts{$label."\0".$serial} = $trust; 207*0886019bSDag-Erling Smørgrav $labels{$label."\0".$serial} = $label; 208*0886019bSDag-Erling Smørgrav } elsif (/^CVS_ID.*Revision: ([^ ]*).*/) { 209*0886019bSDag-Erling Smørgrav print "## Source: \"certdata.txt\" CVS revision $1\n##\n\n"; 210*0886019bSDag-Erling Smørgrav } 211*0886019bSDag-Erling Smørgrav} 212*0886019bSDag-Erling Smørgrav 213*0886019bSDag-Erling Smørgravsub label_to_filename(@) { 214*0886019bSDag-Erling Smørgrav my @res = @_; 215*0886019bSDag-Erling Smørgrav map { s/\0.*//; s/[^[:alnum:]\-]/_/g; $_ = "$_.pem"; } @res; 216*0886019bSDag-Erling Smørgrav return wantarray ? @res : $res[0]; 217*0886019bSDag-Erling Smørgrav} 218*0886019bSDag-Erling Smørgrav 219*0886019bSDag-Erling Smørgravmy $untrusted = 0; 220*0886019bSDag-Erling Smørgravmy $trusted = 0; 221*0886019bSDag-Erling Smørgravmy $now = time; 222*0886019bSDag-Erling Smørgrav 223*0886019bSDag-Erling Smørgravforeach my $it (sort {uc($a) cmp uc($b)} keys %certs) { 224*0886019bSDag-Erling Smørgrav my $fh = *STDOUT; 225*0886019bSDag-Erling Smørgrav my $outputdir; 226*0886019bSDag-Erling Smørgrav my $filename; 227*0886019bSDag-Erling Smørgrav if (exists($expires{$it}) && 228*0886019bSDag-Erling Smørgrav $now >= $expires{$it} + 398 * 24 * 60 * 60) { 229*0886019bSDag-Erling Smørgrav print(STDERR "## Expired: $labels{$it}\n"); 230*0886019bSDag-Erling Smørgrav $outputdir = $untrustdir; 231*0886019bSDag-Erling Smørgrav $untrusted++; 232*0886019bSDag-Erling Smørgrav } elsif (!$trusts{$it}) { 233*0886019bSDag-Erling Smørgrav print(STDERR "## Untrusted: $labels{$it}\n"); 234*0886019bSDag-Erling Smørgrav $outputdir = $untrustdir; 235*0886019bSDag-Erling Smørgrav $untrusted++; 236*0886019bSDag-Erling Smørgrav } else { 237*0886019bSDag-Erling Smørgrav print(STDERR "## Trusted: $labels{$it}\n"); 238*0886019bSDag-Erling Smørgrav $outputdir = $trustdir; 239*0886019bSDag-Erling Smørgrav $trusted++; 240*0886019bSDag-Erling Smørgrav } 241*0886019bSDag-Erling Smørgrav $filename = label_to_filename($labels{$it}); 242*0886019bSDag-Erling Smørgrav open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $outputdir/$filename"; 243*0886019bSDag-Erling Smørgrav print_header($fh, $labels{$it}); 244*0886019bSDag-Erling Smørgrav printcert($fh, $labels{$it}, $certs{$it}); 245*0886019bSDag-Erling Smørgrav if ($outputdir) { 246*0886019bSDag-Erling Smørgrav close($fh) or die "Unable to close: $filename"; 247*0886019bSDag-Erling Smørgrav } else { 248*0886019bSDag-Erling Smørgrav print $fh "\n\n\n"; 249*0886019bSDag-Erling Smørgrav } 250*0886019bSDag-Erling Smørgrav} 251*0886019bSDag-Erling Smørgrav 252*0886019bSDag-Erling Smørgravprintf STDERR "## Trusted certificates: %4d\n", $trusted; 253*0886019bSDag-Erling Smørgravprintf STDERR "## Untrusted certificates: %4d\n", $untrusted; 254