//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// crypto.cpp -- Tripwire crypto implementation
//

#include "stdtwcrypto.h"

#include "crypto.h"
#include "core/errorgeneral.h"
#include "time.h"
#include "core/archive.h"

#include "cryptlib/sha.h"
#include "cryptlib/rng.h"
#include "cryptlib/des.h"
#include "cryptlib/integer.h"
#include "cryptlib/elgamal.h"
#include "cryptlib/des.h"
#ifdef _IDEA_ENCRYPTION
#include "cryptlib/idea.h"
#endif
#ifdef _RSA_ENCRYPTION
#include "cryptlib/rsa.h"
#endif

const uint32 EL_GAMAL_SIG_PUBLIC_MAGIC_NUM = 0x7ae2c945;
const uint32 EL_GAMAL_SIG_PRIVATE_MAGIC_NUM = 0x0d0ffa12;

///////////////////////////////////////////////////////////////////////////////
// macros for reading and writing integers

#define WRITE_INTEGER(I) \
    len = I.MinEncodedSize(Integer::UNSIGNED); \
    ASSERT(len >=0 && len < 9000); \
    i32 = tw_htonl(len); \
    memcpy(pOut, &i32, sizeof(i32)); \
    pOut += sizeof(int32); \
    I.Encode(pOut, len, Integer::UNSIGNED); \
    pOut += len;

#define READ_INTEGER(I) \
    memcpy(&i32, pIn, sizeof(i32)); \
    len = tw_ntohl(i32); \
    ASSERT(len >= 0 && len < 9000); \
    pIn += sizeof(int32); \
    I.Decode(pIn, len, Integer::UNSIGNED); \
    pIn += len;

///////////////////////////////////////////////////////////////////////////////
// class cNullCipher -- no encryption cipher

int  cNullCipher::GetBlockSizePlain()
{
    // lets use the blocksize of TripleDES_Encryption since that may be
    // better than picking a blocksize at random
    return TripleDES_Encryption::BLOCKSIZE;
}

// return the size of data blocks for plaintext and cipertext
int  cNullCipher::GetBlockSizeCipher()
{
    return TripleDES_Encryption::BLOCKSIZE;
}
		
// process a block of data.  indata and outdata may be the same memory
void cNullCipher::ProcessBlock(const void* indata, void* outdata)
{
    memmove(outdata, indata, TripleDES_Encryption::BLOCKSIZE);
}

//////////////////////////////////////////////////////////////////////////////
// class cIDEA
//////////////////////////////////////////////////////////////////////////////

#ifdef _IDEA_ENCRYPTION

class cIDEA_i
{
public:
	IDEA*	mpIDEA;
};

cIDEA::cIDEA()
{
	mpData = new cIDEA_i;

	mpData->mpIDEA = 0;
}

cIDEA::~cIDEA()
{
	delete mpData->mpIDEA;
	delete mpData;
	mpData = 0;
}

void cIDEA::SetKey(iCipher::EncryptionDir dir, const cHashedKey128& key)
{
	// some things we are assuming
	ASSERT(IDEA::KEYLENGTH == 16);
	ASSERT(mpData);

	delete mpData->mpIDEA;
	mpData->mpIDEA = new IDEA((byte*)key.GetKey(), dir == iCipher::ENCRYPT ? ENCRYPTION : DECRYPTION);
}

// return the size of data block this crypter works on
int cIDEA::GetBlockSizePlain()
{
	return IDEA::BLOCKSIZE;
}
		
int cIDEA::GetBlockSizeCipher()
{
	return IDEA::BLOCKSIZE;
}
		
// process a block of data.  indata and outdata may be the same memory
void cIDEA::ProcessBlock(const void* indata, void* outdata)
{
	ASSERT(mpData->mpIDEA);

	if (!mpData->mpIDEA)
	{
		ThrowAndAssert(eInternal(_T("Key not set in symmetric encryption.")));
	}

	mpData->mpIDEA->ProcessBlock((byte*)indata, (byte*)outdata);
}

#endif // _IDEA_ENCRYPTION

//////////////////////////////////////////////////////////////////////////////
// class cTripleDES
//////////////////////////////////////////////////////////////////////////////

class cTripleDES_i
{
public:
	TripleDES_Encryption*	mpEncryptor;
    TripleDES_Decryption*   mpDecryptor;
};

cTripleDES::cTripleDES()
{
	mpData = new cTripleDES_i;

	mpData->mpEncryptor = 0;
    mpData->mpDecryptor = 0;
}

cTripleDES::~cTripleDES()
{
	delete mpData->mpEncryptor;
    delete mpData->mpDecryptor;
	delete mpData;
	mpData = 0;
}

void cTripleDES::SetKey(iCipher::EncryptionDir dir, const cHashedKey192& key)
{
	// some things we are assuming
	ASSERT(TripleDES_Encryption::KEYLENGTH == 24);
    ASSERT(cHashedKey192::GetWriteLen() == 24);
	ASSERT(mpData != 0);

    if (dir == iCipher::ENCRYPT)
    {
        delete mpData->mpEncryptor;
        delete mpData->mpDecryptor;
        mpData->mpDecryptor = 0;
	    mpData->mpEncryptor = new TripleDES_Encryption((byte*)key.GetKey());
    }
    else
    {
        delete mpData->mpEncryptor;
        delete mpData->mpDecryptor;
        mpData->mpEncryptor = 0;
	    mpData->mpDecryptor = new TripleDES_Decryption((byte*)key.GetKey());
    }
}

