## OpenCA::PKCS7
##
## Copyright (C) 1998-1999 Massimiliano Pala (madwolf@openca.org)
## All rights reserved.
##
## This library is free for commercial and non-commercial use as long as
## the following conditions are aheared to.  The following conditions
## apply to all code found in this distribution, be it the RC4, RSA,
## lhash, DES, etc., code; not just the SSL code.  The documentation
## included with this distribution is covered by the same copyright terms
## 
## Copyright remains Massimiliano Pala's, and as such any Copyright notices
## in the code are not to be removed.
## If this package is used in a product, Massimiliano Pala should be given
## attribution as the author of the parts of the library used.
## This can be in the form of a textual message at program startup or
## in documentation (online or textual) provided with the package.
## 
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
## 1. Redistributions of source code must retain the copyright
##    notice, this list of conditions and the following disclaimer.
## 2. Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
## 3. All advertising materials mentioning features or use of this software
##    must display the following acknowledgement:
##    "This product includes OpenCA software written by Massimiliano Pala
##     (madwolf@openca.org) and the OpenCA Group (www.openca.org)"
## 4. If you include any Windows specific code (or a derivative thereof) from 
##    some directory (application code) you must include an acknowledgement:
##    "This product includes OpenCA software (www.openca.org)"
## 
## THIS SOFTWARE IS PROVIDED BY OPENCA DEVELOPERS ``AS IS'' AND
## ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
## ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
## FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
## DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
## OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
## HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
## LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
## OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
## SUCH DAMAGE.
## 
## The licence and distribution terms for any publically available version or
## derivative of this code cannot be changed.  i.e. this code cannot simply be
## copied and put under another distribution licence
## [including the GNU Public Licence.]
##

## the module's errorcode is 79
##
## functions
##
## new			11
## initSignature	12
## getParsed		21
## getSigner		22
## verifyChain		31
## parseDepth		32
## getSignature		23

use strict;
use X500::DN;

package OpenCA::PKCS7;

our ($errno, $errval);

($OpenCA::PKCS7::VERSION = '$Revision: 1.13 $' )=~ s/(?:^.*: (\d+))|(?:\s+\$$)/defined $1?"0\.9":""/eg;

my %params = (
	 inFile => undef,
	 signature => undef,
	 dataFile => undef,
	 caCert => undef,
	 caDir => undef,
	 parsed => undef,
	 context => undef,
	 backend => undef,
	 status => undef,
	 nochain => undef,
);

sub setError {
	my $self = shift;

	if (scalar (@_) == 4) {
		my $keys = { @_ };
		$errval = $keys->{ERRVAL};
		$errno  = $keys->{ERRNO};
	} else {
		$errno  = $_[0];
		$errval = $_[1];
	}

	## support for: return $self->setError (1234, "Something fails.") if (not $xyz);
	return undef;
}

## Create an instance of the Class
sub new {
	my $that = shift;
	my $class = ref($that) || $that;

        my $self = {
		%params,
	};

        bless $self, $class;

	my $keys = { @_ };
	my $tmp;

        $self->{caCert}     = $keys->{CA_CERT};
        $self->{caDir}      = $keys->{CA_DIR};

        $self->{dataFile}   = $keys->{DATAFILE};
        $self->{data}       = $keys->{DATA};

	$self->{inFile}	    = $keys->{INFILE};
        $self->{signature}  = $keys->{SIGNATURE};

	$self->{backend}    = $keys->{SHELL};

	$self->{nochain}    = $keys->{NOCHAIN};

	if( ($self->{inFile}) and ( -e "$self->{inFile}") ) {
		$self->{signature} = "";

		open(FD, "<$self->{inFile}" )
			or return $self->setError (7911021, "OpenCA::PKCS7->new: Cannot open infile ".
						$self->{inFile}." for reading.");
		while ( $tmp = <FD> ) {
			$self->{signature} .= $tmp;
		}
		close(FD);
	};

	if( ($self->{dataFile}) and ( -e "$self->{dataFile}") ) {
		$self->{data} = "";

		open(FD, "<$self->{dataFile}" )
			or return $self->setError (7911023, "OpenCA::PKCS7->new: Cannot open datafile ".
						$self->{dataFile}." for reading.");
		while ( $tmp = <FD> ) {
			$self->{data} .= $tmp;
		}
		close(FD);
	};

        if (not $self->{data} and $self->{inFile} and ( -e "$self->{inFile}")) {
		$self->{data} = "";

		open(FD, "<$self->{inFile}" )
			or return $self->setError (7911025, "OpenCA::PKCS7->new: Cannot open infile ".
						$self->{inFile}." for reading.");
		while ( $tmp = <FD> ) {
			$self->{data} .= $tmp;
		}
		close(FD);
	};

	return $self->setError (7911031, "OpenCA::PKCS7->new: Cannot initialize signature ($errno)\n$errval")
		if (not $self->initSignature() );

        return $self;
}

