Nico Golde:
[apps/madmutt.git] / smime_keys.pl
1 #! /usr/bin/perl -w
2
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 #
7 #     This program is free software; you can redistribute it and/or modify
8 #     it under the terms of the GNU General Public License as published by
9 #     the Free Software Foundation; either version 2 of the License, or
10 #     (at your option) any later version.
11
12 #     This program is distributed in the hope that it will be useful,
13 #     but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #     GNU General Public License for more details.
16
17 #     You should have received a copy of the GNU General Public License
18 #     along with this program; if not, write to the Free Software
19 #     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
20
21 use strict;
22 use File::Copy;
23
24 umask 077;
25
26 require "timelocal.pl";
27
28 sub usage ();
29 sub newfile ($;$$);
30 sub mutt_Q ($ );
31 sub mycopy ($$);
32
33 #  directory setup routines
34 sub mkdir_recursive ($ );
35 sub init_paths ();
36
37 # key/certificate management methods
38 sub list_certs ();
39 sub query_label ();
40 sub add_entry ($$$$$ );
41 sub add_certificate ($$$$;$ );
42 sub add_key ($$$$);
43 sub add_root_cert ($ );
44 sub parse_pem (@ );
45 sub handle_pem (@ );
46 sub modify_entry ($$$;$ );
47 sub remove_pair ($ );
48 sub change_label ($ );
49 sub verify_cert($$);
50 sub do_verify($$$ );
51               
52 # Get the directories mutt uses for certificate/key storage.
53
54 my $mutt = $ENV{MUTT_CMDLINE} || 'mutt';
55 my $opensslbin = "/usr/bin/openssl";
56 my @tempfiles = ();
57 my @cert_tmp_file = ();
58
59 my $tmpdir;
60 my $private_keys_path = mutt_Q 'smime_keys';
61 die "smime_keys is not set in mutt's configuration file"
62         if length $private_keys_path == 0;
63
64 my $certificates_path = mutt_Q 'smime_certificates';
65 die "smime_certificates is not set in mutt's configuration file"
66         if length $certificates_path == 0;
67 my $root_certs_path   = mutt_Q 'smime_ca_location';
68 die "smime_ca_location is not set in mutt's configuration file"
69         if length $root_certs_path == 0;
70
71 my $root_certs_switch;
72 if ( -d $root_certs_path) {
73         $root_certs_switch = -CApath;
74 } else {
75         $root_certs_switch = -CAfile;
76 }
77
78
79 #
80 # OPS
81 #
82
83 if(@ARGV == 1 and $ARGV[0] eq "init") {
84     init_paths;
85 }
86 elsif(@ARGV == 1 and $ARGV[0] eq "list") {
87     list_certs;
88 }
89 elsif(@ARGV == 2 and $ARGV[0] eq "label") {
90     change_label($ARGV[1]);
91 }
92 elsif(@ARGV == 2 and $ARGV[0] eq "add_cert") {
93     my $format = -B $ARGV[1] ? 'DER' : 'PEM'; 
94     my $cmd = "$opensslbin x509 -noout -hash -in $ARGV[1] -inform $format";
95     my $cert_hash = `$cmd`;
96     $? and die "'$cmd' returned $?";
97     chomp($cert_hash); 
98     my $label = query_label;
99     &add_certificate($ARGV[1], \$cert_hash, 1, $label, '?');
100 }
101 elsif(@ARGV == 2 and $ARGV[0] eq "add_pem") {
102     -e $ARGV[1] and -s $ARGV[1] or die("$ARGV[1] is nonexistent or empty.");
103     open(PEM_FILE, "<$ARGV[1]") or die("Can't open $ARGV[1]: $!");
104     my @pem = <PEM_FILE>;
105     close(PEM_FILE);
106     handle_pem(@pem);
107 }
108 elsif( @ARGV == 2 and $ARGV[0] eq "add_p12") {
109     -e $ARGV[1] and -s $ARGV[1] or die("$ARGV[1] is nonexistent or empty.");
110
111     print "\nNOTE: This will ask you for two passphrases:\n";
112     print "       1. The passphrase you used for exporting\n";
113     print "       2. The passphrase you wish to secure your private key with.\n\n";
114
115     my $pem_file = "$ARGV[1].pem";
116     
117     my $cmd = "$opensslbin pkcs12 -in $ARGV[1] -out $pem_file";
118     system $cmd and die "'$cmd' returned $?";
119     
120     -e $pem_file and -s $pem_file or die("Conversion of $ARGV[1] failed.");
121     open(PEM_FILE, $pem_file) or die("Can't open $pem_file: $!");
122     my @pem = <PEM_FILE>;
123     close(PEM_FILE);
124     unlink $pem_file;
125     handle_pem(@pem);
126 }
127 elsif(@ARGV == 4 and $ARGV[0] eq "add_chain") {
128     my $mailbox;
129     my $format = -B $ARGV[2] ? 'DER' : 'PEM'; 
130     my $cmd = "$opensslbin x509 -noout -hash -in $ARGV[2] -inform $format";
131     my $cert_hash = `$cmd`;
132
133     $? and die "'$cmd' returned $?";
134
135     $format = -B $ARGV[3] ? 'DER' : 'PEM'; 
136
137     $cmd = "$opensslbin x509 -noout -hash -in $ARGV[3] -inform $format";
138     my $issuer_hash = `$cmd`;
139     $? and die "'$cmd' returned $?";
140     
141     chomp($cert_hash); 
142     chomp($issuer_hash);
143
144     my $label = query_label;
145     
146     add_certificate($ARGV[3], \$issuer_hash, 0, $label); 
147     my @mailbox = &add_certificate($ARGV[2], \$cert_hash, 1, $label, $issuer_hash);
148     
149     foreach $mailbox (@mailbox) {
150       chomp($mailbox);
151       add_key($ARGV[1], $cert_hash, $mailbox, $label);
152     }
153 }
154 elsif((@ARGV == 2 or @ARGV == 3) and $ARGV[0] eq "verify") {
155     verify_cert($ARGV[1], $ARGV[2]);
156 }
157 elsif(@ARGV == 2 and $ARGV[0] eq "remove") {
158     remove_pair($ARGV[1]);
159 }
160 elsif(@ARGV == 2 and $ARGV[0] eq "add_root") {
161     add_root_cert($ARGV[1]);
162 }
163 else {    
164     usage;
165     exit(1);
166 }
167
168 exit(0);
169
170
171
172
173
174 ##############  sub-routines  ########################
175
176 sub usage () {
177     print <<EOF;
178
179 Usage: smime_keys <operation>  [file(s) | keyID [file(s)]]
180
181         with operation being one of:
182
183         init      : no files needed, inits directory structure.
184
185         list      : lists the certificates stored in database.
186         label     : keyID required. changes/removes/adds label.
187         remove    : keyID required.
188         verify    : 1=keyID and optionally 2=CRL
189                     Verifies the certificate chain, and optionally wether
190                     this certificate is included in supplied CRL (PEM format).
191                     Note: to verify all certificates at the same time,
192                     replace keyID with "all"
193
194         add_cert  : certificate required.
195         add_chain : three files reqd: 1=Key, 2=certificate
196                     plus 3=intermediate certificate(s).
197         add_p12   : one file reqd. Adds keypair to database.
198                     file is PKCS12 (e.g. export from netscape).
199         add_pem   : one file reqd. Adds keypair to database.
200                     (file was converted from e.g. PKCS12).
201
202         add_root  : one file reqd. Adds PEM root certificate to the location
203                     specified within muttrc (smime_verify_* command)
204
205 EOF
206 }
207
208 sub mutt_Q ($) {
209     my $var = shift or die;
210
211     my $cmd = "$mutt -v >/dev/null 2>/dev/null";
212     system ($cmd) == 0 
213         or die<<EOF;
214 Couldn't launch mutt. I attempted to do so by running the command "$mutt".
215 If that's not the right command, you can override it by setting the 
216 environment variable \$MUTT_CMDLINE
217 EOF
218
219     $cmd = "$mutt -Q $var 2>/dev/null";
220     my $answer = `$cmd`;
221
222     $? and die<<EOF;
223 Couldn't look up the value of the mutt variable "$var". 
224 You must set this in your mutt config file. See contrib/smime.rc for an example.
225 EOF
226 #'
227
228     $answer =~ /\"(.*?)\"/ and return $1;
229     
230     $answer =~ /^Mutt (.*?) / and die<<EOF;
231 This script requires mutt 1.5.0 or later. You are using mutt $1.
232 EOF
233     
234     die "Value of $var is weird\n";
235 }
236
237 sub mycopy ($$) {
238     my $source = shift or die;
239     my $dest = shift or die;
240
241     copy $source, $dest or die "Problem copying $source to $dest: $!\n";
242 }
243
244 #
245 #  directory setup routines
246 #
247
248
249 sub mkdir_recursive ($) {
250     my $path = shift or die;
251     my $tmp_path;
252     
253     for my $dir (split /\//, $path) {
254         $tmp_path .= "$dir/";
255
256         -d $tmp_path 
257             or mkdir $tmp_path, 0700
258                 or die "Can't mkdir $tmp_path: $!";
259     }
260 }
261
262 sub init_paths () {
263     mkdir_recursive($certificates_path);
264     mkdir_recursive($private_keys_path);
265
266     my $file;
267
268     $file = $certificates_path . "/.index";
269     -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
270         or die "Can't touch $file: $!";
271
272     $file = $private_keys_path . "/.index";
273     -f $file or open(TMP_FILE, ">$file") and close(TMP_FILE)
274         or die "Can't touch $file: $!";
275 }
276
277
278
279 #
280 # certificate management methods
281 #
282
283 sub list_certs () {
284   my %keyflags = ( 'i', '(Invalid)',  'r', '(Revoked)', 'e', '(Expired)',
285                    'u', '(Unverified)', 'v', '(Valid)', 't', '(Trusted)');
286
287   open(INDEX, "<$certificates_path/.index") or 
288     die "Couldn't open $certificates_path/.index: $!";
289   
290   print "\n";
291   while(<INDEX>) {
292     my $tmp;
293     my @tmp;
294     my $tab = "            ";
295     my @fields = split;
296
297     if($fields[2] eq '-') {
298       print "$fields[1]: Issued for: $fields[0] $keyflags{$fields[4]}\n";
299     } else {
300       print "$fields[1]: Issued for: $fields[0] \"$fields[2]\" $keyflags{$fields[4]}\n";
301     }
302
303     my $certfile = "$certificates_path/$fields[1]";
304     my $cert;
305     {
306         open F, $certfile or
307             die "Couldn't open $certfile: $!";
308         local $/;
309         $cert = <F>;
310         close F;
311     }
312
313     my $subject_in;
314     my $issuer_in;
315     my $date1_in;
316     my $date2_in;
317
318     my $format = -B $certfile ? 'DER' : 'PEM'; 
319     my $cmd = "$opensslbin x509 -subject -issuer -dates -noout -in $certfile -inform $format";
320     ($subject_in, $issuer_in, $date1_in, $date2_in) = `$cmd`;
321     $? and print "ERROR: '$cmd' returned $?\n\n" and next;
322
323
324     my @subject = split(/\//, $subject_in);
325     while(@subject) {
326       $tmp = shift @subject;
327       ($tmp =~ /^CN\=/) and last;
328       undef $tmp;
329     }
330     defined $tmp and @tmp = split (/\=/, $tmp) and
331       print $tab."Subject: $tmp[1]\n";
332
333     my @issuer = split(/\//, $issuer_in);
334     while(@issuer) {
335       $tmp = shift @issuer;
336       ($tmp =~ /^CN\=/) and last;
337       undef $tmp;
338     }
339     defined $tmp and @tmp = split (/\=/, $tmp) and
340       print $tab."Issued by: $tmp[1]";
341
342     if ( defined $date1_in and defined $date2_in ) {
343       @tmp = split (/\=/, $date1_in);
344       $tmp = $tmp[1];
345       @tmp = split (/\=/, $date2_in);
346       print $tab."Certificate is not valid before $tmp".
347         $tab."                      or after  ".$tmp[1];
348     }
349
350     -e "$private_keys_path/$fields[1]" and
351       print "$tab - Matching private key installed -\n";
352
353     $format = -B "$certificates_path/$fields[1]" ? 'DER' : 'PEM'; 
354     $cmd = "$opensslbin x509 -purpose -noout -in $certfile -inform $format";
355     my $purpose_in = `$cmd`;
356     $? and die "'$cmd' returned $?";
357
358     my @purpose = split (/\n/, $purpose_in);
359     print "$tab$purpose[0] (displays S/MIME options only)\n";
360     while(@purpose) {
361       $tmp = shift @purpose;
362       ($tmp =~ /^S\/MIME/ and $tmp =~ /Yes/) or next;
363       my @tmptmp = split (/:/, $tmp);
364       print "$tab  $tmptmp[0]\n";
365     }
366
367     print "\n";
368   }
369   
370   close(INDEX);
371 }
372
373
374
375 sub query_label () {
376     my @words;
377     my $input;
378
379     print "\nYou may assign a label to this key, so you don't have to remember\n";
380     print "the key ID. This has to be _one_ word (no whitespaces).\n\n";
381
382     print "Enter label: ";
383     chomp($input = <STDIN>);
384
385     my ($label, $junk) = split(/\s/, $input, 2);     
386     
387     defined $junk 
388         and print "\nUsing '$label' as label; ignoring '$junk'\n";
389
390     defined $label || ($label =  "-");
391
392     return $label;
393 }
394
395
396
397 sub add_entry ($$$$$) {
398     my $mailbox = shift or die;
399     my $hashvalue = shift or die;
400     my $use_cert = shift;
401     my $label = shift or die;
402     my $issuer_hash = shift;
403
404     my @fields;
405
406     if ($use_cert) {
407         open(INDEX, "+<$certificates_path/.index") or 
408             die "Couldn't open $certificates_path/.index: $!";
409     }
410     else {
411         open(INDEX, "+<$private_keys_path/.index") or 
412             die "Couldn't open $private_keys_path/.index: $!";
413     }
414
415     while(<INDEX>) {
416         @fields = split;
417         return if ($fields[0] eq $mailbox && $fields[1] eq $hashvalue);
418     }
419
420     if ($use_cert) {
421         print INDEX "$mailbox $hashvalue $label $issuer_hash u\n";
422     }
423     else {
424         print INDEX "$mailbox $hashvalue $label \n";
425     }
426
427     close(INDEX);
428 }
429
430
431 sub add_certificate ($$$$;$) {
432     my $filename = shift or die;
433     my $hashvalue = shift or die;
434     my $add_to_index = shift;
435     my $label = shift or die;
436     my $issuer_hash = shift;
437
438     my $iter = 0;
439     my @mailbox;
440     my $mailbox;
441
442     while(-e "$certificates_path/$$hashvalue.$iter") {
443         my ($t1, $t2);
444         my $format = -B $filename ? 'DER' : 'PEM'; 
445         my $cmd = "$opensslbin x509 -in $filename -inform $format -fingerprint -noout";
446         $t1 = `$cmd`;
447         $? and die "'$cmd' returned $?";
448
449         $format = -B "$certificates_path/$$hashvalue.$iter" ? 'DER' : 'PEM'; 
450         $cmd = "$opensslbin x509 -in $certificates_path/$$hashvalue.$iter -inform $format -fingerprint -noout";
451         $t2 = `$cmd`;
452         $? and die "'$cmd' returned $?";
453         
454         $t1 eq $t2 and last;
455
456         $iter++;
457     }
458     $$hashvalue .= ".$iter";
459     
460     if (-e "$certificates_path/$$hashvalue") {
461             print "\nCertificate: $certificates_path/$$hashvalue already installed.\n";
462     }
463     else {
464         mycopy $filename, "$certificates_path/$$hashvalue";
465
466         if ($add_to_index) {
467             my $format = -B $filename ? 'DER' : 'PEM'; 
468             my $cmd = "$opensslbin x509 -in $filename -inform $format -email -noout";
469             @mailbox = `$cmd`;
470             $? and die "'$cmd' returned $?";
471
472             foreach $mailbox (@mailbox) {
473               chomp($mailbox);
474               add_entry($mailbox, $$hashvalue, 1, $label, $issuer_hash);
475
476               print "\ncertificate $$hashvalue ($label) for $mailbox added.\n";
477             }
478             verify_cert($$hashvalue, undef);
479         }
480         else {
481             print "added certificate: $certificates_path/$$hashvalue.\n";
482         }
483     }
484
485     return @mailbox;
486 }
487
488
489 sub add_key ($$$$) {
490     my $file = shift or die;
491     my $hashvalue = shift or die;
492     my $mailbox = shift or die;
493     my $label = shift or die;
494
495     unless (-e "$private_keys_path/$hashvalue") {
496         mycopy $file, "$private_keys_path/$hashvalue";
497     }    
498
499     add_entry($mailbox, $hashvalue, 0, $label, "");
500     print "added private key: " .
501       "$private_keys_path/$hashvalue for $mailbox\n";
502
503
504
505
506
507
508
509 sub parse_pem (@) {
510     my $state = 0;
511     my $cert_iter = 0;
512     my @bag_attribs;
513     my $numBags = 0;
514
515     $cert_tmp_file[$cert_iter] = newfile("cert_tmp.$cert_iter","temp");
516     my $cert_tmp_iter = $cert_tmp_file[$cert_iter];
517     open(CERT_FILE, ">$cert_tmp_iter") 
518         or die "Couldn't open $cert_tmp_iter: $!";
519
520     while($_ = shift(@_)) {
521         if(/^Bag Attributes/) {
522             $numBags++;
523             $state == 0 or  die("PEM-parse error at: $.");
524             $state = 1;
525             $bag_attribs[$cert_iter*4+1] = "";
526             $bag_attribs[$cert_iter*4+2] = "";
527             $bag_attribs[$cert_iter*4+3] = "";
528         }
529
530         ($state == 1) and /localKeyID:\s*(.*)/ 
531             and ($bag_attribs[$cert_iter*4+1] = $1);
532
533         ($state == 1) and /subject=\s*(.*)/    
534             and ($bag_attribs[$cert_iter*4+2] = $1);
535
536         ($state == 1) and /issuer=\s*(.*)/     
537             and ($bag_attribs[$cert_iter*4+3] = $1);
538         
539         if(/^-----/) {
540             if(/BEGIN/) {
541                 print CERT_FILE;
542                 $state = 2;
543
544                 if(/PRIVATE/) {
545                     $bag_attribs[$cert_iter*4] = "K";
546                     next;
547                 }
548                 if(/CERTIFICATE/) {
549                     $bag_attribs[$cert_iter*4] = "C";
550                     next;
551                 }
552                 die("What's this: $_");
553             }
554             if(/END/) {
555                 $state = 0;
556                 print CERT_FILE;
557                 close(CERT_FILE);
558                 $cert_iter++;
559                 $cert_tmp_file[$cert_iter] = newfile("cert_tmp.$cert_iter","temp");
560                 $cert_tmp_iter = $cert_tmp_file[$cert_iter];
561                 open(CERT_FILE, ">$cert_tmp_iter")
562                     or die "Couldn't open $cert_tmp_iter: $!";
563                 next;
564             }
565         }
566         print CERT_FILE;
567     }
568     close(CERT_FILE);
569
570     # I'll add support for unbagged cetificates, in case this is needed.
571     $numBags == $cert_iter or 
572         die("Not all contents were bagged. can't continue.");
573
574     return @bag_attribs;
575 }
576
577
578 # This requires the Bag Attributes to be set
579 sub handle_pem (@) {
580
581     my @pem_contents;
582     my $iter=0;
583     my $root_cert;
584     my $key;
585     my $certificate;
586     my $intermediate;
587     my @mailbox;
588     my $mailbox;
589
590     @pem_contents = &parse_pem(@_);
591
592     # private key and certificate use the same 'localKeyID'
593     while($iter <= $#pem_contents / 4) {
594         if($pem_contents[$iter * 4] eq "K") {
595             $key = $iter;
596             last;
597         }
598         $iter++;
599     }
600     ($iter > $#pem_contents / 2) and die("Couldn't find private key!");
601
602     $pem_contents[($key * 4)+1] or die("Attribute 'localKeyID' wasn't set.");
603
604     $iter = 0;
605     while($iter <= $#pem_contents / 4) {
606         $iter == $key and ($iter++) and next;
607         if($pem_contents[($iter * 4)+1] eq $pem_contents[($key * 4)+1]) {
608             $certificate = $iter;
609             last;
610         }
611         $iter++;
612     }
613     ($iter > $#pem_contents / 4) and die("Couldn't find matching certificate!");
614
615     my $tmp_key = newfile("tmp_key","temp");
616     mycopy $cert_tmp_file[$key], $tmp_key;
617     my $tmp_certificate = newfile("tmp_certificate","temp");
618     mycopy $cert_tmp_file[$certificate], $tmp_certificate;
619
620     # root certificate is self signed
621     $iter = 0;
622
623     while($iter <= $#pem_contents / 4) {
624         if ($iter == $key or $iter == $certificate) {
625             $iter++; 
626             next;
627         }
628
629         if($pem_contents[($iter * 4)+2] eq $pem_contents[($iter * 4)+3]) {
630             $root_cert = $iter;
631             last;
632         }
633         $iter++;
634     }
635     if ($iter > $#pem_contents / 4) {
636       print "Couldn't identify root certificate!\n";
637       $root_cert = -1;      
638     }
639
640     # what's left are intermediate certificates.
641     $iter = 0;
642
643     # needs to be set, so we can check it later
644     $intermediate = $root_cert;
645     my $tmp_issuer_cert = newfile("tmp_issuer_cert","temp");
646     while($iter <= $#pem_contents / 4) {
647         if ($iter == $key or $iter == $certificate or $iter == $root_cert) {
648             $iter++; 
649             next;
650         }
651
652         open (IC, ">> $tmp_issuer_cert") or die "can't open $tmp_issuer_cert: $?";
653         my $cert_tmp_iter = $cert_tmp_file[$iter];
654         open (CERT, "< $cert_tmp_iter") or die "can't open $cert_tmp_iter: $?";
655         print IC while (<CERT>);
656         close IC;
657         close CERT;
658
659         # although there may be many, just need to know if there was any
660         $intermediate = $iter;
661
662         $iter++;
663     }
664
665     # no intermediate certificates ? use root-cert instead (if that was found...)
666     if($intermediate == $root_cert) {
667         if ($root_cert == -1) {
668           die("No root and no intermediate certificates. Can't continue.");
669         }
670         mycopy $cert_tmp_file[$root_cert], $tmp_issuer_cert;
671     }
672
673     my $label = query_label;
674
675     my $format = -B $tmp_certificate ? 'DER' : 'PEM'; 
676     my $cmd = "$opensslbin x509 -noout -hash -in $tmp_certificate -inform $format";
677     my $cert_hash = `$cmd`;
678     $? and die "'$cmd' returned $?";
679
680     $format = -B $tmp_issuer_cert ? 'DER' : 'PEM'; 
681     $cmd = "$opensslbin x509 -noout -hash -in $tmp_issuer_cert -inform $format";
682     my $issuer_hash = `$cmd`;
683     $? and die "'$cmd' returned $?";
684
685     chomp($cert_hash); chomp($issuer_hash);
686
687     # Note: $cert_hash will be changed to reflect the correct filename
688     #       within add_cert() ONLY, so these _have_ to get called first..
689     add_certificate($tmp_issuer_cert, \$issuer_hash, 0, $label);
690     @mailbox = &add_certificate("$tmp_certificate", \$cert_hash, 1, $label, $issuer_hash); 
691     foreach $mailbox (@mailbox) {
692       chomp($mailbox);
693       add_key($tmp_key, $cert_hash, $mailbox, $label);
694     }
695 }
696
697
698
699
700
701
702 sub modify_entry ($$$;$ ) {
703     my $op = shift or die;
704     my $hashvalue = shift or die;
705     my $use_cert = shift;
706     my $crl;
707     my $label;
708     my $path;
709     my @fields;
710
711     $op eq 'L' and ($label = shift or die);
712     $op eq 'V' and ($crl = shift);
713
714
715     if ($use_cert) {
716         $path = $certificates_path;
717     }
718     else {
719         $path = $private_keys_path;
720     }
721
722     open(INDEX, "<$path/.index") or  
723       die "Couldn't open $path/.index: $!";
724     my $newindex = newfile("$path/.index.tmp");
725     open(NEW_INDEX, ">$newindex") or 
726       die "Couldn't create $newindex: $!";
727
728     while(<INDEX>) {
729         @fields = split;
730         if($fields[1] eq $hashvalue or $hashvalue eq 'all') {
731           $op eq 'R' and next;
732           print NEW_INDEX "$fields[0] $fields[1]";
733           if($op eq 'L') {
734             if($use_cert) {
735               print NEW_INDEX " $label $fields[3] $fields[4]";
736             }
737             else {
738               print NEW_INDEX " $label";
739             }
740           }
741           if ($op eq 'V') {
742             print "\n==> about to verify certificate of $fields[0]\n";
743             my $flag = &do_verify($fields[1], $fields[3], $crl);
744             print NEW_INDEX " $fields[2] $fields[3] $flag";
745           }
746           print NEW_INDEX "\n";
747           next;
748         }
749         print NEW_INDEX;
750     }
751     close(INDEX);
752     close(NEW_INDEX);
753
754     rename $newindex, "$path/.index" 
755         or die "Couldn't rename $newindex to $path/.index: $!\n";
756
757     print "\n";
758 }
759
760
761
762
763 sub remove_pair ($ ) {
764   my $keyid = shift or die;
765
766   if (-e "$certificates_path/$keyid") {
767     unlink "$certificates_path/$keyid";
768     modify_entry('R', $keyid, 1);
769     print "Removed certificate $keyid.\n";
770   }
771   else {
772     die "No such certificate: $keyid";
773   }
774
775   if (-e "$private_keys_path/$keyid") {
776     unlink "$private_keys_path/$keyid";
777     modify_entry('R', $keyid, 0);
778     print "Removed private key $keyid.\n";
779   }
780 }
781
782
783
784 sub change_label ($ ) {
785   my $keyid = shift or die;
786   
787   my $label = query_label;
788
789   if (-e "$certificates_path/$keyid") {
790     modify_entry('L', $keyid, 1, $label);
791     print "Changed label for certificate $keyid.\n";
792   }
793   else {
794     die "No such certificate: $keyid";
795   }
796
797   if (-e "$private_keys_path/$keyid") {
798     modify_entry('L', $keyid, 0, $label);
799     print "Changed label for private key $keyid.\n";
800   }
801
802 }
803
804
805
806
807 sub verify_cert ($$) {
808   my $keyid = shift or die;
809   my $crl = shift;
810
811   -e "$certificates_path/$keyid" or $keyid eq 'all'
812     or die "No such certificate: $keyid";
813   modify_entry('V', $keyid, 1, $crl);
814 }
815
816
817
818
819 sub do_verify($$$) {
820
821   my $cert = shift or die;
822   my $issuerid = shift or die;
823   my $crl = shift;
824
825   my $result = 'i';
826   my $trust_q;
827   my $issuer_path;
828   my $cert_path = "$certificates_path/$cert";
829
830   if($issuerid eq '?') {
831     $issuer_path = "$certificates_path/$cert";
832   } else {
833     $issuer_path = "$certificates_path/$issuerid";
834   }
835
836   my $cmd = "$opensslbin verify $root_certs_switch $root_certs_path -purpose smimesign -purpose smimeencrypt -untrusted $issuer_path $cert_path";
837   my $output = `$cmd`;
838   $? and die "'$cmd' returned $?";
839   chop $output;
840   print "\n$output\n";
841
842   ($output =~ /OK/) and ($result = 'v');
843
844   $result eq 'i' and return $result;
845
846   my $format = -B $cert_path ? 'DER' : 'PEM'; 
847   $cmd = "$opensslbin x509 -dates -serial -noout -in $cert_path -inform $format";
848   (my $date1_in, my $date2_in, my $serial_in) = `$cmd`;
849   $? and die "'$cmd' returned $?";
850
851   if ( defined $date1_in and defined $date2_in ) {
852     my @tmp = split (/\=/, $date1_in);
853     my $tmp = $tmp[1];
854     @tmp = split (/\=/, $date2_in);
855     my %months = ('Jan', '00', 'Feb', '01', 'Mar', '02', 'Apr', '03',
856                   'May', '04', 'Jun', '05', 'Jul', '06', 'Aug', '07',
857                   'Sep', '08', 'Oct', '09', 'Nov', '10', 'Dec', '11');
858
859     my @fields =
860       $tmp =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
861
862     $#fields != 5 and print "Expiration Date: Parse Error :  $tmp\n\n" or
863       timegm($fields[4], $fields[3], $fields[2], $fields[1],
864              $months{$fields[0]}, $fields[5]) > time and $result = 'e';
865     $result eq 'e' and print "Certificate is not yet valid.\n" and return $result;
866
867     @fields =
868       $tmp[1] =~ /(\w+)\s*(\d+)\s*(\d+):(\d+):(\d+)\s*(\d+)\s*GMT/;
869
870     $#fields != 5 and print "Expiration Date: Parse Error :  $tmp[1]\n\n" or
871       timegm($fields[4], $fields[3], $fields[2], $fields[1],
872              $months{$fields[0]}, $fields[5]) < time and $result = 'e';
873     $result eq 'e' and print "Certificate has expired.\n" and return $result;
874
875   }
876     
877   if ( defined $crl ) {
878     my @serial = split (/\=/, $serial_in);
879     my $cmd = "$opensslbin crl -text -noout -in $crl | grep -A1 $serial[1]";
880     (my $l1, my $l2) = `$cmd`;
881     $? and die "'$cmd' returned $?";
882     
883     if ( defined $l2 ) {
884       my @revoke_date = split (/:\s/, $l2);
885       print "FAILURE: Certificate $cert has been revoked on $revoke_date[1]\n";
886       $result = 'r';
887     }
888   }    
889   print "\n";
890
891   if ($result eq 'v') {
892     return 't';
893   }
894
895   return $result;
896 }
897
898
899
900 sub add_root_cert ($) {
901   my $root_cert = shift or die;
902
903   my $format = -B $root_cert ? 'DER' : 'PEM'; 
904
905   my $cmd = "$opensslbin x509 -noout -hash -in $root_cert -inform $format";
906   my $root_hash = `$cmd`;
907   $? and die "'$cmd' returned $?";
908
909   if (-d $root_certs_path) {
910     -e "$root_certs_path/$root_hash" or
911         mycopy $root_cert, "$root_certs_path/$root_hash";
912   }
913   else {
914     open(ROOT_CERTS, ">>$root_certs_path") or 
915       die ("Couldn't open $root_certs_path for writing");
916
917     $cmd = "$opensslbin x509 -in $root_cert -inform $format -fingerprint -noout";
918     $? and die "'$cmd' returned $?";
919     chomp(my $md5fp = `$cmd`);
920
921     $cmd = "$opensslbin x509 -in $root_cert -inform $format -text -noout";
922     $? and die "'$cmd' returned $?";
923     my @cert_text = `$cmd`;
924
925     print "Enter a label, name or description for this certificate: ";
926     my $input = <STDIN>;
927
928     my $line = "=======================================\n";
929     print ROOT_CERTS "\n$input$line$md5fp\nPEM-Data:\n";
930
931     $cmd = "$opensslbin x509 -in $root_cert -inform $format";
932     my $cert = `$cmd`;
933     $? and die "'$cmd' returned $?";
934     print ROOT_CERTS $cert;
935     print ROOT_CERTS @cert_text;
936     close (ROOT_CERTS);
937   }
938   
939 }
940
941 sub newfile ($;$$) {
942         # returns a file name which does not exist for tmp file creation
943         my $filename = shift;
944         my $option = shift;
945         $option = "notemp" if (not defined($option));
946         if (! $tmpdir and $option eq "temp") {
947                 $tmpdir = mutt_Q 'tmpdir';
948                 $tmpdir = newfile("$tmpdir/smime");
949                 mkdir $tmpdir, 0700 || die "Can't create $tmpdir: $!\n";
950         }
951         $filename = "$tmpdir/$filename" if ($option eq "temp");
952         my $newfilename = $filename;
953         my $count = 0;
954         while (-e $newfilename) {
955                 $newfilename = "$filename.$count";
956                 $count++;
957         }
958         unshift(@tempfiles,$newfilename);
959         return $newfilename;
960 }
961
962
963 END {
964         # remove all our temporary files in the end:
965         for (@tempfiles){
966                 if (-f) {
967                         unlink;
968                 } elsif (-d) { 
969                         rmdir;
970                 }
971         }
972 }