// return the size of data block this crypter works on
int cTripleDES::GetBlockSizePlain()
{
    ASSERT(TripleDES_Encryption::BLOCKSIZE == TripleDES_Decryption::BLOCKSIZE);
	return TripleDES_Encryption::BLOCKSIZE;
}
		
int cTripleDES::GetBlockSizeCipher()
{
    ASSERT(TripleDES_Encryption::BLOCKSIZE == TripleDES_Decryption::BLOCKSIZE);
	return TripleDES_Encryption::BLOCKSIZE;
}
		
// process a block of data.  indata and outdata may be the same memory
void cTripleDES::ProcessBlock(const void* indata, void* outdata)
{
    ASSERT(mpData != 0);
	ASSERT(mpData->mpEncryptor || mpData->mpDecryptor);

	if (!mpData->mpEncryptor && !mpData->mpDecryptor)
	{
		ThrowAndAssert(INTERNAL_ERROR("crypto.cpp")); //cTWError::E_INTERNAL, TSTRING(_T("Key not set in symmetric encryption."))));
	}

	if (mpData->mpEncryptor)
        mpData->mpEncryptor->ProcessBlock((byte*)indata, (byte*)outdata);
    else
        mpData->mpDecryptor->ProcessBlock((byte*)indata, (byte*)outdata);
}

///////////////////////////////////////////////////////////////////////////////
// class cRSA

#ifdef _RSA_ENCRYPTION

// class cRSAPrivateKey

class cRSAPrivateKey_i
{
public:
    int16          mKeyLength;
	RSAPrivateKey* mpKey;
};

cRSAPrivateKey::cRSAPrivateKey()
{
	mpData = new cRSAPrivateKey_i;
	mpData->mpKey = 0;
    mpData->mKeyLength = 0;
}

cRSAPrivateKey::cRSAPrivateKey(void* pDataStream)
{
	mpData = new cRSAPrivateKey_i;

    int32 len;
    int32 i32;
    int16 i16;

    byte* pIn = (byte*)pDataStream;

    memcpy(&i16, pIn, sizeof(i16));
    mpData->mKeyLength = tw_ntohs(i16);
    pIn += sizeof(int16);

    Integer n, e, d, p, q, dp, dq, u;

    READ_INTEGER(n);
    READ_INTEGER(e);
    READ_INTEGER(d);
    READ_INTEGER(p);
    READ_INTEGER(q);
    READ_INTEGER(dp);
    READ_INTEGER(dq);
    READ_INTEGER(u);

    mpData->mpKey = new RSAPrivateKey(n, e, d, p, q, dp, dq, u);
}

cRSAPrivateKey::~cRSAPrivateKey()
{
	if (mpData) 
	{
		delete mpData->mpKey;
		delete mpData;
	}
}

int cRSAPrivateKey::GetWriteLen() const
{
    ASSERT(mpData->mpKey);

    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetModulus().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetExponent().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetPrime1().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetPrime2().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetDecryptionExponent().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterDP().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterDQ().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterU().IsPositive());

    int len = sizeof(int16) + 
              mpData->mpKey->GetTrapdoorFunction().GetModulus().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetExponent().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetPrime1().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetPrime2().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetDecryptionExponent().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetParameterDP().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetParameterDQ().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetParameterU().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32);

    return len;
}

void cRSAPrivateKey::Write(void* pDataStream) const
{
    ASSERT(mpData->mpKey);

    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetModulus().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetExponent().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetPrime1().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetPrime2().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetDecryptionExponent().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterDP().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterDQ().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetParameterU().IsPositive());

    byte* pOut = (byte*)pDataStream;
    int16 i16;
    int32 i32;

    i16 = tw_htons(mpData->mKeyLength);
    memcpy(pOut, &i16, sizeof(i16));
    pOut += sizeof(int16);

    Integer n, e, d, p, q, dp, dq, u;
    
    n = mpData->mpKey->GetTrapdoorFunction().GetModulus();
    e = mpData->mpKey->GetTrapdoorFunction().GetExponent();
    p = mpData->mpKey->GetTrapdoorFunction().GetPrime1();
    q = mpData->mpKey->GetTrapdoorFunction().GetPrime2();
    d = mpData->mpKey->GetTrapdoorFunction().GetDecryptionExponent();
    dp = mpData->mpKey->GetTrapdoorFunction().GetParameterDP();
    dq = mpData->mpKey->GetTrapdoorFunction().GetParameterDQ();
    u = mpData->mpKey->GetTrapdoorFunction().GetParameterU();

    int32 len;

