xref: /freebsd/secure/caroot/ca-extract.pl (revision 0886019bf853bd30b70aa4b8cdb059830ca6aaad)
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