sub initSignature {
	my $self = shift;
	my $keys = { @_ };
	my $tmp;

	return $self->setError (7912011, "OpenCA::PKCS7->initSignature: No signature specified.")
		if (not $self->{signature});
	return $self->setError (7912012, "OpenCA::PKCS7->initSignature: No data specified.")
		if (not $self->{data});

	if( $self->getParsed() ) {
		return 1;
	} else {
		return $self->setError (7912021, "OpenCA::PKCS7->initSignature: Cannot parse signature ($errno)\n$errval.")
	}
}

sub getParsed {
	my $self = shift;
	my $keys = { @_ };

	my ( $ret, $tmp );

	$tmp = $self->{backend}->verify( SIGNATURE=>$self->{signature},
			## old: DATA_FILE=>$self->{dataFile},
			DATA    => $self->{data},
			CA_CERT => $self->{caCert},
			CA_DIR  => $self->{caDir},
			VERBOSE => 1,
			NOCHAIN => $self->{nochain} );

	## why should verify the signature twice?
	#$tmp = $self->{backend}->verify( SIGNATURE=>$self->{signature},
	#		DATA_FILE=>$self->{dataFile},
	#		NOCHAIN=>1,
	#		VERBOSE=>1 );

	if (not $tmp) {
		$self->{status} = $OpenCA::OpenSSL::errno;
		return $self->setError (7921021, "OpenCA::PKCS7->getParsed: The crypto-backend cannot verify the signature ".
				"(".$OpenCA::OpenSSL::errno.")\n".$OpenCA::OpenSSL::errval);
	}


	if ( not $ret = $self->parseDepth( DEPTH=>"0", DATA=>$tmp ) ) {
		$self->{status} = $OpenCA::OpenSSL::errno;
		return $self->setError (7921031, "OpenCA::PKCS7->getParsed: Cannot parse the signer ($errno)\n$errval");
	}

	$self->{parsed}->{SIGNER} = $ret->{0};

	if ( ( $tmp ) and ( $ret = $self->parseDepth( DATA=>$tmp )) ) {
		$self->{parsed}->{CHAIN} = $ret;
	}

	$self->{parsed}->{SIGNER}->{CERTIFICATE} = 
		$self->{backend}->pkcs7Certs( PKCS7=>$self->{signature});

	$self->{parsed}->{SIGNATURE} = $self->{signature};

	return $self->{parsed};
}

sub status {
        my $self = shift;

        return $self->{status};
}

sub getSigner {
	my $self = shift;

	my $keys = { @_ };
	my ( $tmp, $ret );

	return $self->setError (7922011, "OpenCA::PKCS7->getSigner: The signature was not parsed.")
		if( not $self->{parsed} );
	return $self->{parsed}->{SIGNER};
}