#define WRITE_INTEGER(I) \
    len = I.MinEncodedSize(Integer::UNSIGNED); \
    i32 = tw_htonl(len); \
    memcpy(pOut, &i32, sizeof(i32)); \
    pOut += sizeof(int32); \
    I.Encode(pOut, len, Integer::UNSIGNED); \
    pOut += len;

    WRITE_INTEGER(n);
    WRITE_INTEGER(e);
    WRITE_INTEGER(d);
    WRITE_INTEGER(p);
    WRITE_INTEGER(q);
    WRITE_INTEGER(dp);
    WRITE_INTEGER(dq);
    WRITE_INTEGER(u);
}

// class cRSAPublicKey

class cRSAPublicKey_i
{
public:
    int16           mKeyLength;
	RSAPublicKey*   mpKey;
};

cRSAPublicKey::cRSAPublicKey()
{
	mpData = new cRSAPublicKey_i;
	mpData->mpKey = 0;
    mpData->mKeyLength = 0;
}

cRSAPublicKey::cRSAPublicKey(void* pDataStream)
{
	mpData = new cRSAPublicKey_i;

    Integer n, e;
    int32 len;
    int16 i16;
    int32 i32;

    byte* pIn = (byte*)pDataStream;

    memcpy(&i16, pIn, sizeof(i16));
    mpData->mKeyLength = tw_ntohs(i16);
    pIn += sizeof(int16);

    READ_INTEGER(n);
    READ_INTEGER(e);

    //std::cout << "cRSAPublicKey n = " << n << std::endl;
    //std::cout << "cRSAPublicKey e = " << e << std::endl;

	mpData->mpKey = new RSAPublicKey(n, e);
}

cRSAPublicKey::cRSAPublicKey(const cRSAPrivateKey& privateKey)
{
	mpData = new cRSAPublicKey_i;

    ASSERT(privateKey.mpData);
    ASSERT(privateKey.mpData->mpKey);

    mpData->mKeyLength = privateKey.mpData->mKeyLength;
    mpData->mpKey = new RSAPublicKey(*privateKey.mpData->mpKey);
}

cRSAPublicKey::~cRSAPublicKey()
{
	if (mpData) 
	{
		delete mpData->mpKey;
		delete mpData;
	}
}

int cRSAPublicKey::GetWriteLen() const
{
    ASSERT(mpData->mpKey);

    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetModulus().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetExponent().IsPositive());

    int len = sizeof(int16) + 
              mpData->mpKey->GetTrapdoorFunction().GetModulus().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetTrapdoorFunction().GetExponent().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32);

    return len;
}

void cRSAPublicKey::Write(void* pDataStream) const
{
    ASSERT(mpData->mpKey);

    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetModulus().IsPositive());
    ASSERT(mpData->mpKey->GetTrapdoorFunction().GetExponent().IsPositive());

    int16 i16;
    int32 i32;
    byte* pOut = (byte*)pDataStream;

    i16 = tw_htons(mpData->mKeyLength);
    memcpy(pOut, &i16, sizeof(i16));
    pOut += sizeof(int16);

    Integer n, e;
    
    n = mpData->mpKey->GetTrapdoorFunction().GetModulus();
    e = mpData->mpKey->GetTrapdoorFunction().GetExponent();

    int32 len;

    WRITE_INTEGER(n);
    WRITE_INTEGER(e);
}

#ifdef _DEBUG
void cRSAPublicKey::TraceContents()
{
    cDebug d("cRSAPublicKey::TraceContents");

    {
    std::ostringstream os;
    os << mpData->mpKey->GetTrapdoorFunction().GetModulus();
    d.TraceDebug("Modulus = %s\n", os.str().c_str());
    }
    {
    std::ostringstream os;
    os << mpData->mpKey->GetTrapdoorFunction().GetExponent();
    d.TraceDebug("Exponent = %s\n", os.str().c_str());
    }
}
#endif

// class cRSA

class cRSA_i
{
public:
	int mKeyBits;

    enum Action { ENCRYPT, DECRYPT, SIGN, VERIFY };

    Action mAction;

	const cRSAPrivateKey* mpPrivateKey;
	const cRSAPublicKey* mpPublicKey;

	X917RNG* mpRNG;
};

cRSA::cRSA(KeySize keysize)
{
    Init(keysize);
}

cRSA::cRSA(const cRSAPublicKey& publicKey)
{
    ASSERT(publicKey.mpData);
    Init((KeySize)publicKey.mpData->mKeyLength);
}

cRSA::cRSA(const cRSAPrivateKey& privateKey)
{
    ASSERT(privateKey.mpData);
    Init((KeySize)privateKey.mpData->mKeyLength);
}

void cRSA::Init(KeySize keysize)
{
	mpData = new cRSA_i;
	mpData->mpPrivateKey = 0;
	mpData->mpPublicKey = 0;

	mpData->mKeyBits = (keysize == KEY256) ? 256 :
				  	   (keysize == KEY512) ? 512 :
					   (keysize == KEY1024) ? 1024 :
					   (keysize == KEY2048) ? 2048 :
					   256;

	// Create a random seed and a key
	byte seed[MD5::DATASIZE];
	byte deskey[TripleDES_Encryption::KEYLENGTH];
	RandomizeBytes((int8*)seed, MD5::DATASIZE);
	RandomizeBytes((int8*)deskey, TripleDES_Encryption::KEYLENGTH);

	mpData->mpRNG = new X917RNG(new TripleDES_Encryption(deskey), seed);
}

