use strict;
use warnings;
use Carp;
use MIME::Base64;
use Getopt::Long;
use Time::Local qw( timegm_posix );
use POSIX qw( strftime );
my $generated = '@' . 'generated';
my $inputfh = *STDIN;
my $debug = 0;
my $infile;
my $trustdir = "trusted";
my $untrustdir = "untrusted";
my %labels;
my %certs;
my %trusts;
my %expires;
$debug++
if defined $ENV{'WITH_DEBUG'}
and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/;
GetOptions (
"debug+" => \$debug,
"infile:s" => \$infile,
"trustdir:s" => \$trustdir,
"untrustdir:s" => \$untrustdir)
or die("Error in command line arguments\n$0 [-d] [-i input-file] [-t trust-dir] [-u untrust-dir]\n");
if ($infile) {
open($inputfh, "<", $infile) or die "Failed to open $infile";
}
sub print_header($$)
{
my $dstfile = shift;
my $label = shift;
print $dstfile <<EOFH;
##
## $label
##
## This is a single X.509 certificate for a public Certificate
## Authority (CA). It was automatically extracted from Mozilla's
## root CA list (the file `certdata.txt' in security/nss).
##
## $generated
##
EOFH
}
sub printcert($$$)
{
my ($fh, $label, $certdata) = @_;
return unless $certdata;
open(OUT, "|-", qw(openssl x509 -text -inform DER -fingerprint))
or die "could not pipe to openssl x509";
print OUT $certdata;
close(OUT) or die "openssl x509 failed with exit code $?";
}
sub graboct($)
{
my $ifh = shift;
my $data = "";
while (<$ifh>) {
last if /^END/;
$data .= join('', map { chr(oct($_)) } m/\\([0-7]{3})/g);
}
return $data;
}
sub grabcert($)
{
my $ifh = shift;
my $certdata;
my $cka_label = '';
my $serial = 0;
my $distrust = 0;
while (<$ifh>) {
chomp;
last if ($_ eq '');
if (/^CKA_LABEL UTF8 "([^"]+)"/) {
$cka_label = $1;
}
if (/^CKA_VALUE MULTILINE_OCTAL/) {
$certdata = graboct($ifh);
}
if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
$serial = graboct($ifh);
}
if (/^CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL/)
{
my $distrust_after = graboct($ifh);
my ($year, $mon, $mday, $hour, $min, $sec) = unpack "A2A2A2A2A2A2", $distrust_after;
$distrust_after = timegm_posix($sec, $min, $hour, $mday, $mon - 1, $year + 100);
$expires{$cka_label."\0".$serial} = $distrust_after;
}
}
return ($serial, $cka_label, $certdata);
}
sub grabtrust($) {
my $ifh = shift;
my $cka_label;
my $serial;
my $maytrust = 0;
my $distrust = 0;
while (<$ifh>) {
chomp;
last if ($_ eq '');
if (/^CKA_LABEL UTF8 "([^"]+)"/) {
$cka_label = $1;
}
if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
$serial = graboct($ifh);
}
if (/^CKA_TRUST_SERVER_AUTH CK_TRUST (\S+)$/) {
if ($1 eq 'CKT_NSS_NOT_TRUSTED') {
$distrust = 1;
} elsif ($1 eq 'CKT_NSS_TRUSTED_DELEGATOR') {
$maytrust = 1;
} elsif ($1 ne 'CKT_NSS_MUST_VERIFY_TRUST') {
confess "Unknown trust setting on line $.:\n"
. "$_\n"
. "Script must be updated:";
}
}
}
if (!$maytrust && !$distrust && $debug) {
print STDERR "line $.: no explicit trust/distrust found for $cka_label\n";
}
my $trust = ($maytrust and not $distrust);
return ($serial, $cka_label, $trust);
}
while (<$inputfh>) {
if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
my ($serial, $label, $certdata) = grabcert($inputfh);
if (defined $certs{$label."\0".$serial}) {
warn "Certificate $label duplicated!\n";
}
if (defined $certdata) {
$certs{$label."\0".$serial} = $certdata;
$labels{$label."\0".$serial} = $label;
}
} elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) {
my ($serial, $label, $trust) = grabtrust($inputfh);
if (defined $trusts{$label."\0".$serial}) {
warn "Trust for $label duplicated!\n";
}
$trusts{$label."\0".$serial} = $trust;
$labels{$label."\0".$serial} = $label;
} elsif (/^CVS_ID.*Revision: ([^ ]*).*/) {
print "## Source: \"certdata.txt\" CVS revision $1\n##\n\n";
}
}
sub label_to_filename(@) {
my @res = @_;
map { s/\0.*//; s/[^[:alnum:]\-]/_/g; $_ = "$_.pem"; } @res;
return wantarray ? @res : $res[0];
}
my $untrusted = 0;
my $trusted = 0;
my $now = time;
foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) {
my $fh = *STDOUT;
my $outputdir;
my $filename;
if (exists($expires{$it}) &&
$now >= $expires{$it} + 398 * 24 * 60 * 60) {
print(STDERR "## Expired: $labels{$it}\n");
$outputdir = $untrustdir;
$untrusted++;
} elsif (!$trusts{$it}) {
print(STDERR "## Untrusted: $labels{$it}\n");
$outputdir = $untrustdir;
$untrusted++;
} else {
print(STDERR "## Trusted: $labels{$it}\n");
$outputdir = $trustdir;
$trusted++;
}
$filename = label_to_filename($labels{$it});
open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $outputdir/$filename";
print_header($fh, $labels{$it});
printcert($fh, $labels{$it}, $certs{$it});
if ($outputdir) {
close($fh) or die "Unable to close: $filename";
} else {
print $fh "\n\n\n";
}
}
printf STDERR "## Trusted certificates: %4d\n", $trusted;
printf STDERR "## Untrusted certificates: %4d\n", $untrusted;