From e85bfacd7a5e5f0205317b8f6195c39629057e19 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Mon, 25 May 2015 10:59:50 -0700 Subject: [PATCH] smime_keys: Handle certificate chains in add_cert. (closes #3339) (closes #3559) Find all chains in the certificate provided. For each chain create a separate leaf and intermediate certificate file. Because Mutt controls the label prompt, use a single label for all chains. Also, loosen up cert file parsing to allow attributes even if they aren't delimited by a "Bag Attributes" header. Thanks to David J. Weller-Fahy for his testing and feedback! --- smime_keys.pl | 93 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/smime_keys.pl b/smime_keys.pl index f153e9cdc..0ed3336c8 100755 --- a/smime_keys.pl +++ b/smime_keys.pl @@ -36,6 +36,7 @@ sub mkdir_recursive ($); sub verify_files_exist (@); sub create_tempfile (;$); sub new_cert_structure (); +sub create_cert_chains (@); # openssl helpers sub openssl_exec (@); @@ -294,6 +295,46 @@ sub new_cert_structure () { return $cert_data; } +sub create_cert_chains (@) { + my (@certs) = @_; + + my (%subject_hash, @leaves, @chains); + + foreach my $cert (@certs) { + $cert->{children} = 0; + if ($cert->{subject}) { + $subject_hash{$cert->{subject}} = $cert; + } + } + + foreach my $cert (@certs) { + my $parent = $subject_hash{$cert->{issuer}}; + if (defined($parent)) { + $parent->{children} += 1; + } + } + + @leaves = grep { $_->{children} == 0 } @certs; + foreach my $leaf (@leaves) { + my $chain = []; + my $cert = $leaf; + + while (defined($cert)) { + push @$chain, $cert; + + $cert = $subject_hash{$cert->{issuer}}; + if (defined($cert) && + (scalar(grep {$_ == $cert} @$chain) != 0)) { + $cert = undef; + } + } + + push @chains, $chain; + } + + return @chains; +} + ################## # openssl helpers @@ -499,7 +540,8 @@ sub openssl_parse_pem ($$) { $state = 1; } - if ($state == 1) { + # Allow attributes without the "Bag Attributes" header + if ($state != 2) { if (/localKeyID:\s*(.*)/) { $cert_data->{localKeyID} = $1; } @@ -833,23 +875,40 @@ sub handle_add_cert($) { my ($filename) = @_; my $label = query_label(); + my @cert_contents = openssl_parse_pem($filename, 0); + @cert_contents = grep { $_->{type} eq "C" } @cert_contents; + + my @cert_chains = create_cert_chains(@cert_contents); + print "Found " . scalar(@cert_chains) . " certificate chains\n"; + + foreach my $chain (@cert_chains) { + my $leaf = shift(@$chain); + my $issuer_chain_hash = "?"; + + print "Processing chain:\n"; + if ($leaf->{subject}) { + print "subject=" . $leaf->{subject} . "\n"; + } - my $cert_hash = openssl_hash($filename); - cm_add_certificate($filename, \$cert_hash, 1, $label, '?'); - - # TODO: - # Below is the method from http://kb.wisc.edu/middleware/page.php?id=4091 - # Investigate threading the chain and separating out issuer as an alternative. - - # my @cert_contents = openssl_parse_pem($filename, 0); - # foreach my $cert (@cert_contents) { - # if ($cert->{type} eq "C") { - # my $cert_hash = openssl_hash($cert->{datafile}); - # cm_add_certificate($cert->{datafile}, \$cert_hash, 1, $label, '?'); - # } else { - # print "Ignoring private key\n"; - # } - # } + if (scalar(@$chain) > 0) { + my ($issuer_chain_fh, $issuer_chain_file) = create_tempfile(); + + foreach my $issuer (@$chain) { + my $issuer_datafile = $issuer->{datafile}; + open(my $issuer_fh, "< $issuer_datafile") or + die "can't open $issuer_datafile: $?"; + print $issuer_chain_fh $_ while (<$issuer_fh>); + close($issuer_fh); + } + + close($issuer_chain_fh); + $issuer_chain_hash = openssl_hash($issuer_chain_file); + cm_add_certificate($issuer_chain_file, \$issuer_chain_hash, 0, $label); + } + + my $leaf_hash = openssl_hash($leaf->{datafile}); + cm_add_certificate($leaf->{datafile}, \$leaf_hash, 1, $label, $issuer_chain_hash); + } } sub handle_add_pem ($) { -- 2.40.0