/*
 * Copyright (c) 2004 Beeyond Software Holding BV
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "beecrypt/c++/adapter.h"
#include "beecrypt/c++/lang/NullPointerException.h"
using beecrypt::lang::NullPointerException;
#include "beecrypt/c++/provider/SHA1withDSASignature.h"
#include "beecrypt/c++/security/interfaces/DSAPrivateKey.h"
using beecrypt::security::interfaces::DSAPrivateKey;
#include "beecrypt/c++/security/interfaces/DSAPublicKey.h"
using beecrypt::security::interfaces::DSAPublicKey;

namespace {
	const byte TAG_SEQUENCE = 0x30;
	const byte TAG_INTEGER = 0x02;

	typedef int asn1error;

	const asn1error DER_NOT_ENOUGH_DATA = -1;
	const asn1error DER_IMPLICIT_TAG_LENGTH = -2;
	const asn1error DER_TAG_TOO_LONG = -3;
	const asn1error DER_FORMAT_ERROR = -4;
	const asn1error DER_CONVERSION_ERROR = -5;

	/* compute the size of a DER length encoding */
	int asn1_der_length(int length) throw ()
	{
		if (length < 0x80)
			return 1;
		if (length < 0x100)
			return 2;
		if (length < 0x10000)
			return 3;
		if (length < 0x1000000)
			return 4;
		else
			return 5;
	}

	int asn1_der_length_of(const mpnumber& n) throw ()
	{
		int sigbits = mpbits(n.size, n.data);

		return ((sigbits + 7) >> 3) + (((sigbits & 7) == 0) ? 1 : 0);
	}

	int asn1_der_length_of_rssig(const mpnumber& r, const mpnumber& s) throw ()
	{
		int intlen, seqlen = 0;

		intlen = asn1_der_length_of(r);

		seqlen += 1 + asn1_der_length(intlen) + intlen;

		intlen = asn1_der_length_of(s);

		seqlen += 1 + asn1_der_length(intlen) + intlen;

		return 1 + asn1_der_length(seqlen) + seqlen;
	}

	int asn1_der_encode_length(byte* data, int length) throw ()
	{
		if (length < 0x80)
		{
			data[0] = (byte) length;
			return 1;
		}
		else if (length < 0x100)
		{
			data[0] = (byte) 0x81;
			data[1] = (byte) length;
			return 2;
		}
		else if (length < 0x10000)
		{
			data[0] = (byte) 0x82;
			data[1] = (byte) (length >> 8);
			data[2] = (byte) (length     );
			return 3;
		}
		else if (length < 0x1000000)
		{
			data[0] = (byte) 0x83;
			data[1] = (byte) (length >> 16);
			data[2] = (byte) (length >>  8);
			data[3] = (byte) (length      );
			return 4;
		}
		else
		{
			data[0] = (byte) 0x84;
			data[1] = (byte) (length >> 24);
			data[2] = (byte) (length >> 16);
			data[3] = (byte) (length >>  8);
			data[4] = (byte) (length      );
			return 5;
		}
	}

	int asn1_der_decode_length(const byte* data, int size, int* length) throw (asn1error)
	{
		int length_bytes;
		byte tmp;

		if (size == 0)
			throw DER_NOT_ENOUGH_DATA;

		tmp = *(data++);

		if (tmp < 0x80)
		{
			*length = tmp;
			length_bytes = 0;
		}
		else
		{
			byte length_bytes = tmp & 0x7f;

			if (length_bytes == 0)
				throw DER_IMPLICIT_TAG_LENGTH;
																					
			if (length_bytes >= size)
				throw DER_NOT_ENOUGH_DATA;
				
			if (length_bytes > sizeof(int))
				throw DER_TAG_TOO_LONG;

			int temp = 0;
	                                                                                                    
			for (byte i = 0; i < length_bytes; i++)
			{
				tmp = *(data++);
				temp <<= 8;
				temp += tmp;
			}

			*length = temp;
		}
		return 1 + length_bytes;
	}

	int asn1_der_encode(byte* data, const mpnumber& n) throw ()
	{
		int offset = 1, length = asn1_der_length_of(n);

		data[0] = TAG_INTEGER;

		offset += asn1_der_encode_length(data+offset, length);

		i2osp(data+offset, length, n.data, n.size);

		offset += length;

		return offset;
	}

	int asn1_der_decode(const byte* data, int size, mpnumber& n) throw (asn1error)
	{
		int length, offset = 1;

		if (size < 2)
			throw DER_NOT_ENOUGH_DATA;

		if (data[0] != TAG_INTEGER)
			throw DER_FORMAT_ERROR;

		offset += asn1_der_decode_length(data+offset, size-offset, &length);

		if (length > (size-offset))
			throw DER_NOT_ENOUGH_DATA;

		if (mpnsetbin(&n, data+offset, length))
			throw DER_CONVERSION_ERROR;

		offset += length;

		return offset;
	}

	int asn1_der_encode_rssig(byte* data, const mpnumber& r, const mpnumber& s) throw ()
	{
		int intlen, seqlen = 0;

		intlen = asn1_der_length_of(r);
		seqlen += 1 + asn1_der_length(intlen) + intlen;
		intlen = asn1_der_length_of(s);
		seqlen += 1 + asn1_der_length(intlen) + intlen;

		*(data++) = TAG_SEQUENCE;

		data += asn1_der_encode_length(data, seqlen);
		data += asn1_der_encode(data, r);
		data += asn1_der_encode(data, s);

		return 1 + asn1_der_length(seqlen) + seqlen;
	}

	int asn1_der_decode_rssig(const byte* data, int size, mpnumber& r, mpnumber& s) throw (asn1error)
	{
		int tmp, length, offset = 1;

		if (size < 2)
			throw DER_NOT_ENOUGH_DATA;

		if (data[0] != TAG_SEQUENCE)
			throw DER_FORMAT_ERROR;

		offset += asn1_der_decode_length(data+offset, size-offset, &length);

		if (length > (size-offset))
			throw DER_NOT_ENOUGH_DATA;

		tmp = asn1_der_decode(data+offset, length, r);

		offset += tmp;
		length -= tmp;

		tmp = asn1_der_decode(data+offset, length, s);

		offset += tmp;
		length -= tmp;

		if (length > 0)
			throw DER_FORMAT_ERROR;

		return offset;
	}
}