cRSA::~cRSA()
{
	if (mpData) 
	{
		delete mpData->mpRNG;
		delete mpData;
		mpData = 0;
	}
}

void cRSA::SetEncrypting(const cRSAPublicKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("RSA Key length mismatch.")));


    mpData->mAction = cRSA_i::ENCRYPT;
	mpData->mpPublicKey = pKey;
    mpData->mpPrivateKey = 0;
}

void cRSA::SetDecrypting(const cRSAPrivateKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("RSA Key length mismatch.")));

    mpData->mAction = cRSA_i::DECRYPT;
	mpData->mpPrivateKey = pKey;
    mpData->mpPublicKey = 0;
}

void cRSA::SetSigning(const cRSAPrivateKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("RSA Key length mismatch.")));

    mpData->mAction = cRSA_i::SIGN;
	mpData->mpPrivateKey = pKey;
    mpData->mpPublicKey = 0;
}

void cRSA::SetVerifying(const cRSAPublicKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("RSA Key length mismatch.")));

    mpData->mAction = cRSA_i::VERIFY;
	mpData->mpPublicKey = pKey;
    mpData->mpPrivateKey = 0;
}

// Lets encrypt in 128 bit chunks, even though the crypto
// lib implementation can do arbitrary length plaintext
// encryptions.  We will most likely only use this to 
// encrypt a 128 bit random number anyway.
int cRSA::GetBlockSizePlain()
{
    //Integer i = mpData->mKeyBits  - 1;

    return (mpData->mKeyBits >> 3) - 11;
}

int cRSA::GetBlockSizeCipher()
{
	return mpData->mKeyBits >> 3;
}

void cRSA::ProcessBlock(const void* indata, void* outdata)
{
	ASSERT(mpData);
	ASSERT(mpData->mpPrivateKey != 0 || mpData->mpPublicKey != 0);

	if (mpData->mpPrivateKey == 0 && mpData->mpPublicKey == 0)
		ThrowAndAssert(eInternal(_T("RSA Key length mismatch.")));

	unsigned int l;

	switch (mpData->mAction)
    { 
	    case cRSA_i::ENCRYPT:
	    {
            ASSERT(mpData->mpPublicKey);
		    ASSERT(mpData->mpPublicKey->mpData->mpKey->MaxPlainTextLength() == GetBlockSizePlain());
            ASSERT(mpData->mpPublicKey->mpData->mpKey->CipherTextLength() == GetBlockSizeCipher());
		    mpData->mpPublicKey->mpData->mpKey->Encrypt(*mpData->mpRNG, (const byte *)indata, GetBlockSizePlain(), (byte *)outdata);
            break;
	    }
        case cRSA_i::DECRYPT:
	    {
            ASSERT(mpData->mpPrivateKey);
		    ASSERT(mpData->mpPrivateKey->mpData->mpKey->CipherTextLength() == GetBlockSizeCipher());
		    l = mpData->mpPrivateKey->mpData->mpKey->Decrypt((const byte *)indata, (byte *)outdata);
            if (l != GetBlockSizePlain())
                throw eArchiveCrypto();
            break;
	    }
        case cRSA_i::SIGN:
        {
            ASSERT(mpData->mpPrivateKey);
		    ASSERT(mpData->mpPrivateKey->mpData->mpKey->MaxMessageLength() == GetBlockSizePlain());
            ASSERT(mpData->mpPrivateKey->mpData->mpKey->SignatureLength() == GetBlockSizeCipher());
		    mpData->mpPrivateKey->mpData->mpKey->Sign(*mpData->mpRNG, (const byte *)indata, GetBlockSizePlain(), (byte *)outdata);
            break;
        }
        case cRSA_i::VERIFY:
        {
            ASSERT(mpData->mpPublicKey);
            ASSERT(mpData->mpPublicKey->mpData->mpKey->SignatureLength() == GetBlockSizeCipher());
            l = mpData->mpPublicKey->mpData->mpKey->Recover((const byte *)indata, (byte *)outdata);
            if (l != GetBlockSizePlain())
                throw eArchiveCrypto();
            break;
        }
        default:
        {
            ASSERT(false);
            break;
        }
    }
}

void cRSA::GenerateKeys(cRSAPrivateKey*& retPrivate, cRSAPublicKey*& retPublic)
{
	RSAPrivateKey* pNewPrivateKey = new RSAPrivateKey(*mpData->mpRNG, mpData->mKeyBits);
	RSAPublicKey* pNewPublicKey = new RSAPublicKey(*pNewPrivateKey);

	retPrivate = new cRSAPrivateKey();
	retPrivate->mpData->mpKey = pNewPrivateKey;
    retPrivate->mpData->mKeyLength = mpData->mKeyBits;

	retPublic = new cRSAPublicKey();
	retPublic->mpData->mpKey = pNewPublicKey;
    retPublic->mpData->mKeyLength = mpData->mKeyBits;

#ifdef _DEBUG
	int l;
	l = retPublic->mpData->mpKey->MaxPlainTextLength();
    ASSERT(l == GetBlockSizePlain());
	l = retPublic->mpData->mpKey->CipherTextLength();
    ASSERT(l == GetBlockSizeCipher());
	l = retPrivate->mpData->mpKey->CipherTextLength();
    ASSERT(l == GetBlockSizeCipher());
#endif
}

