You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2219 lines
82 KiB
2219 lines
82 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================
|
|
|
|
// Note: not using precompiled headers. The crypto++ headers create gunk that anyone including them
|
|
// has to link to, so they can't be included in the global project file. They also contain
|
|
// string functions which are deprecated by the global project file so they can't be included after, either.
|
|
// So we can't use the global precompiled header, need to manually include the things we need.
|
|
|
|
#include "winlite.h"
|
|
|
|
#ifdef POSIX
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
/* this stuff needs to be before the crypto headers, as it relies on memdbg, which doesn't
|
|
do the right thing in the face of xdebug getting included below */
|
|
// tier0
|
|
//#include "tier0/tier0.h"
|
|
#include "tier0/basetypes.h"
|
|
#include "tier0/vprof.h"
|
|
//#include "constants.h"
|
|
#include "vstdlib/vstdlib.h"
|
|
#include "strtools.h"
|
|
//#include "version.h"
|
|
//#include "globals.h"
|
|
//#include "ivalidate.h"
|
|
#include "tier1/utlvector.h"
|
|
#include "simplebitstring.h"
|
|
#include "tier1/checksum_sha1.h"
|
|
#include "tier0/memdbgon.h"
|
|
#include "tier0/tslist.h"
|
|
#include "tier0/memdbgoff.h"
|
|
|
|
|
|
#ifdef ENABLE_OPENSSLCONNECTION
|
|
#define USE_OPENSSL_AES_DECRYPT 1
|
|
#endif
|
|
|
|
#ifdef USE_OPENSSL_AES_DECRYPT
|
|
// openssl optimized AES routines
|
|
#include "openssl/aes.h"
|
|
#if defined(_M_IX86) || defined (_M_X64) || defined(__i386__) || defined(__x86_64__)
|
|
#include <emmintrin.h>
|
|
#endif
|
|
#endif
|
|
|
|
// crypto ++
|
|
#include "tier0/valve_off.h"
|
|
#include "../external/crypto++-5.6.3/cryptopushdisablewarnings.h"
|
|
#if _MSC_VER < 1400 // doesn't work with vc8, things below need xdebug
|
|
#define _XDEBUG_ // keep crypto++-5.2 from including xdebug
|
|
// these are defined in xdebug and used in some subsequent headers, define them to be our version
|
|
#define _NEW_CRT new
|
|
#define _DELETE_CRT(_P) delete (_P)
|
|
#define _DELETE_CRT_VEC(_P) delete[] (_P)
|
|
#define _STRING_CRT string
|
|
#endif
|
|
#define CRYPTOPP_DLL
|
|
#undef min
|
|
#undef max
|
|
#undef Verify
|
|
#define VPROF_BUDGETGROUP_ENCRYPTION _T("Encryption")
|
|
#define SPEW_CRYPTO "crypto"
|
|
const int k_cMedBuff = 1024; // medium buffer
|
|
|
|
#if defined(GNUC)
|
|
#pragma GCC diagnostic ignored "-Wshadow"
|
|
#endif
|
|
|
|
#include "../external/crypto++-5.6.3/cryptlib.h"
|
|
#include "../external/crypto++-5.6.3/osrng.h"
|
|
#include "../external/crypto++-5.6.3/crc.h"
|
|
#include "../external/crypto++-5.6.3/modes.h"
|
|
#include "../external/crypto++-5.6.3/files.h"
|
|
#include "../external/crypto++-5.6.3/hex.h"
|
|
#include "../external/crypto++-5.6.3/base64.h"
|
|
#include "../external/crypto++-5.6.3/base32.h"
|
|
#include "../external/crypto++-5.6.3/words.h"
|
|
#include "../external/crypto++-5.6.3/rsa.h"
|
|
#include "../external/crypto++-5.6.3/aes.h"
|
|
#include "../external/crypto++-5.6.3/hmac.h"
|
|
#include "../external/crypto++-5.6.3/zlib.h"
|
|
#include "../external/crypto++-5.6.3/gzip.h"
|
|
#include "../external/crypto++-5.6.3/pwdbased.h"
|
|
using namespace CryptoPP;
|
|
typedef AutoSeededX917RNG<AES> CAutoSeededRNG;
|
|
#include "../external/crypto++-5.6.3/cryptopopdisablewarnings.h"
|
|
|
|
#if defined(GNUC)
|
|
#pragma GCC diagnostic warning "-Wshadow"
|
|
#endif
|
|
|
|
#include "tier0/memdbgon.h"
|
|
#include "tier0/valve_on.h"
|
|
|
|
#include "crypto.h"
|
|
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
|
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
|
|
|
|
|
// list of auto-seeded RNG pointers
|
|
// these are very expensive to construct, so it makes sense to cache them
|
|
CTSList<CAutoSeededRNG> g_tslistPAutoSeededRNG;
|
|
|
|
// to avoid deconstructor order issuses we allow to manually free the list
|
|
void FreeListRNG()
|
|
{
|
|
g_tslistPAutoSeededRNG.Purge();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: thread-safe access to a pool of cryptoPP random number generators
|
|
//-----------------------------------------------------------------------------
|
|
class CPoolAllocatedRNG
|
|
{
|
|
public:
|
|
CPoolAllocatedRNG()
|
|
{
|
|
m_pRNGNode = g_tslistPAutoSeededRNG.Pop();
|
|
if ( !m_pRNGNode )
|
|
{
|
|
m_pRNGNode = new CTSList<CAutoSeededRNG>::Node_t;
|
|
}
|
|
}
|
|
|
|
~CPoolAllocatedRNG()
|
|
{
|
|
g_tslistPAutoSeededRNG.Push( m_pRNGNode );
|
|
}
|
|
|
|
CAutoSeededRNG &GetRNG()
|
|
{
|
|
return m_pRNGNode->elem;
|
|
}
|
|
|
|
private:
|
|
CTSList<CAutoSeededRNG>::Node_t *m_pRNGNode;
|
|
};
|
|
|
|
// force run this static construction code
|
|
class CGlobalInitConstructor
|
|
{
|
|
public:
|
|
CGlobalInitConstructor()
|
|
{
|
|
// we have to use this function once since the underlying static constructor
|
|
// is not thread safe. See use of MicrosoftCryptoProvider in Crypto++
|
|
CAutoSeededRNG rng;
|
|
rng.GenerateByte();
|
|
}
|
|
};
|
|
|
|
volatile static CGlobalInitConstructor s_StaticCryptoConstructor;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encrypts the specified data with the specified key. Uses AES (Rijndael) symmetric
|
|
// encryption. The encrypted data may then be decrypted by calling SymmetricDecrypt
|
|
// with the same key.
|
|
// Input: pubPlaintextData - Data to be encrypted
|
|
// cubPlaintextData - Size of data to be encrypted
|
|
// pIV - Pointer to initialization vector
|
|
// cubIV - Size of initialization vector
|
|
// pubEncryptedData - Pointer to buffer to receive encrypted data
|
|
// pcubEncryptedData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for encrypted data. When the method returns, this will contain
|
|
// the actual size of the encrypted data.
|
|
// pubKey - the key to encrypt the data with
|
|
// cubKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if encryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::SymmetricEncryptWithIV( const uint8 *pubPlaintextData, const uint32 cubPlaintextData,
|
|
const uint8 *pIV, const uint32 cubIV,
|
|
uint8 *pubEncryptedData, uint32 *pcubEncryptedData,
|
|
const uint8 *pubKey, const uint32 cubKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::SymmetricEncrypt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubPlaintextData );
|
|
Assert( cubPlaintextData );
|
|
Assert( pubEncryptedData );
|
|
Assert( pcubEncryptedData );
|
|
Assert( *pcubEncryptedData );
|
|
Assert( pubKey );
|
|
Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen
|
|
|
|
bool bRet = false;
|
|
|
|
uint32 cubEncryptedData = *pcubEncryptedData; // remember how big the caller's buffer is
|
|
bool bUseTempBuffer = false;
|
|
uint8 *pTemp = pubEncryptedData;
|
|
|
|
|
|
//
|
|
// Crypto++ does not play well with overlapping buffers. If the buffers are
|
|
// overlapping, then allocate some temp space to use for the encryption.
|
|
//
|
|
// It does work fine with _identical_ buffers.
|
|
//
|
|
if ( ( pubEncryptedData + cubEncryptedData >= pubPlaintextData ) &&
|
|
( pubPlaintextData + cubPlaintextData >= pubEncryptedData ) )
|
|
{
|
|
pTemp = new uint8[cubEncryptedData];
|
|
bUseTempBuffer = true;
|
|
}
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
if ( pTemp != NULL )
|
|
{
|
|
AESEncryption aesEncrypt( pubKey, cubKey );
|
|
|
|
byte rgubIVEncrypted[k_cMedBuff];
|
|
Assert( Q_ARRAYSIZE( rgubIVEncrypted ) >= aesEncrypt.BlockSize() );
|
|
Assert( pIV != NULL && cubIV >= aesEncrypt.BlockSize() );
|
|
|
|
ArraySink * pOutputSink = new ArraySink( pTemp, *pcubEncryptedData );
|
|
|
|
// encrypt the initial vector with the key
|
|
aesEncrypt.ProcessBlock( pIV, rgubIVEncrypted );
|
|
|
|
// store the encrypted IV in the output - the recipient will need it
|
|
pOutputSink->Put( rgubIVEncrypted, aesEncrypt.BlockSize() );
|
|
|
|
// encrypt the message, given the key & IV
|
|
CBC_Mode_ExternalCipher::Encryption cipher( aesEncrypt, pIV );
|
|
// Note: StreamTransformationFilter now owns the pointer to pOutputSink and will
|
|
// free it when the filter goes out of scope and destructs
|
|
StreamTransformationFilter filter( cipher, pOutputSink );
|
|
filter.Put( (byte *) pubPlaintextData, cubPlaintextData );
|
|
filter.MessageEnd();
|
|
// return length of encrypted data to caller
|
|
*pcubEncryptedData = pOutputSink->TotalPutLength();
|
|
// CryptoPP may leave garbage hanging around in the caller's buffer past the stated output length.
|
|
// Just to be safe, zero out caller's buffer from end out output to end of max buffer
|
|
if ( bUseTempBuffer )
|
|
{
|
|
Q_memcpy( pubEncryptedData, pTemp, *pcubEncryptedData );
|
|
}
|
|
Q_memset( pubEncryptedData + *pcubEncryptedData, 0, (cubEncryptedData - *pcubEncryptedData ) );
|
|
bRet = true;
|
|
}
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::SymmetricEncrypt: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
if ( bUseTempBuffer )
|
|
{
|
|
delete[] pTemp;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encrypts the specified data with the specified key. Uses AES (Rijndael) symmetric
|
|
// encryption. The encrypted data may then be decrypted by calling SymmetricDecrypt
|
|
// with the same key. Generates a random initialization vector of the
|
|
// appropriate size.
|
|
// Input: pubPlaintextData - Data to be encrypted
|
|
// cubPlaintextData - Size of data to be encrypted
|
|
// pubEncryptedData - Pointer to buffer to receive encrypted data
|
|
// pcubEncryptedData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for encrypted data. When the method returns, this will contain
|
|
// the actual size of the encrypted data.
|
|
// pubKey - the key to encrypt the data with
|
|
// cubKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if encryption failed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CCrypto::SymmetricEncrypt( const uint8 *pubPlaintextData, const uint32 cubPlaintextData,
|
|
uint8 *pubEncryptedData, uint32 *pcubEncryptedData,
|
|
const uint8 *pubKey, const uint32 cubKey )
|
|
{
|
|
bool bRet = false;
|
|
|
|
//
|
|
// Generate a random IV
|
|
//
|
|
AESEncryption aesEncrypt( pubKey, cubKey );
|
|
byte rgubIV[k_cMedBuff];
|
|
CPoolAllocatedRNG rng;
|
|
rng.GetRNG().GenerateBlock( rgubIV, aesEncrypt.BlockSize() );
|
|
|
|
bRet = SymmetricEncryptWithIV( pubPlaintextData, cubPlaintextData, rgubIV, aesEncrypt.BlockSize(), pubEncryptedData, pcubEncryptedData, pubKey, cubKey );
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
#ifdef USE_OPENSSL_AES_DECRYPT
|
|
|
|
// Local helper to perform AES+CBC decryption using optimized OpenSSL AES routines
|
|
static bool BDecryptAESUsingOpenSSL( const uint8 *pubEncryptedData, uint32 cubEncryptedData, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, AES_KEY *key, const uint8 *pIV )
|
|
{
|
|
COMPILE_TIME_ASSERT( k_nSymmetricBlockSize == 16 );
|
|
|
|
// Block cipher encrypted text must be a multiple of the block size
|
|
if ( cubEncryptedData % k_nSymmetricBlockSize != 0 )
|
|
return false;
|
|
|
|
// Enough input? Requirement is one padded final block
|
|
if ( cubEncryptedData < k_nSymmetricBlockSize )
|
|
return false;
|
|
|
|
// Enough output space for all the full non-final blocks?
|
|
if ( *pcubPlaintextData < cubEncryptedData - k_nSymmetricBlockSize )
|
|
return false;
|
|
|
|
uint8 rgubWorking[k_nSymmetricBlockSize];
|
|
uint32 nDecrypted = 0;
|
|
|
|
// Process non-final blocks
|
|
#if defined(_M_IX86) || defined (_M_X64) || defined(__i386__) || defined(__x86_64__)
|
|
// ... believe it or not, Steam client on Windows supports Athlon XP without SSE2
|
|
if ( !IsWindows() || GetCPUInformation().m_bSSE2 )
|
|
{
|
|
while ( nDecrypted < cubEncryptedData - k_nSymmetricBlockSize )
|
|
{
|
|
AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key );
|
|
__m128i m128Temp = _mm_xor_si128( _mm_loadu_si128( (__m128i*)pIV ), _mm_loadu_si128( (__m128i*)rgubWorking ) );
|
|
pIV = pubEncryptedData + nDecrypted;
|
|
nDecrypted += k_nSymmetricBlockSize;
|
|
_mm_storeu_si128( (__m128i* RESTRICT)( pubPlaintextData + nDecrypted - k_nSymmetricBlockSize ), m128Temp );
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
while ( nDecrypted < cubEncryptedData - k_nSymmetricBlockSize )
|
|
{
|
|
AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key );
|
|
for ( int i = 0; i < k_nSymmetricBlockSize; ++i )
|
|
pubPlaintextData[nDecrypted + i] = rgubWorking[i] ^ pIV[i];
|
|
pIV = pubEncryptedData + nDecrypted;
|
|
nDecrypted += k_nSymmetricBlockSize;
|
|
}
|
|
}
|
|
|
|
// Process final block into rgubWorking for padding inspection
|
|
Assert( nDecrypted == cubEncryptedData - k_nSymmetricBlockSize );
|
|
AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key );
|
|
for ( int i = 0; i < k_nSymmetricBlockSize; ++i )
|
|
rgubWorking[i] ^= pIV[i];
|
|
|
|
// Get final block padding length and make sure it is backfilled properly (PKCS#5)
|
|
uint8 pad = rgubWorking[ k_nSymmetricBlockSize - 1 ];
|
|
if ( pad < 1 || pad > k_nSymmetricBlockSize )
|
|
return false;
|
|
for ( int i = k_nSymmetricBlockSize - pad; i < k_nSymmetricBlockSize; ++i )
|
|
if ( rgubWorking[i] != pad )
|
|
return false;
|
|
|
|
// Check that we have enough space for final bytes
|
|
if ( *pcubPlaintextData < nDecrypted + k_nSymmetricBlockSize - pad )
|
|
return false;
|
|
|
|
// Write any non-pad bytes from rgubWorking to pubPlaintextData
|
|
for ( int i = 0; i < k_nSymmetricBlockSize - pad; ++i )
|
|
pubPlaintextData[nDecrypted++] = rgubWorking[i];
|
|
|
|
// The old CryptoPP path zeros out the entire destination buffer, but that
|
|
// behavior isn't documented or even expected. We'll just zero out one byte
|
|
// in case anyone relies on string termination, but that zero isn't counted.
|
|
if ( *pcubPlaintextData > nDecrypted )
|
|
pubPlaintextData[nDecrypted] = 0;
|
|
|
|
*pcubPlaintextData = nDecrypted;
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
|
|
/* function, not method */
|
|
static bool SymmetricDecryptWorker( const uint8 *pubEncryptedData, uint32 cubEncryptedData,
|
|
const uint8 * pIV, uint32 cubIV,
|
|
uint8 *pubPlaintextData, uint32 *pcubPlaintextData,
|
|
AESDecryption &aesDecrypt )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::SymmetricDecrypt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubEncryptedData );
|
|
Assert( cubEncryptedData);
|
|
Assert( pIV );
|
|
Assert( cubIV );
|
|
Assert( pubPlaintextData );
|
|
Assert( pcubPlaintextData );
|
|
Assert( *pcubPlaintextData );
|
|
|
|
bool bRet = false;
|
|
|
|
uint32 cubPlaintextData = *pcubPlaintextData; // remember how big the caller's buffer is
|
|
bool bUseTempBuffer = false;
|
|
uint8* pTemp = pubPlaintextData;
|
|
|
|
//
|
|
// Crypto++ does not play nice with decrypting in place. If the buffers are
|
|
// overlapping, then allocate some temp space to use for the decryption.
|
|
//
|
|
// It does work fine with _identical_ buffers, but due to the way we store
|
|
// the IV in the returned encrypted data we never actually hit that case.
|
|
//
|
|
if ( ( pubEncryptedData + cubEncryptedData >= pubPlaintextData ) &&
|
|
( pubPlaintextData + cubPlaintextData >= pubEncryptedData ) )
|
|
{
|
|
pTemp = new uint8[cubPlaintextData];
|
|
bUseTempBuffer = true;
|
|
}
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
if ( pTemp != NULL )
|
|
{
|
|
CryptoPP::ArraySink* pOutputSink = new CryptoPP::ArraySink( pTemp, *pcubPlaintextData );
|
|
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbc( aesDecrypt, pIV );
|
|
// Note: StreamTransformationFilter now owns the pointer to pOutputSink and will
|
|
// free it when the filter goes out of scope and destructs
|
|
CryptoPP::StreamTransformationFilter padding( cbc, pOutputSink );
|
|
padding.Put( pubEncryptedData, cubEncryptedData );
|
|
padding.MessageEnd();
|
|
// return length of decrypted data to caller
|
|
*pcubPlaintextData = pOutputSink->TotalPutLength();
|
|
// CryptoPP may leave garbage hanging around in the caller's buffer past the stated output length.
|
|
// Just to be safe, zero out caller's buffer from end out output to end of max buffer
|
|
if ( bUseTempBuffer )
|
|
{
|
|
Q_memcpy( pubPlaintextData, pTemp, *pcubPlaintextData );
|
|
}
|
|
Q_memset( pubPlaintextData + *pcubPlaintextData, 0, (cubPlaintextData - *pcubPlaintextData ) );
|
|
|
|
bRet = true;
|
|
}
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 4, "CCrypto::SymmetricDecrypt: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
if ( bUseTempBuffer )
|
|
{
|
|
delete[] pTemp;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decrypts the specified data with the specified key. Uses AES (Rijndael) symmetric
|
|
// decryption.
|
|
// Input: pubEncryptedData - Data to be decrypted
|
|
// cubEncryptedData - Size of data to be decrypted
|
|
// pubPlaintextData - Pointer to buffer to receive decrypted data
|
|
// pcubPlaintextData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for decrypted data. When the method returns, this will contain
|
|
// the actual size of the decrypted data.
|
|
// pubKey - the key to decrypt the data with
|
|
// cubKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if decryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::SymmetricDecrypt( const uint8 *pubEncryptedData, uint32 cubEncryptedData,
|
|
uint8 *pubPlaintextData, uint32 *pcubPlaintextData,
|
|
const uint8 *pubKey, const uint32 cubKey )
|
|
{
|
|
Assert( pubEncryptedData );
|
|
Assert( cubEncryptedData);
|
|
Assert( pubPlaintextData );
|
|
Assert( pcubPlaintextData );
|
|
Assert( *pcubPlaintextData );
|
|
Assert( pubKey );
|
|
Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen
|
|
|
|
// the initialization vector (IV) must be stored in the first block of bytes.
|
|
// If the size of encrypted data is not at least the block size, it is not valid
|
|
if ( cubEncryptedData < k_nSymmetricBlockSize )
|
|
return false;
|
|
|
|
#ifdef USE_OPENSSL_AES_DECRYPT
|
|
|
|
AES_KEY key;
|
|
if ( AES_set_decrypt_key( pubKey, cubKey * 8, &key ) < 0 )
|
|
return false;
|
|
|
|
// Our first block is straight AES block encryption of IV with user key, no XOR.
|
|
uint8 rgubIV[ k_nSymmetricBlockSize ];
|
|
AES_decrypt( pubEncryptedData, rgubIV, &key );
|
|
pubEncryptedData += k_nSymmetricBlockSize;
|
|
cubEncryptedData -= k_nSymmetricBlockSize;
|
|
|
|
return BDecryptAESUsingOpenSSL( pubEncryptedData, cubEncryptedData, pubPlaintextData, pcubPlaintextData, &key, rgubIV );
|
|
|
|
#else
|
|
|
|
AESDecryption aesDecrypt( pubKey, cubKey );
|
|
Assert( k_nSymmetricBlockSize == aesDecrypt.BlockSize() );
|
|
|
|
// Decrypt the IV
|
|
byte rgubIV[k_cMedBuff];
|
|
Assert( Q_ARRAYSIZE( rgubIV ) >= aesDecrypt.BlockSize() );
|
|
aesDecrypt.ProcessBlock( pubEncryptedData, rgubIV );
|
|
|
|
// We have now consumed the IV, so remove it from the front of the message
|
|
pubEncryptedData += k_nSymmetricBlockSize;
|
|
cubEncryptedData -= k_nSymmetricBlockSize;
|
|
|
|
// given the IV stored in the message, and the key, decrypt the message
|
|
return SymmetricDecryptWorker( pubEncryptedData, cubEncryptedData,
|
|
rgubIV, aesDecrypt.BlockSize(),
|
|
pubPlaintextData, pcubPlaintextData,
|
|
aesDecrypt );
|
|
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decrypts the specified data with the specified key. Uses AES (Rijndael) symmetric
|
|
// decryption.
|
|
// Input: pubEncryptedData - Data to be decrypted
|
|
// cubEncryptedData - Size of data to be decrypted
|
|
// pIV - Initialization vector. Byte array one block in size.
|
|
// cubIV - size of IV. This should be 16 (one block, 128 bits)
|
|
// pubPlaintextData - Pointer to buffer to receive decrypted data
|
|
// pcubPlaintextData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for decrypted data. When the method returns, this will contain
|
|
// the actual size of the decrypted data.
|
|
// pubKey - the key to decrypt the data with
|
|
// cubKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if decryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::SymmetricDecryptWithIV( const uint8 *pubEncryptedData, uint32 cubEncryptedData,
|
|
const uint8 * pIV, uint32 cubIV,
|
|
uint8 *pubPlaintextData, uint32 *pcubPlaintextData,
|
|
const uint8 *pubKey, const uint32 cubKey )
|
|
{
|
|
Assert( pubEncryptedData );
|
|
Assert( cubEncryptedData);
|
|
Assert( pIV );
|
|
Assert( cubIV );
|
|
Assert( pubPlaintextData );
|
|
Assert( pcubPlaintextData );
|
|
Assert( *pcubPlaintextData );
|
|
Assert( pubKey );
|
|
Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen
|
|
|
|
// IV input into CBC must be exactly one block size
|
|
if ( cubIV != k_nSymmetricBlockSize )
|
|
return false;
|
|
|
|
#ifdef USE_OPENSSL_AES_DECRYPT
|
|
|
|
AES_KEY key;
|
|
if ( AES_set_decrypt_key( pubKey, cubKey * 8, &key ) < 0 )
|
|
return false;
|
|
|
|
return BDecryptAESUsingOpenSSL( pubEncryptedData, cubEncryptedData, pubPlaintextData, pcubPlaintextData, &key, pIV );
|
|
|
|
#else
|
|
|
|
AESDecryption aesDecrypt( pubKey, cubKey );
|
|
Assert( k_nSymmetricBlockSize == aesDecrypt.BlockSize() );
|
|
|
|
return SymmetricDecryptWorker( pubEncryptedData, cubEncryptedData,
|
|
pIV, cubIV,
|
|
pubPlaintextData, pcubPlaintextData,
|
|
aesDecrypt );
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For specified plaintext data size, returns what size of symmetric
|
|
// encrypted data will be
|
|
//-----------------------------------------------------------------------------
|
|
uint32 CCrypto::GetSymmetricEncryptedSize( uint32 cubPlaintextData )
|
|
{
|
|
// empirically determined encrypted size as function of plaintext size for AES encryption
|
|
uint k_cubBlock = 16;
|
|
uint k_cubHeader = 16;
|
|
return k_cubHeader + ( ( cubPlaintextData / k_cubBlock ) * k_cubBlock ) + k_cubBlock;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encrypts the specified data with the specified text password.
|
|
// Uses the SHA256 hash of the password as the key for AES (Rijndael) symmetric
|
|
// encryption. A SHA1 HMAC of the result is appended, for authentication on
|
|
// the receiving end.
|
|
// The encrypted data may then be decrypted by calling DecryptWithPasswordAndAuthenticate
|
|
// with the same password.
|
|
// Input: pubPlaintextData - Data to be encrypted
|
|
// cubPlaintextData - Size of data to be encrypted
|
|
// pubEncryptedData - Pointer to buffer to receive encrypted data
|
|
// pcubEncryptedData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for encrypted data. When the method returns, this will contain
|
|
// the actual size of the encrypted data.
|
|
// pchPassword - text password
|
|
// Output: true if successful, false if encryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::EncryptWithPasswordAndHMAC( const uint8 *pubPlaintextData, uint32 cubPlaintextData,
|
|
uint8 * pubEncryptedData, uint32 * pcubEncryptedData,
|
|
const char *pchPassword )
|
|
{
|
|
//
|
|
// Generate a random IV
|
|
//
|
|
byte rgubIV[k_nSymmetricBlockSize];
|
|
CPoolAllocatedRNG rng;
|
|
rng.GetRNG().GenerateBlock( rgubIV, k_nSymmetricBlockSize );
|
|
|
|
return EncryptWithPasswordAndHMACWithIV( pubPlaintextData, cubPlaintextData, rgubIV, k_nSymmetricBlockSize, pubEncryptedData, pcubEncryptedData, pchPassword );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encrypts the specified data with the specified text password.
|
|
// Uses the SHA256 hash of the password as the key for AES (Rijndael) symmetric
|
|
// encryption. A SHA1 HMAC of the result is appended, for authentication on
|
|
// the receiving end.
|
|
// The encrypted data may then be decrypted by calling DecryptWithPasswordAndAuthenticate
|
|
// with the same password.
|
|
// Input: pubPlaintextData - Data to be encrypted
|
|
// cubPlaintextData - Size of data to be encrypted
|
|
// pIV - IV to use for AES encryption. Should be random and never used before unless you know
|
|
// exactly what you're doing.
|
|
// cubIV - size of the IV - should be same ase the AES blocksize.
|
|
// pubEncryptedData - Pointer to buffer to receive encrypted data
|
|
// pcubEncryptedData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for encrypted data. When the method returns, this will contain
|
|
// the actual size of the encrypted data.
|
|
// pchPassword - text password
|
|
// Output: true if successful, false if encryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::EncryptWithPasswordAndHMACWithIV( const uint8 *pubPlaintextData, uint32 cubPlaintextData,
|
|
const uint8 * pIV, uint32 cubIV,
|
|
uint8 * pubEncryptedData, uint32 * pcubEncryptedData,
|
|
const char *pchPassword )
|
|
{
|
|
uint8 rgubKey[k_nSymmetricKeyLen];
|
|
if ( !pchPassword || !pchPassword[0] )
|
|
return false;
|
|
|
|
if ( !cubPlaintextData )
|
|
return false;
|
|
|
|
uint32 cubBuffer = *pcubEncryptedData;
|
|
uint32 cubExpectedResult = GetSymmetricEncryptedSize( cubPlaintextData ) + sizeof( SHADigest_t );
|
|
|
|
if ( cubBuffer < cubExpectedResult )
|
|
return false;
|
|
|
|
try
|
|
{
|
|
CryptoPP::SHA256().CalculateDigest( rgubKey, (const uint8 *)pchPassword, Q_strlen( pchPassword ) );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 4, "CCrypto::EncryptWithPassword: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
return false;
|
|
}
|
|
|
|
bool bRet = SymmetricEncryptWithIV( pubPlaintextData, cubPlaintextData, pIV, cubIV, pubEncryptedData, pcubEncryptedData, rgubKey, k_nSymmetricKeyLen );
|
|
if ( bRet )
|
|
{
|
|
// calc HMAC
|
|
uint32 cubEncrypted = *pcubEncryptedData;
|
|
*pcubEncryptedData += sizeof( SHADigest_t );
|
|
if ( cubBuffer < *pcubEncryptedData )
|
|
return false;
|
|
|
|
SHADigest_t *pHMAC = (SHADigest_t*)( pubEncryptedData + cubEncrypted );
|
|
bRet = CCrypto::GenerateHMAC( pubEncryptedData, cubEncrypted, rgubKey, k_nSymmetricKeyLen, pHMAC );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decrypts the specified data with the specified password. Uses AES (Rijndael) symmetric
|
|
// decryption. First, the HMAC is verified - if it is not correct, then we know that
|
|
// the key is incorrect or the data is corrupted, and the decryption fails.
|
|
// Input: pubEncryptedData - Data to be decrypted
|
|
// cubEncryptedData - Size of data to be decrypted
|
|
// pubPlaintextData - Pointer to buffer to receive decrypted data
|
|
// pcubPlaintextData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for decrypted data. When the method returns, this will contain
|
|
// the actual size of the decrypted data.
|
|
// pchPassword - the text password to decrypt the data with
|
|
// Output: true if successful, false if decryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::DecryptWithPasswordAndAuthenticate( const uint8 * pubEncryptedData, uint32 cubEncryptedData,
|
|
uint8 * pubPlaintextData, uint32 * pcubPlaintextData,
|
|
const char *pchPassword )
|
|
{
|
|
uint8 rgubKey[k_nSymmetricKeyLen];
|
|
if ( !pchPassword || !pchPassword[0] )
|
|
return false;
|
|
|
|
if ( cubEncryptedData <= sizeof( SHADigest_t ) )
|
|
return false;
|
|
|
|
try
|
|
{
|
|
CryptoPP::SHA256().CalculateDigest( rgubKey, (const uint8 *)pchPassword, Q_strlen( pchPassword ) );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 4, "CCrypto::EncryptWithPassword: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
return false;
|
|
}
|
|
|
|
uint32 cubCiphertext = cubEncryptedData - sizeof( SHADigest_t );
|
|
SHADigest_t *pHMAC = (SHADigest_t*)( pubEncryptedData + cubCiphertext );
|
|
SHADigest_t hmacActual;
|
|
bool bRet = CCrypto::GenerateHMAC( pubEncryptedData, cubCiphertext, rgubKey, k_nSymmetricKeyLen, &hmacActual );
|
|
|
|
if ( bRet )
|
|
{
|
|
// invalid ciphertext or key
|
|
if ( Q_memcmp( &hmacActual, pHMAC, sizeof( SHADigest_t ) ) )
|
|
return false;
|
|
|
|
bRet = SymmetricDecrypt( pubEncryptedData, cubCiphertext, pubPlaintextData, pcubPlaintextData, rgubKey, k_nSymmetricKeyLen );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generates a new pair of private/public RSA keys
|
|
// Input: pubPublicKey - Pointer to buffer to receive public key (should be of size k_nRSAKeyLenMax)
|
|
// pcubPublicKey - Pointer to variable that contains size of pubPublicKey buffer. At exit,
|
|
// this is filled in with the actual size of the public key
|
|
// pubPrivateKey - Pointer to buffer to receive private key (should be of size k_nRSAKeyLenMax)
|
|
// pcubPrivateKey - Pointer to variable that contains size of pubPrivateKey buffer. At exit,
|
|
// this is filled in with the actual size of the private key
|
|
// Output: true if successful, false if key generation failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSAGenerateKeys( uint8 *pubPublicKey, uint32 *pcubPublicKey, uint8 *pubPrivateKey, uint32 *pcubPrivateKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSAGenerateKeys", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
bool bRet = false;
|
|
Assert( pubPublicKey );
|
|
Assert( pcubPublicKey );
|
|
Assert( pubPrivateKey );
|
|
Assert( pcubPrivateKey );
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
// generate private key
|
|
ArraySink arraySinkPrivateKey( pubPrivateKey, *pcubPrivateKey );
|
|
CPoolAllocatedRNG rng;
|
|
RSAES_OAEP_SHA_Decryptor priv( rng.GetRNG(), k_nRSAKeyBits );
|
|
priv.DEREncode( arraySinkPrivateKey );
|
|
*pcubPrivateKey = arraySinkPrivateKey.TotalPutLength();
|
|
// generate public key
|
|
ArraySink arraySinkPublicKey( pubPublicKey, *pcubPublicKey );
|
|
RSAES_OAEP_SHA_Encryptor pub(priv);
|
|
pub.DEREncode( arraySinkPublicKey );
|
|
*pcubPublicKey = arraySinkPublicKey.TotalPutLength();
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAGenerateKeys: crypto++ threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Encrypts the specified data with the specified RSA public key.
|
|
// The encrypted data may then be decrypted by calling RSADecrypt with the
|
|
// corresponding RSA private key.
|
|
// Input: pubPlaintextData - Data to be encrypted
|
|
// cubPlaintextData - Size of data to be encrypted
|
|
// pubEncryptedData - Pointer to buffer to receive encrypted data
|
|
// pcubEncryptedData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for encrypted data. When the method returns, this will contain
|
|
// the actual size of the encrypted data.
|
|
// pubPublicKey - the RSA public key to encrypt the data with
|
|
// cubPublicKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if encryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSAEncrypt( const uint8 *pubPlaintextData, uint32 cubPlaintextData,
|
|
uint8 *pubEncryptedData, uint32 *pcubEncryptedData,
|
|
const uint8 *pubPublicKey, const uint32 cubPublicKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSAEncrypt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
bool bRet = false;
|
|
Assert( cubPlaintextData > 0 ); // must pass in some data
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true );
|
|
RSAES_OAEP_SHA_Encryptor rsaEncryptor( stringSourcePublicKey );
|
|
|
|
// calculate how many blocks of encryption will we need to do
|
|
AssertFatal( rsaEncryptor.FixedMaxPlaintextLength() <= ULONG_MAX );
|
|
uint32 cBlocks = 1 + ( ( cubPlaintextData - 1 ) / (uint32)rsaEncryptor.FixedMaxPlaintextLength() );
|
|
// calculate how big the output will be
|
|
AssertFatal( rsaEncryptor.FixedCiphertextLength() <= ULONG_MAX / cBlocks );
|
|
uint32 cubCipherText = cBlocks * (uint32)rsaEncryptor.FixedCiphertextLength();
|
|
Assert( cubCipherText > 0 );
|
|
|
|
// ensure there is sufficient room in output buffer for result
|
|
if ( cubCipherText > ( *pcubEncryptedData ) )
|
|
{
|
|
AssertMsg2( false, "CCrypto::RSAEncrypt: insufficient output buffer for encryption, needed %d got %d\n",
|
|
cubCipherText, *pcubEncryptedData );
|
|
return false;
|
|
}
|
|
|
|
// encrypt the message, using as many blocks as required
|
|
CPoolAllocatedRNG rng;
|
|
for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ )
|
|
{
|
|
// encrypt either all remaining plaintext, or maximum allowed plaintext per RSA encryption operation
|
|
uint32 cubToEncrypt = min( cubPlaintextData, (uint32)rsaEncryptor.FixedMaxPlaintextLength() );
|
|
// encrypt the plaintext
|
|
rsaEncryptor.Encrypt( rng.GetRNG(), pubPlaintextData, cubToEncrypt, pubEncryptedData );
|
|
// adjust input and output pointers and remaining plaintext byte count
|
|
pubPlaintextData += cubToEncrypt;
|
|
cubPlaintextData -= cubToEncrypt;
|
|
pubEncryptedData += rsaEncryptor.FixedCiphertextLength();
|
|
}
|
|
Assert( 0 == cubPlaintextData ); // should have no remaining plaintext to encrypt
|
|
*pcubEncryptedData = cubCipherText;
|
|
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAEncrypt: Encrypt() threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decrypts the specified data with the specified RSA private key
|
|
// Input: pubEncryptedData - Data to be decrypted
|
|
// cubEncryptedData - Size of data to be decrypted
|
|
// pubPlaintextData - Pointer to buffer to receive decrypted data
|
|
// pcubPlaintextData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for decrypted data. When the method returns, this will contain
|
|
// the actual size of the decrypted data.
|
|
// pubPrivateKey - the RSA private key key to decrypt the data with
|
|
// cubPrivateKey - Size of the key (must be k_nSymmetricKeyLen)
|
|
// Output: true if successful, false if decryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSADecrypt( const uint8 *pubEncryptedData, uint32 cubEncryptedData,
|
|
uint8 *pubPlaintextData, uint32 *pcubPlaintextData,
|
|
const uint8 *pubPrivateKey, const uint32 cubPrivateKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSADecrypt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
bool bRet = false;
|
|
|
|
Assert( cubEncryptedData > 0 ); // must pass in some data
|
|
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true );
|
|
RSAES_OAEP_SHA_Decryptor rsaDecryptor( stringSourcePrivateKey );
|
|
|
|
// calculate how many blocks of decryption will we need to do
|
|
AssertFatal( rsaDecryptor.FixedCiphertextLength() <= ULONG_MAX );
|
|
uint32 cubFixedCiphertextLength = (uint32)rsaDecryptor.FixedCiphertextLength();
|
|
// Ensure encrypted data is valid and has length that is exact multiple of 128 bytes
|
|
if ( 0 != ( cubEncryptedData % cubFixedCiphertextLength ) )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: invalid ciphertext length %d, needs to be a multiple of %d\n",
|
|
cubEncryptedData, cubFixedCiphertextLength );
|
|
return false;
|
|
}
|
|
uint32 cBlocks = cubEncryptedData / cubFixedCiphertextLength;
|
|
|
|
// calculate how big the maximum output will be
|
|
size_t cubMaxPlaintext = rsaDecryptor.MaxPlaintextLength( rsaDecryptor.FixedCiphertextLength() );
|
|
AssertFatal( cubMaxPlaintext <= ULONG_MAX / cBlocks );
|
|
uint32 cubPlaintextDataMax = cBlocks * (uint32)cubMaxPlaintext;
|
|
Assert( cubPlaintextDataMax > 0 );
|
|
// ensure there is sufficient room in output buffer for result
|
|
if ( cubPlaintextDataMax >= ( *pcubPlaintextData ) )
|
|
{
|
|
AssertMsg2( false, "CCrypto::RSADecrypt: insufficient output buffer for decryption, needed %d got %d\n",
|
|
cubPlaintextDataMax, *pcubPlaintextData );
|
|
return false;
|
|
}
|
|
|
|
// decrypt the data, using as many blocks as required
|
|
CPoolAllocatedRNG rng;
|
|
uint32 cubPlaintextData = 0;
|
|
for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ )
|
|
{
|
|
// decrypt one block (always of fixed size)
|
|
int cubToDecrypt = cubFixedCiphertextLength;
|
|
DecodingResult decodingResult = rsaDecryptor.Decrypt( rng.GetRNG(), pubEncryptedData, cubToDecrypt, pubPlaintextData );
|
|
if ( !decodingResult.isValidCoding )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: failed to decrypt\n" );
|
|
return false;
|
|
}
|
|
// adjust input and output pointers and remaining encrypted byte count
|
|
pubEncryptedData += cubToDecrypt;
|
|
cubEncryptedData -= cubToDecrypt;
|
|
pubPlaintextData += decodingResult.messageLength;
|
|
AssertFatal( decodingResult.messageLength <= ULONG_MAX );
|
|
cubPlaintextData += (uint32)decodingResult.messageLength;
|
|
}
|
|
Assert( 0 == cubEncryptedData ); // should have no remaining encrypted data to decrypt
|
|
*pcubPlaintextData = cubPlaintextData;
|
|
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: Decrypt() threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Decrypts the specified data with the specified RSA PUBLIC key,
|
|
// using no padding (eg un-padded signature).
|
|
// Input: pubEncryptedData - Data to be decrypted
|
|
// cubEncryptedData - Size of data to be decrypted
|
|
// pubPlaintextData - Pointer to buffer to receive decrypted data
|
|
// pcubPlaintextData - Pointer to a variable that at time of call contains the size of
|
|
// the receive buffer for decrypted data. When the method returns, this will contain
|
|
// the actual size of the decrypted data.
|
|
// pubPublicKey - the RSA public key key to decrypt the data with
|
|
// cubPublicKey - Size of the key
|
|
// Output: true if successful, false if decryption failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSAPublicDecrypt_NoPadding( const uint8 *pubEncryptedData, uint32 cubEncryptedData,
|
|
uint8 *pubPlaintextData, uint32 *pcubPlaintextData,
|
|
const uint8 *pubPublicKey, const uint32 cubPublicKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSADecrypt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
bool bRet = false;
|
|
|
|
Assert( cubEncryptedData > 0 ); // must pass in some data
|
|
|
|
// BUGBUG taylor
|
|
// This probably only works for reasonably small ciphertext sizes.
|
|
|
|
try // handle any exceptions crypto++-5.2 may throw
|
|
{
|
|
StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true );
|
|
|
|
// 1. We need to use a Verifier because a Decryptor expects a private key,
|
|
// which is encoded differently
|
|
// 2. We are using neither PKCS1v15 padding nor SHA in any way, we are simply
|
|
// using this object as a means of instantiating the key decryption function
|
|
RSASSA_PKCS1v15_SHA_Verifier pub( stringSourcePublicKey );
|
|
|
|
// Ask for the data to be decrypted
|
|
// Caveat: this may "succeed" even if the ciphertext is bogus, so it
|
|
// is up to the caller to do any MAC or other sanity checking
|
|
Integer x = pub.AccessKey().ApplyFunction(Integer(pubEncryptedData, cubEncryptedData));
|
|
|
|
// Result is an 'Integer', essentially a string of bytes for our
|
|
// purposes though.
|
|
uint32 nBytes = x.ByteCount();
|
|
if ( nBytes > *pcubPlaintextData )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// don't tell it to encode to the full buffer size, because it will
|
|
// pre-pad with zeros. Just squeeze it in to the first nBytes of the
|
|
// buffer.
|
|
x.Encode( pubPlaintextData, nBytes );
|
|
*pcubPlaintextData = nBytes;
|
|
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAPublicDecrypt_NoPadding: Decrypt() threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generates an RSA signature block for the specified data with the specified
|
|
// RSA private key. The signature can be verified by calling RSAVerifySignature
|
|
// with the RSA public key.
|
|
// Input: pubData - Data to be signed
|
|
// cubData - Size of data to be signed
|
|
// pubSignature - Pointer to buffer to receive signature block
|
|
// pcubSignature - Pointer to a variable that at time of call contains the size of
|
|
// the pubSignature buffer. When the method returns, this will contain
|
|
// the actual size of the signature block
|
|
// pubPrivateKey - The RSA private key to use to sign the data
|
|
// cubPrivateKey - Size of the key
|
|
// Output: true if successful, false if signature failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSASign( const uint8 *pubData, const uint32 cubData,
|
|
uint8 *pubSignature, uint32 *pcubSignature,
|
|
const uint8 *pubPrivateKey, const uint32 cubPrivateKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSASign", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( pubPrivateKey );
|
|
Assert( cubPrivateKey > 0 );
|
|
Assert( pubSignature );
|
|
Assert( pcubSignature );
|
|
bool bRet = false;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true );
|
|
RSASSA_PKCS1v15_SHA_Signer rsaSigner( stringSourcePrivateKey );
|
|
CPoolAllocatedRNG rng;
|
|
size_t len = rsaSigner.SignMessage( rng.GetRNG(), (byte *)pubData, cubData, pubSignature );
|
|
AssertFatal( len <= ULONG_MAX );
|
|
*pcubSignature = (uint32)len;
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: SignMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Verifies that signature block is authentic for given data & RSA public key
|
|
// Input: pubData - Data that was signed
|
|
// cubData - Size of data that was signed signed
|
|
// pubSignature - Signature block
|
|
// cubSignature - Size of signature block
|
|
// pubPublicKey - The RSA public key to use to verify the signature
|
|
// (must be from same pair as RSA private key used to generate signature)
|
|
// cubPublicKey - Size of the key
|
|
// Output: true if successful and signature is authentic, false if signature does not match or other error
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSAVerifySignature( const uint8 *pubData, const uint32 cubData,
|
|
const uint8 *pubSignature, const uint32 cubSignature,
|
|
const uint8 *pubPublicKey, const uint32 cubPublicKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSAVerifySignature", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( pubSignature );
|
|
Assert( pubPublicKey );
|
|
|
|
bool bRet = false;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true );
|
|
RSASSA_PKCS1v15_SHA_Verifier pub( stringSourcePublicKey );
|
|
bRet = pub.VerifyMessage( pubData, cubData, pubSignature, cubSignature );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: VerifyMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generates an RSA signature block for the specified data with the specified
|
|
// RSA private key. The signature can be verified by calling RSAVerifySignature
|
|
// with the RSA public key.
|
|
// Input: pubData - Data to be signed
|
|
// cubData - Size of data to be signed
|
|
// pubSignature - Pointer to buffer to receive signature block
|
|
// pcubSignature - Pointer to a variable that at time of call contains the size of
|
|
// the pubSignature buffer. When the method returns, this will contain
|
|
// the actual size of the signature block
|
|
// pubPrivateKey - The RSA private key to use to sign the data
|
|
// cubPrivateKey - Size of the key
|
|
// Output: true if successful, false if signature failed
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSASignSHA256( const uint8 *pubData, const uint32 cubData,
|
|
uint8 *pubSignature, uint32 *pcubSignature,
|
|
const uint8 *pubPrivateKey, const uint32 cubPrivateKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSASign", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( pubPrivateKey );
|
|
Assert( cubPrivateKey > 0 );
|
|
Assert( pubSignature );
|
|
Assert( pcubSignature );
|
|
bool bRet = false;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true );
|
|
RSASS<PKCS1v15, SHA256>::Signer rsaSigner( stringSourcePrivateKey );
|
|
CPoolAllocatedRNG rng;
|
|
size_t len = rsaSigner.SignMessage( rng.GetRNG(), (byte *)pubData, cubData, pubSignature );
|
|
AssertFatal( len <= ULONG_MAX );
|
|
*pcubSignature = (uint32)len;
|
|
bRet = true;
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: SignMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Verifies that signature block is authentic for given data & RSA public key
|
|
// Input: pubData - Data that was signed
|
|
// cubData - Size of data that was signed signed
|
|
// pubSignature - Signature block
|
|
// cubSignature - Size of signature block
|
|
// pubPublicKey - The RSA public key to use to verify the signature
|
|
// (must be from same pair as RSA private key used to generate signature)
|
|
// cubPublicKey - Size of the key
|
|
// Output: true if successful and signature is authentic, false if signature does not match or other error
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::RSAVerifySignatureSHA256( const uint8 *pubData, const uint32 cubData,
|
|
const uint8 *pubSignature, const uint32 cubSignature,
|
|
const uint8 *pubPublicKey, const uint32 cubPublicKey )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::RSAVerifySignature", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( pubSignature );
|
|
Assert( pubPublicKey );
|
|
|
|
bool bRet = false;
|
|
try // handle any exceptions crypto++ may throw
|
|
{
|
|
StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true );
|
|
RSASS<PKCS1v15, SHA256>::Verifier pub( stringSourcePublicKey );
|
|
bRet = pub.VerifyMessage( pubData, cubData, pubSignature, cubSignature );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: VerifyMessage threw exception %s (%d)\n",
|
|
e.what(), e.GetErrorType() );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Hex-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Input: pubData - Data to encode
|
|
// cubData - Size of data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// cchEncodedData - Size of pchEncodedData buffer
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::HexEncode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::HexEncode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( cubData );
|
|
Assert( pchEncodedData );
|
|
Assert( cchEncodedData > 0 );
|
|
|
|
if ( cchEncodedData < ( ( cubData * 2 ) + 1 ) )
|
|
{
|
|
Assert( cchEncodedData >= ( cubData * 2 ) + 1 ); // expands to 2x input + NULL, must have room in output buffer
|
|
*pchEncodedData = '\0';
|
|
return false;
|
|
}
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData );
|
|
// Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
HexEncoder hexEncoder( pArraySinkOutput );
|
|
hexEncoder.Put( pubData, cubData );
|
|
hexEncoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
if ( len >= cchEncodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::HexEncode: insufficient output buffer for encoding, needed %d got %d\n",
|
|
len, cchEncodedData );
|
|
return false;
|
|
}
|
|
|
|
pchEncodedData[len] = 0; // NULL-terminate
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Hex-decodes a block of data. (Text -> binary representation.)
|
|
// Input: pchData - Null-terminated hex-encoded string
|
|
// pubDecodedData - Pointer to buffer to store output in
|
|
// pcubDecodedData - Pointer to variable that contains size of
|
|
// output buffer. At exit, is filled in with actual size
|
|
// of decoded data.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::HexDecode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::HexDecode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pchData );
|
|
Assert( pubDecodedData );
|
|
Assert( pcubDecodedData );
|
|
Assert( *pcubDecodedData );
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData );
|
|
// Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
HexDecoder hexDecoder( pArraySinkOutput );
|
|
hexDecoder.Put( (byte *) pchData, Q_strlen( pchData ) );
|
|
hexDecoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
if ( len > *pcubDecodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::HexDecode: insufficient output buffer for decoding, needed %d got %d\n",
|
|
len, *pcubDecodedData );
|
|
return false;
|
|
}
|
|
*pcubDecodedData = len;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static const int k_LineBreakEveryNGroups = 18; // line break every 18 groups of 4 characters (every 72 characters)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the expected buffer size that should be passed to Base64Encode.
|
|
// Input: cubData - Size of data to encode
|
|
// bInsertLineBreaks - If line breaks should be inserted automatically
|
|
//-----------------------------------------------------------------------------
|
|
uint32 CCrypto::Base64EncodeMaxOutput( const uint32 cubData, const char *pszLineBreak )
|
|
{
|
|
// terminating null + 4 chars per 3-byte group + line break after every 18 groups (72 output chars) + final line break
|
|
uint32 nGroups = (cubData+2)/3;
|
|
str_size cchRequired = 1 + nGroups*4 + ( pszLineBreak ? Q_strlen(pszLineBreak)*(1+(nGroups-1)/k_LineBreakEveryNGroups) : 0 );
|
|
return cchRequired;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base64-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Input: pubData - Data to encode
|
|
// cubData - Size of data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// cchEncodedData - Size of pchEncodedData buffer
|
|
// bInsertLineBreaks - If "\n" line breaks should be inserted automatically
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32 cchEncodedData, bool bInsertLineBreaks )
|
|
{
|
|
const char *pszLineBreak = bInsertLineBreaks ? "\n" : NULL;
|
|
uint32 cchRequired = Base64EncodeMaxOutput( cubData, pszLineBreak );
|
|
(void)cchRequired;
|
|
AssertMsg2( cchEncodedData >= cchRequired, "CCrypto::Base64Encode: insufficient output buffer for encoding, needed %d got %d\n", cchRequired, cchEncodedData );
|
|
return Base64Encode( pubData, cubData, pchEncodedData, &cchEncodedData, pszLineBreak );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base64-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Input: pubData - Data to encode
|
|
// cubData - Size of data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// pcchEncodedData - Pointer to size of pchEncodedData buffer; adjusted to number of characters written (before NULL)
|
|
// pszLineBreak - String to be inserted every 72 characters; empty string or NULL pointer for no line breaks
|
|
// Note: if pchEncodedData is NULL and *pcchEncodedData is zero, *pcchEncodedData is filled with the actual required length
|
|
// for output. A simpler approximation for maximum output size is (cubData * 4 / 3) + 5 if there are no linebreaks.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32* pcchEncodedData, const char *pszLineBreak )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::Base64Encode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
|
|
if ( pchEncodedData == NULL )
|
|
{
|
|
AssertMsg( *pcchEncodedData == 0, "NULL output buffer with non-zero size passed to Base64Encode" );
|
|
*pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak );
|
|
return true;
|
|
}
|
|
|
|
const uint8 *pubDataEnd = pubData + cubData;
|
|
char *pchEncodedDataStart = pchEncodedData;
|
|
str_size unLineBreakLen = pszLineBreak ? Q_strlen( pszLineBreak ) : 0;
|
|
int nNextLineBreak = unLineBreakLen ? k_LineBreakEveryNGroups : INT_MAX;
|
|
|
|
const char * const pszBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
uint32 cchEncodedData = *pcchEncodedData;
|
|
if ( cchEncodedData == 0 )
|
|
goto out_of_space;
|
|
|
|
--cchEncodedData; // pre-decrement for the terminating null so we don't forget about it
|
|
|
|
// input 3 x 8-bit, output 4 x 6-bit
|
|
while ( pubDataEnd - pubData >= 3 )
|
|
{
|
|
if ( cchEncodedData < 4 + unLineBreakLen )
|
|
goto out_of_space;
|
|
|
|
if ( nNextLineBreak == 0 )
|
|
{
|
|
memcpy( pchEncodedData, pszLineBreak, unLineBreakLen );
|
|
pchEncodedData += unLineBreakLen;
|
|
cchEncodedData -= unLineBreakLen;
|
|
nNextLineBreak = k_LineBreakEveryNGroups;
|
|
}
|
|
|
|
uint32 un24BitsData;
|
|
un24BitsData = (uint32) pubData[0] << 16;
|
|
un24BitsData |= (uint32) pubData[1] << 8;
|
|
un24BitsData |= (uint32) pubData[2];
|
|
pubData += 3;
|
|
|
|
pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ];
|
|
pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ];
|
|
pchEncodedData[2] = pszBase64Chars[ (un24BitsData >> 6) & 63 ];
|
|
pchEncodedData[3] = pszBase64Chars[ (un24BitsData ) & 63 ];
|
|
pchEncodedData += 4;
|
|
cchEncodedData -= 4;
|
|
--nNextLineBreak;
|
|
}
|
|
|
|
// Clean up remaining 1 or 2 bytes of input, pad output with '='
|
|
if ( pubData != pubDataEnd )
|
|
{
|
|
if ( cchEncodedData < 4 + unLineBreakLen )
|
|
goto out_of_space;
|
|
|
|
if ( nNextLineBreak == 0 )
|
|
{
|
|
memcpy( pchEncodedData, pszLineBreak, unLineBreakLen );
|
|
pchEncodedData += unLineBreakLen;
|
|
cchEncodedData -= unLineBreakLen;
|
|
}
|
|
|
|
uint32 un24BitsData;
|
|
un24BitsData = (uint32) pubData[0] << 16;
|
|
if ( pubData+1 != pubDataEnd )
|
|
{
|
|
un24BitsData |= (uint32) pubData[1] << 8;
|
|
}
|
|
pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ];
|
|
pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ];
|
|
pchEncodedData[2] = pubData+1 != pubDataEnd ? pszBase64Chars[ (un24BitsData >> 6) & 63 ] : '=';
|
|
pchEncodedData[3] = '=';
|
|
pchEncodedData += 4;
|
|
cchEncodedData -= 4;
|
|
}
|
|
|
|
if ( unLineBreakLen )
|
|
{
|
|
if ( cchEncodedData < unLineBreakLen )
|
|
goto out_of_space;
|
|
memcpy( pchEncodedData, pszLineBreak, unLineBreakLen );
|
|
pchEncodedData += unLineBreakLen;
|
|
cchEncodedData -= unLineBreakLen;
|
|
}
|
|
|
|
*pchEncodedData = 0;
|
|
*pcchEncodedData = pchEncodedData - pchEncodedDataStart;
|
|
return true;
|
|
|
|
out_of_space:
|
|
*pchEncodedData = 0;
|
|
*pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak );
|
|
AssertMsg( false, "CCrypto::Base64Encode: insufficient output buffer (up to n*4/3+5 bytes required, plus linebreaks)" );
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base64-decodes a block of data. (Text -> binary representation.)
|
|
// Input: pchData - Null-terminated hex-encoded string
|
|
// pubDecodedData - Pointer to buffer to store output in
|
|
// pcubDecodedData - Pointer to variable that contains size of
|
|
// output buffer. At exit, is filled in with actual size
|
|
// of decoded data.
|
|
// Note: if NULL is passed as the output buffer and *pcubDecodedData is zero, the function
|
|
// will calculate the actual required size and place it in *pcubDecodedData. A simpler upper
|
|
// bound on the required size is ( strlen(pchData)*3/4 + 1 ).
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::Base64Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters )
|
|
{
|
|
return Base64Decode( pchData, ~0u, pubDecodedData, pcubDecodedData, bIgnoreInvalidCharacters );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base64-decodes a block of data. (Text -> binary representation.)
|
|
// Input: pchData - base64-encoded string, null terminated
|
|
// cchDataMax - maximum length of string unless a null is encountered first
|
|
// pubDecodedData - Pointer to buffer to store output in
|
|
// pcubDecodedData - Pointer to variable that contains size of
|
|
// output buffer. At exit, is filled in with actual size
|
|
// of decoded data.
|
|
// Note: if NULL is passed as the output buffer and *pcubDecodedData is zero, the function
|
|
// will calculate the actual required size and place it in *pcubDecodedData. A simpler upper
|
|
// bound on the required size is ( strlen(pchData)*3/4 + 2 ).
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::Base64Decode( const char *pchData, uint32 cchDataMax, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::Base64Decode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
|
|
uint32 cubDecodedData = *pcubDecodedData;
|
|
uint32 cubDecodedDataOrig = cubDecodedData;
|
|
|
|
if ( pubDecodedData == NULL )
|
|
{
|
|
AssertMsg( *pcubDecodedData == 0, "NULL output buffer with non-zero size passed to Base64Decode" );
|
|
cubDecodedDataOrig = cubDecodedData = ~0u;
|
|
}
|
|
|
|
// valid base64 character range: '+' (0x2B) to 'z' (0x7A)
|
|
// table entries are 0-63, -1 for invalid entries, -2 for '='
|
|
static const char rgchInvBase64[] = {
|
|
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
|
-1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7,
|
|
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
|
23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31,
|
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
|
47, 48, 49, 50, 51
|
|
};
|
|
COMPILE_TIME_ASSERT( Q_ARRAYSIZE(rgchInvBase64) == 0x7A - 0x2B + 1 );
|
|
|
|
uint32 un24BitsWithSentinel = 1;
|
|
while ( cchDataMax-- > 0 )
|
|
{
|
|
char c = *pchData++;
|
|
|
|
if ( (uint8)(c - 0x2B) >= Q_ARRAYSIZE( rgchInvBase64 ) )
|
|
{
|
|
if ( c == '\0' )
|
|
break;
|
|
|
|
if ( !bIgnoreInvalidCharacters && !( c == '\r' || c == '\n' || c == '\t' || c == ' ' ) )
|
|
goto decode_failed;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
c = rgchInvBase64[(uint8)(c - 0x2B)];
|
|
if ( c < 0 )
|
|
{
|
|
if ( c == -2 ) // -2 -> terminating '='
|
|
break;
|
|
|
|
if ( !bIgnoreInvalidCharacters )
|
|
goto decode_failed;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
un24BitsWithSentinel <<= 6;
|
|
un24BitsWithSentinel |= c;
|
|
if ( un24BitsWithSentinel & (1<<24) )
|
|
{
|
|
if ( cubDecodedData < 3 ) // out of space? go to final write logic
|
|
break;
|
|
if ( pubDecodedData )
|
|
{
|
|
pubDecodedData[0] = (uint8)( un24BitsWithSentinel >> 16 );
|
|
pubDecodedData[1] = (uint8)( un24BitsWithSentinel >> 8);
|
|
pubDecodedData[2] = (uint8)( un24BitsWithSentinel );
|
|
pubDecodedData += 3;
|
|
}
|
|
cubDecodedData -= 3;
|
|
un24BitsWithSentinel = 1;
|
|
}
|
|
}
|
|
|
|
// If un24BitsWithSentinel contains data, output the remaining full bytes
|
|
if ( un24BitsWithSentinel >= (1<<6) )
|
|
{
|
|
// Possibilities are 3, 2, 1, or 0 full output bytes.
|
|
int nWriteBytes = 3;
|
|
while ( un24BitsWithSentinel < (1<<24) )
|
|
{
|
|
nWriteBytes--;
|
|
un24BitsWithSentinel <<= 6;
|
|
}
|
|
|
|
// Write completed bytes to output
|
|
while ( nWriteBytes-- > 0 )
|
|
{
|
|
if ( cubDecodedData == 0 )
|
|
{
|
|
AssertMsg( false, "CCrypto::Base64Decode: insufficient output buffer (up to n*3/4+2 bytes required)" );
|
|
goto decode_failed;
|
|
}
|
|
if ( pubDecodedData )
|
|
{
|
|
*pubDecodedData++ = (uint8)(un24BitsWithSentinel >> 16);
|
|
}
|
|
--cubDecodedData;
|
|
un24BitsWithSentinel <<= 8;
|
|
}
|
|
}
|
|
|
|
*pcubDecodedData = cubDecodedDataOrig - cubDecodedData;
|
|
return true;
|
|
|
|
decode_failed:
|
|
*pcubDecodedData = cubDecodedDataOrig - cubDecodedData;
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generate a SHA1 hash
|
|
// Input: pchInput - Plaintext string of item to hash (null terminated)
|
|
// pOutDigest - Pointer to receive hashed digest output
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateSHA1Digest( const uint8 *pubInput, const int cubInput, SHADigest_t *pOutDigest )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::GenerateSHA1Digest", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubInput );
|
|
Assert( cubInput > 0 );
|
|
Assert( pOutDigest );
|
|
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
CryptoPP::SHA().CalculateDigest( *pOutDigest, pubInput, cubInput );
|
|
}
|
|
catch(...)
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generate a hash Salt - be careful, over-writing an existing salt
|
|
// will render the hashed value unverifiable.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateSalt( Salt_t *pSalt )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::GenerateSalt", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pSalt );
|
|
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
CPoolAllocatedRNG rng;
|
|
rng.GetRNG().GenerateBlock( (byte*)pSalt, sizeof(Salt_t) );
|
|
}
|
|
catch(...)
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generate a SHA1 hash using a salt.
|
|
// Input: pchInput - Plaintext string of item to hash (null terminated)
|
|
// pSalt - Salt
|
|
// pOutDigest - Pointer to receive salted digest output
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateSaltedSHA1Digest( const char *pchInput, const Salt_t *pSalt, SHADigest_t *pOutDigest )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::GenerateSaltedSHA1Digest", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pchInput );
|
|
Assert( pSalt );
|
|
Assert( pOutDigest );
|
|
|
|
int iInputLen = Q_strlen( pchInput );
|
|
uint8 *pubSaltedInput = new uint8[ iInputLen + sizeof( Salt_t ) ];
|
|
|
|
// Insert half the salt before the input string and half at the end.
|
|
// This is probably unnecessary (to split the salt) but we're stuck with it for historical reasons.
|
|
uint8 *pubCursor = pubSaltedInput;
|
|
Q_memcpy( pubCursor, (uint8 *)pSalt, sizeof(Salt_t) / 2 );
|
|
pubCursor += sizeof( Salt_t ) / 2;
|
|
Q_memcpy( pubCursor, pchInput, iInputLen );
|
|
pubCursor += iInputLen;
|
|
Q_memcpy( pubCursor, (uint8 *)pSalt + sizeof(Salt_t) / 2, sizeof(Salt_t) / 2 );
|
|
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
CryptoPP::SHA().CalculateDigest( *pOutDigest, pubSaltedInput, iInputLen + sizeof( Salt_t ) );
|
|
}
|
|
catch(...)
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
delete [] pubSaltedInput;
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generates a random block of data
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateRandomBlock( uint8 *pubDest, int cubDest )
|
|
{
|
|
CPoolAllocatedRNG rng;
|
|
rng.GetRNG().GenerateBlock( pubDest, cubDest );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generate a keyed-hash MAC using SHA1
|
|
// Input: pubData - Plaintext data to digest
|
|
// cubData - length of data
|
|
// pubKey - key to use in HMAC
|
|
// cubKey - length of key
|
|
// pOutDigest - Pointer to receive hashed digest output
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateHMAC( const uint8 *pubData, uint32 cubData, const uint8 *pubKey, uint32 cubKey, SHADigest_t *pOutputDigest )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::GenerateHMAC", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( cubData > 0 );
|
|
Assert( pubKey );
|
|
Assert( cubKey > 0 );
|
|
Assert( pOutputDigest );
|
|
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
CryptoPP::HMAC< CryptoPP::SHA1 > hmac( pubKey, cubKey );
|
|
hmac.CalculateDigest( *pOutputDigest, pubData, cubData );
|
|
}
|
|
catch(...)
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Generate a keyed-hash MAC using SHA-256
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::GenerateHMAC256( const uint8 *pubData, uint32 cubData, const uint8 *pubKey, uint32 cubKey, SHA256Digest_t *pOutputDigest )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::GenerateHMAC256", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( cubData > 0 );
|
|
Assert( pubKey );
|
|
Assert( cubKey > 0 );
|
|
Assert( pOutputDigest );
|
|
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
CryptoPP::HMAC< CryptoPP::SHA256 > hmac( pubKey, cubKey );
|
|
hmac.CalculateDigest( *pOutputDigest, pubData, cubData );
|
|
}
|
|
catch(...)
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
bool CCrypto::BGzipBuffer( const uint8 *pubData, uint32 cubData, CCryptoOutBuffer &bufOutput )
|
|
{
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
std::string gzip_output;
|
|
StringSource( (byte *)pubData, cubData, true, new Gzip( new StringSink( gzip_output ) ) );
|
|
bufOutput.Set( (uint8*)gzip_output.c_str(), (uint32)gzip_output.length() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
bool CCrypto::BGunzipBuffer( const uint8 *pubData, uint32 cubData, CCryptoOutBuffer &bufOutput )
|
|
{
|
|
bool bSuccess = true;
|
|
try
|
|
{
|
|
std::string gunzip_output;
|
|
StringSource( (byte *)pubData, cubData, true, new Gunzip( new StringSink( gunzip_output ) ) );
|
|
bufOutput.Set( (uint8*)gunzip_output.c_str(), (uint32)gunzip_output.length() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
//! These are all needed to get around stack-overflow bug in Initialize()
|
|
class HexDecoderTKS : public HexDecoder
|
|
{
|
|
public:
|
|
HexDecoderTKS(BufferedTransformation *attachment, const int *pnDecodingArray)
|
|
: HexDecoder(attachment)
|
|
{
|
|
BaseN_Decoder::IsolatedInitialize( MakeParameters( Name::DecodingLookupArray(), pnDecodingArray )( Name::Log2Base(), 4 ) );
|
|
}
|
|
};
|
|
|
|
class Base32DecoderTKS : public Base32Decoder
|
|
{
|
|
public:
|
|
Base32DecoderTKS(BufferedTransformation *attachment, const int *pnDecodingArray)
|
|
: Base32Decoder(attachment)
|
|
{
|
|
BaseN_Decoder::IsolatedInitialize( MakeParameters( Name::DecodingLookupArray(), pnDecodingArray )( Name::Log2Base(), 5 ) );
|
|
}
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Implement hex encoding / decoding using a custom lookup table.
|
|
// This is a class because the decoding is done via a generated
|
|
// reverse-lookup table, and to save time it's best to just create
|
|
// that table once.
|
|
//-----------------------------------------------------------------------------
|
|
CCustomHexEncoder::CCustomHexEncoder( const char *pchEncodingTable )
|
|
{
|
|
m_bValidEncoding = false;
|
|
if ( Q_strlen( pchEncodingTable ) == sizeof( m_rgubEncodingTable ) )
|
|
{
|
|
Q_memcpy( m_rgubEncodingTable, pchEncodingTable, sizeof( m_rgubEncodingTable ) );
|
|
|
|
BaseN_Decoder::InitializeDecodingLookupArray( m_rgnDecodingTable, m_rgubEncodingTable, 16, false );
|
|
m_bValidEncoding = true;
|
|
}
|
|
else
|
|
{
|
|
AssertMsg( false, "CCrypto::CustomHexEncoder: Improper encoding table\n" );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CCustomHexEncoder::~CCustomHexEncoder()
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Hex-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Uses the supplied custom encoding characters
|
|
// Input: pubData - Data to encode
|
|
// cubData - Size of data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// cchEncodedData - Size of pchEncodedData buffer
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomHexEncoder::Encode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::CustomHexEncode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( cubData );
|
|
Assert( pchEncodedData );
|
|
Assert( cchEncodedData > 0 );
|
|
|
|
if ( !m_bValidEncoding )
|
|
return false;
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData );
|
|
// Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
HexEncoder hexEncoder( pArraySinkOutput );
|
|
hexEncoder.IsolatedInitialize( MakeParameters( Name::EncodingLookupArray(), (const uint8 *)m_rgubEncodingTable ) );
|
|
hexEncoder.Put( pubData, cubData );
|
|
hexEncoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
pchEncodedData[len] = 0; // NULL-terminate
|
|
if ( len >= cchEncodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::CustomHexEncode: insufficient output buffer for encoding, needed %d got %d\n",
|
|
len, cchEncodedData );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Hex-decodes a block of data. (Text -> binary representation.)
|
|
// With custom encoding-table
|
|
// Input: pchData - Null-terminated hex-encoded string
|
|
// pubDecodedData - Pointer to buffer to store output in
|
|
// pcubDecodedData - Pointer to variable that contains size of
|
|
// output buffer. At exit, is filled in with actual size
|
|
// of decoded data.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomHexEncoder::Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::CustomHexDecode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pchData );
|
|
Assert( pubDecodedData );
|
|
Assert( pcubDecodedData );
|
|
Assert( *pcubDecodedData );
|
|
|
|
if ( !m_bValidEncoding )
|
|
return false;
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData );
|
|
// Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
HexDecoderTKS hexDecoder( pArraySinkOutput, (const int *)m_rgnDecodingTable );
|
|
hexDecoder.Put( (byte *) pchData, Q_strlen( pchData ) );
|
|
hexDecoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
if ( len > *pcubDecodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::CustomHexDecode: insufficient output buffer for decoding, needed %d got %d\n",
|
|
len, *pcubDecodedData );
|
|
return false;
|
|
}
|
|
*pcubDecodedData = len;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Implement hex encoding / decoding using a custom lookup table.
|
|
// This is a class because the decoding is done via a generated
|
|
// reverse-lookup table, and to save time it's best to just create
|
|
// that table once.
|
|
//-----------------------------------------------------------------------------
|
|
CCustomBase32Encoder::CCustomBase32Encoder( const char *pchEncodingTable )
|
|
{
|
|
m_bValidEncoding = false;
|
|
if ( Q_strlen( pchEncodingTable ) == sizeof( m_rgubEncodingTable ) )
|
|
{
|
|
Q_memcpy( m_rgubEncodingTable, pchEncodingTable, sizeof( m_rgubEncodingTable ) );
|
|
|
|
BaseN_Decoder::InitializeDecodingLookupArray( m_rgnDecodingTable, m_rgubEncodingTable, 32, false );
|
|
m_bValidEncoding = true;
|
|
}
|
|
else
|
|
{
|
|
AssertMsg( false, "CCrypto::CustomBase32Encoder: Improper encoding table\n" );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CCustomBase32Encoder::~CCustomBase32Encoder()
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base32-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Uses the supplied custom encoding table
|
|
// Input: pubData - Data to encode
|
|
// cubData - Size of data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// cchEncodedData - Size of pchEncodedData buffer
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomBase32Encoder::Encode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::CustomBase32Encode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pubData );
|
|
Assert( cubData );
|
|
Assert( pchEncodedData );
|
|
Assert( cchEncodedData > 0 );
|
|
|
|
if ( !m_bValidEncoding )
|
|
return false;
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData );
|
|
// Note: Base32Encoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
Base32Encoder base32Encoder( pArraySinkOutput );
|
|
base32Encoder.IsolatedInitialize( MakeParameters( Name::EncodingLookupArray(), (const uint8 *)m_rgubEncodingTable ) );
|
|
base32Encoder.Put( pubData, cubData );
|
|
base32Encoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
pchEncodedData[len] = 0; // NULL-terminate
|
|
if ( len >= cchEncodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::CustomBase32Encode: insufficient output buffer for encoding, needed %d got %d\n",
|
|
len, cchEncodedData );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base32-decodes a block of data. (Text -> binary representation.)
|
|
// With custom encoding table
|
|
// Input: pchData - Null-terminated hex-encoded string
|
|
// pubDecodedData - Pointer to buffer to store output in
|
|
// pcubDecodedData - Pointer to variable that contains size of
|
|
// output buffer. At exit, is filled in with actual size
|
|
// of decoded data.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomBase32Encoder::Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::CustomBase32Decode", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
Assert( pchData );
|
|
Assert( pubDecodedData );
|
|
Assert( pcubDecodedData );
|
|
Assert( *pcubDecodedData );
|
|
|
|
if ( !m_bValidEncoding )
|
|
return false;
|
|
|
|
ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData );
|
|
// Note: Base32Encoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs
|
|
Base32DecoderTKS base32Decoder( pArraySinkOutput, (const int *)m_rgnDecodingTable );
|
|
base32Decoder.Put( (byte *) pchData, Q_strlen( pchData ) );
|
|
base32Decoder.MessageEnd();
|
|
|
|
uint32 len = pArraySinkOutput->TotalPutLength();
|
|
if ( len > *pcubDecodedData )
|
|
{
|
|
AssertMsg2( false, "CCrypto::CustomBase32Decode: insufficient output buffer for decoding, needed %d got %d\n",
|
|
len, *pcubDecodedData );
|
|
return false;
|
|
}
|
|
*pcubDecodedData = len;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base32-encodes a block of data. (Binary -> text representation.) The output
|
|
// is null-terminated and can be treated as a string.
|
|
// Uses the supplied custom encoding table, and a bit-stream input source
|
|
// (not necessarily an integer number of bytes).
|
|
// Input: pBitStringData - Data to encode
|
|
// pchEncodedData - Pointer to string buffer to store output in
|
|
// cchEncodedData - Size of pchEncodedData buffer
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomBase32Encoder::Encode( CSimpleBitString *pBitStringData, char *pchEncodedData, uint32 cchEncodedData )
|
|
{
|
|
// This is useful if you have, say, 125 bits of information and
|
|
// want to encode them into 25 base32-encoded characters.
|
|
uint32 cBits = pBitStringData->GetCurrNumBits();
|
|
|
|
uint32 cCharacters = (cBits / 5);
|
|
uint32 cBitsRemainder = cBits % 5;
|
|
if ( cBitsRemainder )
|
|
cCharacters++;
|
|
|
|
// GTE because of NULL
|
|
if ( cCharacters >= cchEncodedData )
|
|
return false;
|
|
|
|
CSimpleBitString::iterator itBitString( *pBitStringData );
|
|
uint ich = 0;
|
|
for ( ; ich < cCharacters; ++ich )
|
|
{
|
|
uint32 unCodon = itBitString.GetNextBits( 5 );
|
|
// Pad w/ zero bits to integer num codons
|
|
if ( ich == (cCharacters - 1) )
|
|
unCodon <<= cBitsRemainder;
|
|
|
|
pchEncodedData[ich] = m_rgubEncodingTable[ unCodon ];
|
|
}
|
|
|
|
// NULL
|
|
pchEncodedData[ich] = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Base32-decodes a block of data. (Text -> binary representation.)
|
|
// With custom encoding table, and a BitString output
|
|
// Input: pchData - Null-terminated base32-encoded string
|
|
// pBitStringDecodedData - Pointer to BitString to receive decoded data
|
|
//-----------------------------------------------------------------------------
|
|
bool CCustomBase32Encoder::Decode( const char *pchData, CSimpleBitString *pBitStringDecodedData )
|
|
{
|
|
// Note: 25 base32-encoded characters contain 125 bits of information.
|
|
// Decoded into a byte buffer, this yields 15 bytes plus 5 bits of padding.
|
|
// Decoded into a CSimpleBitString, it will yield all 125 bits
|
|
while ( *pchData )
|
|
{
|
|
uint32 unData = m_rgnDecodingTable[(unsigned)*pchData++];
|
|
if ( unData == 0xFFFFFFFF )
|
|
return false;
|
|
|
|
pBitStringDecodedData->AppendBits( unData, 5 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef DBGFLAG_VALIDATE
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: validates memory structures
|
|
//-----------------------------------------------------------------------------
|
|
void CCrypto::ValidateStatics( CValidator &validator, const char *pchName )
|
|
{
|
|
ValidateObj( g_tslistPAutoSeededRNG );
|
|
}
|
|
#endif // DBGFLAG_VALIDATE
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given a plaintext password, check whether it matches an existing
|
|
// hash
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::BValidatePasswordHash( const char *pchInput, EPasswordHashAlg hashType, const PasswordHash_t &DigestStored, const Salt_t &Salt, PasswordHash_t *pDigestComputed )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::BValidatePasswordHash", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
|
|
bool bResult = false;
|
|
size_t cDigest = k_HashLengths[hashType];
|
|
Assert( cDigest != 0 );
|
|
PasswordHash_t tmpDigest;
|
|
PasswordHash_t *pOutputDigest = pDigestComputed;
|
|
if ( pOutputDigest == NULL )
|
|
{
|
|
pOutputDigest = &tmpDigest;
|
|
}
|
|
|
|
BGeneratePasswordHash( pchInput, hashType, Salt, *pOutputDigest );
|
|
bResult = ( 0 == Q_memcmp( &DigestStored, pOutputDigest, cDigest ) );
|
|
|
|
return bResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given a plaintext password and salt, generate a password hash of
|
|
// the requested type.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::BGeneratePasswordHash( const char *pchInput, EPasswordHashAlg hashType, const Salt_t &Salt, PasswordHash_t &OutPasswordHash )
|
|
{
|
|
VPROF_BUDGET( "CCrypto::BGeneratePasswordHash", VPROF_BUDGETGROUP_ENCRYPTION );
|
|
|
|
bool bResult = false;
|
|
size_t cDigest = k_HashLengths[hashType];
|
|
|
|
switch ( hashType )
|
|
{
|
|
case k_EHashSHA1:
|
|
bResult = CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)&OutPasswordHash.sha );
|
|
break;
|
|
case k_EHashBigPassword:
|
|
{
|
|
//
|
|
// This is a fake algorithm to test widening of the column. It's a salted SHA-1 hash with 0x01 padding
|
|
// on either side of it.
|
|
//
|
|
size_t cDigestSHA1 = k_HashLengths[k_EHashSHA1];
|
|
size_t cPadding = ( cDigest - cDigestSHA1 ) / 2;
|
|
|
|
AssertMsg( ( ( cDigest - cDigestSHA1 ) % 2 ) == 0, "Invalid hash width for k_EHashBigPassword, needs to be even." );
|
|
|
|
CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)( (uint8 *)&OutPasswordHash.bigpassword + cPadding ) );
|
|
Q_memset( (uint8 *)&OutPasswordHash, 0x01, cPadding );
|
|
Q_memset( (uint8 *)&OutPasswordHash + cPadding + cDigestSHA1 , 0x01, cPadding );
|
|
bResult = true;
|
|
break;
|
|
}
|
|
case k_EHashPBKDF2_1000:
|
|
bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 1000, OutPasswordHash );
|
|
break;
|
|
case k_EHashPBKDF2_5000:
|
|
bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 5000, OutPasswordHash );
|
|
break;
|
|
case k_EHashPBKDF2_10000:
|
|
bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 10000, OutPasswordHash );
|
|
break;
|
|
case k_EHashSHA1WrappedWithPBKDF2_10000:
|
|
bResult = CCrypto::BGenerateWrappedSHA1PasswordHash( pchInput, Salt, 10000, OutPasswordHash );
|
|
break;
|
|
default:
|
|
AssertMsg1( false, "Invalid password hash type %u passed to BGeneratePasswordHash\n", hashType );
|
|
bResult = false;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given a plaintext password and salt and a count of rounds, generate a PBKDF2 hash
|
|
// with the requested number of rounds.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::BGeneratePBKDF2Hash( const char* pchInput, const Salt_t &Salt, unsigned int rounds, PasswordHash_t &OutPasswordHash )
|
|
{
|
|
PKCS5_PBKDF2_HMAC<SHA256> pbkdf;
|
|
|
|
unsigned int iterations = pbkdf.DeriveKey( (byte *)&OutPasswordHash.pbkdf2, sizeof(OutPasswordHash.pbkdf2), 0, (const byte *)pchInput, Q_strlen(pchInput), (const byte *)&Salt, sizeof(Salt), rounds );
|
|
|
|
return ( iterations == rounds );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given a plaintext password and salt and a count of rounds, generate a SHA1 hash wrapped with
|
|
// a PBKDF2 hash with the specified number of rounds.
|
|
// Used to provide a stronger password hash for accounts that haven't logged in in a while.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::BGenerateWrappedSHA1PasswordHash( const char *pchInput, const Salt_t &Salt, unsigned int rounds, PasswordHash_t &OutPasswordHash )
|
|
{
|
|
bool bResult;
|
|
bResult = CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)&OutPasswordHash.sha );
|
|
if ( bResult )
|
|
{
|
|
PKCS5_PBKDF2_HMAC<SHA256> pbkdf;
|
|
unsigned int iterations = pbkdf.DeriveKey( (byte *)&OutPasswordHash.pbkdf2, sizeof(OutPasswordHash.pbkdf2), 0, (const byte *)&OutPasswordHash.sha, sizeof(OutPasswordHash.sha), (const byte *)&Salt, sizeof(Salt), rounds );
|
|
|
|
bResult = ( iterations == rounds );
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given an existing password hash and salt, attempt to construct a stronger
|
|
// password hash and return the new hash type.
|
|
//
|
|
// Currently the only transformation available is from a SHA1 (or BigPassword)
|
|
// hash to a PBKDF2 hash with 10,000 rounds. In the future this function
|
|
// may be extended to allow additional transformations.
|
|
//-----------------------------------------------------------------------------
|
|
bool CCrypto::BUpgradeOrWrapPasswordHash( PasswordHash_t &InPasswordHash, EPasswordHashAlg hashTypeIn, const Salt_t &Salt, PasswordHash_t &OutPasswordHash, EPasswordHashAlg &hashTypeOut )
|
|
{
|
|
bool bResult = true;;
|
|
|
|
if ( hashTypeIn == k_EHashSHA1 || hashTypeIn == k_EHashBigPassword )
|
|
{
|
|
//
|
|
// Can wrap a SHA1 hash with any PBKDF variant, but right now only 10,000 rounds is
|
|
// implemented.
|
|
//
|
|
if ( hashTypeOut == k_EHashPBKDF2_10000 )
|
|
{
|
|
hashTypeOut = k_EHashSHA1WrappedWithPBKDF2_10000;
|
|
|
|
byte * pbHash;
|
|
if ( hashTypeIn == k_EHashSHA1 )
|
|
{
|
|
pbHash = (byte *)&InPasswordHash.sha;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Need to unroll BigPasswordHash into unpadded SHA1
|
|
//
|
|
size_t cDigest = k_HashLengths[k_EHashBigPassword];
|
|
size_t cDigestSHA1 = k_HashLengths[k_EHashSHA1];
|
|
size_t cPadding = ( cDigest - cDigestSHA1 ) / 2;
|
|
|
|
AssertMsg( ( ( cDigest - cDigestSHA1 ) % 2 ) == 0, "Invalid hash width for k_EHashBigPassword, needs to be even." );
|
|
pbHash = (byte *)&InPasswordHash.sha + cPadding;
|
|
}
|
|
|
|
PKCS5_PBKDF2_HMAC<SHA256> pbkdf;
|
|
PasswordHash_t passOut;
|
|
unsigned int iterations = pbkdf.DeriveKey( (byte *)passOut.pbkdf2, sizeof(passOut.pbkdf2), 0, pbHash, k_HashLengths[k_EHashSHA1], (const byte *)&Salt, sizeof(Salt), 10000 );
|
|
|
|
bResult = ( iterations == 10000 );
|
|
if ( bResult )
|
|
{
|
|
Q_memcpy( &OutPasswordHash, &passOut, sizeof(OutPasswordHash) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( hashTypeOut == k_EHashPBKDF2_10000 );
|
|
bResult = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bResult = false;
|
|
Assert( false );
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|