#include "crypto.h"
#include <tier0/vprof.h>
#include <tier1/utlbuffer.h>
bool CCrypto::HexEncode( const void *pData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData )
{
VPROF_BUDGET( "CCrypto::HexEncode", VPROF_BUDGETGROUP_ENCRYPTION );
Assert( pData );
Assert( cubData );
Assert( pchEncodedData );
Assert( cchEncodedData > 0 );
if ( cchEncodedData < ( ( cubData * 2 ) + 1 ) )
{
Assert( cchEncodedData >= ( cubData * 2 ) + 1 ); *pchEncodedData = '\0';
return false;
}
const uint8 *pubData = (const uint8 *)pData;
for ( uint32 i = 0; i < cubData; ++i )
{
uint8 c = *pubData++;
*pchEncodedData++ = "0123456789ABCDEF"[c >> 4];
*pchEncodedData++ = "0123456789ABCDEF"[c & 15];
}
*pchEncodedData = '\0';
return true;
}
bool CCrypto::HexDecode( const char *pchData, void *pDecodedData, uint32 *pcubDecodedData )
{
VPROF_BUDGET( "CCrypto::HexDecode", VPROF_BUDGETGROUP_ENCRYPTION );
Assert( pchData );
Assert( pDecodedData );
Assert( pcubDecodedData );
Assert( *pcubDecodedData );
const char *pchDataOrig = pchData;
uint8 *pubDecodedData = (uint8 *)pDecodedData;
uint32 cubOut = 0;
uint32 cubOutMax = *pcubDecodedData;
while ( cubOut < cubOutMax )
{
uint8 val = 0;
while ( true ) {
uint8 c = (uint8)*pchData++;
if ( (uint8)(c - '0') < 10 )
{
val = (uint8)(c - '0') * 16;
break;
}
if ( (uint8)(c - 'a') < 6 )
{
val = (uint8)(c - 'a') * 16 + 160;
break;
}
if ( (uint8)(c - 'A') < 6 )
{
val = (uint8)(c - 'A') * 16 + 160;
break;
}
if ( c == 0 )
{
*pcubDecodedData = cubOut;
return true;
}
}
while ( true ) {
uint8 c = (uint8)*pchData++;
if ( (uint8)(c - '0') < 10 )
{
val += (uint8)(c - '0');
break;
}
if ( (uint8)(c - 'a') < 6 )
{
val += (uint8)(c - 'a') + 10;
break;
}
if ( (uint8)(c - 'A') < 6 )
{
val += (uint8)(c - 'A') + 10;
break;
}
if ( c == 0 )
{
*pcubDecodedData = cubOut;
return true;
}
}
pubDecodedData[cubOut++] = val;
}
if ( *pchData == 0 )
{
*pcubDecodedData = cubOut;
return true;
}
AssertMsg2( false, "CCrypto::HexDecode: insufficient output buffer (input length %u, output size %u)", (uint) V_strlen( pchDataOrig ), cubOutMax );
(void)pchDataOrig; return false;
}
static const int k_LineBreakEveryNGroups = 18;
uint32 CCrypto::Base64EncodeMaxOutput( size_t cubData, const char *pszLineBreak )
{
str_size nGroups = str_size( (cubData+2)/3 );
str_size cchRequired = 1 + nGroups*4 + ( pszLineBreak ? V_strlen(pszLineBreak)*(1+(nGroups-1)/k_LineBreakEveryNGroups) : 0 );
return cchRequired;
}
bool CCrypto::Base64Encode_Legacy( const void *pubData, size_t cubData, char *pchEncodedData, size_t cchEncodedData, bool bInsertLineBreaks )
{
const char *pszLineBreak = bInsertLineBreaks ? "\n" : NULL;
#ifdef DBGFLAG_ASSERT
uint32 cchRequired = Base64EncodeMaxOutput( cubData, pszLineBreak );
AssertMsg2( cchEncodedData >= cchRequired, "CCrypto::Base64Encode: insufficient output buffer for encoding, needed %d got %d\n", (int)cchRequired, (int)cchEncodedData );
#endif
uint32 cbEncodedData = (uint32)cchEncodedData;
return Base64Encode( pubData, cubData, pchEncodedData, &cbEncodedData, pszLineBreak );
}
bool CCrypto::Base64Encode( const void *pData, size_t 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 *pubData = (const uint8 *)pData;
const uint8 *pubDataEnd = pubData + cubData;
char *pchEncodedDataStart = pchEncodedData;
str_size unLineBreakLen = pszLineBreak ? V_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;
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;
}
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;
}
bool CCrypto::Base64Decode_Legacy( const char *pchData, void *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters )
{
return Base64Decode( pchData, ~0u, pubDecodedData, pcubDecodedData, bIgnoreInvalidCharacters );
}
bool CCrypto::Base64Decode( const char *pchData, size_t cchDataMax, void *pDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters )
{
VPROF_BUDGET( "CCrypto::Base64Decode", VPROF_BUDGETGROUP_ENCRYPTION );
if ( pchData == nullptr )
{
*pcubDecodedData = 0;
return true;
}
uint8 *pubDecodedData = (uint8 *)pDecodedData;
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;
}
static const signed 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( V_ARRAYSIZE(rgchInvBase64) == 0x7A - 0x2B + 1 );
uint32 un24BitsWithSentinel = 1;
while ( cchDataMax-- > 0 )
{
char c = *pchData++;
if ( (uint8)(c - 0x2B) >= V_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 ( (signed char)c < 0 )
{
if ( (signed char)c == -2 ) break;
if ( !bIgnoreInvalidCharacters )
goto decode_failed;
else
continue;
}
un24BitsWithSentinel <<= 6;
un24BitsWithSentinel |= c;
if ( un24BitsWithSentinel & (1<<24) )
{
if ( cubDecodedData < 3 ) 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 >= (1<<6) )
{
int nWriteBytes = 3;
while ( un24BitsWithSentinel < (1<<24) )
{
nWriteBytes--;
un24BitsWithSentinel <<= 6;
}
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;
}
inline bool BParsePEMHeaderOrFooter( const char *&pchPEM, const char *pchEnd, const char *pszBeginOrEnd, const char *pszExpectedType )
{
for (;;)
{
if ( pchPEM >= pchEnd || *pchPEM == '\0' )
return false;
if ( !V_isspace( *pchPEM ) )
break;
++pchPEM;
}
if ( pchPEM >= pchEnd || *pchPEM != '-' )
return false;
while ( *pchPEM == '-' )
{
++pchPEM;
if ( pchPEM >= pchEnd || *pchPEM == '\0' )
return false;
}
for (;;)
{
if ( pchPEM >= pchEnd || *pchPEM == '\0' )
return false;
if ( *pchPEM != ' ' && *pchPEM != '\t' )
break;
++pchPEM;
}
int l = V_strlen( pszBeginOrEnd );
if ( pchPEM + l >= pchEnd || V_strnicmp( pchPEM, pszBeginOrEnd, l ) != 0 )
return false;
pchPEM += l;
for (;;)
{
if ( pchPEM >= pchEnd || *pchPEM == '\0' )
return false;
if ( *pchPEM != ' ' && *pchPEM != '\t' )
break;
++pchPEM;
}
const char *pszType = pchPEM;
for (;;)
{
if ( pchPEM >= pchEnd || *pchPEM == '\0' || *pchPEM == '\r' || *pchPEM == '\n' )
return false;
if ( *pchPEM == '-' )
break;
++pchPEM;
}
if ( pszExpectedType )
{
int cchExpectedType = V_strlen( pszExpectedType );
if ( pszType + cchExpectedType > pchPEM || V_strnicmp( pszType, pszExpectedType, cchExpectedType ) != 0 )
return false;
}
while ( pchPEM < pchEnd && *pchPEM == '-' )
++pchPEM;
while ( pchPEM < pchEnd && V_isspace( *pchPEM ) )
++pchPEM;
return true;
}
const char *CCrypto::LocatePEMBody( const char *pchPEM, uint32 *pcch, const char *pszExpectedType )
{
if ( !pchPEM || !pcch || !*pcch )
return nullptr;
const char *pchEnd = pchPEM + *pcch;
if ( !BParsePEMHeaderOrFooter( pchPEM, pchEnd, "BEGIN", pszExpectedType ) )
return nullptr;
const char *pchBody = pchPEM;
for (;;)
{
if ( pchPEM >= pchEnd )
return nullptr;
if ( *pchPEM == '-' )
break;
++pchPEM;
}
uint32 cchBody = pchPEM - pchBody;
if ( !BParsePEMHeaderOrFooter( pchPEM, pchEnd, "END", pszExpectedType ) )
return nullptr;
*pcch = cchBody;
return pchBody;
}
bool CCrypto::DecodeBase64ToBuf( const char *pszEncoded, uint32 cbEncoded, CUtlBuffer &buf )
{
uint32 cubDecodeSize = cbEncoded * 3 / 4 + 1;
buf.EnsureCapacity( cubDecodeSize );
if ( !CCrypto::Base64Decode( pszEncoded, cbEncoded, (uint8*)buf.Base(), &cubDecodeSize ) )
return false;
buf.SeekPut( CUtlBuffer::SEEK_HEAD, cubDecodeSize );
return true;
}
bool CCrypto::DecodePEMBody( const char *pszPem, uint32 cch, CUtlBuffer &buf, const char *pszExpectedType )
{
const char *pszBody = CCrypto::LocatePEMBody( pszPem, &cch, pszExpectedType );
if ( !pszBody )
return false;
return DecodeBase64ToBuf( pszBody, cch, buf );
}