Attached is a patch which hopefully implements better certificate chain
handling for add_cert.  Although I've tried to test it, I am not a
S/MIME user and don't have access to a large number of certificates to
play with.  I would greatly appreciate if smime_keys users would help me
test this out.

Note that this patch should handle the case of multiple leafs in the
certificate, but again I could use some help testing this.

-Kevin

# HG changeset patch
# User Kevin McCarthy <ke...@8t8.us>
# Date 1432328933 25200
#      Fri May 22 14:08:53 2015 -0700
# Node ID 0355d3924bd7b6608651cb3ec970d90848c438fc
# Parent  577987ca2d02d898880a19f7fd0ee3e7baa41798
smime_keys: Handle certificate chains in add_cert. (closes #3339) (closes #3559)

Find all chains in the certificate provided.  For each chain, prompt for
a label and separately add the intermediate and leaf certificates.

diff --git a/smime_keys.pl b/smime_keys.pl
--- a/smime_keys.pl
+++ b/smime_keys.pl
@@ -31,16 +31,17 @@
 sub usage ();
 sub mutt_Q ($);
 sub mycopy ($$);
 sub query_label ();
 sub mkdir_recursive ($);
 sub verify_files_exist (@);
 sub create_tempfile (;$);
 sub new_cert_structure ();
+sub create_cert_chains (@);
 
 # openssl helpers
 sub openssl_exec (@);
 sub openssl_format ($);
 sub openssl_x509_query ($@);
 sub openssl_hash ($);
 sub openssl_fingerprint ($);
 sub openssl_emails ($);
@@ -289,16 +290,56 @@
   $cert_data->{type} = "";
   $cert_data->{localKeyID} = "";
   $cert_data->{subject} = "";
   $cert_data->{issuer} = "";
 
   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
 ##################
 
 sub openssl_exec (@) {
   my (@args) = @_;
 
@@ -827,34 +868,51 @@
     cm_modify_entry('L', $keyid, 0, $label);
     print "Changed label for private key $keyid.\n";
   }
 }
 
 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_hash = openssl_hash($filename);
-  cm_add_certificate($filename, \$cert_hash, 1, $label, '?');
+  my @cert_chains = create_cert_chains(@cert_contents);
+  print "Found " . scalar(@cert_chains) . " certificate chains\n";
 
-  # 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.
+  foreach my $chain (@cert_chains) {
+    my $leaf = shift(@$chain);
+    my $issuer_chain_hash = "?";
 
-  # 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";
-  #   }
-  # }
+    print "Processing chain:\n";
+    if ($leaf->{subject}) {
+      print "subject=" . $leaf->{subject} . "\n";
+    }
+    my $label = query_label();
+
+    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 ($) {
   my ($filename) = @_;
 
   my @pem_contents;
   my $iter;
   my $key;

Attachment: signature.asc
Description: PGP signature

Reply via email to