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