#endif // _RSA_ENCRYPTION

///////////////////////////////////////////////////////////////////////////////
// class cElGamalSig

// class cElGamalSigPrivateKey

class cElGamalSigPrivateKey_i
{
public:
    int16          mKeyLength;
	ElGamalSigPrivateKey* mpKey;
};

cElGamalSigPrivateKey::cElGamalSigPrivateKey()
{
	mpData = new cElGamalSigPrivateKey_i;
	mpData->mpKey = 0;
    mpData->mKeyLength = 0;
}

cElGamalSigPrivateKey::cElGamalSigPrivateKey(void* pDataStream)
{
	mpData = new cElGamalSigPrivateKey_i;

    int32 len;
    int32 i32;
    int16 i16;
    uint32 magicNum;

    byte* pIn = (byte*)pDataStream;

    memcpy(&i16, pIn, sizeof(i16));
    mpData->mKeyLength = tw_ntohs(i16);
    pIn += sizeof(int16);

    memcpy(&i32, pIn, sizeof(i32));
    magicNum = tw_ntohl(i32);
    pIn += sizeof(int32);

    if (magicNum != EL_GAMAL_SIG_PRIVATE_MAGIC_NUM)
        ThrowAndAssert( eArchiveOpen() );

    Integer p, q, g, y, x;

    READ_INTEGER(p);
    READ_INTEGER(q);
    READ_INTEGER(g);
    READ_INTEGER(y);
    READ_INTEGER(x);

    mpData->mpKey = new ElGamalSigPrivateKey(p, q, g, y, x);
}

cElGamalSigPrivateKey::~cElGamalSigPrivateKey()
{
	if (mpData) 
	{
		delete mpData->mpKey;
		delete mpData;
	}
}

int cElGamalSigPrivateKey::GetWriteLen() const
{
    ASSERT(mpData->mpKey != 0);

    ASSERT(mpData->mpKey->GetPrime().IsPositive());
    ASSERT(mpData->mpKey->GetParameterQ().IsPositive());
    ASSERT(mpData->mpKey->GetParameterG().IsPositive());
    ASSERT(mpData->mpKey->GetParameterY().IsPositive());
    ASSERT(mpData->mpKey->GetParameterX().IsPositive());

    int len = sizeof(int16) + sizeof(int32) +
              mpData->mpKey->GetPrime().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterQ().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterG().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterY().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterX().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32);

    return len;
}

void cElGamalSigPrivateKey::Write(void* pDataStream) const
{
    ASSERT(mpData->mpKey != 0);

    ASSERT(mpData->mpKey->GetPrime().IsPositive());
    ASSERT(mpData->mpKey->GetParameterQ().IsPositive());
    ASSERT(mpData->mpKey->GetParameterG().IsPositive());
    ASSERT(mpData->mpKey->GetParameterY().IsPositive());
    ASSERT(mpData->mpKey->GetParameterX().IsPositive());

    byte* pOut = (byte*)pDataStream;
    int16 i16;
    int32 i32;

    i16 = tw_htons(mpData->mKeyLength);
    memcpy(pOut, &i16, sizeof(i16));
    pOut += sizeof(int16);

    i32 = tw_htonl(EL_GAMAL_SIG_PRIVATE_MAGIC_NUM);
    memcpy(pOut, &i32, sizeof(i32));
    pOut += sizeof(int32);

    Integer p, q, g, y, x;
    
    p = mpData->mpKey->GetPrime();
    q = mpData->mpKey->GetParameterQ();
    g = mpData->mpKey->GetParameterG();
    y = mpData->mpKey->GetParameterY();
    x = mpData->mpKey->GetParameterX();

    int32 len;

    WRITE_INTEGER(p);
    WRITE_INTEGER(q);
    WRITE_INTEGER(g);
    WRITE_INTEGER(y);
    WRITE_INTEGER(x);
}

// class cElGamalSigPublicKey

class cElGamalSigPublicKey_i
{
public:
    int16           mKeyLength;
	ElGamalSigPublicKey*   mpKey;
};

cElGamalSigPublicKey::cElGamalSigPublicKey()
{
	mpData = new cElGamalSigPublicKey_i;
	mpData->mpKey = 0;
    mpData->mKeyLength = 0;
}

cElGamalSigPublicKey::cElGamalSigPublicKey(void* pDataStream)
{
	mpData = new cElGamalSigPublicKey_i;

    Integer p, q, g, y;
    int32 len;
    int16 i16;
    int32 i32;
    uint32 magicNum;

    byte* pIn = (byte*)pDataStream;

    memcpy(&i16, pIn, sizeof(i16));
    mpData->mKeyLength = tw_ntohs(i16);
    pIn += sizeof(int16);

    memcpy(&i32, pIn, sizeof(i32));
    magicNum = tw_ntohl(i32);
    pIn += sizeof(int32);

    if (magicNum != EL_GAMAL_SIG_PUBLIC_MAGIC_NUM)
		ThrowAndAssert( eArchiveOpen() );

    READ_INTEGER(p);
    READ_INTEGER(q);
    READ_INTEGER(g);
    READ_INTEGER(y);

    //std::cout << "cElGamalSigPublicKey n = " << n << std::endl;
    //std::cout << "cElGamalSigPublicKey e = " << e << std::endl;

	mpData->mpKey = new ElGamalSigPublicKey(p, q, g, y);
}

