# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
use strict;
use File::Copy;
use File::Glob ':glob';
+use File::Temp qw(tempfile tempdir);
umask 077;
use Time::Local;
+# helper routines
sub usage ();
-sub newfile ($;$$);
-sub mutt_Q ($ );
+sub mutt_Q ($);
sub mycopy ($$);
-
-# directory setup routines
-sub mkdir_recursive ($ );
-sub init_paths ();
+sub query_label ();
+sub mkdir_recursive ($);
+sub verify_files_exist (@);
+sub create_tempfile (;$);
+sub new_cert_structure ();
+
+# openssl helpers
+sub openssl_format ($);
+sub openssl_hash ($);
+sub openssl_fingerprint ($);
+sub openssl_emails ($);
+sub openssl_do_verify($$$);
+sub openssl_parse_pem ($$);
# key/certificate management methods
-sub list_certs ();
-sub query_label ();
-sub add_entry ($$$$$ );
-sub add_certificate ($$$$;$ );
-sub add_key ($$$$);
-sub add_root_cert ($ );
-sub parse_pem (@ );
-sub handle_pem (@ );
-sub modify_entry ($$$;$ );
-sub remove_pair ($ );
-sub change_label ($ );
-sub verify_cert($$);
-sub do_verify($$$ );
-
-# Get the directories mutt uses for certificate/key storage.
+sub cm_list_certs ();
+sub cm_add_entry ($$$$$);
+sub cm_add_certificate ($$$$;$);
+sub cm_add_key ($$$$);
+sub cm_modify_entry ($$$;$);
+
+# op handlers
+sub handle_init_paths ();
+sub handle_change_label ($);
+sub handle_add_cert ($);
+sub handle_add_pem ($);
+sub handle_add_p12 ($);
+sub handle_add_chain ($$$);
+sub handle_verify_cert($$);
+sub handle_remove_pair ($);
+sub handle_add_root_cert ($);
+
my $mutt = $ENV{MUTT_CMDLINE} || 'mutt';
my $opensslbin = "/usr/bin/openssl";
-my @tempfiles = ();
-my @cert_tmp_file = ();
-
my $tmpdir;
+
+# Get the directories mutt uses for certificate/key storage.
+
my $private_keys_path = mutt_Q 'smime_keys';
die "smime_keys is not set in mutt's configuration file"
- if length $private_keys_path == 0;
+ if length $private_keys_path == 0;
my $certificates_path = mutt_Q 'smime_certificates';
die "smime_certificates is not set in mutt's configuration file"
- if length $certificates_path == 0;
+ if length $certificates_path == 0;
+
my $root_certs_path = mutt_Q 'smime_ca_location';
die "smime_ca_location is not set in mutt's configuration file"
- if length $root_certs_path == 0;
+ if length $root_certs_path == 0;
my $root_certs_switch;
if ( -d $root_certs_path) {
- $root_certs_switch = -CApath;
+ $root_certs_switch = -CApath;
} else {
- $root_certs_switch = -CAfile;
+ $root_certs_switch = -CAfile;
}
-#
+######
# OPS
-#
+######
if(@ARGV == 1 and $ARGV[0] eq "init") {
- init_paths;
+ handle_init_paths();
}
elsif(@ARGV == 1 and $ARGV[0] eq "list") {
- list_certs;
+ cm_list_certs();
}
elsif(@ARGV == 2 and $ARGV[0] eq "label") {
- change_label($ARGV[1]);
+ handle_change_label($ARGV[1]);
}
elsif(@ARGV == 2 and $ARGV[0] eq "add_cert") {
- my $format = -B $ARGV[1] ? 'DER' : 'PEM';
- my $cmd = "$opensslbin x509 -noout -hash -in $ARGV[1] -inform $format";
- my $cert_hash = `$cmd`;
- $? and die "'$cmd' returned $?";
- chomp($cert_hash);
- my $label = query_label;
- &add_certificate($ARGV[1], \$cert_hash, 1, $label, '?');
+ verify_files_exist($ARGV[1]);
+ handle_add_cert($ARGV[1]);
}
elsif(@ARGV == 2 and $ARGV[0] eq "add_pem") {
- -e $ARGV[1] and -s $ARGV[1] or die("$ARGV[1] is nonexistent or empty.");
- open(PEM_FILE, "<$ARGV[1]") or die("Can't open $ARGV[1]: $!");
- my @pem = <PEM_FILE>;
- close(PEM_FILE);
- handle_pem(@pem);
+ verify_files_exist($ARGV[1]);
+ handle_add_pem($ARGV[1]);
}
elsif( @ARGV == 2 and $ARGV[0] eq "add_p12") {
- -e $ARGV[1] and -s $ARGV[1] or die("$ARGV[1] is nonexistent or empty.");
-
- print "\nNOTE: This will ask you for two passphrases:\n";
- print " 1. The passphrase you used for exporting\n";
- print " 2. The passphrase you wish to secure your private key with.\n\n";
-
- my $pem_file = "$ARGV[1].pem";
-
- my $cmd = "$opensslbin pkcs12 -in $ARGV[1] -out $pem_file";
- system $cmd and die "'$cmd' returned $?";
-
- -e $pem_file and -s $pem_file or die("Conversion of $ARGV[1] failed.");
- open(PEM_FILE, $pem_file) or die("Can't open $pem_file: $!");
- my @pem = <PEM_FILE>;
- close(PEM_FILE);
- unlink $pem_file;
- handle_pem(@pem);
+ verify_files_exist($ARGV[1]);
+ handle_add_p12($ARGV[1]);
}
elsif(@ARGV == 4 and $ARGV[0] eq "add_chain") {
- my $mailbox;
- my $format = -B $ARGV[2] ? 'DER' : 'PEM';
- my $cmd = "$opensslbin x509 -noout -hash -in $ARGV[2] -inform $format";
- my $cert_hash = `$cmd`;
-
- $? and die "'$cmd' returned $?";
-
- $format = -B $ARGV[3] ? 'DER' : 'PEM';
-
- $cmd = "$opensslbin x509 -noout -hash -in $ARGV[3] -inform $format";
- my $issuer_hash = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- chomp($cert_hash);
- chomp($issuer_hash);
-
- my $label = query_label;
-
- add_certificate($ARGV[3], \$issuer_hash, 0, $label);
- my @mailbox = &add_certificate($ARGV[2], \$cert_hash, 1, $label, $issuer_hash);
-
- foreach $mailbox (@mailbox) {
- chomp($mailbox);
- add_key($ARGV[1], $cert_hash, $mailbox, $label);
- }
+ verify_files_exist($ARGV[1], $ARGV[2], $ARGV[3]);
+ handle_add_chain($ARGV[1], $ARGV[2], $ARGV[3]);
}
elsif((@ARGV == 2 or @ARGV == 3) and $ARGV[0] eq "verify") {
- verify_cert($ARGV[1], $ARGV[2]);
+ verify_files_exist($ARGV[2]) if (@ARGV == 3);
+ handle_verify_cert($ARGV[1], $ARGV[2]);
}
elsif(@ARGV == 2 and $ARGV[0] eq "remove") {
- remove_pair($ARGV[1]);
+ handle_remove_pair($ARGV[1]);
}
elsif(@ARGV == 2 and $ARGV[0] eq "add_root") {
- add_root_cert($ARGV[1]);
+ verify_files_exist($ARGV[1]);
+ handle_add_root_cert($ARGV[1]);
}
-else {
- usage;
- exit(1);
+else {
+ usage();
+ exit(1);
}
exit(0);
+############## sub-routines ########################
-
-############## sub-routines ########################
+###################
+# helper routines
+###################
sub usage () {
print <<EOF;
}
sub mutt_Q ($) {
- my $var = shift or die;
+ my ($var) = @_;
- my $cmd = "$mutt -v >/dev/null 2>/dev/null";
- system ($cmd) == 0
- or die<<EOF;
+ my $cmd = "$mutt -v >/dev/null 2>/dev/null";
+ system ($cmd) == 0 or die<<EOF;
Couldn't launch mutt. I attempted to do so by running the command "$mutt".
-If that's not the right command, you can override it by setting the
+If that's not the right command, you can override it by setting the
environment variable \$MUTT_CMDLINE
EOF
- $cmd = "$mutt -Q $var 2>/dev/null";
- my $answer = `$cmd`;
+ $cmd = "$mutt -Q $var 2>/dev/null";
+ my $answer = `$cmd`;
- $? and die<<EOF;
-Couldn't look up the value of the mutt variable "$var".
+ $? and die<<EOF;
+Couldn't look up the value of the mutt variable "$var".
You must set this in your mutt config file. See contrib/smime.rc for an example.
EOF
-#'
- $answer =~ /\"(.*?)\"/ and return bsd_glob($1, GLOB_TILDE | GLOB_NOCHECK);
-
- $answer =~ /^Mutt (.*?) / and die<<EOF;
+ $answer =~ /\"(.*?)\"/ and return bsd_glob($1, GLOB_TILDE | GLOB_NOCHECK);
+
+ $answer =~ /^Mutt (.*?) / and die<<EOF;
This script requires mutt 1.5.0 or later. You are using mutt $1.
EOF
-
- die "Value of $var is weird\n";
+
+ die "Value of $var is weird\n";
}
sub mycopy ($$) {
- my $source = shift or die;
- my $dest = shift or die;
+ my ($source, $dest) = @_;
- copy $source, $dest or die "Problem copying $source to $dest: $!\n";
+ copy $source, $dest or die "Problem copying $source to $dest: $!\n";
}
-#
-# directory setup routines
-#
+sub query_label () {
+ my $input;
+ my $label;
+ my $junk;
+
+ print "\nYou may assign a label to this key, so you don't have to remember\n";
+ print "the key ID. This has to be _one_ word (no whitespaces).\n\n";
+
+ print "Enter label: ";
+ $input = <STDIN>;
+ if (defined($input) && ($input !~ /^\s*$/)) {
+ chomp($input);
+ $input =~ s/^\s+//;
+ ($label, $junk) = split(/\s/, $input, 2);
+
+ if (defined($junk)) {
+ print "\nUsing '$label' as label; ignoring '$junk'\n";
+ }
+ }
+
+ if ((! defined($label)) || ($label =~ /^\s*$/)) {
+ $label = "-";
+ }
+
+ return $label;
+}
sub mkdir_recursive ($) {
- my $path = shift or die;
- my $tmp_path;
-
- for my $dir (split /\//, $path) {
- $tmp_path .= "$dir/";
-
- -d $tmp_path
- or mkdir $tmp_path, 0700
- or die "Can't mkdir $tmp_path: $!";
+ my ($path) = @_;
+ my $tmp_path;
+
+ for my $dir (split /\//, $path) {
+ $tmp_path .= "$dir/";
+
+ -d $tmp_path
+ or mkdir $tmp_path, 0700
+ or die "Can't mkdir $tmp_path: $!";
+ }
+}
+
+sub verify_files_exist (@) {
+ my (@files) = @_;
+
+ foreach my $file (@files) {
+ if ((! -e $file) || (! -s $file)) {
+ die("$file is nonexistent or empty.");
}
+ }
}
-sub init_paths () {
- mkdir_recursive($certificates_path);
- mkdir_recursive($private_keys_path);
+# Returns a list ($fh, $filename)
+sub create_tempfile (;$) {
+ my ($directory) = @_;
+
+ if (! defined($directory)) {
+ if (! defined($tmpdir)) {
+ $tmpdir = tempdir(CLEANUP => 1);
+ }
+ $directory = $tmpdir;
+ }
- my $file;
+ return tempfile(DIR => $directory);
+}
- $file = $certificates_path . "/.index";
- -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
- or die "Can't touch $file: $!";
+# Creates a cert data structure used by openssl_parse_pem
+sub new_cert_structure () {
+ my $cert_data = {};
- $file = $private_keys_path . "/.index";
- -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
- or die "Can't touch $file: $!";
+ $cert_data->{datafile} = "";
+ $cert_data->{type} = "";
+ $cert_data->{localKeyID} = "";
+ $cert_data->{subject} = "";
+ $cert_data->{issuer} = "";
+
+ return $cert_data;
}
+##################
+# openssl helpers
+##################
-#
+sub openssl_format ($) {
+ my ($filename) = @_;
+
+ return -B $filename ? 'DER' : 'PEM';
+}
+
+sub openssl_hash ($) {
+ my ($filename) = @_;
+
+ my $format = openssl_format($filename);
+ my $cmd = "$opensslbin x509 -noout -hash -in $filename -inform $format";
+ my $cert_hash = `$cmd`;
+ $? and die "'$cmd' returned $?";
+
+ chomp($cert_hash);
+ return $cert_hash;
+}
+
+sub openssl_fingerprint ($) {
+ my ($filename) = @_;
+
+ my $format = openssl_format($filename);
+ my $cmd = "$opensslbin x509 -in $filename -inform $format -fingerprint -noout";
+ my $fingerprint = `$cmd`;
+ $? and die "'$cmd' returned $?";
+
+ chomp($fingerprint);
+ return $fingerprint;
+}
+
+sub openssl_emails ($) {
+ my ($filename) = @_;
+
+ my $format = openssl_format($filename);
+ my $cmd = "$opensslbin x509 -in $filename -inform $format -email -noout";
+ my @mailboxes = `$cmd`;
+ $? and die "'$cmd' returned $?";
+
+ chomp(@mailboxes);
+ return @mailboxes;
+}
+
+sub openssl_do_verify ($$$) {
+ my ($cert, $issuerid, $crl) = @_;
+
+ my $result = 't';
+ my $issuer_path;
+ my $cert_path = "$certificates_path/$cert";
+
+ if($issuerid eq '?') {
+ $issuer_path = "$certificates_path/$cert";
+ } else {
+ $issuer_path = "$certificates_path/$issuerid";
+ }
+
+ my $cmd = "$opensslbin verify $root_certs_switch $root_certs_path -purpose smimesign -purpose smimeencrypt -untrusted $issuer_path $cert_path";
+ my $output = `$cmd`;
+ chomp $output;
+ if ($?) {
+ print "'$cmd' returned exit code " . ($? >> 8) . " with output:\n";
+ print "$output\n\n";
+ print "Marking as invalid\n";
+ return 'i';
+ }
+ print "\n$output\n";
+
+ if ($output !~ /OK/) {
+ return 'i';
+ }
+
+ my $format = openssl_format($cert_path);
+ $cmd = "$opensslbin x509 -dates -serial -noout -in $cert_path -inform $format";
+ (my $not_before, my $not_after, my $serial_in) = `$cmd`;
+ $? and die "'$cmd' returned $?";
+
+ if ( defined $not_before and defined $not_after ) {
+ my %months = ('Jan', '00', 'Feb', '01', 'Mar', '02', 'Apr', '03',
+ 'May', '04', 'Jun', '05', 'Jul', '06', 'Aug', '07',
+ 'Sep', '08', 'Oct', '09', 'Nov', '10', 'Dec', '11');
+
+ my @tmp = split (/\=/, $not_before);
+ my $not_before_date = $tmp[1];
+ my @fields =
+ $not_before_date =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
+ if ($#fields == 5) {
+ if (timegm($fields[4], $fields[3], $fields[2], $fields[1],
+ $months{$fields[0]}, $fields[5]) > time) {
+ print "Certificate is not yet valid.\n";
+ return 'e';
+ }
+ } else {
+ print "Expiration Date: Parse Error : $not_before_date\n\n";
+ }
+
+ @tmp = split (/\=/, $not_after);
+ my $not_after_date = $tmp[1];
+ @fields =
+ $not_after_date =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
+ if ($#fields == 5) {
+ if (timegm($fields[4], $fields[3], $fields[2], $fields[1],
+ $months{$fields[0]}, $fields[5]) < time) {
+ print "Certificate has expired.\n";
+ return 'e';
+ }
+ } else {
+ print "Expiration Date: Parse Error : $not_after_date\n\n";
+ }
+ }
+
+ if ( defined $crl ) {
+ my @serial = split (/\=/, $serial_in);
+ my $cmd = "$opensslbin crl -text -noout -in $crl | grep -A1 $serial[1]";
+ (my $l1, my $l2) = `$cmd`;
+ $? and die "'$cmd' returned $?";
+
+ if ( defined $l2 ) {
+ my @revoke_date = split (/:\s/, $l2);
+ print "FAILURE: Certificate $cert has been revoked on $revoke_date[1]\n";
+ $result = 'r';
+ }
+ }
+ print "\n";
+
+ return $result;
+}
+
+sub openssl_parse_pem ($$) {
+ my ($filename, $attrs_required) = @_;
+
+ my $state = 0;
+ my $cert_data;
+ my @certs;
+ my $cert_count = 0;
+ my $bag_count = 0;
+ my $cert_tmp_fh;
+ my $cert_tmp_filename;
+
+ $cert_data = new_cert_structure();
+ ($cert_tmp_fh, $cert_data->{datafile}) = create_tempfile();
+
+ open(PEM_FILE, "<$filename") or die("Can't open $filename: $!");
+ while(<PEM_FILE>) {
+ if(/^Bag Attributes/) {
+ $bag_count++;
+ $state == 0 or die("PEM-parse error at: $.");
+ $state = 1;
+ }
+
+ if ($state == 1) {
+ if (/localKeyID:\s*(.*)/) {
+ $cert_data->{localKeyID} = $1;
+ }
+
+ if (/subject=\s*(.*)/) {
+ $cert_data->{subject} = $1;
+ }
+
+ if (/issuer=\s*(.*)/) {
+ $cert_data->{issuer} = $1;
+ }
+ }
+
+
+ if(/^-----/) {
+ if(/BEGIN/) {
+ print $cert_tmp_fh $_;
+ $state = 2;
+
+ if(/PRIVATE/) {
+ $cert_data->{type} = "K";
+ next;
+ }
+ if(/CERTIFICATE/) {
+ $cert_data->{type} = "C";
+ next;
+ }
+ die("What's this: $_");
+ }
+ if(/END/) {
+ $state = 0;
+ print $cert_tmp_fh $_;
+ close($cert_tmp_fh);
+
+ $cert_count++;
+ push (@certs, $cert_data);
+
+ $cert_data = new_cert_structure();
+ ($cert_tmp_fh, $cert_data->{datafile}) = create_tempfile();
+ next;
+ }
+ }
+ print $cert_tmp_fh $_;
+ }
+ close($cert_tmp_fh);
+ close(PEM_FILE);
+
+ if ($attrs_required && ($bag_count != $cert_count)) {
+ die("Not all contents were bagged. can't continue.");
+ }
+
+ return @certs;
+}
+
+
+#################################
# certificate management methods
-#
+#################################
-sub list_certs () {
+sub cm_list_certs () {
my %keyflags = ( 'i', '(Invalid)', 'r', '(Revoked)', 'e', '(Expired)',
- 'u', '(Unverified)', 'v', '(Valid)', 't', '(Trusted)');
+ 'u', '(Unverified)', 'v', '(Valid)', 't', '(Trusted)');
- open(INDEX, "<$certificates_path/.index") or
+ open(INDEX, "<$certificates_path/.index") or
die "Couldn't open $certificates_path/.index: $!";
-
+
print "\n";
while(<INDEX>) {
my $tmp;
die "Couldn't open $certfile: $!";
local $/;
$cert = <F>;
- close F;
+ close F;
}
my $subject_in;
my $date1_in;
my $date2_in;
- my $format = -B $certfile ? 'DER' : 'PEM';
+ my $format = openssl_format($certfile);
my $cmd = "$opensslbin x509 -subject -issuer -dates -noout -in $certfile -inform $format";
($subject_in, $issuer_in, $date1_in, $date2_in) = `$cmd`;
$? and print "ERROR: '$cmd' returned $?\n\n" and next;
$tmp = $tmp[1];
@tmp = split (/\=/, $date2_in);
print $tab."Certificate is not valid before $tmp".
- $tab." or after ".$tmp[1];
+ $tab." or after ".$tmp[1];
}
-e "$private_keys_path/$fields[1]" and
print "$tab - Matching private key installed -\n";
- $format = -B "$certificates_path/$fields[1]" ? 'DER' : 'PEM';
+ $format = openssl_format("$certificates_path/$fields[1]");
$cmd = "$opensslbin x509 -purpose -noout -in $certfile -inform $format";
my $purpose_in = `$cmd`;
$? and die "'$cmd' returned $?";
print "\n";
}
-
- close(INDEX);
-}
-
-
-sub query_label () {
- my @words;
- my $input;
-
- print "\nYou may assign a label to this key, so you don't have to remember\n";
- print "the key ID. This has to be _one_ word (no whitespaces).\n\n";
-
- print "Enter label: ";
- chomp($input = <STDIN>);
-
- my ($label, $junk) = split(/\s/, $input, 2);
-
- defined $junk
- and print "\nUsing '$label' as label; ignoring '$junk'\n";
-
- defined $label || ($label = "-");
-
- return $label;
+ close(INDEX);
}
+sub cm_add_entry ($$$$$) {
+ my ($mailbox, $hashvalue, $use_cert, $label, $issuer_hash) = @_;
+ my @fields;
-sub add_entry ($$$$$) {
- my $mailbox = shift or die;
- my $hashvalue = shift or die;
- my $use_cert = shift;
- my $label = shift or die;
- my $issuer_hash = shift;
-
- my @fields;
-
- if ($use_cert) {
- open(INDEX, "+<$certificates_path/.index") or
- die "Couldn't open $certificates_path/.index: $!";
- }
- else {
- open(INDEX, "+<$private_keys_path/.index") or
- die "Couldn't open $private_keys_path/.index: $!";
- }
+ if ($use_cert) {
+ open(INDEX, "+<$certificates_path/.index") or
+ die "Couldn't open $certificates_path/.index: $!";
+ }
+ else {
+ open(INDEX, "+<$private_keys_path/.index") or
+ die "Couldn't open $private_keys_path/.index: $!";
+ }
- while(<INDEX>) {
- @fields = split;
- return if ($fields[0] eq $mailbox && $fields[1] eq $hashvalue);
- }
+ while(<INDEX>) {
+ @fields = split;
+ return if ($fields[0] eq $mailbox && $fields[1] eq $hashvalue);
+ }
- if ($use_cert) {
- print INDEX "$mailbox $hashvalue $label $issuer_hash u\n";
- }
- else {
- print INDEX "$mailbox $hashvalue $label \n";
- }
+ if ($use_cert) {
+ print INDEX "$mailbox $hashvalue $label $issuer_hash u\n";
+ }
+ else {
+ print INDEX "$mailbox $hashvalue $label \n";
+ }
- close(INDEX);
+ close(INDEX);
}
+sub cm_add_certificate ($$$$;$) {
+ my ($filename, $hashvalue, $add_to_index, $label, $issuer_hash) = @_;
-sub add_certificate ($$$$;$) {
- my $filename = shift or die;
- my $hashvalue = shift or die;
- my $add_to_index = shift;
- my $label = shift or die;
- my $issuer_hash = shift;
+ my $iter = 0;
+ my @mailboxes;
- my $iter = 0;
- my @mailbox;
- my $mailbox;
+ my $fp1 = openssl_fingerprint($filename);
- while(-e "$certificates_path/$$hashvalue.$iter") {
- my ($t1, $t2);
- my $format = -B $filename ? 'DER' : 'PEM';
- my $cmd = "$opensslbin x509 -in $filename -inform $format -fingerprint -noout";
- $t1 = `$cmd`;
- $? and die "'$cmd' returned $?";
+ while (-e "$certificates_path/$$hashvalue.$iter") {
+ my $fp2 = openssl_fingerprint("$certificates_path/$$hashvalue.$iter");
- $format = -B "$certificates_path/$$hashvalue.$iter" ? 'DER' : 'PEM';
- $cmd = "$opensslbin x509 -in $certificates_path/$$hashvalue.$iter -inform $format -fingerprint -noout";
- $t2 = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- $t1 eq $t2 and last;
+ last if $fp1 eq $fp2;
+ $iter++;
+ }
+ $$hashvalue .= ".$iter";
- $iter++;
- }
- $$hashvalue .= ".$iter";
-
- if (-e "$certificates_path/$$hashvalue") {
- print "\nCertificate: $certificates_path/$$hashvalue already installed.\n";
+ if (-e "$certificates_path/$$hashvalue") {
+ print "\nCertificate: $certificates_path/$$hashvalue already installed.\n";
+ }
+ else {
+ mycopy $filename, "$certificates_path/$$hashvalue";
+
+ if ($add_to_index) {
+ @mailboxes = openssl_emails($filename);
+ foreach my $mailbox (@mailboxes) {
+ cm_add_entry($mailbox, $$hashvalue, 1, $label, $issuer_hash);
+ print "\ncertificate $$hashvalue ($label) for $mailbox added.\n";
+ }
+ cm_modify_entry('V', $$hashvalue, 1);
}
else {
- mycopy $filename, "$certificates_path/$$hashvalue";
-
- if ($add_to_index) {
- my $format = -B $filename ? 'DER' : 'PEM';
- my $cmd = "$opensslbin x509 -in $filename -inform $format -email -noout";
- @mailbox = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- foreach $mailbox (@mailbox) {
- chomp($mailbox);
- add_entry($mailbox, $$hashvalue, 1, $label, $issuer_hash);
-
- print "\ncertificate $$hashvalue ($label) for $mailbox added.\n";
- }
- verify_cert($$hashvalue, undef);
- }
- else {
- print "added certificate: $certificates_path/$$hashvalue.\n";
- }
+ print "added certificate: $certificates_path/$$hashvalue.\n";
}
+ }
- return @mailbox;
+ return @mailboxes;
}
-
-sub add_key ($$$$) {
- my $file = shift or die;
- my $hashvalue = shift or die;
- my $mailbox = shift or die;
- my $label = shift or die;
+sub cm_add_key ($$$$) {
+ my ($file, $hashvalue, $mailbox, $label) = @_;
unless (-e "$private_keys_path/$hashvalue") {
mycopy $file, "$private_keys_path/$hashvalue";
- }
+ }
- add_entry($mailbox, $hashvalue, 0, $label, "");
+ cm_add_entry($mailbox, $hashvalue, 0, $label, "");
print "added private key: " .
"$private_keys_path/$hashvalue for $mailbox\n";
-}
-
-
-
-
-
-
-sub parse_pem (@) {
- my $state = 0;
- my $cert_iter = 0;
- my @bag_attribs;
- my $numBags = 0;
-
- $cert_tmp_file[$cert_iter] = newfile("cert_tmp.$cert_iter","temp");
- my $cert_tmp_iter = $cert_tmp_file[$cert_iter];
- open(CERT_FILE, ">$cert_tmp_iter")
- or die "Couldn't open $cert_tmp_iter: $!";
-
- while($_ = shift(@_)) {
- if(/^Bag Attributes/) {
- $numBags++;
- $state == 0 or die("PEM-parse error at: $.");
- $state = 1;
- $bag_attribs[$cert_iter*4+1] = "";
- $bag_attribs[$cert_iter*4+2] = "";
- $bag_attribs[$cert_iter*4+3] = "";
- }
-
- ($state == 1) and /localKeyID:\s*(.*)/
- and ($bag_attribs[$cert_iter*4+1] = $1);
-
- ($state == 1) and /subject=\s*(.*)/
- and ($bag_attribs[$cert_iter*4+2] = $1);
-
- ($state == 1) and /issuer=\s*(.*)/
- and ($bag_attribs[$cert_iter*4+3] = $1);
-
- if(/^-----/) {
- if(/BEGIN/) {
- print CERT_FILE;
- $state = 2;
-
- if(/PRIVATE/) {
- $bag_attribs[$cert_iter*4] = "K";
- next;
- }
- if(/CERTIFICATE/) {
- $bag_attribs[$cert_iter*4] = "C";
- next;
- }
- die("What's this: $_");
- }
- if(/END/) {
- $state = 0;
- print CERT_FILE;
- close(CERT_FILE);
- $cert_iter++;
- $cert_tmp_file[$cert_iter] = newfile("cert_tmp.$cert_iter","temp");
- $cert_tmp_iter = $cert_tmp_file[$cert_iter];
- open(CERT_FILE, ">$cert_tmp_iter")
- or die "Couldn't open $cert_tmp_iter: $!";
- next;
- }
- }
- print CERT_FILE;
- }
- close(CERT_FILE);
-
- # I'll add support for unbagged cetificates, in case this is needed.
- $numBags == $cert_iter or
- die("Not all contents were bagged. can't continue.");
-
- return @bag_attribs;
}
+sub cm_modify_entry ($$$;$) {
+ my ($op, $hashvalue, $use_cert, $opt_param) = @_;
-# This requires the Bag Attributes to be set
-sub handle_pem (@) {
-
- my @pem_contents;
- my $iter=0;
- my $root_cert;
- my $key;
- my $certificate;
- my $intermediate;
- my @mailbox;
- my $mailbox;
-
- @pem_contents = &parse_pem(@_);
+ my $crl;
+ my $label;
+ my $path;
+ my @fields;
- # private key and certificate use the same 'localKeyID'
- while($iter <= $#pem_contents / 4) {
- if($pem_contents[$iter * 4] eq "K") {
- $key = $iter;
- last;
- }
- $iter++;
- }
- ($iter > $#pem_contents / 2) and die("Couldn't find private key!");
+ $op eq 'L' and ($label = $opt_param);
+ $op eq 'V' and ($crl = $opt_param);
- $pem_contents[($key * 4)+1] or die("Attribute 'localKeyID' wasn't set.");
- $iter = 0;
- while($iter <= $#pem_contents / 4) {
- $iter == $key and ($iter++) and next;
- if($pem_contents[($iter * 4)+1] eq $pem_contents[($key * 4)+1]) {
- $certificate = $iter;
- last;
- }
- $iter++;
- }
- ($iter > $#pem_contents / 4) and die("Couldn't find matching certificate!");
+ if ($use_cert) {
+ $path = $certificates_path;
+ }
+ else {
+ $path = $private_keys_path;
+ }
- my $tmp_key = newfile("tmp_key","temp");
- mycopy $cert_tmp_file[$key], $tmp_key;
- my $tmp_certificate = newfile("tmp_certificate","temp");
- mycopy $cert_tmp_file[$certificate], $tmp_certificate;
+ open(INDEX, "<$path/.index") or
+ die "Couldn't open $path/.index: $!";
+ my ($newindex_fh, $newindex) = create_tempfile();
- # root certificate is self signed
- $iter = 0;
+ while(<INDEX>) {
+ @fields = split;
+ if($fields[1] eq $hashvalue or $hashvalue eq 'all') {
+ $op eq 'R' and next;
- while($iter <= $#pem_contents / 4) {
- if ($iter == $key or $iter == $certificate) {
- $iter++;
- next;
- }
+ print $newindex_fh "$fields[0] $fields[1]";
- if($pem_contents[($iter * 4)+2] eq $pem_contents[($iter * 4)+3]) {
- $root_cert = $iter;
- last;
+ if($op eq 'L') {
+ if($use_cert) {
+ print $newindex_fh " $label $fields[3] $fields[4]";
}
- $iter++;
- }
- if ($iter > $#pem_contents / 4) {
- print "Couldn't identify root certificate!\n";
- $root_cert = -1;
- }
-
- # what's left are intermediate certificates.
- $iter = 0;
-
- # needs to be set, so we can check it later
- $intermediate = $root_cert;
- my $tmp_issuer_cert = newfile("tmp_issuer_cert","temp");
- while($iter <= $#pem_contents / 4) {
- if ($iter == $key or $iter == $certificate or $iter == $root_cert) {
- $iter++;
- next;
+ else {
+ print $newindex_fh " $label";
}
+ }
- open (IC, ">> $tmp_issuer_cert") or die "can't open $tmp_issuer_cert: $?";
- my $cert_tmp_iter = $cert_tmp_file[$iter];
- open (CERT, "< $cert_tmp_iter") or die "can't open $cert_tmp_iter: $?";
- print IC while (<CERT>);
- close IC;
- close CERT;
-
- # although there may be many, just need to know if there was any
- $intermediate = $iter;
-
- $iter++;
- }
-
- # no intermediate certificates ? use root-cert instead (if that was found...)
- if($intermediate == $root_cert) {
- if ($root_cert == -1) {
- die("No root and no intermediate certificates. Can't continue.");
- }
- mycopy $cert_tmp_file[$root_cert], $tmp_issuer_cert;
- }
-
- my $label = query_label;
-
- my $format = -B $tmp_certificate ? 'DER' : 'PEM';
- my $cmd = "$opensslbin x509 -noout -hash -in $tmp_certificate -inform $format";
- my $cert_hash = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- $format = -B $tmp_issuer_cert ? 'DER' : 'PEM';
- $cmd = "$opensslbin x509 -noout -hash -in $tmp_issuer_cert -inform $format";
- my $issuer_hash = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- chomp($cert_hash); chomp($issuer_hash);
-
- # Note: $cert_hash will be changed to reflect the correct filename
- # within add_cert() ONLY, so these _have_ to get called first..
- add_certificate($tmp_issuer_cert, \$issuer_hash, 0, $label);
- @mailbox = &add_certificate("$tmp_certificate", \$cert_hash, 1, $label, $issuer_hash);
- foreach $mailbox (@mailbox) {
- chomp($mailbox);
- add_key($tmp_key, $cert_hash, $mailbox, $label);
- }
-}
-
-
-
-
-
-
-sub modify_entry ($$$;$ ) {
- my $op = shift or die;
- my $hashvalue = shift or die;
- my $use_cert = shift;
- my $crl;
- my $label;
- my $path;
- my @fields;
-
- $op eq 'L' and ($label = shift or die);
- $op eq 'V' and ($crl = shift);
-
-
- if ($use_cert) {
- $path = $certificates_path;
- }
- else {
- $path = $private_keys_path;
- }
+ if ($op eq 'V') {
+ print "\n==> about to verify certificate of $fields[0]\n";
+ my $flag = openssl_do_verify($fields[1], $fields[3], $crl);
+ print $newindex_fh " $fields[2] $fields[3] $flag";
+ }
- open(INDEX, "<$path/.index") or
- die "Couldn't open $path/.index: $!";
- my $newindex = newfile("$path/.index.tmp");
- open(NEW_INDEX, ">$newindex") or
- die "Couldn't create $newindex: $!";
-
- while(<INDEX>) {
- @fields = split;
- if($fields[1] eq $hashvalue or $hashvalue eq 'all') {
- $op eq 'R' and next;
- print NEW_INDEX "$fields[0] $fields[1]";
- if($op eq 'L') {
- if($use_cert) {
- print NEW_INDEX " $label $fields[3] $fields[4]";
- }
- else {
- print NEW_INDEX " $label";
- }
- }
- if ($op eq 'V') {
- print "\n==> about to verify certificate of $fields[0]\n";
- my $flag = &do_verify($fields[1], $fields[3], $crl);
- print NEW_INDEX " $fields[2] $fields[3] $flag";
- }
- print NEW_INDEX "\n";
- next;
- }
- print NEW_INDEX;
+ print $newindex_fh "\n";
+ next;
}
- close(INDEX);
- close(NEW_INDEX);
+ print $newindex_fh $_;
+ }
+ close(INDEX);
+ close($newindex_fh);
- rename $newindex, "$path/.index"
- or die "Couldn't rename $newindex to $path/.index: $!\n";
+ move $newindex, "$path/.index"
+ or die "Couldn't move $newindex to $path/.index: $!\n";
- print "\n";
+ print "\n";
}
+##############
+# Op handlers
+##############
+sub handle_init_paths () {
+ mkdir_recursive($certificates_path);
+ mkdir_recursive($private_keys_path);
-sub remove_pair ($ ) {
- my $keyid = shift or die;
+ my $file;
- if (-e "$certificates_path/$keyid") {
- unlink "$certificates_path/$keyid";
- modify_entry('R', $keyid, 1);
- print "Removed certificate $keyid.\n";
- }
- else {
- die "No such certificate: $keyid";
- }
+ $file = $certificates_path . "/.index";
+ -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
+ or die "Can't touch $file: $!";
- if (-e "$private_keys_path/$keyid") {
- unlink "$private_keys_path/$keyid";
- modify_entry('R', $keyid, 0);
- print "Removed private key $keyid.\n";
- }
+ $file = $private_keys_path . "/.index";
+ -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
+ or die "Can't touch $file: $!";
}
+sub handle_change_label ($) {
+ my ($keyid) = @_;
-
-sub change_label ($ ) {
- my $keyid = shift or die;
-
- my $label = query_label;
+ my $label = query_label();
if (-e "$certificates_path/$keyid") {
- modify_entry('L', $keyid, 1, $label);
+ cm_modify_entry('L', $keyid, 1, $label);
print "Changed label for certificate $keyid.\n";
}
else {
}
if (-e "$private_keys_path/$keyid") {
- modify_entry('L', $keyid, 0, $label);
+ 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_hash = openssl_hash($filename);
+ cm_add_certificate($filename, \$cert_hash, 1, $label, '?');
-sub verify_cert ($$) {
- my $keyid = shift or die;
- my $crl = shift;
+ # 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.
- -e "$certificates_path/$keyid" or $keyid eq 'all'
- or die "No such certificate: $keyid";
- modify_entry('V', $keyid, 1, $crl);
+ # 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";
+ # }
+ # }
}
+sub handle_add_pem ($) {
+ my ($filename) = @_;
+
+ my @pem_contents;
+ my $iter;
+ my $key;
+ my $certificate;
+ my $root_cert;
+ my $issuer_cert_file;
+
+ @pem_contents = openssl_parse_pem($filename, 1);
+
+ # look for key
+ $iter = 0;
+ while($iter <= $#pem_contents) {
+ if ($pem_contents[$iter]->{type} eq "K") {
+ $key = $pem_contents[$iter];
+ splice(@pem_contents, $iter, 1);
+ last;
+ }
+ $iter++;
+ }
+ defined($key) or die("Couldn't find private key!");
+ $key->{localKeyID} or die("Attribute 'localKeyID' wasn't set.");
+
+ # private key and certificate use the same 'localKeyID'
+ $iter = 0;
+ while($iter <= $#pem_contents) {
+ if (($pem_contents[$iter]->{type} eq "C") &&
+ ($pem_contents[$iter]->{localKeyID} eq $key->{localKeyID})) {
+ $certificate = $pem_contents[$iter];
+ splice(@pem_contents, $iter, 1);
+ last;
+ }
+ $iter++;
+ }
+ defined($certificate) or die("Couldn't find matching certificate!");
+ if ($#pem_contents < 0) {
+ die("No root and no intermediate certificates. Can't continue.");
+ }
+ # Look for a self signed root certificate
+ $iter = 0;
+ while($iter <= $#pem_contents) {
+ if ($pem_contents[$iter]->{subject} eq $pem_contents[$iter]->{issuer}) {
+ $root_cert = $pem_contents[$iter];
+ splice(@pem_contents, $iter, 1);
+ last;
+ }
+ $iter++;
+ }
+ if (defined($root_cert)) {
+ $issuer_cert_file = $root_cert->{datafile};
+ } else {
+ print "Couldn't identify root certificate!\n";
+ }
-sub do_verify($$$) {
-
- my $cert = shift or die;
- my $issuerid = shift or die;
- my $crl = shift;
+ # what's left are intermediate certificates.
+ if ($#pem_contents >= 0) {
+ my ($tmp_issuer_cert_fh, $tmp_issuer_cert) = create_tempfile();
+ $issuer_cert_file = $tmp_issuer_cert;
- my $result = 'i';
- my $trust_q;
- my $issuer_path;
- my $cert_path = "$certificates_path/$cert";
+ $iter = 0;
+ while($iter <= $#pem_contents) {
+ my $cert_datafile = $pem_contents[$iter]->{datafile};
+ open (CERT, "< $cert_datafile") or die "can't open $cert_datafile: $?";
+ print $tmp_issuer_cert_fh $_ while (<CERT>);
+ close CERT;
- if($issuerid eq '?') {
- $issuer_path = "$certificates_path/$cert";
- } else {
- $issuer_path = "$certificates_path/$issuerid";
+ $iter++;
+ }
+ close $tmp_issuer_cert_fh;
}
- my $cmd = "$opensslbin verify $root_certs_switch $root_certs_path -purpose smimesign -purpose smimeencrypt -untrusted $issuer_path $cert_path";
- my $output = `$cmd`;
- $? and die "'$cmd' returned $?";
- chop $output;
- print "\n$output\n";
+ handle_add_chain($key->{datafile}, $certificate->{datafile}, $issuer_cert_file);
+}
- ($output =~ /OK/) and ($result = 'v');
+sub handle_add_p12 ($) {
+ my ($filename) = @_;
- $result eq 'i' and return $result;
+ print "\nNOTE: This will ask you for two passphrases:\n";
+ print " 1. The passphrase you used for exporting\n";
+ print " 2. The passphrase you wish to secure your private key with.\n\n";
- my $format = -B $cert_path ? 'DER' : 'PEM';
- $cmd = "$opensslbin x509 -dates -serial -noout -in $cert_path -inform $format";
- (my $date1_in, my $date2_in, my $serial_in) = `$cmd`;
- $? and die "'$cmd' returned $?";
+ my ($pem_fh, $pem_file) = create_tempfile();
+ close($pem_fh);
- if ( defined $date1_in and defined $date2_in ) {
- my @tmp = split (/\=/, $date1_in);
- my $tmp = $tmp[1];
- @tmp = split (/\=/, $date2_in);
- my %months = ('Jan', '00', 'Feb', '01', 'Mar', '02', 'Apr', '03',
- 'May', '04', 'Jun', '05', 'Jul', '06', 'Aug', '07',
- 'Sep', '08', 'Oct', '09', 'Nov', '10', 'Dec', '11');
+ my $cmd = "$opensslbin pkcs12 -in $filename -out $pem_file";
+ system $cmd and die "'$cmd' returned $?";
- my @fields =
- $tmp =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
+ -e $pem_file and -s $pem_file or die("Conversion of $filename failed.");
+ handle_add_pem($pem_file);
+}
- $#fields != 5 and print "Expiration Date: Parse Error : $tmp\n\n" or
- timegm($fields[4], $fields[3], $fields[2], $fields[1],
- $months{$fields[0]}, $fields[5]) > time and $result = 'e';
- $result eq 'e' and print "Certificate is not yet valid.\n" and return $result;
+sub handle_add_chain ($$$) {
+ my ($key_file, $cert_file, $issuer_file) = @_;
- @fields =
- $tmp[1] =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
+ my $cert_hash = openssl_hash($cert_file);
+ my $issuer_hash = openssl_hash($issuer_file);
- $#fields != 5 and print "Expiration Date: Parse Error : $tmp[1]\n\n" or
- timegm($fields[4], $fields[3], $fields[2], $fields[1],
- $months{$fields[0]}, $fields[5]) < time and $result = 'e';
- $result eq 'e' and print "Certificate has expired.\n" and return $result;
+ my $label = query_label();
- }
-
- if ( defined $crl ) {
- my @serial = split (/\=/, $serial_in);
- my $cmd = "$opensslbin crl -text -noout -in $crl | grep -A1 $serial[1]";
- (my $l1, my $l2) = `$cmd`;
- $? and die "'$cmd' returned $?";
-
- if ( defined $l2 ) {
- my @revoke_date = split (/:\s/, $l2);
- print "FAILURE: Certificate $cert has been revoked on $revoke_date[1]\n";
- $result = 'r';
- }
- }
- print "\n";
+ cm_add_certificate($issuer_file, \$issuer_hash, 0, $label);
+ my @mailbox = cm_add_certificate($cert_file, \$cert_hash, 1, $label, $issuer_hash);
- if ($result eq 'v') {
- return 't';
+ foreach my $mailbox (@mailbox) {
+ cm_add_key($key_file, $cert_hash, $mailbox, $label);
}
+}
- return $result;
+sub handle_verify_cert ($$) {
+ my ($keyid, $crl) = @_;
+
+ -e "$certificates_path/$keyid" or $keyid eq 'all'
+ or die "No such certificate: $keyid";
+ cm_modify_entry('V', $keyid, 1, $crl);
}
+sub handle_remove_pair ($) {
+ my ($keyid) = @_;
+ if (-e "$certificates_path/$keyid") {
+ unlink "$certificates_path/$keyid";
+ cm_modify_entry('R', $keyid, 1);
+ print "Removed certificate $keyid.\n";
+ }
+ else {
+ die "No such certificate: $keyid";
+ }
-sub add_root_cert ($) {
- my $root_cert = shift or die;
+ if (-e "$private_keys_path/$keyid") {
+ unlink "$private_keys_path/$keyid";
+ cm_modify_entry('R', $keyid, 0);
+ print "Removed private key $keyid.\n";
+ }
+}
- my $format = -B $root_cert ? 'DER' : 'PEM';
+sub handle_add_root_cert ($) {
+ my ($root_cert) = @_;
- my $cmd = "$opensslbin x509 -noout -hash -in $root_cert -inform $format";
- my $root_hash = `$cmd`;
- $? and die "'$cmd' returned $?";
+ my $root_hash = openssl_hash($root_cert);
if (-d $root_certs_path) {
-e "$root_certs_path/$root_hash" or
mycopy $root_cert, "$root_certs_path/$root_hash";
}
else {
- open(ROOT_CERTS, ">>$root_certs_path") or
+ open(ROOT_CERTS, ">>$root_certs_path") or
die ("Couldn't open $root_certs_path for writing");
- $cmd = "$opensslbin x509 -in $root_cert -inform $format -fingerprint -noout";
- $? and die "'$cmd' returned $?";
- chomp(my $md5fp = `$cmd`);
+ my $md5fp = openssl_fingerprint($root_cert);
+ my $format = openssl_format($root_cert);
- $cmd = "$opensslbin x509 -in $root_cert -inform $format -text -noout";
+ my $cmd = "$opensslbin x509 -in $root_cert -inform $format -text -noout";
$? and die "'$cmd' returned $?";
my @cert_text = `$cmd`;
print ROOT_CERTS @cert_text;
close (ROOT_CERTS);
}
-
-}
-
-sub newfile ($;$$) {
- # returns a file name which does not exist for tmp file creation
- my $filename = shift;
- my $option = shift;
- $option = "notemp" if (not defined($option));
- if (! $tmpdir and $option eq "temp") {
- $tmpdir = mutt_Q 'tmpdir';
- $tmpdir = newfile("$tmpdir/smime");
- mkdir $tmpdir, 0700 || die "Can't create $tmpdir: $!\n";
- }
- $filename = "$tmpdir/$filename" if ($option eq "temp");
- my $newfilename = $filename;
- my $count = 0;
- while (-e $newfilename) {
- $newfilename = "$filename.$count";
- $count++;
- }
- unshift(@tempfiles,$newfilename);
- return $newfilename;
-}
-
-
-END {
- # remove all our temporary files in the end:
- for (@tempfiles){
- if (-f) {
- unlink;
- } elsif (-d) {
- rmdir;
- }
- }
+
}