1#!/usr/bin/perl 2# $OpenBSD: format-pem.pl,v 1.7 2024/11/01 11:19:13 sthen Exp $ 3# 4# Copyright (c) 2016 Stuart Henderson <sthen@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18# To update cert.pem based on the certificates included in Mozilla NSS, 19# pkg_add curl for mk-ca-bundle(1), and: 20# - perl format-pem.pl < cert.pem > /dev/null 2> calist.old 21# - mk-ca-bundle 22# - perl format-pem.pl < ca-bundle.crt > certnew.pem 2> calist.new 23# Summarize additions/removals for review: 24# - diff calist.old calist.new 25 26use strict; 27use warnings; 28 29use File::Temp qw/ :seekable /; 30if (! eval {require Date::Parse;1;}) { 31 print STDERR "Date::Parse not available - install p5-Time-TimeDate to check cert dates.\n"; 32} else { 33 use Date::Parse; 34} 35 36my $tmp = File::Temp->new(TEMPLATE => '/tmp/splitcert.XXXXXXXX'); 37my $t = $tmp->filename; 38 39my $certs = 0; 40my $incert = 0; 41my %ca; 42my $rcsid = '# $'.'OpenBSD$'; 43 44while(<>) { 45 $rcsid = $_ if ($_ =~ m/^# \$[O]penBSD/); 46 $incert++ if ($_ =~ m/^-----BEGIN CERTIFICATE-----/); 47 print $tmp $_ if ($incert); 48 49 if ($_ =~ m/^-----END CERTIFICATE-----/) { 50 $certs++; 51 52 my $issuer = `openssl x509 -in $t -noout -issuer`; 53 $issuer =~ s/^issuer= (.*)\n/$1/; 54 my $subj = `openssl x509 -in $t -noout -subject`; 55 $subj =~ s/^subject= (.*)\n/$1/; 56 57 my $o = `openssl x509 -in $t -noout -nameopt sep_multiline,use_quote,esc_msb -subject`; 58 if ($o =~ /O=/) { 59 $o =~ s/.*O=([^\n]*).*/$1/sm; 60 } else { 61 $o = $subj; 62 } 63 64 if (defined $ca{$o}{$subj}) { 65 print STDERR "ERROR: '$subj': duplicate\n"; 66 $ca{$o}{$subj}{'valid'} = 0; 67 } 68 69 $ca{$o}{$subj}{'valid'} = 1; 70 71 if ($issuer ne $subj) { 72 print STDERR "ERROR: '$subj' not self-signed"; 73 $ca{$o}{$subj}{'valid'} = 0; 74 } 75 76 if (eval {require Date::Parse;1;}) { 77 my $startdate = `openssl x509 -in $t -startdate -noout`; 78 my $enddate = `openssl x509 -in $t -enddate -noout`; 79 $startdate =~ s/notBefore=(.*)\n/$1/; 80 $enddate =~ s/notAfter=(.*)\n/$1/; 81 my $starttime = str2time($startdate); 82 my $endtime = str2time($enddate); 83 84 if ($starttime > time) { 85 print STDERR "ERROR: '$subj' not valid yet\n"; 86 $ca{$o}{$subj}{'valid'} = 0; 87 } 88 if ($endtime < time) { 89 print STDERR "ERROR: '$subj' expired on $startdate\n"; 90 $ca{$o}{$subj}{'valid'} = 0; 91 } elsif ($endtime < time + 86400 * 365 * 2) { 92 print STDERR "WARNING: '$subj' expires on $enddate\n"; 93 } 94 } 95 96 my $info = qx/openssl x509 -in $t -text -fingerprint -sha1 -certopt no_pubkey,no_sigdump,no_issuer -noout/; 97 $info .= qx/openssl x509 -in $t -fingerprint -sha256 -noout/; 98 my $cert = qx/openssl x509 -in $t/; 99 100 my $verify = qx/openssl verify -CAfile $t $t 2>&1/; 101 if (not $verify =~ /^$t: OK$/) { 102 print STDERR "ERROR: '$subj' cannot be verified with libressl\n---\n$verify---\n"; 103 $ca{$o}{$subj}{'valid'} = 0; 104 } 105 106 $ca{$o}{$subj}{'subj'} = $subj; 107 $ca{$o}{$subj}{'info'} = $info; 108 $ca{$o}{$subj}{'cert'} = $cert; 109 110 $tmp->seek(0, SEEK_SET); 111 $incert = 0; 112 } 113} 114 115close $tmp; 116chomp $rcsid; 117print $rcsid; 118foreach my $o (sort{$a cmp $b} keys %ca) { 119 print "\n### $o\n\n"; 120 foreach my $subj (sort{$a cmp $b} keys %{ $ca{$o} }) { 121 if ($ca{$o}{$subj}{'valid'} == 1) { 122 print "=== $subj\n"; 123 print $ca{$o}{$subj}{'info'}; 124 print $ca{$o}{$subj}{'cert'}; 125 } 126 } 127} 128 129# print a visual summary at the end 130foreach my $o (sort{$a cmp $b} keys %ca) { 131 print STDERR "\n$o\n"; 132 foreach my $subj (sort{$a cmp $b} keys %{ $ca{$o} }) { 133 print STDERR " $subj\n"; 134 } 135} 136