aboutsummaryrefslogtreecommitdiff
path: root/secure/caroot/ca-extract.pl
diff options
context:
space:
mode:
Diffstat (limited to 'secure/caroot/ca-extract.pl')
-rwxr-xr-xsecure/caroot/ca-extract.pl253
1 files changed, 253 insertions, 0 deletions
diff --git a/secure/caroot/ca-extract.pl b/secure/caroot/ca-extract.pl
new file mode 100755
index 000000000000..75f8352e384e
--- /dev/null
+++ b/secure/caroot/ca-extract.pl
@@ -0,0 +1,253 @@
+#!/usr/bin/env perl
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2011, 2013 Matthias Andree <mandree@FreeBSD.org>
+# Copyright (c) 2018 Allan Jude <allanjude@FreeBSD.org>
+# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#
+# ca-extract.pl -- Extract trusted and untrusted certificates from
+# Mozilla's certdata.txt.
+#
+# Rewritten in September 2011 by Matthias Andree to heed untrust
+#
+
+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 $?";
+}
+
+# converts a datastream that is to be \177-style octal constants
+# from <> to a (binary) string and returns it
+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;
+ # We store the label in a separate hash because truncating the key
+ # with \0 was causing garbage data after the end of the text.
+ $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;