// distribution boxbackup-0.10 (svn version: 494)
//  
// Copyright (c) 2003 - 2006
//      Ben Summers and contributors.  All rights reserved.
//  
// 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 above 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 use of this software and associated advertising materials must 
//    display the following acknowledgment:
//        This product includes software developed by Ben Summers.
// 4. The names of the Authors may not be used to endorse or promote
//    products derived from this software without specific prior written
//    permission.
// 
// [Where legally impermissible the Authors do not disclaim liability for 
// direct physical injury or death caused solely by defects in the software 
// unless it is modified by a third party.]
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
//  
//  
//  
// --------------------------------------------------------------------------
//
// File
//		Name:    CipherContext.cpp
//		Purpose: Context for symmetric encryption / descryption
//		Created: 1/12/03
//
// --------------------------------------------------------------------------

#include "Box.h"

#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE
#include "CipherContext.h"
#include "CipherDescription.h"
#include "CipherException.h"
#include "Random.h"

#include "MemLeakFindOn.h"

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::CipherContext()
//		Purpose: Constructor
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
CipherContext::CipherContext()
	: mInitialised(false),
	  mWithinTransform(false),
	  mPaddingOn(true)
#ifdef HAVE_OLD_SSL
	, mFunction(Decrypt),
	  mpDescription(0)
#endif
{
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::~CipherContext()
//		Purpose: Destructor
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
CipherContext::~CipherContext()
{
	if(mInitialised)
	{
		// Clean up
		EVP_CIPHER_CTX_cleanup(&ctx);
		mInitialised = false;
	}
#ifdef HAVE_OLD_SSL
	if(mpDescription != 0)
	{
		delete mpDescription;
		mpDescription = 0;
	}
#endif
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::Init(CipherContext::CipherFunction, const CipherDescription &)
//		Purpose: Initialises the context, specifying the direction for the encryption, and a
//				 description of the cipher to use, it's keys, etc
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription)
{
	// Check for bad usage
	if(mInitialised)
	{
		THROW_EXCEPTION(CipherException, AlreadyInitialised)
	}
	if(Function != Decrypt && Function != Encrypt)
	{
		THROW_EXCEPTION(CipherException, BadArguments)
	}
	
	// Initialise the cipher
#ifndef HAVE_OLD_SSL
	EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does

	if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL, Function) != 1)
#else
	// Store function for later
	mFunction = Function;

	// Use old version of init call
	if(EVP_CipherInit(&ctx, rDescription.GetCipher(), NULL, NULL, Function) != 1)
#endif
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}
	
	try
	{
#ifndef HAVE_OLD_SSL
		// Let the description set up everything else
		rDescription.SetupParameters(&ctx);
#else
		// With the old version, a copy needs to be taken first.
		mpDescription = rDescription.Clone();
		// Mark it as not a leak, otherwise static cipher contexts cause supriously memory leaks to be reported
		MEMLEAKFINDER_NOT_A_LEAK(mpDescription);
		mpDescription->SetupParameters(&ctx);
#endif
	}
	catch(...)
	{
		EVP_CIPHER_CTX_cleanup(&ctx);
		throw;
	}

	// mark as initialised
	mInitialised = true;
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::Reset()
//		Purpose: Reset the context, so it can be initialised again with a different key
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
void CipherContext::Reset()
{
	if(mInitialised)
	{
		// Clean up
		EVP_CIPHER_CTX_cleanup(&ctx);
		mInitialised = false;
	}
#ifdef HAVE_OLD_SSL
	if(mpDescription != 0)
	{
		delete mpDescription;
		mpDescription = 0;
	}
#endif
	mWithinTransform = false;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::Begin()
//		Purpose: Begin a transformation
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
void CipherContext::Begin()
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}

	// Warn if in a transformation (not an error, because a context might not have been finalised if an exception occured)
	if(mWithinTransform)
	{
		TRACE0("CipherContext::Begin called when context flagged as within a transform\n");
	}

	// Initialise the cipher context again
	if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}
	
	// Mark as being within a transform
	mWithinTransform = true;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::Transform(void *, int, const void *, int)
//		Purpose: Transforms the data in the in buffer to the out buffer. If pInBuffer == 0 && InLength == 0
//				 then Final() is called instead.
//				 Returns the number of bytes placed in the out buffer.
//				 There must be room in the out buffer for all the data in the in buffer.
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}
	
	if(!mWithinTransform)
	{
		THROW_EXCEPTION(CipherException, BeginNotCalled)
	}

	// Check parameters
	if(pOutBuffer == 0 || OutLength < 0 || (pInBuffer != 0 && InLength <= 0) || (pInBuffer == 0 && InLength != 0))
	{
		THROW_EXCEPTION(CipherException, BadArguments)
	}
	
	// Is this the final call?
	if(pInBuffer == 0)
	{
		return Final(pOutBuffer, OutLength);
	}
	
	// Check output buffer size
	if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx)))
	{
		THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
	}
	
	// Do the transform
	int outLength = OutLength;
	if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPUpdateFailure)
	}

	return outLength;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::Final(void *, int)
