[1/3] Initial infrastructure for SSL root bundle in base

This setup will add the trusted certificates from the Mozilla NSS bundle
to base.

This commit includes:
- CAROOT option to opt out of installation of certs
- mtree amendments for final destinations
- infrastructure to fetch/update certs, along with instructions

A follow-up commit will add a certctl(8) utility to give the user control
over trust specifics. Another follow-up commit will actually commit the
initial result of updatecerts.

This work was done primarily by allanjude@, with minor contributions by
myself.

No objection from:	secteam
Relnotes:	yes
Differential Revision:	https://reviews.freebsd.org/D16856
This commit is contained in:
Kyle Evans 2019-10-02 01:05:29 +00:00
parent 150c95edfe
commit f27f39db77
8 changed files with 355 additions and 0 deletions

View File

@ -194,6 +194,12 @@
uk_UA.KOI8-U
..
..
certs
blacklisted
..
trusted
..
..
dict
..
doc

View File

@ -8,6 +8,8 @@ SUBDIR_PARALLEL=
SUBDIR.${MK_TESTS}+= tests
SUBDIR.${MK_CAROOT}+= caroot
# These are the programs which depend on crypto, but not Kerberos.
SPROGS= lib/libfetch lib/libpam lib/libradius lib/libtelnet \
bin/ed libexec/telnetd usr.bin/fetch usr.bin/telnet \

272
secure/caroot/MAca-bundle.pl Executable file
View File

@ -0,0 +1,272 @@
#!/usr/bin/env perl
##
## MAca-bundle.pl -- Regenerate ca-root-nss.crt from the Mozilla certdata.txt
##
## Rewritten in September 2011 by Matthias Andree to heed untrust
##
## Copyright (c) 2011, 2013 Matthias Andree <mandree@FreeBSD.org>
## All rights reserved.
## Copyright (c) 2018, Allan Jude <allanjude@FreeBSD.org>
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
##
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
##
## * 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 COPYRIGHT HOLDERS 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
## COPYRIGHT HOLDER 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.
use strict;
use Carp;
use MIME::Base64;
use Getopt::Long;
my $VERSION = '$FreeBSD$';
my $inputfh = *STDIN;
my $debug = 0;
my $infile;
my $outputdir;
my %labels;
my %certs;
my %trusts;
$debug++
if defined $ENV{'WITH_DEBUG'}
and $ENV{'WITH_DEBUG'} !~ m/(?i)^(no|0|false|)$/;
GetOptions (
"debug+" => \$debug,
"infile:s" => \$infile,
"outputdir:s" => \$outputdir)
or die("Error in command line arguments\n$0 [-d] [-i input-file] [-o output-dir]\n");
if ($infile) {
open($inputfh, "<", $infile) or die "Failed to open $infile";
}
sub print_header($$)
{
my $dstfile = shift;
my $label = shift;
if ($outputdir) {
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).
##
## Extracted from nss
## with $VERSION
##
EOFH
} else {
print $dstfile <<EOH;
##
## ca-root-nss.crt -- Bundle of CA Root Certificates
##
## This is a bundle of X.509 certificates of public Certificate
## Authorities (CA). These were automatically extracted from Mozilla's
## root CA list (the file `certdata.txt').
##
## Extracted from nss
## with $VERSION
##
EOH
}
}
sub printcert($$$)
{
my ($fh, $label, $certdata) = @_;
return unless $certdata;
open(OUT, "|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/;
my (undef,@oct) = split /\\/;
my @bin = map(chr(oct), @oct);
$data .= join('', @bin);
}
return $data;
}
sub grabcert($)
{
my $ifh = shift;
my $certdata;
my $cka_label;
my $serial;
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);
}
}
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|EMAIL_PROTECTION|CODE_SIGNING) CK_TRUST (\S+)$/)
{
if ($2 eq 'CKT_NSS_NOT_TRUSTED') {
$distrust = 1;
} elsif ($2 eq 'CKT_NSS_TRUSTED_DELEGATOR') {
$maytrust = 1;
} elsif ($2 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);
}
if (!$outputdir) {
print_header(*STDOUT, "");
}
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";
}
$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];
}
# weed out untrusted certificates
my $untrusted = 0;
foreach my $it (keys %trusts) {
if (!$trusts{$it}) {
if (!exists($certs{$it})) {
warn "Found trust for nonexistent certificate $labels{$it}\n" if $debug;
} else {
delete $certs{$it};
warn "Skipping untrusted $labels{$it}\n" if $debug;
$untrusted++;
}
}
}
if (!$outputdir) {
print "## Untrusted certificates omitted from this bundle: $untrusted\n\n";
}
print STDERR "## Untrusted certificates omitted from this bundle: $untrusted\n";
my $certcount = 0;
foreach my $it (sort {uc($a) cmp uc($b)} keys %certs) {
my $fh = *STDOUT;
my $filename;
if (!exists($trusts{$it})) {
die "Found certificate without trust block,\naborting";
}
if ($outputdir) {
$filename = label_to_filename($labels{$it});
open($fh, ">", "$outputdir/$filename") or die "Failed to open certificate $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";
}
$certcount++;
print STDERR "Trusting $certcount: $labels{$it}\n" if $debug;
}
if ($certcount < 25) {
die "Certificate count of $certcount is implausibly low.\nAbort";
}
if (!$outputdir) {
print "## Number of certificates: $certcount\n";
print "## End of file.\n";
}
print STDERR "## Number of certificates: $certcount\n";