using namespace beecrypt::provider;

SHA1withDSASignature::SHA1withDSASignature()
{
}

AlgorithmParameters* SHA1withDSASignature::engineGetParameters() const
{
	return 0;
}

void SHA1withDSASignature::engineSetParameter(const AlgorithmParameterSpec& spec) throw (InvalidAlgorithmParameterException)
{
	throw InvalidAlgorithmParameterException("not supported for this algorithm");
}

void SHA1withDSASignature::engineInitSign(const PrivateKey& key, SecureRandom* random) throw (InvalidKeyException)
{
	const DSAPrivateKey* dsa = dynamic_cast<const DSAPrivateKey*>(&key);
	if (dsa)
	{
		/* copy key information */
		transform(_params.p, dsa->getParams().getP());
		transform(_params.q, dsa->getParams().getQ());
		transform(_params.g, dsa->getParams().getG());
		transform(_x, dsa->getX());

		/* reset the hash function */
		sha1Reset(&_sp);

		_srng = random;
	}
	else
		throw InvalidKeyException("key must be a DSAPrivateKey");
}

void SHA1withDSASignature::engineInitVerify(const PublicKey& key) throw (InvalidKeyException)
{
	const DSAPublicKey* dsa = dynamic_cast<const DSAPublicKey*>(&key);
	if (dsa)
	{
		/* copy key information */
		transform(_params.p, dsa->getParams().getP());
		transform(_params.q, dsa->getParams().getQ());
		transform(_params.g, dsa->getParams().getG());
		transform(_y, dsa->getY());

		/* reset the hash function */
		sha1Reset(&_sp);

		_srng = 0;
	}
	else
		throw InvalidKeyException("key must be a DSAPrivateKey");
}

void SHA1withDSASignature::engineUpdate(byte b)
{
	sha1Update(&_sp, &b, 1);
}

void SHA1withDSASignature::engineUpdate(const byte* data, int offset, int len)
{
	sha1Update(&_sp, data+offset, len);
}

void SHA1withDSASignature::rawsign(mpnumber& r, mpnumber& s) throw (SignatureException)
{
	mpnumber hm;
	byte digest[20];

	sha1Digest(&_sp, digest);
	mpnsetbin(&hm, digest, 20);

	if (_srng)
	{
		randomGeneratorContextAdapter rngc(_srng);
		if (dsasign(&_params.p, &_params.q, &_params.g, &rngc, &hm, &_x, &r, &s))
			throw SignatureException("internal error in dsasign function");
	}
	else
	{
		randomGeneratorContext rngc(randomGeneratorDefault());
		if (dsasign(&_params.p, &_params.q, &_params.g, &rngc, &hm, &_x, &r, &s))
			throw SignatureException("internal error in dsasign function");
	}
}

bool SHA1withDSASignature::rawvrfy(const mpnumber& r, const mpnumber& s) throw ()
{
	mpnumber hm;
	byte digest[20];

	sha1Digest(&_sp, digest);
	mpnsetbin(&hm, digest, 20);

	return dsavrfy(&_params.p, &_params.q, &_params.g, &hm, &_y, &r, &s);
}

bytearray* SHA1withDSASignature::engineSign() throw (SignatureException)
{
	mpnumber r, s;

	rawsign(r, s);

	bytearray* signature = new bytearray(asn1_der_length_of_rssig(r, s));

	asn1_der_encode_rssig(signature->data(), r, s);

	return signature;
}

int SHA1withDSASignature::engineSign(byte* signature, int offset, int len) throw (ShortBufferException, SignatureException)
{
	if (!signature)
		throw NullPointerException();

	mpnumber r, s;

	rawsign(r, s);

	if (asn1_der_length_of_rssig(r, s) > (len - offset))
		throw ShortBufferException();

	return asn1_der_encode_rssig(signature+offset, r, s);
}

int SHA1withDSASignature::engineSign(bytearray& signature) throw (SignatureException)
{
	mpnumber r, s;

	rawsign(r, s);

	signature.resize(asn1_der_length_of_rssig(r, s));

	return asn1_der_encode_rssig(signature.data(), r, s);
}

bool SHA1withDSASignature::engineVerify(const byte* signature, int offset, int len) throw (SignatureException)
{
	if (!signature)
		throw NullPointerException();

	mpnumber r, s;

	try
	{
		asn1_der_decode_rssig(signature+offset, len-offset, r, s);
	}
	catch (asn1error&)
	{
		throw SignatureException("invalid signature");
	}

	return rawvrfy(r, s);
}