sub verifyChain {
	my $self = shift;

	my $keys = { @_ };
	my ( $tmp, $ret );

	#unnecessary because the signature was already loaded
	#if ( $self->{inFile} ) {
	#	$tmp=$self->{backend}->verify( SIGNATURE_FILE => $self->{inFile},
	#				       DATA           => $self->{data},
	#				       CA_CERT        => $self->{caCert},
	#				       CA_DIR         => $self->{caDir},
	#				       VERBOSE        => 1 );
	#} else {
		$tmp=$self->{backend}->verify( SIGNATURE => $self->{signature},
					       DATA      => $self->{data},
					       CA_CERT   => $self->{caCert},
					       CA_DIR    => $self->{caDir},
					       VERBOSE   => 1 );
	#};

	return $self->setError (7931021, "OpenCA::PKCS7->verifyChain: The crypto-backend fails ".
				"(".$OpenCA::OpenSSL::errno.")\n".$OpenCA::OpenSSL::errval)
		if (not $tmp);

	## Returns if signature is not valid (verify returned an error)
	return $self->setError (7931022, "OpenCA::PKCS7->verifyChain: The crypto-backend fails.")
		if( $? != 0 );

	if ( not $ret = $self->parseDepth( DEPTH=>"0", DATA=>$tmp ) ) {
		return $self->setError (7931031, "OpenCA::PKCS7->verifyChain: Cannot parse the signer ($errno)\n$errval");
	}

	return $ret;
}

sub parseDepth {

	my $self = shift;
	my $keys = { @_ };

	my $depth = $keys->{DEPTH};
	my $data  = $keys->{DATA};
	my @dnList = ();
	my @ouList = ();

	my ( $serial, $dn, $email, $cn, @ou, $o, $c );
	my ( $currentDepth, $subject, $tmp, $line, $ret );
	
	return $self->setError (7932011, "OpenCA::PKCS7->parseDepth: No data specified.")
		if (not $data);

	my @lines = split ( /(\n|\r)/ , $data );

	while( $line = shift @lines ) {

		if ($line =~ /^\s*error:20:/i) {
			$self->setError (7932021, "OpenCA::PKCS7->parseDepth: The chain is not complete.");
			$self->{status} = 20;
		} elsif ($line =~ /^\s*error:18:/i) {
			if (!$self->{nochain}) {
				$self->setError (7932023, "OpenCA::PKCS7->parseDepth: Selfsigned certificate at depth 0.");
				$self->{status} = 18;
			}
		} elsif ($line =~ /^\s*error:/i) {
			$self->setError (7932039, "OpenCA::PKCS7->parseDepth: ".
					"There is a problem with the verification of the chain ($line).");
			($self->{status}) = ( $line =~ /^\s*error:([^:]*):/ );
		}

		next if( $line != /^depth/i );

		( $currentDepth, $serial, $dn ) =
			( $line =~ /depth:([\d]+) serial:([a-f\d]+) subject:(.*)/ );
		$ret->{$currentDepth}->{SERIAL} = hex ($serial) ;
		$ret->{$currentDepth}->{DN} = $dn;

		## Split the Subject into separate fields
		@dnList = split( /[\,\/]+/, $dn );
		@ouList = ();

		my $tmpOU;

		## load the differnt parts of the DN into DN_HASH
		print "OpenCA::PKCS7->parseDepth: DN: ".$dn."<br>\n" if ($self->{DEBUG});
		my $x500_dn = X500::DN->ParseRFC2253 ($dn);
		if (not $x500_dn) {
        		print "OpenCA::PKCS7->parseDepth: X500::DN failed<br>\n" if ($self->{DEBUG});
			return $self->setError (7932081, "OpenCA::PKCS7->parseDepth: X500::DN failed.");
			return undef;
		}
		my $rdn;
		foreach $rdn ($x500_dn->getRDNs()) {
			next if ($rdn->isMultivalued());
			my @attr_types = $rdn->getAttributeTypes();
			my $type  = $attr_types[0];
			my $value = $rdn->getAttributeValue ($type);
			push (@{$ret->{$currentDepth}->{DN_HASH}->{uc($type)}}, $value);
			print "OpenCA::PKCS7->parseDepth: DN_HASH: $type=$value<br>\n" if ($self->{DEBUG});
		}

	}

	return $ret;
}

sub getSignature {
	my $self = shift;

	return $self->setError (7923011, "OpenCA::PKCS7->getSignature: There is no signature present.")
		if( not $self->{signature} );
	return $self->{signature};
}

sub getSignerCert {
	my $self = shift;

	my $keys = { @_ };


}

# Autoload methods go after =cut, and are processed by the autosplit program.

1;