File Coverage

blib/lib/OpenCA/OpenSSL/SMIME.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package OpenCA::OpenSSL::SMIME;
2              
3             ##
4             ## General Errorcodes:
5             ##
6             ## The errorcodes consists of seven numbers:
7             ## 1234567
8             ## 12: module
9             ## 34: function
10             ## 567: errorcode
11             ##
12             ## The modules errorcode is 80.
13             ##
14             ## The functions use the following errorcodes:
15             ##
16             ## new 00
17             ## set_params 01
18             ## errno 02
19             ## err 03
20             ## sign 10
21             ## verify 11
22             ## encrypt 12
23             ## decrypt 13
24             ## get_mime 14
25             ## get_last_signer 15
26             ## status 16
27             ## status_code 17
28             ##
29             ## _setError xx
30             ## _set_status 94
31             ## _strip_headers 95
32             ## _exec 96
33             ## _save_headers 97
34             ## _sync_data 98
35             ## _save_tmp 99
36              
37 5     5   164 use 5.006;
  5         21  
  5         272  
38 5     5   31 use warnings;
  5         14  
  5         183  
39 5     5   28 use strict;
  5         9  
  5         165  
40 5     5   10725 use File::Temp;
  5         95127  
  5         440  
41 5     5   9024 use MIME::Parser;
  0            
  0            
