1## OpenCA::PKCS7
2##
3## Copyright (C) 1998-1999 Massimiliano Pala (madwolf@openca.org)
4## All rights reserved.
5##
6## This library is free for commercial and non-commercial use as long as
7## the following conditions are aheared to.  The following conditions
8## apply to all code found in this distribution, be it the RC4, RSA,
9## lhash, DES, etc., code; not just the SSL code.  The documentation
10## included with this distribution is covered by the same copyright terms
11##
12## // Copyright remains Massimiliano Pala's, and as such any Copyright notices
13## in the code are not to be removed.
14## If this package is used in a product, Massimiliano Pala should be given
15## attribution as the author of the parts of the library used.
16## This can be in the form of a textual message at program startup or
17## in documentation (online or textual) provided with the package.
18##
19## Redistribution and use in source and binary forms, with or without
20## modification, are permitted provided that the following conditions
21## are met:
22## 1. Redistributions of source code must retain the copyright
23##    notice, this list of conditions and the following disclaimer.
24## 2. Redistributions in binary form must reproduce the above copyright
25##    notice, this list of conditions and the following disclaimer in the
26##    documentation and/or other materials provided with the distribution.
27## 3. All advertising materials mentioning features or use of this software
28##    must display the following acknowledgement:
29## //   "This product includes OpenCA software written by Massimiliano Pala
30## //    (madwolf@openca.org) and the OpenCA Group (www.openca.org)"
31## 4. If you include any Windows specific code (or a derivative thereof) from
32##    some directory (application code) you must include an acknowledgement:
33##    "This product includes OpenCA software (www.openca.org)"
34##
35## THIS SOFTWARE IS PROVIDED BY OPENCA DEVELOPERS ``AS IS'' AND
36## ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38## ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
39## FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
40## DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
41## OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42## HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
43## LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
44## OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45## SUCH DAMAGE.
46##
47## The licence and distribution terms for any publically available version or
48## derivative of this code cannot be changed.  i.e. this code cannot simply be
49## copied and put under another distribution licence
50## [including the GNU Public Licence.]
51##
52
53## // the module's errorcode is 79
54##
55## functions
56##
57## new			11
58## initSignature	12
59## getParsed		21
60## getSigner		22
61## verifyChain		31
62## parseDepth		32
63## getSignature		23
64
65use strict;
66use Carp qw(cluck);
67use X500::DN;
68
69package OpenCA::PKCS7;
70
71our ($errno, $errval);
72
73($OpenCA::PKCS7::VERSION = '$Revision: 1.1.1.1 $' )=~ s/(?:^.*: (\d+))|(?:\s+\$$)/defined $1?"0\.9":""/eg;
74
75my %params = (
76	 inFile => undef,
77	 signature => undef,
78	 dataFile => undef,
79	 caCert => undef,
80	 caDir => undef,
81	 parsed => undef,
82	 context => undef,
83	 backend => undef,
84	 status => undef,
85	 statusStack => [],
86	 nochain => undef,
87         opaquesig => undef,
88         ignoreErrors => undef,
89         ignoreErrorsHash => undef,
90);
91
92sub setError {
93	my $self = shift;
94
95	if (scalar (@_) == 4) {
96		my $keys = { @_ };
97		$self->{errno}  = $keys->{ERRNO};
98		$self->{errval} = $keys->{ERRVAL};
99	} else {
100		$self->{errno}  = $_[0];
101		$self->{errval} = $_[1];
102	}
103	$errno  = $self->{errno};
104	$errval = $self->{errval};
105
106	## support for: return $self->setError (1234, "Something fails.") if (not $xyz);
107	return undef;
108}
109
110sub errno
111{
112    my $self = shift;
113    return $self->{errno};
114}
115
116sub errval
117{
118    my $self = shift;
119    return $self->{errval};
120}
121
122## Create an instance of the Class
123sub new {
124	my $that = shift;
125	my $class = ref($that) || $that;
126
127        my $self = {
128		%params,
129	};
130
131        bless $self, $class;
132
133	my $keys = { @_ };
134	my $tmp;
135
136	$self->{gettext}    = $keys->{GETTEXT};
137	return $self->setError (7911020, "OpenCA::PKCS7->new: The translation function must be specified.")
138            if (not $self->{gettext});
139
140        $self->{caCert}     = $keys->{CA_CERT};
141        $self->{caDir}      = $keys->{CA_DIR};
142
143        $self->{dataFile}   = $keys->{DATAFILE};
144        $self->{data}       = $keys->{DATA};
145
146	$self->{inFile}	    = $keys->{INFILE};
147        $self->{signature}  = $keys->{SIGNATURE};
148
149	$self->{backend}    = $keys->{SHELL};
150
151	$self->{nochain}    = $keys->{NOCHAIN};
152	$self->{opaquesig}  = $keys->{OPAQUESIGNATURE};
153
154	$self->{ignoreErrors} = $keys->{IGNOREERRORS};
155
156
157	# you can explicitly exclude certain error codes from being
158	# returned. to use this features, call the constructor with the
159	# error(s) to exclude:
160	#
161	# my $sig = new OpenCA::PKCS7(..., IGNOREERRORS => 26, ...)
162	#
163	# or to exclude more than one error:
164	# my $sig = new OpenCA::PKCS7(..., IGNOREERRORS => [26, 18], ...)
165	#
166
167	# convert to arrayref if a scalar was passed in
168	if (defined $self->{ignoreErrors} and not ref($self->{ignoreErrors})) {
169	    $self->{ignoreErrors} = [ $self->{ignoreErrors} ];
170	}
171	$self->{ignoreErrors} = [] unless defined $self->{ignoreErrors};
172
173	# nochain: ignore errors on selfsigned certs and incorrect key usage
174	push(@{$self->{ignoreErrors}}, (18, 26)) if (exists $self->{nochain} and $self->{nochain});
175
176	# create hash from the list of errors to ignore
177	$self->{ignoreErrorsHash} = { map { $_, 1 } @{$self->{ignoreErrors}}};
178
179
180	if( ($self->{inFile}) and ( -e "$self->{inFile}") ) {
181		$self->{signature} = "";
182
183		open(FD, "<$self->{inFile}" )
184			or return $self->setError (7911021,
185                                      $self->{gettext} ("OpenCA::PKCS7->new: Cannot open infile __FILENAME__ for reading.",
186                                                        "__FILENAME__", $self->{inFile}));
187		while ( $tmp = <FD> ) {
188			$self->{signature} .= $tmp;
189		}
190		close(FD);
191	};
192
193	if( ($self->{dataFile}) and ( -e "$self->{dataFile}") ) {
194		$self->{data} = "";
195
196		open(FD, "<$self->{dataFile}" )
197			or return $self->setError (7911023,
198                                      $self->{gettext} ("OpenCA::PKCS7->new: Cannot open datafile __FILENAME__ for reading.",
199                                                        "__FILENAME__", $self->{dataFile}));
200		while ( $tmp = <FD> ) {
201			$self->{data} .= $tmp;
202		}
203		close(FD);
204	};
205
206        if (not $self->{opaquesig} and not $self->{data} and $self->{inFile} and ( -e "$self->{inFile}")) {
207		$self->{data} = "";
208
209		open(FD, "<$self->{inFile}" )
210			or return $self->setError (7911025,
211                                      $self->{gettext} ("OpenCA::PKCS7->new: Cannot open infile __FILENAME__ for reading.",
212                                                        "__FILENAME__", $self->{inFile}));
213		while ( $tmp = <FD> ) {
214			$self->{data} .= $tmp;
215		}
216		close(FD);
217	};
218
219	return $self->setError (7911031,
220                   $self->{gettext} ("OpenCA::PKCS7->new: Cannot initialize signature (__ERRNO__). __ERRVAL__",
221                                     "__ERRNO__", $self->errno,
222                                     "__ERRVAL__", $self->errval))
223		if (not $self->initSignature() );
224
225        return $self;
226}
227
228sub initSignature {
229	my $self = shift;
230	my $keys = { @_ };
231	my $tmp;
232
233	return $self->setError (7912011,
234                   $self->{gettext} ("OpenCA::PKCS7->initSignature: No signature specified."))
235		if (not $self->{signature});
236	return $self->setError (7912012,
237                   $self->{gettext} ("OpenCA::PKCS7->initSignature: No data specified."))
238		if (not $self->{data} and (not $self->{opaquesig}));
239
240	if( $self->getParsed() ) {
241		return 1;
242	} else {
243		return $self->setError (7912021,
244                           $self->{gettext} ("OpenCA::PKCS7->initSignature: Cannot parse signature (__ERRNO__). __ERRVAL__",
245                                             "__ERRNO__", $self->errno,
246                                             "__ERRVAL__", $self->errval))
247	}
248}
249
250sub getParsed {
251	my $self = shift;
252	my $keys = { @_ };
253
254	my ( $ret, $tmp );
255
256	$tmp = $self->{backend}->verify( SIGNATURE=>$self->{signature},
257			OPAQUESIGNATURE => $self->{opaquesig},
258			## old: DATA_FILE=>$self->{dataFile},
259			DATA    => $self->{data},
260			CA_CERT => $self->{caCert},
261			CA_DIR  => $self->{caDir},
262			VERBOSE => 1);
263	print "OpenCA::PKCS7->getParsed: OpenCA::OpenSSL->verify returns:<br>\n".
264	      $tmp."<br>\n"
265		if ($self->{DEBUG});
266
267	## why should verify the signature twice?
268	#$tmp = $self->{backend}->verify( SIGNATURE=>$self->{signature},
269	#		OPAQUESIGNATURE => $self->{opaquesig},
270	#		DATA_FILE=>$self->{dataFile},
271	#		NOCHAIN=>1,
272	#		VERBOSE=>1 );
273
274	if (not $tmp) {
275	    if (not $self->{ignoreErrorsHash}->{$OpenCA::OpenSSL::errno}) {
276		$self->setstatus($OpenCA::OpenSSL::errno);
277
278		return $self->setError (7921021,
279                           $self->{gettext} ("OpenCA::PKCS7->getParsed: The crypto-backend cannot verify the signature (__ERRNO__). __ERRVAL__",
280                                             "__ERRNO__", $OpenCA::OpenSSL::errno,
281                                             "__ERRVAL__", $OpenCA::OpenSSL::errval));
282	    }
283	}
284
285
286	if ( not $ret = $self->parseDepth( DEPTH=>"0", DATA=>$tmp ) ) {
287	    print STDERR "Error on output of openca-sv.\n$tmp\n";
288	    if (not $self->{ignoreErrorsHash}->{$OpenCA::OpenSSL::errno}) {
289		$self->setstatus($OpenCA::OpenSSL::errno);
290
291		return $self->setError (7921031,
292                           $self->{gettext} ("OpenCA::PKCS7->getParsed: Cannot parse the signer (__ERRNO__). __ERRVAL__",
293                                             "__ERRNO__", $self->errno,
294                                             "__ERRVAL__", $self->errval));
295	    }
296	}
297
298	$self->{parsed}->{SIGNER} = $ret->{0};
299
300	if ( ( $tmp ) and ( $ret = $self->parseDepth( DATA=>$tmp )) ) {
301		$self->{parsed}->{CHAIN} = $ret;
302	}
303
304	$self->{parsed}->{SIGNER}->{CERTIFICATE} =
305		$self->{backend}->pkcs7Certs( PKCS7=>$self->{signature});
306	if (not defined $self->{parsed}->{SIGNER}->{CERTIFICATE})
307        {
308		return $self->setError (7921033,
309                           $self->{gettext} ("OpenCA::PKCS7->getParsed: The extraction of the certificates from the signature failed (__ERRNO__). __ERRVAL__",
310                                             "__ERRNO__", $self->{backend}->errno,
311                                             "__ERRVAL__", $self->{backend}->errval));
312	}
313
314	$self->{parsed}->{SIGNATURE} = $self->{signature};
315
316	return $self->{parsed};
317}
318
319# returns the last status set by the module
320# Options:
321# FORMAT => ARRAY:
322#   return arrayref containing an array of all error codes encountered,
323#   individual error codes may appear more than once
324# FORMAT => HASH:
325#   return hashref containing all error codes encountered (key: errorcode,
326#   value: 1)
327# FORMAT not set or other value: return last error
328sub status {
329        my $self = shift;
330	my $keys = { @_ };
331
332	if (exists $keys->{FORMAT} and $keys->{FORMAT} eq "ARRAY") {
333	    return $self->{statusStack};
334	} elsif (exists $keys->{FORMAT} and $keys->{FORMAT} eq "HASH") {
335	    return { map { $_, 1 } @{$self->{statusStack}}};
336	}
337
338        return $self->{status};
339}
340
341
342# add one single error to the error stack
343sub setstatus {
344    my $self = shift;
345    my $err = shift;
346
347    $self->{status} = $err;
348    if (ref($self->{statusStack}) eq "ARRAY") {
349	push(@{$self->{statusStack}}, $err);
350    } else {
351	carp("self->{statusStack} is not an Arrayref");
352    }
353}
354
355
356sub getSigner {
357	my $self = shift;
358
359	my $keys = { @_ };
360	my ( $tmp, $ret );
361
362	return $self->setError (7922011,
363                   $self->{gettext} ("OpenCA::PKCS7->getSigner: The signature was not parsed."))
364		if( not $self->{parsed} );
365	return $self->{parsed}->{SIGNER};
366}
367
368sub verifyChain {
369	my $self = shift;
370
371	my $keys = { @_ };
372	my ( $tmp, $ret );
373
374	#unnecessary because the signature was already loaded
375	#if ( $self->{inFile} ) {
376	#	$tmp=$self->{backend}->verify( SIGNATURE_FILE => $self->{inFile},
377        #                                      OPAQUESIGNATURE => $self->{opaquesig},
378	#				       DATA           => $self->{data},
379	#				       CA_CERT        => $self->{caCert},
380	#				       CA_DIR         => $self->{caDir},
381	#				       VERBOSE        => 1 );
382	#} else {
383		$tmp=$self->{backend}->verify( SIGNATURE => $self->{signature},
384					       OPAQUESIGNATURE => $self->{opaquesig},
385					       DATA      => $self->{data},
386					       CA_CERT   => $self->{caCert},
387					       CA_DIR    => $self->{caDir},
388					       VERBOSE   => 1 );
389	#};
390
391	return $self->setError (7931021,
392                   $self->{gettext} ("OpenCA::PKCS7->verifyChain: The crypto-backend fails (__ERRNO__). __ERRVAL__",
393				     "__ERRNO__", $OpenCA::OpenSSL::errno,
394                                     "__ERRVAL__", $OpenCA::OpenSSL::errval))
395		if (not $tmp);
396
397	## Returns if signature is not valid (verify returned an error)
398	return $self->setError (7931022,
399                   $self->{gettext} ("OpenCA::PKCS7->verifyChain: The crypto-backend fails."))
400		if( $? != 0 );
401
402	if ( not $ret = $self->parseDepth( DEPTH=>"0", DATA=>$tmp ) ) {
403		print STDERR "Error on output of openca-sv.\n$tmp\n";
404		return $self->setError (7931031,
405                           $self->{gettext} ("OpenCA::PKCS7->verifyChain: Cannot parse the signer (__ERRNO__). __ERRVAL__",
406                                             "__ERRNO__", $self->errno,
407                                             "__ERRVAL__", $self->errval));
408	}
409
410	return $ret;
411}
412
413sub parseDepth {
414
415	my $self = shift;
416	my $keys = { @_ };
417
418	my $depth = $keys->{DEPTH};
419	my $data  = $keys->{DATA};
420	my @dnList = ();
421	my @ouList = ();
422
423	my ( $serial, $dn, $email, $cn, @ou, $o, $c );
424	my ( $currentDepth, $subject, $tmp, $line, $ret );
425
426	return $self->setError (7932011,
427                   $self->{gettext} ("OpenCA::PKCS7->parseDepth: No data specified."))
428		if (not $data);
429
430	my @lines = split ( /(\n|\r)/ , $data );
431
432	while( $line = shift @lines ) {
433
434		print "OpenCA::PKCS7->parseDepth: line: $line<br>\n"
435			if ($self->{DEBUG});
436		if ($line =~ /^\s*error:20:/i and
437		    not $self->{ignoreErrorsHash}->{20}) {
438			$self->setError (7932021,
439                            $self->{gettext} ("OpenCA::PKCS7->parseDepth: The chain is not complete."));
440			$self->setstatus(20);
441		} elsif ($line =~ /^\s*error:18:/i and
442		    not $self->{ignoreErrorsHash}->{18}) {
443		        $self->setError (7932023,
444					 $self->{gettext} ("OpenCA::PKCS7->parseDepth: Selfsigned certificate at depth 0."));
445			$self->setstatus(18);
446		} elsif ($line =~ /^\s*error:26:/i and
447		    not $self->{ignoreErrorsHash}->{26}) {
448		        ## OpenCA uses CA certs to sign the role
449		        $self->setError (7932025,
450					 $self->{gettext} ("OpenCA::PKCS7->parseDepth: One of the certificates was used for a not allowed operation. Usually a CA certificate signed data or a normal certificate acts as a CA. (__ERRVAL__)",
451							   "__ERRVAL__", $line));
452			$self->setstatus(26);
453		} elsif ($line =~ /^\s*error:([^:]*):/i and
454		    not $self->{ignoreErrorsHash}->{$1}) {
455			$self->setError (7932039,
456                            $self->{gettext} ("OpenCA::PKCS7->parseDepth: There is a problem with the verification of the chain. (__ERRVAL__)",
457                                              "__ERRVAL__", $line));
458			$self->setstatus($1);
459		}
460
461		next if( $line !~ /^depth/i );
462
463		( $currentDepth, $serial, $dn ) =
464			( $line =~ /depth:([\d]+) serial:([a-fA-F\d]+) subject:(.*)/ );
465		use Math::BigInt;
466		my $serial_obj = Math::BigInt->new ('0x'.$serial);
467		$ret->{$currentDepth}->{SERIAL} = $serial_obj->bstr();
468		$ret->{$currentDepth}->{DN} = $dn;
469		if ($self->{DEBUG})
470		{
471			print "OpenCA::PKCS7->parseDepth: depth: $currentDepth<br>\n";
472			print "OpenCA::PKCS7->parseDepth: serial:".
473			      $ret->{$currentDepth}->{SERIAL}."<br>\n";
474			print "OpenCA::PKCS7->parseDepth: dn:".
475			      $ret->{$currentDepth}->{DN}."<br>\n";
476		}
477
478		## Split the Subject into separate fields
479		@dnList = split( /[\,\/]+/, $dn );
480		@ouList = ();
481
482		my $tmpOU;
483
484		## load the differnt parts of the DN into DN_HASH
485		print "OpenCA::PKCS7->parseDepth: DN: ".$dn."<br>\n" if ($self->{DEBUG});
486		my $x500_dn = X500::DN->ParseRFC2253 ($dn);
487		if (not $x500_dn) {
488        		print "OpenCA::PKCS7->parseDepth: X500::DN failed<br>\n" if ($self->{DEBUG});
489			return $self->setError (7932081,
490                                   $self->{gettext} ("OpenCA::PKCS7->parseDepth: X500::DN failed."));
491			return undef;
492		}
493		my $rdn;
494		foreach $rdn ($x500_dn->getRDNs()) {
495			next if ($rdn->isMultivalued());
496			my @attr_types = $rdn->getAttributeTypes();
497			my $type  = $attr_types[0];
498			my $value = $rdn->getAttributeValue ($type);
499			push (@{$ret->{$currentDepth}->{DN_HASH}->{uc($type)}}, $value);
500			print "OpenCA::PKCS7->parseDepth: DN_HASH: $type=$value<br>\n" if ($self->{DEBUG});
501		}
502
503	}
504
505	return $ret;
506}
507
508sub getSignature {
509	my $self = shift;
510
511	return $self->setError (7923011,
512                   $self->{gettext} ("OpenCA::PKCS7->getSignature: There is no signature present."))
513		if( not $self->{signature} );
514	return $self->{signature};
515}
516
517sub getSignerCert {
518	my $self = shift;
519
520	my $keys = { @_ };
521
522
523}
524
525# Autoload methods go after =cut, and are processed by the autosplit program.
526
5271;
528