From 99f8ce096bc5569adbfea1911dbcda24c28d8d8b Mon Sep 17 00:00:00 2001 From: Ben Summers Date: Fri, 14 Oct 2005 08:50:54 +0000 Subject: Box Backup 0.09 with a few tweeks --- lib/crypto/CipherContext.cpp | 615 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100755 lib/crypto/CipherContext.cpp (limited to 'lib/crypto/CipherContext.cpp') diff --git a/lib/crypto/CipherContext.cpp b/lib/crypto/CipherContext.cpp new file mode 100755 index 00000000..42707497 --- /dev/null +++ b/lib/crypto/CipherContext.cpp @@ -0,0 +1,615 @@ +// -------------------------------------------------------------------------- +// +// 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 PLATFORM_OLD_OPENSSL + , 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL + // 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL +// -------------------------------------------------------------------------- +// +// 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL + 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 PLATFORM_OLD_OPENSSL + // 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 PLATFORM_OLD_OPENSSL + // 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 PLATFORM_OLD_OPENSSL + if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1) + { + THROW_EXCEPTION(CipherException, EVPSetPaddingFailure) + } +#endif + mPaddingOn = Padding; +} + + + -- cgit v1.2.3