1##
2##  MAca-bundle.pl -- Regenerate ca-root-nss.crt from the Mozilla certdata.txt
3##
4##  Rewritten in September 2011 by Matthias Andree to heed untrust
5##
6
7##  Copyright (c) 2011, 2013 Matthias Andree <mandree@FreeBSD.org>
8##  All rights reserved.
9##
10##  Redistribution and use in source and binary forms, with or without
11##  modification, are permitted provided that the following conditions are
12##  met:
13##
14##  * Redistributions of source code must retain the above copyright
15##  notice, this list of conditions and the following disclaimer.
16##
17##  * Redistributions in binary form must reproduce the above copyright
18##  notice, this list of conditions and the following disclaimer in the
19##  documentation and/or other materials provided with the distribution.
20##
21##  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22##  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23##  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24##  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25##  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26##  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27##  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28##  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29##  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30##  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31##  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32##  POSSIBILITY OF SUCH DAMAGE.
33
34use strict;
35use Carp;
36use MIME::Base64;
37
38#   configuration
39print <<EOH;
40##
41##  ca-root-nss.crt -- Bundle of CA Root Certificates
42##
43##  This is a bundle of X.509 certificates of public Certificate
44##  Authorities (CA). These were automatically extracted from Mozilla's
45##  root CA list (the file `certdata.txt').
46##
47##  It contains certificates trusted for server authentication.
48##
49##  Extracted from nss-%%VERSION_NSS%%
50##
51EOH
52my $debug = 0;
53$debug++
54    if defined $ENV{'WITH_DEBUG'}
55	and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/;
56
57my %certs;
58my %trusts;
59
60# returns a string like YYMMDDhhmmssZ of current time in GMT zone
61sub timenow()
62{
63	my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = gmtime(time);
64	return sprintf "%02d%02d%02d%02d%02d%02dZ", $year-100, $mon+1, $mday, $hour, $min, $sec;
65}
66
67sub printcert_plain($$)
68{
69    my ($label, $certdata) = @_;
70    print "=== $label ===\n" if $label;
71    print
72	"-----BEGIN CERTIFICATE-----\n",
73	MIME::Base64::encode_base64($certdata),
74	"-----END CERTIFICATE-----\n\n";
75}
76
77sub printcert_info($$)
78{
79    my (undef, $certdata) = @_;
80    return unless $certdata;
81    open(OUT, "|openssl x509 -text -inform DER -fingerprint")
82            || die "could not pipe to openssl x509";
83    print OUT $certdata;
84    close(OUT) or die "openssl x509 failed with exit code $?";
85}
86
87sub printcert($$) {
88    my ($a, $b) = @_;
89    printcert_info($a, $b);
90}
91
92# converts a datastream that is to be \177-style octal constants
93# from <> to a (binary) string and returns it
94sub graboct()
95{
96    my $data;
97
98    while (<>) {
99	last if /^END/;
100	my (undef,@oct) = split /\\/;
101	my @bin = map(chr(oct), @oct);
102	$data .= join('', @bin);
103    }
104
105    return $data;
106}
107
108sub grabcert()
109{
110    my $certdata;
111    my $cka_label = '';
112    my $serial = 0;
113    my $distrust = 0;
114
115    while (<>) {
116	chomp;
117	last if ($_ eq '');
118
119	if (/^CKA_LABEL UTF8 "([^"]+)"/) {
120	    $cka_label = $1;
121	}
122
123	if (/^CKA_VALUE MULTILINE_OCTAL/) {
124	    $certdata = graboct();
125	}
126
127	if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
128	    $serial = graboct();
129	}
130
131	if (/^CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL/)
132	{
133	    my $distrust_after = graboct();
134	    my $time_now = timenow();
135	    if ($time_now >= $distrust_after) { $distrust = 1; }
136	    if ($debug) {
137		printf STDERR "line $.: $cka_label ser #%d: distrust after %s, now: %s -> distrust $distrust\n", $serial, $distrust_after, timenow();
138	    }
139	    if ($distrust) {
140		return undef;
141	    }
142	}
143    }
144    return ($serial, $cka_label, $certdata);
145}
146
147sub grabtrust() {
148    my $cka_label;
149    my $serial;
150    my $maytrust = 0;
151    my $distrust = 0;
152
153    while (<>) {
154	chomp;
155	last if ($_ eq '');
156
157	if (/^CKA_LABEL UTF8 "([^"]+)"/) {
158	    $cka_label = $1;
159	}
160
161	if (/^CKA_SERIAL_NUMBER MULTILINE_OCTAL/) {
162	    $serial = graboct();
163	}
164
165	if (/^CKA_TRUST_SERVER_AUTH CK_TRUST (\S+)$/)
166	{
167	    if ($1 eq      'CKT_NSS_NOT_TRUSTED') {
168		$distrust = 1;
169	    } elsif ($1 eq 'CKT_NSS_TRUSTED_DELEGATOR') {
170		$maytrust = 1;
171	    } elsif ($1 ne 'CKT_NSS_MUST_VERIFY_TRUST') {
172		confess "Unknown trust setting on line $.:\n"
173		. "$_\n"
174		. "Script must be updated:";
175	    }
176	}
177    }
178
179    if (!$maytrust && !$distrust && $debug) {
180	print STDERR "line $.: no explicit trust/distrust found for $cka_label\n";
181    }
182
183    my $trust = ($maytrust and not $distrust);
184    return ($serial, $cka_label, $trust);
185}
186
187my $untrusted = 0;
188
189while (<>) {
190    if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
191	my ($serial, $label, $certdata) = grabcert();
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	} else { # $certdata undefined? distrust_after in effect
198	    $untrusted ++;
199	}
200    } elsif (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) {
201	my ($serial, $label, $trust) = grabtrust();
202	if (defined $trusts{$label."\0".$serial}) {
203	    warn "Trust for $label duplicated!\n";
204	}
205	$trusts{$label."\0".$serial} = $trust;
206    } elsif (/^CVS_ID.*Revision: ([^ ]*).*/) {
207        print "##  Source: \"certdata.txt\" CVS revision $1\n##\n\n";
208    }
209}
210
211sub printlabel(@) {
212    my @res = @_;
213    map { s/\0.*//; s/[^[:print:]]/_/g; "\"$_\""; } @res;
214    return wantarray ? @res : $res[0];
215}
216
217# weed out untrusted certificates
218foreach my $it (keys %trusts) {
219    if (!$trusts{$it}) {
220	if (!exists($certs{$it})) {
221	    warn "Found trust for nonexistent certificate ".printlabel($it)."\n" if $debug;
222	} else {
223	    delete $certs{$it};
224	    warn "Skipping untrusted ".printlabel($it)."\n" if $debug;
225	    $untrusted++;
226	}
227    }
228}
229
230print		"##  Untrusted certificates omitted from this bundle: $untrusted\n\n";
231print STDERR	"##  Untrusted certificates omitted from this bundle: $untrusted\n";
232
233my $certcount = 0;
234foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) {
235    if (!exists($trusts{$it})) {
236	die "Found certificate without trust block,\naborting";
237    }
238    printcert("", $certs{$it});
239    print "\n\n\n";
240    $certcount++;
241    print STDERR "Trusting $certcount: ".printlabel($it)."\n" if $debug;
242}
243
244if ($certcount < 25) {
245    die "Certificate count of $certcount is implausibly low.\nAbort";
246}
247
248print		"##  Number of certificates: $certcount\n";
249print STDERR	"##  Number of certificates: $certcount\n";
250print "##  End of file.\n";
251