21
secure/caroot/Makefile Normal file
View File

@ -0,0 +1,21 @@
# $FreeBSD$
PACKAGE= caroot
CLEANFILES+= certdata.txt
SUBDIR+= trusted
SUBDIR+= blacklisted
.include <bsd.prog.mk>
# To be used by secteam@ to update the trusted certificates
fetchcerts: .PHONY
fetch --no-sslv3 --no-tlsv1 -o certdata.txt 'https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt'
cleancerts: .PHONY
@${MAKE} -C ${.CURDIR}/trusted ${.TARGET}
updatecerts: .PHONY cleancerts fetchcerts
perl ${.CURDIR}/MAca-bundle.pl -i certdata.txt -o ${.CURDIR}/trusted

34
secure/caroot/README Normal file
View File

@ -0,0 +1,34 @@
# $FreeBSD$
This directory contains the scripts to update the TLS CA Root Certificates
that comprise the 'root trust store'.
The 'updatecerts' make target should be run periodically by secteam@
specifically when there is an important change to the list of trusted root
certificates included by Mozilla.
It will:
1) Remove the old trusted certificates (cleancerts)
2) Download the latest certdata.txt from Mozilla (fetchcerts)
3) Split certdata.txt into the individual .pem files (updatecerts)
Then the results should manually be inspected (svn status)
1) Any no-longer-trusted certificates should be moved to the
blacklisted directory (svn mv)
2) any newly added certificates will need to be added (svn add)
The following make targets exist:
cleancerts:
Delete the old certificates, run as a dependency of updatecerts.
fetchcerts:
Download the latest certdata.txt from the Mozilla NSS hg repo
See the changelog here:
https://hg.mozilla.org/projects/nss/log/tip/lib/ckfw/builtins/certdata.txt
updatecerts:
Runs a perl script (MAca-bundle.pl) on the downloaded certdata.txt
to generate the individual certificate files (.pem) and store them
in the trusted/ directory.

View File

@ -0,0 +1,7 @@
# $FreeBSD$
BINDIR= /usr/share/certs/blacklisted
FILES=
.include <bsd.prog.mk>

View File

@ -0,0 +1,12 @@
# $FreeBSD$
BINDIR= /usr/share/certs/trusted
TRUSTED_CERTS!= ls ${.CURDIR}/*.pem 2> /dev/null || true
FILES+= ${TRUSTED_CERTS}
cleancerts:
@[ -z "${TRUSTED_CERTS}" ] || rm ${TRUSTED_CERTS}
.include <bsd.prog.mk>

View File

@ -77,6 +77,7 @@ __DEFAULT_YES_OPTIONS = \
BZIP2 \
CALENDAR \
CAPSICUM \
CAROOT \
CASPER \
CCD \
CDDL \