3 # Copyright (C) 2001-2002 Oliver Ehli <elmy@acm.org>
4 # Copyright (C) 2001 Mike Schiraldi <raldi@research.netsol.com>
5 # Copyright (C) 2003 Bjoern Jacke <bjoern@j3e.de>
6 # Copyright (C) 2015 Kevin J. McCarthy <kevin@8t8.us>
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 use File::Glob ':glob';
25 use File::Temp qw(tempfile tempdir);
36 sub mkdir_recursive ($);
37 sub verify_files_exist (@);
38 sub create_tempfile (;$);
39 sub new_cert_structure ();
40 sub create_cert_chains (@);
44 sub openssl_format ($);
45 sub openssl_x509_query ($@);
47 sub openssl_fingerprint ($);
48 sub openssl_emails ($);
49 sub openssl_p12_to_pem ($$);
50 sub openssl_verify ($$);
51 sub openssl_crl_text($);
52 sub openssl_trust_flag ($$;$);
53 sub openssl_parse_pem ($$);
54 sub openssl_dump_cert ($);
55 sub openssl_purpose_flag ($$);
57 # key/certificate management methods
59 sub cm_add_entry ($$$$$$;$);
61 sub cm_add_indexed_cert ($$$);
62 sub cm_add_key ($$$$$$);
63 sub cm_modify_entry ($$$;$);
64 sub cm_find_entry ($$);
65 sub cm_refresh_index ();
68 sub handle_init_paths ();
69 sub handle_change_label ($);
70 sub handle_add_cert ($);
71 sub handle_add_pem ($);
72 sub handle_add_p12 ($);
73 sub handle_add_chain ($$$);
74 sub handle_verify_cert($$);
75 sub handle_remove_pair ($);
76 sub handle_add_root_cert ($);
79 my $mutt = $ENV{MUTT_CMDLINE} || 'mutt';
80 my $opensslbin = "/usr/bin/openssl";
83 # Get the directories mutt uses for certificate/key storage.
85 my $private_keys_path = mutt_Q 'smime_keys';
86 die "smime_keys is not set in mutt's configuration file"
87 if length $private_keys_path == 0;
89 my $certificates_path = mutt_Q 'smime_certificates';
90 die "smime_certificates is not set in mutt's configuration file"
91 if length $certificates_path == 0;
93 my $root_certs_path = mutt_Q 'smime_ca_location';
94 die "smime_ca_location is not set in mutt's configuration file"
95 if length $root_certs_path == 0;
97 my $root_certs_switch;
98 if ( -d $root_certs_path) {
99 $root_certs_switch = -CApath;
101 $root_certs_switch = -CAfile;
109 if (@ARGV == 1 and $ARGV[0] eq "init") {
112 elsif (@ARGV == 1 and $ARGV[0] eq "refresh") {
115 elsif (@ARGV == 1 and $ARGV[0] eq "list") {
118 elsif (@ARGV == 2 and $ARGV[0] eq "label") {
119 handle_change_label($ARGV[1]);
121 elsif (@ARGV == 2 and $ARGV[0] eq "add_cert") {
122 verify_files_exist($ARGV[1]);
123 handle_add_cert($ARGV[1]);
125 elsif (@ARGV == 2 and $ARGV[0] eq "add_pem") {
126 verify_files_exist($ARGV[1]);
127 handle_add_pem($ARGV[1]);
129 elsif ( @ARGV == 2 and $ARGV[0] eq "add_p12") {
130 verify_files_exist($ARGV[1]);
131 handle_add_p12($ARGV[1]);
133 elsif (@ARGV == 4 and $ARGV[0] eq "add_chain") {
134 verify_files_exist($ARGV[1], $ARGV[2], $ARGV[3]);
135 handle_add_chain($ARGV[1], $ARGV[2], $ARGV[3]);
137 elsif ((@ARGV == 2 or @ARGV == 3) and $ARGV[0] eq "verify") {
138 verify_files_exist($ARGV[2]) if (@ARGV == 3);
139 handle_verify_cert($ARGV[1], $ARGV[2]);
141 elsif (@ARGV == 2 and $ARGV[0] eq "remove") {
142 handle_remove_pair($ARGV[1]);
144 elsif (@ARGV == 2 and $ARGV[0] eq "add_root") {
145 verify_files_exist($ARGV[1]);
146 handle_add_root_cert($ARGV[1]);
156 ############## sub-routines ########################
166 Usage: smime_keys <operation> [file(s) | keyID [file(s)]]
168 with operation being one of:
170 init : no files needed, inits directory structure.
171 refresh : refreshes certificate and key index files.
172 Updates trust flag (expiration).
173 Adds purpose flag if missing.
175 list : lists the certificates stored in database.
176 label : keyID required. changes/removes/adds label.
177 remove : keyID required.
178 verify : 1=keyID and optionally 2=CRL
179 Verifies the certificate chain, and optionally whether
180 this certificate is included in supplied CRL (PEM format).
181 Note: to verify all certificates at the same time,
182 replace keyID with "all"
184 add_cert : certificate required.
185 add_chain : three files reqd: 1=Key, 2=certificate
186 plus 3=intermediate certificate(s).
187 add_p12 : one file reqd. Adds keypair to database.
188 file is PKCS12 (e.g. export from netscape).
189 add_pem : one file reqd. Adds keypair to database.
190 (file was converted from e.g. PKCS12).
192 add_root : one file reqd. Adds PEM root certificate to the location
193 specified within muttrc (smime_verify_* command)
201 my $cmd = "$mutt -v >/dev/null 2>/dev/null";
202 system ($cmd) == 0 or die<<EOF;
203 Couldn't launch mutt. I attempted to do so by running the command "$mutt".
204 If that's not the right command, you can override it by setting the
205 environment variable \$MUTT_CMDLINE
208 $cmd = "$mutt -Q $var 2>/dev/null";
212 Couldn't look up the value of the mutt variable "$var".
213 You must set this in your mutt config file. See contrib/smime.rc for an example.
216 $answer =~ /\"(.*?)\"/ and return bsd_glob($1, GLOB_TILDE | GLOB_NOCHECK);
218 $answer =~ /^Mutt (.*?) / and die<<EOF;
219 This script requires mutt 1.5.0 or later. You are using mutt $1.
222 die "Value of $var is weird\n";
226 my ($source, $dest) = @_;
228 copy $source, $dest or die "Problem copying $source to $dest: $!\n";
236 print "\nYou may assign a label to this key, so you don't have to remember\n";
237 print "the key ID. This has to be _one_ word (no whitespaces).\n\n";
239 print "Enter label: ";
242 if (defined($input) && ($input !~ /^\s*$/)) {
245 ($label, $junk) = split(/\s/, $input, 2);
247 if (defined($junk)) {
248 print "\nUsing '$label' as label; ignoring '$junk'\n";
252 if ((! defined($label)) || ($label =~ /^\s*$/)) {
259 sub mkdir_recursive ($) {
263 for my $dir (split /\//, $path) {
264 $tmp_path .= "$dir/";
267 or mkdir $tmp_path, 0700
268 or die "Can't mkdir $tmp_path: $!";
272 sub verify_files_exist (@) {
275 foreach my $file (@files) {
276 if ((! -e $file) || (! -s $file)) {
277 die("$file is nonexistent or empty.");
282 # Returns a list ($fh, $filename)
283 sub create_tempfile (;$) {
284 my ($directory) = @_;
286 if (! defined($directory)) {
287 if (! defined($tmpdir)) {
288 $tmpdir = tempdir(CLEANUP => 1);
290 $directory = $tmpdir;
293 return tempfile(DIR => $directory);
296 # Creates a cert data structure used by openssl_parse_pem
297 sub new_cert_structure () {
300 $cert_data->{datafile} = "";
301 $cert_data->{type} = "";
302 $cert_data->{localKeyID} = "";
303 $cert_data->{subject} = "";
304 $cert_data->{issuer} = "";
309 sub create_cert_chains (@) {
312 my (%subject_hash, @leaves, @chains);
314 foreach my $cert (@certs) {
315 $cert->{children} = 0;
316 if ($cert->{subject}) {
317 $subject_hash{$cert->{subject}} = $cert;
321 foreach my $cert (@certs) {
322 my $parent = $subject_hash{$cert->{issuer}};
323 if (defined($parent)) {
324 $parent->{children} += 1;
328 @leaves = grep { $_->{children} == 0 } @certs;
329 foreach my $leaf (@leaves) {
333 while (defined($cert)) {
336 $cert = $subject_hash{$cert->{issuer}};
337 if (defined($cert) &&
338 (scalar(grep {$_ == $cert} @$chain) != 0)) {
343 push @chains, $chain;
354 sub openssl_exec (@) {
359 open($fh, "-|", $opensslbin, @args)
360 or die "Failed to run '$opensslbin @args': $!";
363 # NOTE: Callers should check the value of $? for the exit status.
365 die "Syserr closing '$opensslbin @args' pipe: $!";
372 sub openssl_format ($) {
375 return -B $filename ? 'DER' : 'PEM';
378 sub openssl_x509_query ($@) {
379 my ($filename, @query) = @_;
381 my $format = openssl_format($filename);
382 my @args = ("x509", "-in", $filename, "-inform", $format, "-noout", @query);
383 return openssl_exec(@args);
386 sub openssl_hash ($) {
389 my $cert_hash = join("", openssl_x509_query($filename, "-hash"));
390 $? and die "openssl -hash '$filename' returned $?";
396 sub openssl_fingerprint ($) {
399 my $fingerprint = join("", openssl_x509_query($filename, "-fingerprint"));
400 $? and die "openssl -fingerprint '$filename' returned $?";
406 sub openssl_emails ($) {
409 my @mailboxes = openssl_x509_query($filename, "-email");
410 $? and die "openssl -email '$filename' returned $?";
416 sub openssl_p12_to_pem ($$) {
417 my ($p12_file, $pem_file) = @_;
419 my @args = ("pkcs12", "-in", $p12_file, "-out", $pem_file);
421 $? and die "openssl pkcs12 conversion returned $?";
424 sub openssl_verify ($$) {
425 my ($issuer_path, $cert_path) = @_;
427 my @args = ("verify", $root_certs_switch, $root_certs_path,
428 "-untrusted", $issuer_path, $cert_path);
429 my $output = join("", openssl_exec(@args));
435 sub openssl_crl_text($) {
438 my @args = ("crl", "-text", "-noout", "-in", $crl);
439 my @output = openssl_exec(@args);
440 $? and die "openssl crl -text '$crl' returned $?";
445 sub openssl_trust_flag ($$;$) {
446 my ($cert, $issuerid, $crl) = @_;
448 print "==> about to verify certificate of $cert\n";
452 my $cert_path = "$certificates_path/$cert";
454 if ($issuerid eq '?') {
455 $issuer_path = "$certificates_path/$cert";
457 $issuer_path = "$certificates_path/$issuerid";
460 my $output = openssl_verify($issuer_path, $cert_path);
462 print "openssl verify returned exit code " . ($? >> 8) . " with output:\n";
464 print "Marking certificate as invalid\n";
469 if ($output !~ /OK/) {
473 my ($not_before, $not_after, $serial_in) = openssl_x509_query($cert_path, "-dates", "-serial");
474 $? and die "openssl -dates -serial '$cert_path' returned $?";
476 if ( defined $not_before and defined $not_after ) {
477 my %months = ('Jan', '00', 'Feb', '01', 'Mar', '02', 'Apr', '03',
478 'May', '04', 'Jun', '05', 'Jul', '06', 'Aug', '07',
479 'Sep', '08', 'Oct', '09', 'Nov', '10', 'Dec', '11');
481 my @tmp = split (/\=/, $not_before);
482 my $not_before_date = $tmp[1];
484 $not_before_date =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
486 if (timegm($fields[4], $fields[3], $fields[2], $fields[1],
487 $months{$fields[0]}, $fields[5]) > time) {
488 print "Certificate is not yet valid.\n";
492 print "Expiration Date: Parse Error : $not_before_date\n\n";
495 @tmp = split (/\=/, $not_after);
496 my $not_after_date = $tmp[1];
498 $not_after_date =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
500 if (timegm($fields[4], $fields[3], $fields[2], $fields[1],
501 $months{$fields[0]}, $fields[5]) < time) {
502 print "Certificate has expired.\n";
506 print "Expiration Date: Parse Error : $not_after_date\n\n";
510 if ( defined $crl ) {
512 my @serial = split (/\=/, $serial_in);
513 my $match_line = undef;
514 my @crl_lines = openssl_crl_text($crl);
515 for (my $index = 0; $index <= $#crl_lines; $index++) {
516 if ($crl_lines[$index] =~ /Serial Number:\s*\Q$serial[1]\E\b/) {
517 $match_line = $crl_lines[$index + 1];
522 if ( defined $match_line ) {
523 my @revoke_date = split (/:\s/, $match_line);
524 print "FAILURE: Certificate $cert has been revoked on $revoke_date[1]\n";
533 sub openssl_parse_pem ($$) {
534 my ($filename, $attrs_required) = @_;
542 my $cert_tmp_filename;
544 $cert_data = new_cert_structure();
545 ($cert_tmp_fh, $cert_data->{datafile}) = create_tempfile();
547 open(PEM_FILE, "<$filename") or die("Can't open $filename: $!");
549 if (/^Bag Attributes/) {
551 $state == 0 or die("PEM-parse error at: $.");
555 # Allow attributes without the "Bag Attributes" header
557 if (/localKeyID:\s*(.*)/) {
558 $cert_data->{localKeyID} = $1;
561 if (/subject=\s*(.*)/) {
562 $cert_data->{subject} = $1;
565 if (/issuer=\s*(.*)/) {
566 $cert_data->{issuer} = $1;
573 print $cert_tmp_fh $_;
577 $cert_data->{type} = "K";
581 $cert_data->{type} = "C";
584 die("What's this: $_");
588 print $cert_tmp_fh $_;
592 push (@certs, $cert_data);
594 $cert_data = new_cert_structure();
595 ($cert_tmp_fh, $cert_data->{datafile}) = create_tempfile();
599 print $cert_tmp_fh $_;
604 if ($attrs_required && ($bag_count != $cert_count)) {
605 die("Not all contents were bagged. can't continue.");
611 sub openssl_dump_cert ($) {
614 my $format = openssl_format($filename);
615 my @args = ("x509", "-in", $filename, "-inform", $format);
616 my $output = join("", openssl_exec(@args));
617 $? and die "openssl x509 certificate dump returned $?";
622 sub openssl_purpose_flag ($$) {
623 my ($filename, $certhash) = @_;
625 print "==> checking purpose flags for $certhash\n";
629 my @output = openssl_x509_query($filename, "-purpose");
630 $? and die "openssl -purpose '$filename' returned $?";
632 foreach my $line (@output) {
633 if ($line =~ /^S\/MIME signing\s*:\s*Yes/) {
637 elsif ($line =~ /^S\/MIME encryption\s*:\s*Yes/) {
644 print "\tWARNING: neither encryption nor signing flags are enabled.\n";
645 print "\t $certhash will not be usable by Mutt.\n";
653 #################################
654 # certificate management methods
655 #################################
657 sub cm_list_certs () {
658 my %keyflags = ( 'i', '(Invalid)', 'r', '(Revoked)', 'e', '(Expired)',
659 'u', '(Unverified)', 'v', '(Valid)', 't', '(Trusted)');
661 open(INDEX, "<$certificates_path/.index") or
662 die "Couldn't open $certificates_path/.index: $!";
671 if ($fields[2] eq '-') {
672 print "$fields[1]: Issued for: $fields[0] $keyflags{$fields[4]}\n";
674 print "$fields[1]: Issued for: $fields[0] \"$fields[2]\" $keyflags{$fields[4]}\n";
677 my $certfile = "$certificates_path/$fields[1]";
681 die "Couldn't open $certfile: $!";
687 my ($subject_in, $issuer_in, $date1_in, $date2_in) =
688 openssl_x509_query($certfile, "-subject", "-issuer", "-dates");
689 $? and print "ERROR: openssl -subject -issuer -dates '$certfile' returned $?\n\n" and next;
692 my @subject = split(/\//, $subject_in);
694 $tmp = shift @subject;
695 ($tmp =~ /^CN\=/) and last;
698 defined $tmp and @tmp = split (/\=/, $tmp) and
699 print $tab."Subject: $tmp[1]\n";
701 my @issuer = split(/\//, $issuer_in);
703 $tmp = shift @issuer;
704 ($tmp =~ /^CN\=/) and last;
707 defined $tmp and @tmp = split (/\=/, $tmp) and
708 print $tab."Issued by: $tmp[1]";
710 if ( defined $date1_in and defined $date2_in ) {
711 @tmp = split (/\=/, $date1_in);
713 @tmp = split (/\=/, $date2_in);
714 print $tab."Certificate is not valid before $tmp".
715 $tab." or after ".$tmp[1];
718 -e "$private_keys_path/$fields[1]" and
719 print "$tab - Matching private key installed -\n";
721 my @purpose = openssl_x509_query($certfile, "-purpose");
722 $? and die "openssl -purpose '$certfile' returned $?";
725 print "$tab$purpose[0] (displays S/MIME options only)\n";
727 $tmp = shift @purpose;
728 ($tmp =~ /^S\/MIME/ and $tmp =~ /Yes/) or next;
729 my @tmptmp = split (/:/, $tmp);
730 print "$tab $tmptmp[0]\n";
739 sub cm_add_entry ($$$$$$;$) {
740 my ($mailbox, $hashvalue, $use_cert, $label, $trust, $purpose, $issuer_hash) = @_;
742 if (! defined($issuer_hash) ) {
747 open(INDEX, "+<$certificates_path/.index") or
748 die "Couldn't open $certificates_path/.index: $!";
751 open(INDEX, "+<$private_keys_path/.index") or
752 die "Couldn't open $private_keys_path/.index: $!";
757 if (($fields[0] eq $mailbox) && ($fields[1] eq $hashvalue)) {
763 print INDEX "$mailbox $hashvalue $label $issuer_hash $trust $purpose\n";
768 # Returns the hashvalue.index of the stored cert
769 sub cm_add_cert ($) {
773 my $hashvalue = openssl_hash($filename);
774 my $fp1 = openssl_fingerprint($filename);
776 while (-e "$certificates_path/$hashvalue.$iter") {
777 my $fp2 = openssl_fingerprint("$certificates_path/$hashvalue.$iter");
779 last if $fp1 eq $fp2;
782 $hashvalue .= ".$iter";
784 if (-e "$certificates_path/$hashvalue") {
785 print "\nCertificate: $certificates_path/$hashvalue already installed.\n";
788 mycopy $filename, "$certificates_path/$hashvalue";
794 # Returns a reference containing the hashvalue, mailboxes, trust flag, and purpose
795 # flag of the stored cert.
796 sub cm_add_indexed_cert ($$$) {
797 my ($filename, $label, $issuer_hash) = @_;
801 $cert_data->{hashvalue} = cm_add_cert($filename);
802 $cert_data->{mailboxes} = [ openssl_emails($filename) ];
803 $cert_data->{trust} = openssl_trust_flag($cert_data->{hashvalue}, $issuer_hash);
804 $cert_data->{purpose} = openssl_purpose_flag($filename, $cert_data->{hashvalue});
806 foreach my $mailbox (@{$cert_data->{mailboxes}}) {
807 cm_add_entry($mailbox, $cert_data->{hashvalue}, 1, $label,
808 $cert_data->{trust}, $cert_data->{purpose}, $issuer_hash);
809 print "\ncertificate ", $cert_data->{hashvalue}, " ($label) for $mailbox added.\n";
815 sub cm_add_key ($$$$$$) {
816 my ($file, $hashvalue, $mailbox, $label, $trust, $purpose) = @_;
818 unless (-e "$private_keys_path/$hashvalue") {
819 mycopy $file, "$private_keys_path/$hashvalue";
822 cm_add_entry($mailbox, $hashvalue, 0, $label, $trust, $purpose);
823 print "added private key: " .
824 "$private_keys_path/$hashvalue for $mailbox\n";
827 sub cm_modify_entry ($$$;$) {
828 my ($op, $hashvalue, $use_cert, $opt_param) = @_;
836 $op eq 'L' and ($label = $opt_param);
837 $op eq 'T' and ($trust = $opt_param);
838 $op eq 'P' and ($purpose = $opt_param);
841 $path = $certificates_path;
844 $path = $private_keys_path;
847 open(INDEX, "<$path/.index") or
848 die "Couldn't open $path/.index: $!";
849 my ($newindex_fh, $newindex) = create_tempfile();
854 # fields: mailbox hash label issuer_hash trust purpose
857 if ($fields[1] eq $hashvalue or $hashvalue eq 'all') {
865 $fields[3] = "?" if ($#fields < 3);
870 $fields[3] = "?" if ($#fields < 3);
871 $fields[4] = "u" if ($#fields < 4);
872 $fields[5] = $purpose;
875 print $newindex_fh join(" ", @fields), "\n";
878 print $newindex_fh $_, "\n";
884 move $newindex, "$path/.index"
885 or die "Couldn't move $newindex to $path/.index: $!\n";
888 # This returns the first matching entry.
889 sub cm_find_entry ($$) {
890 my ($hashvalue, $use_cert) = @_;
892 my ($path, $index_fh);
895 $path = $certificates_path;
898 $path = $private_keys_path;
901 open($index_fh, "<$path/.index") or
902 die "Couldn't open $path/.index: $!";
904 while (<$index_fh>) {
907 if ($fields[1] eq $hashvalue) {
917 # Refreshes trust flags, and adds purpose if missing
918 # (e.g. from an older index format)
919 sub cm_refresh_index () {
922 my ($last_hash, $last_trust, $last_purpose) = ("", "", "");
924 open($index_fh, "<$certificates_path/.index") or
925 die "Couldn't open $certificates_path/.index: $!";
926 my ($newindex_fh, $newindex) = create_tempfile();
928 while (<$index_fh>) {
931 # fields: mailbox hash label issuer_hash trust purpose
934 if ($fields[1] eq $last_hash) {
935 $fields[4] = $last_trust;
936 $fields[5] = $last_purpose;
939 # Don't overwrite a revoked flag, because we don't have the CRL
940 if ($fields[4] ne "r") {
941 $fields[4] = openssl_trust_flag($fields[1], $fields[3]);
945 $fields[5] = openssl_purpose_flag("$certificates_path/$fields[1]", $fields[1]);
948 # To update an old private keys index format, always push the trust
950 if (-e "$private_keys_path/$fields[1]") {
951 cm_modify_entry ("T", $fields[1], 0, $fields[4]);
952 cm_modify_entry ("P", $fields[1], 0, $fields[5]);
955 $last_hash = $fields[1];
956 $last_trust = $fields[4];
957 $last_purpose = $fields[5];
960 print $newindex_fh join(" ", @fields), "\n";
965 move $newindex, "$certificates_path/.index"
966 or die "Couldn't move $newindex to $certificates_path/.index: $!\n";
974 sub handle_init_paths () {
975 mkdir_recursive($certificates_path);
976 mkdir_recursive($private_keys_path);
980 $file = $certificates_path . "/.index";
981 -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
982 or die "Can't touch $file: $!";
984 $file = $private_keys_path . "/.index";
985 -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
986 or die "Can't touch $file: $!";
989 sub handle_change_label ($) {
992 my $label = query_label();
994 if (-e "$certificates_path/$keyid") {
995 cm_modify_entry('L', $keyid, 1, $label);
996 print "Changed label for certificate $keyid.\n";
999 die "No such certificate: $keyid";
1002 if (-e "$private_keys_path/$keyid") {
1003 cm_modify_entry('L', $keyid, 0, $label);
1004 print "Changed label for private key $keyid.\n";
1008 sub handle_add_cert($) {
1009 my ($filename) = @_;
1011 my $label = query_label();
1012 my @cert_contents = openssl_parse_pem($filename, 0);
1013 @cert_contents = grep { $_->{type} eq "C" } @cert_contents;
1015 my @cert_chains = create_cert_chains(@cert_contents);
1016 print "Found " . scalar(@cert_chains) . " certificate chains\n";
1018 foreach my $chain (@cert_chains) {
1019 my $leaf = shift(@$chain);
1020 my $issuer_chain_hash = "?";
1022 print "Processing chain:\n";
1023 if ($leaf->{subject}) {
1024 print "subject=" . $leaf->{subject} . "\n";
1027 if (scalar(@$chain) > 0) {
1028 my ($issuer_chain_fh, $issuer_chain_file) = create_tempfile();
1030 foreach my $issuer (@$chain) {
1031 my $issuer_datafile = $issuer->{datafile};
1032 open(my $issuer_fh, "< $issuer_datafile") or
1033 die "can't open $issuer_datafile: $?";
1034 print $issuer_chain_fh $_ while (<$issuer_fh>);
1038 close($issuer_chain_fh);
1039 $issuer_chain_hash = cm_add_cert($issuer_chain_file);
1042 cm_add_indexed_cert($leaf->{datafile}, $label, $issuer_chain_hash);
1046 sub handle_add_pem ($) {
1047 my ($filename) = @_;
1054 my $issuer_cert_file;
1056 @pem_contents = openssl_parse_pem($filename, 1);
1060 while ($iter <= $#pem_contents) {
1061 if ($pem_contents[$iter]->{type} eq "K") {
1062 $key = $pem_contents[$iter];
1063 splice(@pem_contents, $iter, 1);
1068 defined($key) or die("Couldn't find private key!");
1069 $key->{localKeyID} or die("Attribute 'localKeyID' wasn't set.");
1071 # private key and certificate use the same 'localKeyID'
1073 while ($iter <= $#pem_contents) {
1074 if (($pem_contents[$iter]->{type} eq "C") &&
1075 ($pem_contents[$iter]->{localKeyID} eq $key->{localKeyID})) {
1076 $certificate = $pem_contents[$iter];
1077 splice(@pem_contents, $iter, 1);
1082 defined($certificate) or die("Couldn't find matching certificate!");
1084 if ($#pem_contents < 0) {
1085 die("No root and no intermediate certificates. Can't continue.");
1088 # Look for a self signed root certificate
1090 while ($iter <= $#pem_contents) {
1091 if ($pem_contents[$iter]->{subject} eq $pem_contents[$iter]->{issuer}) {
1092 $root_cert = $pem_contents[$iter];
1093 splice(@pem_contents, $iter, 1);
1098 if (defined($root_cert)) {
1099 $issuer_cert_file = $root_cert->{datafile};
1101 print "Couldn't identify root certificate!\n";
1104 # what's left are intermediate certificates.
1105 if ($#pem_contents >= 0) {
1106 my ($tmp_issuer_cert_fh, $tmp_issuer_cert) = create_tempfile();
1107 $issuer_cert_file = $tmp_issuer_cert;
1110 while ($iter <= $#pem_contents) {
1111 my $cert_datafile = $pem_contents[$iter]->{datafile};
1112 open (CERT, "< $cert_datafile") or die "can't open $cert_datafile: $?";
1113 print $tmp_issuer_cert_fh $_ while (<CERT>);
1118 close $tmp_issuer_cert_fh;
1121 handle_add_chain($key->{datafile}, $certificate->{datafile}, $issuer_cert_file);
1124 sub handle_add_p12 ($) {
1125 my ($filename) = @_;
1127 print "\nNOTE: This will ask you for two passphrases:\n";
1128 print " 1. The passphrase you used for exporting\n";
1129 print " 2. The passphrase you wish to secure your private key with.\n\n";
1131 my ($pem_fh, $pem_file) = create_tempfile();
1134 openssl_p12_to_pem($filename, $pem_file);
1135 -e $pem_file and -s $pem_file or die("Conversion of $filename failed.");
1137 handle_add_pem($pem_file);
1140 sub handle_add_chain ($$$) {
1141 my ($key_file, $cert_file, $issuer_file) = @_;
1143 my $label = query_label();
1145 my $issuer_hash = cm_add_cert($issuer_file);
1146 my $cert_data = cm_add_indexed_cert($cert_file, $label, $issuer_hash);
1148 foreach my $mailbox (@{$cert_data->{mailboxes}}) {
1149 cm_add_key($key_file, $cert_data->{hashvalue}, $mailbox, $label,
1150 $cert_data->{trust}, $cert_data->{purpose});
1154 sub handle_verify_cert ($$) {
1155 my ($keyid, $crl) = @_;
1157 -e "$certificates_path/$keyid" or $keyid eq 'all'
1158 or die "No such certificate: $keyid";
1160 my @fields = cm_find_entry($keyid, 1);
1161 if (scalar(@fields)) {
1162 my $issuer_hash = $fields[3];
1163 my $trust = openssl_trust_flag($keyid, $issuer_hash, $crl);
1165 cm_modify_entry('T', $keyid, 0, $trust);
1166 cm_modify_entry('T', $keyid, 1, $trust);
1170 sub handle_remove_pair ($) {
1173 if (-e "$certificates_path/$keyid") {
1174 unlink "$certificates_path/$keyid";
1175 cm_modify_entry('R', $keyid, 1);
1176 print "Removed certificate $keyid.\n";
1179 die "No such certificate: $keyid";
1182 if (-e "$private_keys_path/$keyid") {
1183 unlink "$private_keys_path/$keyid";
1184 cm_modify_entry('R', $keyid, 0);
1185 print "Removed private key $keyid.\n";
1189 sub handle_add_root_cert ($) {
1190 my ($root_cert) = @_;
1192 my $root_hash = openssl_hash($root_cert);
1194 if (-d $root_certs_path) {
1195 -e "$root_certs_path/$root_hash" or
1196 mycopy $root_cert, "$root_certs_path/$root_hash";
1199 open(ROOT_CERTS, ">>$root_certs_path") or
1200 die ("Couldn't open $root_certs_path for writing");
1202 my $md5fp = openssl_fingerprint($root_cert);
1204 my @cert_text = openssl_x509_query($root_cert, "-text");
1205 $? and die "openssl -text '$root_cert' returned $?";
1207 print "Enter a label, name or description for this certificate: ";
1208 my $input = <STDIN>;
1210 my $line = "=======================================\n";
1211 print ROOT_CERTS "\n$input$line$md5fp\nPEM-Data:\n";
1213 my $cert = openssl_dump_cert($root_cert);
1214 print ROOT_CERTS $cert;
1215 print ROOT_CERTS @cert_text;