diff options
Diffstat (limited to 'common/crypto.cpp')
| -rw-r--r-- | common/crypto.cpp | 2219 |
1 files changed, 2219 insertions, 0 deletions
diff --git a/common/crypto.cpp b/common/crypto.cpp new file mode 100644 index 0000000..2805b78 --- /dev/null +++ b/common/crypto.cpp @@ -0,0 +1,2219 @@ +//========= 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; +} + |