// 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