//		Purpose: Transforms the data as per Transform, and returns the final data in the out buffer.
//				 Returns the number of bytes written in the out buffer.
//				 Two main causes of exceptions being thrown: 1) Data is corrupt, and so the end isn't
//				 padded properly. 2) Padding is off, and the data to be encrypted isn't a multiple
//				 of a block long.
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
int CipherContext::Final(void *pOutBuffer, int OutLength)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}

	if(!mWithinTransform)
	{
		THROW_EXCEPTION(CipherException, BeginNotCalled)
	}

	// Check parameters
	if(pOutBuffer == 0 || OutLength < 0)
	{
		THROW_EXCEPTION(CipherException, BadArguments)
	}

	// Check output buffer size
	if(OutLength < (2 * EVP_CIPHER_CTX_block_size(&ctx)))
	{
		THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
	}
	
	// Do the transform
	int outLength = OutLength;
#ifndef HAVE_OLD_SSL
	if(EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPFinalFailure)
	}
#else
	OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength);
#endif
	
	mWithinTransform = false;

	return outLength;
}


#ifdef HAVE_OLD_SSL
// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::OldOpenSSLFinal(unsigned char *, int &)
//		Purpose: The old version of OpenSSL needs more work doing to finalise the cipher,
//				 and reset it so that it's ready for another go.
//		Created: 27/3/04
//
// --------------------------------------------------------------------------
void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut)
{
	// Old version needs to use a different form, and then set up the cipher again for next time around
	int outLength = rOutLengthOut;
	// Have to emulate padding off...
	int blockSize = EVP_CIPHER_CTX_block_size(&ctx);
	if(mPaddingOn)
	{
		// Just use normal final call
		if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1)
		{
			THROW_EXCEPTION(CipherException, EVPFinalFailure)
		}
	}
	else
	{
		// Padding is off. OpenSSL < 0.9.7 doesn't support this, so it has to be
		// bodged in there. Which isn't nice.
		if(mFunction == Decrypt)
		{
			// NASTY -- fiddling around with internals like this is bad.
			// But only way to get this working on old versions of OpenSSL.
			if(!EVP_EncryptUpdate(&ctx,Buffer,&outLength,ctx.buf,0)
				|| outLength != blockSize)
			{
				THROW_EXCEPTION(CipherException, EVPFinalFailure)
			}
			// Clean up
			EVP_CIPHER_CTX_cleanup(&ctx);
		}
		else
		{
			// Check that the length is correct
			if((ctx.buf_len % blockSize) != 0)
			{
				THROW_EXCEPTION(CipherException, EVPFinalFailure)
			}
			// For encryption, assume that the last block entirely is
			// padding, and remove it.
			char temp[1024];
			outLength = sizeof(temp);
			if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1)
			{
				THROW_EXCEPTION(CipherException, EVPFinalFailure)
			}
			// Remove last block, assuming it's full of padded bytes only.
			outLength -= blockSize;
			// Copy anything to the main buffer
			// (can't just use main buffer, because it might overwrite something important)
			if(outLength > 0)
			{
				::memcpy(Buffer, temp, outLength);
			}
		}
	}
	// Reinitialise the cipher for the next time around
	if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL, mFunction) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}
	mpDescription->SetupParameters(&ctx);

	// Update length for caller
	rOutLengthOut = outLength;
}
#endif

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::InSizeForOutBufferSize(int)
//		Purpose: Returns the maximum amount of data that can be sent in
//				 given a output buffer size.
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
int CipherContext::InSizeForOutBufferSize(int OutLength)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}

	// Strictly speaking, the *2 is unnecessary. However... 
	// Final() is paranoid, and requires two input blocks of space to work.
	return OutLength - (EVP_CIPHER_CTX_block_size(&ctx) * 2);
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::MaxOutSizeForInBufferSize(int)
//		Purpose: Returns the maximum output size for an input of a given length.
//				 Will tend to over estimate, as it needs to allow space for Final() to be called.
//		Created: 3/12/03
//
// --------------------------------------------------------------------------
int CipherContext::MaxOutSizeForInBufferSize(int InLength)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}

	// Final() is paranoid, and requires two input blocks of space to work, and so we need to add
	// three blocks on to be absolutely sure.
	return InLength + (EVP_CIPHER_CTX_block_size(&ctx) * 3);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::TransformBlock(void *, int, const void *, int)