cElGamalSigPublicKey::cElGamalSigPublicKey(const cElGamalSigPrivateKey& privateKey)
{
	mpData = new cElGamalSigPublicKey_i;

    ASSERT(privateKey.mpData != 0);
    ASSERT(privateKey.mpData->mpKey != 0);

    mpData->mKeyLength = privateKey.mpData->mKeyLength;
    mpData->mpKey = new ElGamalSigPublicKey(*privateKey.mpData->mpKey);
}

cElGamalSigPublicKey::~cElGamalSigPublicKey()
{
	if (mpData) 
	{
		delete mpData->mpKey;
		delete mpData;
	}
}

int cElGamalSigPublicKey::GetWriteLen() const
{
    ASSERT(mpData->mpKey != 0);

    ASSERT(mpData->mpKey->GetPrime().IsPositive());
    ASSERT(mpData->mpKey->GetParameterQ().IsPositive());
    ASSERT(mpData->mpKey->GetParameterG().IsPositive());
    ASSERT(mpData->mpKey->GetParameterY().IsPositive());

    int len = sizeof(int16) + sizeof(int32) +
              mpData->mpKey->GetPrime().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterQ().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterG().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32) +
              mpData->mpKey->GetParameterY().MinEncodedSize(Integer::UNSIGNED) + sizeof(int32);

    return len;

}

void cElGamalSigPublicKey::Write(void* pDataStream) const
{
    ASSERT(mpData->mpKey != 0);

    ASSERT(mpData->mpKey->GetPrime().IsPositive());
    ASSERT(mpData->mpKey->GetParameterQ().IsPositive());
    ASSERT(mpData->mpKey->GetParameterG().IsPositive());
    ASSERT(mpData->mpKey->GetParameterY().IsPositive());

    byte* pOut = (byte*)pDataStream;
    int16 i16;
    int32 i32;

    i16 = tw_htons(mpData->mKeyLength);
    memcpy(pOut, &i16, sizeof(i16));
    pOut += sizeof(int16);

    i32 = tw_htonl(EL_GAMAL_SIG_PUBLIC_MAGIC_NUM);
    memcpy(pOut, &i32, sizeof(i32));
    pOut += sizeof(int32);

    Integer p, q, g, y;
    
    p = mpData->mpKey->GetPrime();
    q = mpData->mpKey->GetParameterQ();
    g = mpData->mpKey->GetParameterG();
    y = mpData->mpKey->GetParameterY();

    int32 len;

    WRITE_INTEGER(p);
    WRITE_INTEGER(q);
    WRITE_INTEGER(g);
    WRITE_INTEGER(y);
}

bool cElGamalSigPublicKey::IsEqual(const cElGamalSigPublicKey& rhs) const
{
    return this->mpData->mpKey->GetPrime() == rhs.mpData->mpKey->GetPrime() &&
           this->mpData->mpKey->GetParameterQ() == rhs.mpData->mpKey->GetParameterQ() && 
           this->mpData->mpKey->GetParameterG() == rhs.mpData->mpKey->GetParameterG() &&
           this->mpData->mpKey->GetParameterY() == rhs.mpData->mpKey->GetParameterY()
           ;
}

#ifdef _DEBUG
void cElGamalSigPublicKey::TraceContents()
{
    cDebug d("cElGamalSigPublicKey::TraceContents");

    {
    std::ostringstream os;
    os << mpData->mpKey->GetPrime();
    d.TraceDebug("Prime = %s\n", os.str().c_str());
    }
    {
    std::ostringstream os;
    os << mpData->mpKey->GetParameterQ();
    d.TraceDebug("Q = %s\n", os.str().c_str());
    }
    {
    std::ostringstream os;
    os << mpData->mpKey->GetParameterG();
    d.TraceDebug("G = %s\n", os.str().c_str());
    }
    {
    std::ostringstream os;
    os << mpData->mpKey->GetParameterY();
    d.TraceDebug("Y = %s\n", os.str().c_str());
    }
}
#endif

// class cElGamalSig

class cElGamalSig_i
{
public:
	int mKeyBits;

    enum Action { /*ENCRYPT, DECRYPT,*/ SIGN, VERIFY };

    Action mAction;

	const cElGamalSigPrivateKey* mpPrivateKey;
	const cElGamalSigPublicKey* mpPublicKey;

    SHA mSHA;
	X917RNG* mpRNG;
};

cElGamalSig::cElGamalSig(KeySize keysize)
{
    Init(keysize);
}

cElGamalSig::cElGamalSig(const cElGamalSigPublicKey& publicKey)
{
    ASSERT(publicKey.mpData != 0);
    Init((KeySize)publicKey.mpData->mKeyLength);
}