42              
43             ## our $VERSION = substr q$Revision: 1.2 $, 10;
44             ($OpenCA::OpenSSL::SMIME::VERSION = '$Revision: 1.2 $' )=~ s/(?:^.*: (\d+))|(?:\s+\$$)/defined $1?"0\.9":""/eg;
45             our $errno = undef;
46             our $errval = undef;
47              
48              
49             ####### Private subs and vars
50              
51             my %smime = (
52             backend => undef,
53             file => undef, # file containing message
54             entity => undef, # MIME::Entity representation
55             ca_certs => {}, # Hash of OpenCA::X509
56             ca_certs_file => undef, # File containing the DER encodings
57             DEBUG => undef,
58             tmpDir => undef,
59             status => [ 0, "" ], # Status of last decrypt/verify
60             header_cache => undef, # MIME::Header for saving orig. headers
61             needs_extract => undef, # does actual state require extration
62             # of headers?
63             # 1: yes, priorize new headers
64             # -1: yes, priorize old headers
65             last_signer => undef, # Filename for last received signer
66             # cert (from verify)
67             last_signer_x509=> undef, # OpenCA::X509 for last received signer
68             errno => 0,
69             err => undef);
70              
71             # Supported ciphers, first is default.
72             my @Ciphers = qw(des3 des rc2-40 rc2-64 rc2-128);
73              
74             # Saves data on a temporary file, returns filename.
75             # Returns open filehandle, filename in list context.
76              
77             my(@tmpfiles);
78             sub _save_tmp {
79             my($self, $data);
80             ($self, $data, @_) = @_;
81              
82             $self->_setError(0, "");
83              
84             my($args) = { @_ };
85             my($tfh, $tf) = File::Temp::tempfile(
86             DIR => $self->{tmpDir},
87             UNLINK => 0);
88             push(@tmpfiles, $tf);
89              
90             if(ref($data) && ref($data) eq 'GLOB') {
91             while(<$data>) {
92             return $self->_setError(8099001,
93             'OpenCA::OpenSSL::SMIME->_save_tmp: ' .
94             'Error writing in tempfile')
95             unless($tfh->print($_));
96             }
97             } else {
98             return $self->_setError(8099001, 'OpenCA::OpenSSL::SMIME->_save_tmp: ' .
99             'Error writing in tempfile')
100             unless($tfh->print($data));
101             }
102              
103             return ($tfh, $tf) if(wantarray);
104             return $self->_setError(8099002, 'OpenCA::OpenSSL::SMIME->_save_tmp: ' .
105             'Error closing tempfile')
106             unless($tfh->close);
107             return $tf;
108             }
109              
110             # Sets and reports error states
111             sub _setError {
112             my $self = shift;
113              
114             if (scalar (@_) == 4) {
115             my $keys = { @_ };
116             $self->{errno} = $keys->{ERRNO};
117             $self->{errval} = $keys->{ERRVAL};
118             } else {
119             $self->{errno} = $_[0];
120             $self->{errval} = $_[1];
121             }
122              
123             $errno = $self->{errno};
124             $errval = $self->{errval};
125              
126             return undef unless($self->{errno});
127              
128             $self->_debug ("_setError: errno: $self->{errno}");
129             $self->_debug ("_setError: errval: $self->{errval}");
130             warn("$self->{errval} ($self->{errno})") if($^W);
131             ## support for: return $self->_setError (1234, "Something fails.") if (not $xyz);
132             return undef;
133             }
134              
135             sub _debug
136             {
137             my $self = shift;
138              
139             return 1 if (not $self->{DEBUG});
140              
141             print STDERR "OpenCA::OpenSSL::SMIME->".join (" ", @_)."\n";
142              
143             return 1;
144             }
145              
146             # Syncing textual and MIME::Entity representations of the object.
147             sub _sync_data {
148             my($self) = shift;
149              
150             $self->_setError(0, "");
151              
152             if($self->{file} && ! $self->{entity}) {
153             my($parser) = MIME::Parser->new();
154             $parser->output_under($self->{tmpDir});
155             $self->{entity} = $parser->parse_open($self->{file})
156             or return $self->_setError(
157             8098001,
158             'OpenCA::OpenSSL::SMIME->_sync_data: ' .
159             'Error parsing input file');
160             undef($parser);
161            
162             return $self->_setError(8098002, 'OpenCA::OpenSSL::SMIME->_sync_data: ' .
163             'Error parsing into MIME::Entity')
164             unless($self->{entity});
165              
166             } elsif(! $self->{file} && $self->{entity}) {
167              
168             my($fh, $f) = $self->_save_tmp("") or return(undef);
169             $self->{entity}->print($fh)
170             or return $self->_setError(
171             8098003,
172             'OpenCA::OpenSSL::SMIME->_sync_data: ' .
173             'Error saving MIME::Entity into ' .
174             'tempfile');
175             $fh->close
176             or return $self->_setError(
177             8098004,
178             'OpenCA::OpenSSL::SMIME->_sync_data: ' .
179             'Error closing tempfile');
180             $self->{file} = $f;
181             }
182              
183             return 1;
184             }
185              
186             # Saves non-mime headers, to be restored at end of process. Syncs if no entity.
187             sub _save_headers {
188             my($self) = shift;
189             my($tag);
190              
191             $self->_setError(0, "");
192              
193             # Do we really need to extract any headers?
194             return(1) unless($self->{needs_extract});
195              
196             # If we have no entity, get it.
197             unless($self->{entity}) {
198             $self->_sync_data() or return(undef);
199             }
200              
201             # // If we have no headers' cache, create a new one.
202             $self->{headers_cache} = MIME::Head->new()
203             unless($self->{headers_cache});
204              
205             # Copy headers
206             foreach($self->{entity}->head()->tags()) {
207             next if(/^(content|MIME)/i); # // don't save MIME headers
208             $tag = $_;
209             if($self->{needs_extract} < 0) { # priority: old headers
210             next if($self->{headers_cache}->count($tag));
211             # it is in the cache, skip
212             } else { # priority: new headers
213             $self->{headers_cache}->delete($tag);
214             }
215             foreach($self->{entity}->head()->get_all($tag)) {
216             $self->{headers_cache}->add($tag, $_);
217             }
218             }
219              
220             $self->{needs_extract} = undef;
221             return 1;
222             }
223              
224             # Forks and execs subprocess, capturing stderr and exit code. returns
225             # (exit_code, stderr) in list context and exit_code on scalar context.
226             sub _exec {
227             my($self, @arg) = @_;
228              
229             $self->_debug ("_exec: resetting errorcode");
230             $self->_setError(0, "");
231              
232             my $command = join ' ', @arg;
233             $self->_debug ("_exec: command $command");
234              
235             ## exexcution in parent environment
236              
237             my $res = $self->{backend}->_execute_command (COMMAND => $command,
238             KEY_USAGE => $self->{ENGINE});
239             if (not defined $res)
240             {
241             $self->_setError ($self->{backend}->errno,
242             $self->{backend}->errval);
243             if(wantarray) {
244             return($self->errno, $self->errval);
245             } else {
246             return($self->errno);
247             }
248             } else {
249             $res = "" if ($res == 1);
250             if (wantarray) {
251             return(0, $res);
252             } else {
253             return(0);
254             }
255             }
256              
257             # my($child, $res);
258             # defined($child = open(OUT, '-|'))
259             # or return($self->_setError(8096001, 'OpenCA::OpenSSL::SMIME->_exec: Can\'t fork'));
260             # if($child) {
261             # $res = join('', );
262             # close(OUT) or not $! or return($self->_setError(8096002, 'OpenCA::OpenSSL::SMIME->_exec: Problems executing ' . join(' ', @arg)));
263             #
264             # } else {
265             # select(STDERR); $| = 1; # make unbuffered
266             # select(STDOUT); $| = 1; # make unbuffered
267             # open(STDERR, ">&STDOUT") or die "Can't dup stdout";
268             # open(STDOUT, ">/dev/null") or die "Can't close stdout";
269             #
270             # exec(@arg) or die "Can't exec";
271             # }
272             #
273             # if(wantarray) {
274             # return($?, $res);
275             # } else {
276             # return($?);
277             # }
278             }
279              
280             # Strips non-mime headers.
281             sub _strip_headers {
282             my($self) = shift;
283             my($modified);
284              
285             $self->_setError(0, "");
286              
287             return($self->_setError(8095001, 'OpenCA::OpenSSL::SMIME->_strip_headers: no entity found')) unless ($self->{entity});
288              
289             $modified = 0;
290             foreach($self->{entity}->head()->tags()) {
291             next if(/^(content|MIME)/i); # // don't touch MIME headers
292             $self->{entity}->head()->delete($_);
293             $modified++;
294             }
295              
296             if($modified) { # we made any changes?
297             $self->{file} = undef; # original file is no longer valid
298             }
299             return 1;
300             }
301              
302             # Sets status for last operation
303             sub _set_status {
304             my($self) = shift;
305              
306             confess("Invalid invocation") unless (@_ == 2);
307             $self->{status} = [ @_ ];
308              
309             return 1;
310             }
311              
312             ###### Public methods
313              
314             ## Create an instance of the Class
315             sub new {
316             my $that = shift;
317             my $class = ref($that) || $that;
318              
319             my $self = {
320             %smime,
321             };
322             bless $self, $class;
323              
324             if(ref($that)) { # get some defaults from creator
325             $self->{backend} = $that->{backend};
326             $self->{gettext} = $that->{gettext};
327             $self->{tmpDir} = $that->{tmpDir};
328             $self->{DEBUG} = $that->{DEBUG};
329             $self->{ca_certs} = $that->{ca_certs};
330             $self->{ca_certs_file} = $that->{ca_certs_file};
331             }
332             $self->set_params( @_ ) or return(undef);
333              
334             return $self->_setError(8000000, 'OpenCA::OpenSSL::SMIME->new: Missing required parameter: GETTEXT')
335             unless($self->{gettext});
336             return $self->_setError(8000001,
337             $self->{gettext} ("OpenCA::OpenSSL::SMIME->new: Missing required parameter: SHELL"))
338             unless($self->{backend});
339             return $self->_setError(8000002,
340             $self->{gettext} ("OpenCA::OpenSSL::SMIME->new: Invalid required parameter: SHELL"))
341             unless(ref($self->{backend}));
342              
343             return $self;
344             }
345              
346             sub set_params {
347             my $self = shift;
348             my $params = { @_ };
349             my $key;
350              
351             $self->_setError(0, "");
352              
353             foreach $key (keys %{$params}) {
354             $self->{backend} = $params->{$key} if ($key eq 'SHELL');
355             $self->{gettext} = $params->{$key} if ($key eq 'GETTEXT');
356             $self->{tmpDir} = $params->{$key} if ($key eq 'TMPDIR');
357             $self->{DEBUG} = $params->{$key} if ($key eq 'DEBUG');
358             $self->{ENGINE} = $params->{$key} if ($key =~ /ENGINE/i);
359             }
360              
361             # Default for tmpDir.
362             $self->{tmpDir} = File::Temp::tempdir(TMPDIR => 1, CLEANUP => 1)
363             unless($self->{tmpDir});
364              
365             # Check and save CA_CERTS
366             if($params->{CA_CERTS}) {
367             return $self->_setError(8001001,
368             $self->{gettext} ("OpenCA::OpenSSL::SMIME->set_params: Invalid parameter for CA_CERTS"))
369             unless(ref($params->{CA_CERTS}) eq 'ARRAY');
370              
371             my($data);
372             foreach(@{$params->{CA_CERTS}}) {
373             return $self->_setError(8001002,
374             $self->{gettext} ("OpenCA::OpenSSL::SMIME->set_params: Invalid array element for CA_CERTS"))
375             unless(ref($_) && $_->getPEM());
376             $self->{ca_certs}->{$_->getParsed()->{DN}} = $_;
377             $data .= $_->getPEM() . "\n";
378             }
379             $self->{ca_certs_file} = $self->_save_tmp($data);
380             }
381              
382             # Processing of input data
383             if($params->{ENTITY}) {
384              
385             return $self->_setError(8001003,
386             $self->{gettext} ("OpenCA::OpenSSL::SMIME->set_params: Invalid data source"))
387             unless(ref($params->{ENTITY}));
388              
389             $self->{entity} = $params->{ENTITY};
390             $self->{file} = undef;
391             $self->{headers_cache} = undef;
392             $self->{needs_extract} = 1;
393              
394             } elsif($params->{DATA}) {
395              
396             if(! ref($params->{DATA}) || ref($params->{DATA}) eq 'GLOB') {
397             $self->{file} = $self->_save_tmp($params->{DATA})
398             or return(undef);
399             } elsif(ref($params->{DATA}) && ref($params->{DATA}) eq 'ARRAY') {
400             $self->{file} = $self->_save_tmp(join('', @{$params->{DATA}}))
401             or return(undef);
402             } else {
403             return $self->_setError(8001003,
404             $self->{gettext} ("OpenCA::OpenSSL::SMIME->set_params: Invalid argument to DATA"));
405             }
406              
407             $self->{entity} = undef;
408             $self->{headers_cache} = undef;
409             $self->{needs_extract} = 1;
410              
411             } elsif($params->{INFILE}) {
412              
413             $self->{file} = $params->{INFILE};
414             $self->{entity} = undef;
415             $self->{headers_cache} = undef;
416             $self->{needs_extract} = 1;
417              
418             } elsif(! $self->{file} || ! $self->{entity}) {
419             return $self->_setError(8001004,
420             $self->{gettext} ("OpenCA::OpenSSL::SMIME->set_params: Missing data source"));
421             }
422              
423             return 1;
424             }
425              
426             sub get_param {
427             my $self = shift;
428             my $key = shift;
429             my $params = { @_ };
430              
431             return $params->{$key} if $params->{$key};
432             return $params->{uc $key} if $params->{uc $key};
433             return $self->{$key} if $self->{$key};
434             return $self->{uc $key} if $self->{uc $key};
435              
436             return undef;
437             }
438              
439             sub errno {
440             my $self = shift;
441             if(ref($self)) {
442             return $self->{errno};
443             } else {
444             return $errno;
445             }
446             }
447              
448             sub errval {
449             my $self = shift;
450             if(ref($self)) {
451             return $self->{errval};
452             } else {
453             return $errval;
454             }
455             }
456              
457             sub sign {
458             my($self, %params) = @_;
459             my($certfile, $keyfile, $cafile, $oldentity, $oldfile, $oldhead);
460              
461             $self->_setError(0, "");
462              
463             return($self->_setError(8010001,
464             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: Missing required parameter: CERTIFICATE"))) unless($params{CERTIFICATE});
465              
466             return($self->_setError(8010002,
467             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: Invalid required parameter: CERTIFICATE"))) unless(ref($params{CERTIFICATE}));
468              
469             return($self->_setError(8010003,
470             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: Missing required parameter: PRIVATE_KEY"))) unless($params{PRIVATE_KEY});
471              
472             # If we have no entity, get it.
473             unless($self->{entity}) {
474             $self->_sync_data() or return(undef);
475             }
476              
477             # Set up certificate and key.
478             $certfile = $self->_save_tmp($params{CERTIFICATE}->getPEM())
479             or return(undef);
480             $keyfile = $self->_save_tmp($params{PRIVATE_KEY})
481             or return(undef);
482            
483             # Generate chain of trust
484             unless($params{NO_INCLUDE_CERTS}) {
485             my(%cadata, $catext, $flag);
486             $cadata{$params{CERTIFICATE}->getParsed->{ISSUER}} = undef;
487             $flag = 1;
488             while($flag) {
489             $flag = 0;
490             foreach(keys(%cadata)) {
491             if(! $cadata{$_} && $self->{ca_certs}->{$_}) {
492             $flag = 1;
493             $cadata{$_} = $self->{ca_certs}->{$_};
494             $cadata{$cadata{$_}->getParsed->{ISSUER}} ||= undef;
495             }
496             }
497             }
498             foreach(keys(%cadata)) {
499             next unless($cadata{$_});
500             $catext .= $cadata{$_}->getPEM() . "\n";
501             }
502             $cafile = $self->_save_tmp($catext) if($catext);
503             }
504              
505             # Create a copy of the entity, the headers cache and the filename
506             if($self->{headers_cache}) {
507             $oldhead = $self->{headers_cache}->dup()
508             or return($self->_setError(8010004,
509             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: Can't duplicate headers cache for backup")));
510             }
511             $oldentity = $self->{entity}->dup()
512             or return($self->_setError(8010005,
513             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: Can't duplicate message for backup")));
514             $oldfile = $self->{file};
515              
516             # Save headers if necessary.
517             unless($params{NO_COPY_HEADERS}) {
518             $self->_save_headers() or return(undef);
519             }
520              
521             # Strip non-MIME headers and sync
522             unless($params{NO_STRIP_HEADERS}) {
523             $self->_strip_headers() or return(undef);
524             }
525             $self->_sync_data() or return(undef);
526              
527             $ENV{'pwd'} = "$params{KEY_PASSWORD}"
528             if (defined($params{KEY_PASSWORD}));
529              
530             my(@command, $outfile);
531             $outfile = $self->_save_tmp("");
532             push(@command, "smime", "-sign");
533              
534             push(@command, "-engine", $self->get_param ("ENGINE", %params),
535             "-keyform", $self->get_param ("KEYFORM", %params))
536             if($self->get_param ("ENGINE", %params));
537              
538             push(@command, "-nocerts") if($params{NO_INCLUDE_CERTS});
539              
540             if($params{DETACH}) {
541             # FIXME : find out why detached smime
542             # get corrupted in transit
543             } else {
544             push(@command, "-nodetach");
545             }
546              
547             push(@command, "-passin", "env:pwd") if($params{KEY_PASSWORD});
548             push(@command, "-certfile", $cafile) if($cafile);
549             push(@command, "-signer", $certfile,
550             "-inkey", $keyfile,
551             "-in", $self->{file},
552             "-out", $outfile);
553              
554             my($ec, $res) = $self->_exec(@command);
555              
556             foreach my $oo ( @command ) {
557             $self->_debug("SMIME COMMAND: $oo");
558             }
559              
560             delete($ENV{'pwd'}) if (defined($params{KEY_PASSWORD}));
561              
562             unless(defined($ec) && $ec == 0) {
563             # Restore
564             unless($params{NO_COPY_HEADERS}) {
565             if($oldhead) {
566             $self->{headers_cache} = $oldhead;
567             } else {
568             $self->{headers_cache} = undef;
569             }
570             }
571             unless($params{NO_STRIP_HEADERS}) {
572             $self->{entity} = $oldentity;
573             $self->{file} = $oldfile;
574             }
575             return($self->_setError(8010006,
576             $self->{gettext} ("OpenCA::OpenSSL::SMIME->sign: unknown problem signing: __ERRVAL__",
577             "__ERRVAL__", $res)));
578             }
579              
580             $self->{file} = $outfile; # Save result
581             $self->{entity} = undef;
582             $self->{needs_extract} = -1; # When signing we want the original
583             # headers
584             return 1;
585             }
586              
587             sub verify {
588             my($self, %params) = @_;
589             my($certfile, $signerfile, @command, $outfile, $oldhead);
590              
591             $self->_setError(0, "");
592             $self->_set_status(0, "");
593              
594             # Clear last signer
595             $self->{last_signer} = undef;
596             $self->{last_signer_x509} = undef;
597              
598             # Check parameters
599             unless(! $params{CERTIFICATE} || ref($params{CERTIFICATE})) {
600             return($self->_setError(8011001,
601             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: Invalid argument for CERTIFICATE")));
602             }
603              
604             unless($params{CERTIFICATE} || $params{USES_EMBEDDED_CERT}) {
605             return($self->_setError(8011002,
606             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: No certificate specified and not using embedded certificate")));
607             }
608              
609             # Set up files
610             if($params{CERTIFICATE}) {
611             $certfile = $self->_save_tmp($params{CERTIFICATE}->getPEM())
612             or return(undef);
613             }
614             $outfile = $self->_save_tmp("") or return(undef);
615             $signerfile = $self->_save_tmp("") or return(undef);
616              
617             # Sync data
618             $self->_sync_data() or return(undef);
619              
620             # Create a copy of the headers cache
621             if($self->{headers_cache}) {
622             $oldhead = $self->{headers_cache}->dup()
623             or return($self->_setError(8011003,
624             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: Can't duplicate headers cache for backup")));
625             }
626              
627             # Save headers if necessary.
628             unless($params{NO_COPY_HEADERS}) {
629             $self->_save_headers() or return(undef);
630             }
631              
632             push(@command, "smime", "-verify");
633             push(@command, "-engine", $self->get_param ("ENGINE", %params),
634             "-keyform", $self->get_param ("KEYFORM", %params))
635             if($self->get_param ("ENGINE", %params));
636             push(@command, "-nointern") unless($params{USES_EMBEDDED_CERT});
637             push(@command, "-CAfile", $self->{ca_certs_file}) if($self->{ca_certs_file});
638             push(@command, "-certfile", $certfile) if($certfile);
639             push(@command, "-in", $self->{file},
640             "-out", $outfile,
641             "-signer", $signerfile);
642              
643             my($ec, $res) = $self->_exec(@command);
644             unless(defined($ec) && $ec == 0) {
645             # Restore headers
646             unless($params{NO_COPY_HEADERS}) {
647             if($oldhead) {
648             $self->{headers_cache} = $oldhead;
649             } else {
650             $self->{headers_cache} = undef;
651             }
652             }
653             # Possible errors reported by openssl:
654             # :No such file or directory:
655             # fatal: file missing!
656             # :no content type: fatal: not even a mime stream
657             # :invalid mime type: not a smime content-type
658             # :wrong content type: pkcs7 found but it is not a signed
659             # envelope
660             # :certificate verify error:
661             # Verify error:self signed certificate in certificate chain
662             # ca cert found, but not trusted
663             # Verify error:unable to get local issuer certificate
664             # missing chain of trust
665             # Verify error:certificate has expired
666             # exactly that
667             # :digest failure:
668             # :signature failure: modified message
669             if($res =~ /:invalid mime type:|:wrong content type:/si) {
670             $self->_set_status(1100, 'message not signed');
671             } elsif($res =~ /:certificate verify error:/si) {
672             if($res =~ /Verify error:unable to get local issuer certificate/) {
673             $self->_set_status(1110, 'invalid certificate chain');
674             } elsif($res =~ /Verify error:unable to get local issuer certificate/) {
675             $self->_set_status(1111, 'no chain of trust supplied');
676             } elsif($res =~ /Verify error:certificate has expired/) {
677             $self->_set_status(1112, 'certificate has expired');
678             } elsif($res =~ /Verify error:certificate is not yet valid/) {
679             $self->_set_status(1113, 'certificate is not yet valid');
680             } else {
681             $self->_set_status(1119, 'unknown certificate problem: '. ($res =~ /Verify error:(.*)/)[0]);
682             }
683             } elsif($res =~ /:digest failure:|:signature failure:/) {
684             $self->_set_status(1105, 'corrupted message');
685             } elsif($res =~ /:no content type:/) {
686             return($self->_setError(8011004,
687             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: found invalid mime stream")));
688             } elsif($res =~ /:No such file or directory:/) {
689             return($self->_setError(8011005,
690             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: missing file")));
691             } else {
692             return($self->_setError(8011006,
693             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: unknown error: __ERRVAL__",
694             "__ERRVAL__", $res)));
695             }
696             return undef;
697             }
698              
699             # Save result
700             $self->{file} = $outfile;
701             $self->{entity} = undef;
702             # When verifying we want the encapsulated headers
703             $self->{needs_extract} = 1;
704             # Save received signer certificate.
705             $self->{last_signer} = $signerfile if(-s $signerfile);
706              
707             return 1;
708             }
709              
710             sub encrypt {
711             my($self, %params) = @_;
712             my($certfile, $oldentity, $oldfile, $oldhead, $cipher);
713              
714             $self->_setError(0, "");
715              
716             return($self->_setError(8012001,
717             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: Missing required parameter: CERTIFICATE"))) unless($params{CERTIFICATE});
718             return($self->_setError(8012002,
719             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: Invalid required parameter: CERTIFICATE"))) unless(ref($params{CERTIFICATE}));
720              
721             # Default for cipher, check correctness.
722             $cipher = lc($params{CIPHER} || $Ciphers[0]);
723             return $self->_setError(8012003,
724             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: Invalid cipher: __CIPHER__",
725             "__CIPHER__", $cipher))
726             unless(grep({ $_ eq $cipher } @Ciphers));
727              
728              
729             # If we have no entity, get it.
730             unless($self->{entity}) {
731             $self->_sync_data() or return(undef);
732             }
733              
734             # Set up certificate.
735             $certfile = $self->_save_tmp($params{CERTIFICATE}->getPEM())
736             or return(undef);
737            
738             # Create a copy of the entity, the headers cache and the filename
739             if($self->{headers_cache}) {
740             $oldhead = $self->{headers_cache}->dup()
741             or return($self->_setError(8012004,
742             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: Can't duplicate headers cache for backup")));
743             }
744             $oldentity = $self->{entity}->dup()
745             or return($self->_setError(8012005,
746             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: Can't duplicate message for backup")));
747             $oldfile = $self->{file};
748              
749             # Save headers if necessary.
750             unless($params{NO_COPY_HEADERS}) {
751             $self->_save_headers() or return(undef);
752             }
753              
754             # Strip non-MIME headers and sync
755             unless($params{NO_STRIP_HEADERS}) {
756             $self->_strip_headers() or return(undef);
757             }
758             $self->_sync_data() or return(undef);
759              
760             my(@command, $outfile);
761             $outfile = $self->_save_tmp("");
762             push(@command, "smime", "-encrypt");
763              
764             push(@command, "-engine", $self->get_param ("ENGINE", %params),
765             "-keyform", $self->get_param ("KEYFORM", %params))
766             if($self->get_param ("ENGINE", %params));
767              
768             push(@command, "-in", $self->{file},
769             "-out", $outfile,
770             "-$cipher",
771             $certfile);
772              
773             my($ec, $res) = $self->_exec(@command);
774              
775             unless(defined($ec) && $ec == 0) {
776             # Restore
777             unless($params{NO_COPY_HEADERS}) {
778             if($oldhead) {
779             $self->{headers_cache} = $oldhead;
780             } else {
781             $self->{headers_cache} = undef;
782             }
783             }
784             unless($params{NO_STRIP_HEADERS}) {
785             $self->{entity} = $oldentity;
786             $self->{file} = $oldfile;
787             }
788             if (not defined $ec)
789             {
790             $ec = $self->errno;
791             $res = $self->errval;
792             }
793             return($self->_setError(8012006,
794             $self->{gettext} ("OpenCA::OpenSSL::SMIME->encrypt: unknown problem encrypting (__ERRNO__). __ERRVAL__",
795             "__ERRNO__", $ec,
796             "__ERRVAL__", $res)));
797             return undef;
798             }
799              
800             $self->{file} = $outfile; # Save result
801             $self->{entity} = undef;
802             $self->{needs_extract} = -1; # When encrypting we want the original
803             # headers
804             return 1;
805             }
806              
807             sub decrypt {
808             my($self, %params) = @_;
809             my($certfile, $keyfile, $oldhead);
810              
811             $self->_setError(0, "");
812             $self->_set_status(0, "");
813              
814             return($self->_setError(8013001,
815             $self->{gettext} ("OpenCA::OpenSSL::SMIME->decrypt: Missing required parameter: CERTIFICATE"))) unless($params{CERTIFICATE});
816             return($self->_setError(8013002,
817             $self->{gettext} ("OpenCA::OpenSSL::SMIME->decrypt: Invalid required parameter: CERTIFICATE"))) unless(ref($params{CERTIFICATE}));
818             return($self->_setError(8013003,
819             $self->{gettext} ("OpenCA::OpenSSL::SMIME->decrypt: Missing required parameter: PRIVATE_KEY"))) unless($params{PRIVATE_KEY});
820              
821             # Set up certificate and key.
822             $certfile = $self->_save_tmp($params{CERTIFICATE}->getPEM())
823             or return(undef);
824             $keyfile = $self->_save_tmp($params{PRIVATE_KEY})
825             or return(undef);
826              
827             # If we have no entity, get it.
828             unless($self->{entity}) {
829             $self->_sync_data() or return(undef);
830             }
831              
832             # Sync data
833             $self->_sync_data() or return(undef);
834              
835             # Create a copy of the headers cache
836             if($self->{headers_cache}) {
837             $oldhead = $self->{headers_cache}->dup()
838             or return($self->_setError(8013004,
839             $self->{gettext} ("OpenCA::OpenSSL::SMIME->decrypt: Can't duplicate headers cache for backup")));
840             }
841              
842             # Save headers if necessary.
843             unless($params{NO_COPY_HEADERS}) {
844             $self->_save_headers() or return(undef);
845             }
846              
847             $ENV{'pwd'} = "$params{KEY_PASSWORD}"
848             if (defined($params{KEY_PASSWORD}));
849              
850             my(@command, $outfile);
851             $outfile = $self->_save_tmp("");
852             push(@command, "smime", "-decrypt");
853              
854             push(@command, "-engine", $self->get_param ("ENGINE", %params),
855             "-keyform", $self->get_param ("KEYFORM", %params))
856             if($self->get_param ("ENGINE", %params));
857              
858             push(@command, "-passin", "env:pwd") if($params{KEY_PASSWORD});
859             push(@command, "-recip", $certfile,
860             "-inkey", $keyfile,
861             "-in", $self->{file},
862             "-out", $outfile);
863              
864             my($ec, $res) = $self->_exec(@command);
865             unless(defined($ec) && $ec == 0) {
866             # Restore headers
867             unless($params{NO_COPY_HEADERS}) {
868             if($oldhead) {
869             $self->{headers_cache} = $oldhead;
870             } else {
871             $self->{headers_cache} = undef;
872             }
873             }
874             # Possible errors reported by openssl:
875             # :No such file or directory:
876             # fatal: file missing!
877             # :no content type: fatal: not even a mime stream
878             # :invalid mime type: not a smime content-type
879             # :wrong content type: pkcs7 found but it is not a signed
880             # envelope
881             # :no recipient matches certificate:
882             # message not for us
883              
884             if($res =~ /:invalid mime type:|:wrong content type:/si) {
885             $self->_set_status(1300, 'message not encrypted');
886             } elsif($res =~ /:no recipient matches certificate:/) {
887             $self->_set_status(1301, 'this certificate can\'t decrypt this message');
888             } elsif($res =~ /:no content type:/) {
889             return($self->_setError(8011004,
890             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: found invalid mime stream")));
891             } elsif($res =~ /:No such file or directory:/) {
892             return($self->_setError(8011005,
893             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: missing file")));
894             } else {
895             return($self->_setError(8011006,
896             $self->{gettext} ("OpenCA::OpenSSL::SMIME->verify: unknown error: __ERRVAL__",
897             "__ERRVAL__", $res)));
898             }
899             return undef;
900             }
901              
902             # Save result
903             $self->{file} = $outfile;
904             $self->{entity} = undef;
905             # When decrypting we want the encapsulated headers
906             $self->{needs_extract} = 1;
907              
908             return 1;
909             }
910              
911             # Restores saved headers, in a duplicate of the entity. Returns new entity
912             sub get_mime {
913             my($self) = shift;
914             my($newe, $newf, $tfh, $tag);
915              
916             $self->_setError(0, "");
917              
918             # If we have no entity, get it.
919             unless($self->{entity}) {
920             $self->_sync_data() or return(undef);
921             }
922              
923             # Duplicate entity
924             $newe = $self->{entity}->dup() or return(undef);
925              
926             if ($self->{headers_cache}) {
927             # Do we need to extract any header?
928             unless($self->{needs_extract}) {
929             $newe->head($self->{headers_cache}); # replace it
930             } else {
931             # Restore headers
932             foreach($self->{headers_cache}->tags()) {
933             $tag = $_;
934             if($self->{needs_extract} < 0) {
935             # priority: old headers, delete new ones
936             $newe->head()->delete($tag);
937             } else {
938             # priority: new headers, skip if
939             # already here
940             next if($newe->head()->count($tag));
941             }
942             # copy
943             foreach($self->{headers_cache}->get_all($tag)) {
944             $newe->head()->add($tag, $_);
945             }
946             }
947             }
948             }
949              
950             # // In scalar context, we're done
951             return($newe) unless(wantarray);
952              
953             # Save new file
954             ($tfh, $newf) = $self->_save_tmp("") or return(undef);
955             $self->{entity}->print($tfh)
956             or return $self->_setError(8014001,
957             $self->{gettext} ("OpenCA::OpenSSL::SMIME->get_mime: Error saving MIME::Entity into tempfile"));
958             $tfh->close
959             or return $self->_setError(8014002,
960             $self->{gettext} ("OpenCA::OpenSSL::SMIME->get_mime: Error closing tempfile"));
961             return($newe, $newf);
962             }
963              
964             # // Returns last seen signer's certificate in verify operation
965             # FIXME: should know how to handle multiple signers
966             sub get_last_signer {
967             my($self) = shift;
968             my($crt);
969              
970             return(undef) unless($self->{last_signer} && -s $self->{last_signer});
971              
972             $self->{last_signer_x509} = OpenCA::X509->new(
973             SHELL => $self->{backend},
974             GETTEXT => $self->{gettext},
975             INFILE => $self->{last_signer})
976             unless($self->{last_signer_x509});
977             return($self->{last_signer_x509});
978             }
979              
980             # Return last operation status string, when unsuccessful
981             sub status {
982             my($self) = shift;
983              
984             return($self->{status}->[1]);
985             }
986              
987             # Return last operation status code, when unsuccessful
988             sub status_code {
989             my($self) = shift;
990              
991             return($self->{status}->[0]);
992             }
993              
994             #------------------------------
995             1;
996              
997             __END__