//		Purpose: Transform one block to another all in one go, no Final required.
//		Created: 1/12/03
//
// --------------------------------------------------------------------------
int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}
	
	// Warn if in a transformation
	if(mWithinTransform)
	{
		TRACE0("CipherContext::TransformBlock called when context flagged as within a transform\n");
	}

	// Check output buffer size
	if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx)))
	{
		// Check if padding is off, in which case the buffer can be smaller
		if(!mPaddingOn && OutLength <= InLength)
		{
			// This is OK.
		}
		else
		{
			THROW_EXCEPTION(CipherException, OutputBufferTooSmall);
		}
	}
	
	// Initialise the cipher context again
	if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}
	
	// Do the entire block
	int outLength = 0;
	try
	{
		// Update
		outLength = OutLength;
		if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
		{
			THROW_EXCEPTION(CipherException, EVPUpdateFailure)
		}
		// Finalise
		int outLength2 = OutLength - outLength;
#ifndef HAVE_OLD_SSL
		if(EVP_CipherFinal_ex(&ctx, ((unsigned char*)pOutBuffer) + outLength, &outLength2) != 1)
		{
			THROW_EXCEPTION(CipherException, EVPFinalFailure)
		}
#else
		OldOpenSSLFinal(((unsigned char*)pOutBuffer) + outLength, outLength2);
#endif
		outLength += outLength2;
	}
	catch(...)
	{
		// Finalise the context, so definately ready for the next caller
		int outs = OutLength;
#ifndef HAVE_OLD_SSL
		EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outs);
#else
		OldOpenSSLFinal((unsigned char*)pOutBuffer, outs);
#endif
		throw;
	}

	return outLength;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::GetIVLength()
//		Purpose: Returns the size of the IV for this context
//		Created: 3/12/03
//
// --------------------------------------------------------------------------
int CipherContext::GetIVLength()
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}
	
	return EVP_CIPHER_CTX_iv_length(&ctx);
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::SetIV(const void *)
//		Purpose: Sets the IV for this context (must be correctly sized, use GetIVLength)
//		Created: 3/12/03
//
// --------------------------------------------------------------------------
void CipherContext::SetIV(const void *pIV)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}
	
	// Warn if in a transformation
	if(mWithinTransform)
	{
		TRACE0("CipherContext::SetIV called when context flagged as within a transform\n");
	}

	// Set IV
	if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}

#ifdef HAVE_OLD_SSL
	// Update description
	if(mpDescription != 0)
	{
		mpDescription->SetIV(pIV);
	}
#endif
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::SetRandomIV(int &)
//		Purpose: Set a random IV for the context, and return a pointer to the IV used,
//				 and the length of this IV in the rLengthOut arg.
//		Created: 3/12/03
//
// --------------------------------------------------------------------------
const void *CipherContext::SetRandomIV(int &rLengthOut)
{
	if(!mInitialised)
	{
		THROW_EXCEPTION(CipherException, NotInitialised)
	}
	
	// Warn if in a transformation
	if(mWithinTransform)
	{
		TRACE0("CipherContext::SetRandomIV called when context flagged as within a transform\n");
	}

	// Get length of IV
	unsigned int ivLen = EVP_CIPHER_CTX_iv_length(&ctx);
	if(ivLen > sizeof(mGeneratedIV))
	{
		THROW_EXCEPTION(CipherException, IVSizeImplementationLimitExceeded)
	}
	
	// Generate some random data
	Random::Generate(mGeneratedIV, ivLen);

	// Set IV
	if(EVP_CipherInit(&ctx, NULL, NULL, mGeneratedIV, -1) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPInitFailure)
	}	

#ifdef HAVE_OLD_SSL
	// Update description
	if(mpDescription != 0)
	{
		mpDescription->SetIV(mGeneratedIV);
	}
#endif

	// Return the IV and it's length
	rLengthOut = ivLen;
	return mGeneratedIV;
}


// --------------------------------------------------------------------------
//
// Function
//		Name:    CipherContext::UsePadding(bool)
//		Purpose: Set whether or not the context uses padding. 
//		Created: 12/12/03
//
// --------------------------------------------------------------------------
void CipherContext::UsePadding(bool Padding)
{
#ifndef HAVE_OLD_SSL
	if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1)
	{
		THROW_EXCEPTION(CipherException, EVPSetPaddingFailure)
	}
#endif
	mPaddingOn = Padding;
}





syntax highlighted by Code2HTML, v. 0.9.1