cElGamalSig::cElGamalSig(const cElGamalSigPrivateKey& privateKey)
{
    ASSERT(privateKey.mpData != 0);
    Init((KeySize)privateKey.mpData->mKeyLength);
}

void cElGamalSig::Init(KeySize keysize)
{
	mpData = new cElGamalSig_i;
	mpData->mpPrivateKey = 0;
	mpData->mpPublicKey = 0;

	mpData->mKeyBits = (keysize == KEY256) ? 256 :
				  	   (keysize == KEY512) ? 512 :
					   (keysize == KEY1024) ? 1024 :
					   (keysize == KEY2048) ? 2048 :
					   256;

	// Create a random seed and a key
	byte seed[SHA::DATASIZE];
	byte deskey[TripleDES_Encryption::KEYLENGTH];
	RandomizeBytes((int8*)seed, SHA::DATASIZE);
	RandomizeBytes((int8*)deskey, TripleDES_Encryption::KEYLENGTH);

	mpData->mpRNG = new X917RNG(new TripleDES_Encryption(deskey), seed);
}

cElGamalSig::~cElGamalSig()
{
	if (mpData) 
	{
		delete mpData->mpRNG;
		delete mpData;
		mpData = 0;
	}
}

void cElGamalSig::SetSigning(const cElGamalSigPrivateKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("Signature Key length mismatch.")));

    mpData->mAction = cElGamalSig_i::SIGN;
	mpData->mpPrivateKey = pKey;
    mpData->mpPublicKey = 0;
}

void cElGamalSig::SetVerifying(const cElGamalSigPublicKey* pKey)
{
    ASSERT(pKey->mpData->mKeyLength == mpData->mKeyBits);
    if (pKey->mpData->mKeyLength != mpData->mKeyBits)
		ThrowAndAssert(eInternal(_T("Signature Key length mismatch.")));

    mpData->mAction = cElGamalSig_i::VERIFY;
	mpData->mpPublicKey = pKey;
    mpData->mpPrivateKey = 0;
}

int cElGamalSig::GetBlockSizePlain()
{
    return PLAIN_BLOCK_SIZE;
}

int cElGamalSig::GetBlockSizeCipher()
{
	return PLAIN_BLOCK_SIZE + (NumberTheory::DiscreteLogWorkFactor(mpData->mKeyBits) >> 1) + 4;
        // got this from nbtheory.cpp in crypto++ lib
        // El Gamal's sig size = 2 * 2 * (DiscreteLogWorkFactor() >> 3)
}

void cElGamalSig::ProcessBlock(const void* indata, void* outdata)
{
	ASSERT(mpData != 0);
	ASSERT(mpData->mpPrivateKey != 0 || mpData->mpPublicKey != 0);

    int8 shaSig[SHA::DIGESTSIZE];

    if (mpData->mpPrivateKey == 0 && mpData->mpPublicKey == 0)
		ThrowAndAssert(eInternal(_T("Signature Key length mismatch.")));

	switch (mpData->mAction)
    { 
        case cElGamalSig_i::SIGN:
        {
            ASSERT(mpData->mpPrivateKey != 0);
            ASSERT((int)mpData->mpPrivateKey->mpData->mpKey->SignatureLength() + PLAIN_BLOCK_SIZE <= GetBlockSizeCipher());

            memmove(outdata, indata, PLAIN_BLOCK_SIZE);

            mpData->mSHA.CalculateDigest((byte*)shaSig, (byte*)outdata, PLAIN_BLOCK_SIZE);

            RandomizeBytes((int8 *)outdata + PLAIN_BLOCK_SIZE, GetBlockSizeCipher() - PLAIN_BLOCK_SIZE);
		    mpData->mpPrivateKey->mpData->mpKey->Sign(*mpData->mpRNG, (const byte *)shaSig, SHA::DIGESTSIZE, (byte *)outdata + PLAIN_BLOCK_SIZE);

            /*
	            Integer m((const byte*)indata, PLAIN_BLOCK_SIZE);

                std::cout << "Signing:\n";
                std::cout << "M = " << m << std::endl;

	            const byte* signature = (const byte *)outdata + PLAIN_BLOCK_SIZE;
                int qLen = mpData->mpPrivateKey->mpData->mpKey->q.ByteCount();
	            Integer rs(signature, qLen);
	            Integer ss(signature+qLen, qLen);
                std::cout << "Stored R = " << rs << std::endl;
                std::cout << "Stored S = " << ss << std::endl;
            */

            break;
        }
        case cElGamalSig_i::VERIFY:
        {
            ASSERT(mpData->mpPublicKey != 0);
            ASSERT((int)mpData->mpPublicKey->mpData->mpKey->SignatureLength() + PLAIN_BLOCK_SIZE <= GetBlockSizeCipher());

            mpData->mSHA.CalculateDigest((byte*)shaSig, (byte*)indata, PLAIN_BLOCK_SIZE);

            /*
	            const byte* signature = (const byte *)indata + PLAIN_BLOCK_SIZE;
                int qLen = mpData->mpPublicKey->mpData->mpKey->q.ByteCount();
                Integer m((const byte*)indata, PLAIN_BLOCK_SIZE);
	            Integer r(signature, qLen);
	            Integer s(signature+qLen, qLen);
                std::cout << "Verifying:\n";
                std::cout << "M = " << m << std::endl;
                std::cout << "R = " << r << std::endl;
                std::cout << "S = " << s << std::endl;
            */

            if (mpData->mpPublicKey->mpData->mpKey->Verify((const byte *)shaSig, SHA::DIGESTSIZE, (const byte *)indata + PLAIN_BLOCK_SIZE) == false)
                throw eArchiveCrypto();
            memmove(outdata, indata, PLAIN_BLOCK_SIZE);
            break;
        }
        default:
        {
            ASSERT(false);
            break;
        }
    }
}

void cElGamalSig::GenerateKeys(cElGamalSigPrivateKey*& retPrivate, cElGamalSigPublicKey*& retPublic)
{
	ElGamalSigPrivateKey* pNewPrivateKey = new ElGamalSigPrivateKey(*mpData->mpRNG, mpData->mKeyBits);
	ElGamalSigPublicKey* pNewPublicKey = new ElGamalSigPublicKey(*pNewPrivateKey);

	retPrivate = new cElGamalSigPrivateKey();
	retPrivate->mpData->mpKey = pNewPrivateKey;
    retPrivate->mpData->mKeyLength = (int16)mpData->mKeyBits;

	retPublic = new cElGamalSigPublicKey();
	retPublic->mpData->mpKey = pNewPublicKey;
    retPublic->mpData->mKeyLength = (int16)mpData->mKeyBits;

#ifdef _DEBUG
	int l;
	l = retPublic->mpData->mpKey->SignatureLength();
    ASSERT(l + PLAIN_BLOCK_SIZE <= GetBlockSizeCipher());
	l = retPrivate->mpData->mpKey->SignatureLength();
    ASSERT(l + PLAIN_BLOCK_SIZE <= GetBlockSizeCipher());
#endif
}

///////////////////////////////////////////////////////////////////////////////
// class cHashedKey128 -- A 128 bit key based on the hash value of some data

cHashedKey128::cHashedKey128(const TSTRING& data)
{
	SHA sha;

    ASSERT(SHA::DIGESTSIZE >= KEYLEN);
	sha.CalculateDigest((byte*)mKey, (byte*)data.data(), data.length() * sizeof(TCHAR));
}

cHashedKey128::cHashedKey128(void* pData, int dataLen)
{
	SHA sha;

    ASSERT(SHA::DIGESTSIZE >= KEYLEN);
    ASSERT(SHA::DIGESTSIZE <= BUFSIZE);
	sha.CalculateDigest((byte*)mKey, (byte*)pData, dataLen);
}

cHashedKey128::~cHashedKey128()
{
    RandomizeBytes(mKey, KEYLEN);
}

///////////////////////////////////////////////////////////////////////////////
// class cHashedKey192 -- A 192 bit key based on the hash value of some data

cHashedKey192::cHashedKey192(const TSTRING& data)
{
	SHA sha;

    ASSERT(SHA::DIGESTSIZE == 20);
    ASSERT(40 >= KEYLEN);
    
    byte localKey[40];
    sha.CalculateDigest(localKey, (byte*)data.data(), data.length() * sizeof(TCHAR));
    sha.CalculateDigest(localKey + 20, localKey, 20);
    memcpy(mKey, localKey, KEYLEN);
}

cHashedKey192::cHashedKey192(void* pData, int dataLen)
{
	SHA sha;

    ASSERT(SHA::DIGESTSIZE == 20);
    ASSERT(40 >= KEYLEN);
    
    byte localKey[40];
    sha.CalculateDigest(localKey, (byte*)pData, dataLen);
    sha.CalculateDigest(localKey + 20, localKey, 20);
    memcpy(mKey, localKey, KEYLEN);
}

cHashedKey192::~cHashedKey192()
{
    RandomizeBytes(mKey, KEYLEN);
}

///////////////////////////////////////////////////////////////////////////////
// void RandomizeBytes(byte* destbuf, int len) -- Fill a buffer with random bytes

static bool gRandomizeBytesSeeded = false;

void RandomizeBytes(int8* destbuf, int len)
{
    if (!gRandomizeBytesSeeded)
    {
        // generate a rancom number from processor timing.
        // this should be fairly unpredictable.
        time_t mask, start = time(NULL);
        for (mask = 0xb147688c; time(NULL) - start < 1; mask += 0x8984cc88)
            ;

        #ifdef _DEBUG
        time_t t = time(NULL);
        t ^= mask;
        
        //time_t t = 920492046 - 3600; // try to the same random values each time
        //std::cerr << (*(time_t*)mask()) << ":" << t << std::endl;

        srand( t );
        #else
	    srand( time(NULL) ^ mask );
        #endif
        gRandomizeBytesSeeded = true;
    }

	int i;
	for (i = 0; i < len; ++i)
		destbuf[i] = (byte)( (rand() * 256 / RAND_MAX) ^ 0xdc );  // 0xdc came from random.org
}

