mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
1786 lines
68 KiB
C
1786 lines
68 KiB
C
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#include "ghttpCommon.h"
|
|
#if defined(MATRIXSSL)
|
|
#include "../matrixssl/matrixssl.h"
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
GHTTPBool ghttpSetRequestEncryptionEngine(GHTTPRequest request, GHTTPEncryptionEngine engine)
|
|
{
|
|
GHIConnection * connection = ghiRequestToConnection(request);
|
|
if(!connection)
|
|
return GHTTPFalse;
|
|
|
|
// Translate default into the actual engine name
|
|
// We don't want to set the engine value to "default" because
|
|
// we'd lose the ability to determine the engine name in other places
|
|
if (engine == GHTTPEncryptionEngine_Default)
|
|
{
|
|
#if defined(MATRIXSSL)
|
|
engine = GHTTPEncryptionEngine_MatrixSsl;
|
|
#elif defined(REVOEXSSL)
|
|
engine = GHTTPEncryptionEngine_RevoEx;
|
|
#else
|
|
engine = GHTTPEncryptionEngine_GameSpy;
|
|
#endif
|
|
}
|
|
|
|
// If the same engine has previously been set then we're done
|
|
if (connection->encryptor.mEngine == engine)
|
|
return GHTTPTrue;
|
|
|
|
// If a different engine has previously been set then we're screwed
|
|
if (connection->encryptor.mInterface != NULL &&
|
|
connection->encryptor.mEngine != engine)
|
|
{
|
|
return GHTTPFalse;
|
|
}
|
|
|
|
// If the URL is HTTPS but the engine is specific as NONE then we can't connect
|
|
if((engine == GHTTPEncryptionEngine_None) && (strncmp(connection->URL, "https://", 8) == 0))
|
|
return GHTTPFalse;
|
|
|
|
// Initialize the engine
|
|
connection->encryptor.mEngine = engine;
|
|
|
|
if (engine == GHTTPEncryptionEngine_None)
|
|
{
|
|
connection->encryptor.mInterface = NULL;
|
|
return GHTTPTrue; // this is the default, just return
|
|
}
|
|
else
|
|
{
|
|
// 02OCT07 BED: Design was changed to only allow one engine at a time
|
|
// Assert that the specified engine is the one supported
|
|
if (engine != GHTTPEncryptionEngine_Default)
|
|
{
|
|
#if defined(MATRIXSSL)
|
|
GS_ASSERT(engine==GHTTPEncryptionEngine_MatrixSsl);
|
|
#elif defined(REVOEXSSL)
|
|
GS_ASSERT(engine==GHTTPEncryptionEngine_RevoEx);
|
|
#else
|
|
GS_ASSERT(engine==GHTTPEncryptionEngine_GameSpy);
|
|
#endif
|
|
}
|
|
|
|
connection->encryptor.mInterface = NULL;
|
|
connection->encryptor.mInitFunc = ghiEncryptorSslInitFunc;
|
|
connection->encryptor.mStartFunc = ghiEncryptorSslStartFunc;
|
|
connection->encryptor.mCleanupFunc = ghiEncryptorSslCleanupFunc;
|
|
connection->encryptor.mEncryptFunc = ghiEncryptorSslEncryptFunc;
|
|
connection->encryptor.mDecryptFunc = ghiEncryptorSslDecryptFunc;
|
|
connection->encryptor.mInitialized = GHTTPFalse;
|
|
connection->encryptor.mSessionStarted = GHTTPFalse;
|
|
connection->encryptor.mSessionEstablished = GHTTPFalse;
|
|
connection->encryptor.mEncryptOnBuffer = GHTTPTrue;
|
|
connection->encryptor.mEncryptOnSend = GHTTPFalse;
|
|
connection->encryptor.mLibSendsHandshakeMessages = GHTTPFalse;
|
|
return GHTTPTrue;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// ********************* MATRIXSSL ENCRYPTION ENGINE ********************* //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifdef MATRIXSSL
|
|
|
|
// SSL requires a certificate validator
|
|
static int ghiSslCertValidator(struct sslCertInfo* theCertInfo, void* theUserData)
|
|
{
|
|
// Taken from matrisSslExample
|
|
sslCertInfo_t *next;
|
|
/*
|
|
Make sure we are checking the last cert in the chain
|
|
*/
|
|
next = theCertInfo;
|
|
while (next->next != NULL) {
|
|
next = next->next;
|
|
}
|
|
return next->verified;
|
|
}
|
|
|
|
// Init the engine
|
|
GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
sslKeys_t *keys = NULL;
|
|
sslSessionId_t *id = NULL;
|
|
|
|
int ecodeResult;
|
|
|
|
if (matrixSslOpen() < 0)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
if (matrixSslReadKeys(&keys, NULL, NULL, NULL, NULL) < 0)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
if (matrixSslNewSession((ssl_t**)&theEncryptor->mInterface, keys, id, 0) < 0)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
matrixSslSetCertValidator((ssl_t*)theEncryptor->mInterface, ghiSslCertValidator, NULL);
|
|
|
|
theEncryptor->mInitialized = GHTTPTrue;
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Start the handshake process
|
|
GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
sslBuf_t helloWrapper;
|
|
|
|
// Prepare the hello message
|
|
helloWrapper.buf = connection->sendBuffer.data;
|
|
helloWrapper.size = connection->sendBuffer.size;
|
|
helloWrapper.start = connection->sendBuffer.data + connection->sendBuffer.pos;
|
|
helloWrapper.end = helloWrapper.start; // start writing here
|
|
|
|
ecodeResult = matrixSslEncodeClientHello((ssl_t*)theEncryptor->mInterface, &helloWrapper, 0); // 0 = cipher
|
|
if (ecodeResult != 0)
|
|
return GHIEncryptionResult_Error; // error!
|
|
|
|
// Adjust the sendBuffer to account for the new data
|
|
connection->sendBuffer.len += (int)(helloWrapper.end - helloWrapper.start);
|
|
connection->sendBuffer.encrypted = GHTTPTrue;
|
|
theEncryptor->mSessionStarted = GHTTPTrue;
|
|
}
|
|
|
|
// Destroy the engine
|
|
GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
matrixSslClose();
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Encrypt some data
|
|
// - theEncryptedLength is reduced by the length of data written to theEncryptedBuffer
|
|
GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * thePlainTextBuffer,
|
|
int thePlainTextLength,
|
|
char * theEncryptedBuffer,
|
|
int * theEncryptedLength)
|
|
{
|
|
int encodeResult = 0;
|
|
|
|
// SSL buffer wrapper
|
|
// Append to theDecryptedBuffer
|
|
sslBuf_t encryptedBuf;
|
|
encryptedBuf.buf = theEncryptedBuffer; // buf starts here
|
|
encryptedBuf.start = theEncryptedBuffer; // readpos, set to start
|
|
encryptedBuf.end = theEncryptedBuffer; // writepos, set to start
|
|
encryptedBuf.size = *theEncryptedLength; // total size of buf
|
|
|
|
// perform the encryption
|
|
encodeResult = matrixSslEncode(connection->encryptor.mInterface,
|
|
(unsigned char*)thePlainTextBuffer, *thePlainTextLength, &encryptedBuf);
|
|
|
|
if (encodeResult == SSL_ERROR)
|
|
return GHIEncryptionResult_Error;
|
|
else if (encodeResult == SSL_FULL)
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
else
|
|
{
|
|
//*thePlainTextLength = *thePlainTextLength; // we always use the entire buffer
|
|
*theEncryptedLength -= (int)(encryptedBuf.end - encryptedBuf.start);
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
}
|
|
|
|
// Decrypt some data
|
|
// - During the handshaking process, this may result in data being appended to the send buffer
|
|
// - Data may be left in the encrypted buffer
|
|
// - theEncryptedLength becomes the length of data read from theEncryptedBuffer
|
|
// - theDecryptedLength becomes the length of data written to theDecryptedBuffer
|
|
GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * theEncryptedBuffer,
|
|
int * theEncryptedLength,
|
|
char * theDecryptedBuffer,
|
|
int * theDecryptedLength)
|
|
{
|
|
GHTTPBool decryptMore = GHTTPTrue;
|
|
int decodeResult = 0;
|
|
|
|
// SSL buffer wrappers
|
|
sslBuf_t inBuf;
|
|
sslBuf_t decryptedBuf;
|
|
int encryptedStartSize = *theEncryptedLength;
|
|
|
|
// Read from theEncryptedBuffer - Have to cast away the "const"
|
|
inBuf.buf = (unsigned char*)theEncryptedBuffer;
|
|
inBuf.start = (unsigned char*)theEncryptedBuffer;
|
|
inBuf.end = (unsigned char*)theEncryptedBuffer + *theEncryptedLength;
|
|
inBuf.size = *theEncryptedLength;
|
|
|
|
// Append to theDecryptedBuffer
|
|
decryptedBuf.buf = theDecryptedBuffer; // buf starts here
|
|
decryptedBuf.start = theDecryptedBuffer; // readpos, set to start
|
|
decryptedBuf.end = theDecryptedBuffer; // writepos, set to start
|
|
decryptedBuf.size = *theDecryptedLength; // total size of buf
|
|
|
|
// Perform the decode operation
|
|
// - may require multiple tries
|
|
while(decryptMore != GHTTPFalse && ((inBuf.end-inBuf.start) > 0))
|
|
{
|
|
unsigned char error = 0;
|
|
unsigned char alertlevel = 0;
|
|
unsigned char alertdescription = 0;
|
|
|
|
// perform the decode, this will decode a single SSL message at a time
|
|
decodeResult = matrixSslDecode(theEncryptor->mInterface, &inBuf, &decryptedBuf,
|
|
&error, &alertlevel, &alertdescription);
|
|
switch(decodeResult)
|
|
{
|
|
case SSL_SUCCESS:
|
|
// a message was handled internally by matrixssl
|
|
// No data is appeneded to the decrypted buffer
|
|
if (matrixSslHandshakeIsComplete(theEncryptor->mInterface))
|
|
theEncryptor->mSessionEstablished = GHTTPTrue;
|
|
break;
|
|
|
|
case SSL_PROCESS_DATA:
|
|
// We've received app data, continue on.
|
|
// App data was appended to the decrypted buffer
|
|
break;
|
|
|
|
case SSL_SEND_RESPONSE:
|
|
{
|
|
// we must send an SSL response which has been written to decryptedBuf
|
|
// transfer this response to the connection's sendBuffer
|
|
int responseSize = decryptedBuf.end - decryptedBuf.start;
|
|
|
|
// force disable-encryption
|
|
// this may seem like a hack, but it's the best way to avoid
|
|
// unnecessary data copies without modifying matrixSSL
|
|
theEncryptor->mSessionEstablished = GHTTPFalse;
|
|
ghiTrySendThenBuffer(connection, decryptedBuf.start, responseSize);
|
|
theEncryptor->mSessionEstablished = GHTTPTrue;
|
|
|
|
// Remove the bytes from the decrypted buffer (we don't want to return them to the app)
|
|
decryptedBuf.end = decryptedBuf.start; // bug?
|
|
break;
|
|
}
|
|
|
|
case SSL_ERROR:
|
|
// error decoding the data
|
|
decryptMore = GHTTPFalse;
|
|
break;
|
|
|
|
case SSL_ALERT:
|
|
// server sent an alert
|
|
if (alertdescription == SSL_ALERT_CLOSE_NOTIFY)
|
|
decryptMore = GHTTPFalse;
|
|
break;
|
|
|
|
case SSL_PARTIAL:
|
|
// need to read more data from the socket(inbuf incomplete)
|
|
decryptMore = GHTTPFalse;
|
|
break;
|
|
|
|
case SSL_FULL:
|
|
{
|
|
// decodeBuffer is too small, need to increase size and try again
|
|
decryptMore = GHTTPFalse;
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Store off the lengths
|
|
*theEncryptedLength = encryptedStartSize - (inBuf.end - inBuf.start);
|
|
*theDecryptedLength = decryptedBuf.end - decryptedBuf.start;
|
|
|
|
// Return status to app
|
|
if (decodeResult == SSL_FULL)
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
else if (decodeResult == SSL_ERROR || decodeResult == SSL_ALERT)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
//if ((int)(decryptedBuf.end - decryptedBuf.start) > 0)
|
|
// printf ("Decrypted: %d bytes\r\n", *theDecryptedLength);
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// ********************* REVOEX SSL ENCRYPTION ENGINE ******************** //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#elif defined(REVOEXSSL)
|
|
#include <revolution/ssl.h>
|
|
|
|
typedef struct gsRevoExInterface
|
|
{
|
|
SSLId mId;
|
|
SSLClientCertId mClientCertId;
|
|
SSLRootCAId mRootCAId;
|
|
GHTTPBool mConnected; // means "connected to socket", not "connected to remote machine"
|
|
} gsRevoExInterface;
|
|
|
|
// Init the engine
|
|
GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
int i=0;
|
|
gsRevoExInterface* sslInterface = NULL;
|
|
|
|
// There is only one place where this function should be called,
|
|
// and it should check if the engine has been initialized
|
|
GS_ASSERT(theEncryptor->mInitialized == GHTTPFalse);
|
|
GS_ASSERT(theEncryptor->mInterface == NULL);
|
|
|
|
// allocate the interface (need one per connection)
|
|
theEncryptor->mInterface = gsimalloc(sizeof(gsRevoExInterface));
|
|
if (theEncryptor->mInterface == NULL)
|
|
{
|
|
// memory allocation failed
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError,
|
|
"Failed to allocate SSL interface (out of memory: %d bytes)\r\n", sizeof(gsRevoExInterface));
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
memset(theEncryptor->mInterface, 0, sizeof(gsRevoExInterface));
|
|
sslInterface = (gsRevoExInterface*)theEncryptor->mInterface;
|
|
|
|
{
|
|
int verifyOption = 0;
|
|
int rcode = 0;
|
|
|
|
verifyOption = SSL_VERIFY_COMMON_NAME
|
|
| SSL_VERIFY_ROOT_CA
|
|
| SSL_VERIFY_DATE
|
|
| SSL_VERIFY_CHAIN
|
|
| SSL_VERIFY_SUBJECT_ALT_NAME;
|
|
|
|
// todo serverAddress, is this used for certificate name?
|
|
sslInterface->mId = SSLNew(verifyOption, connection->serverAddress);
|
|
|
|
rcode = SSLSetBuiltinRootCA(sslInterface->mId, SSL_ROOTCA_NINTENDO_1);
|
|
if(rcode != SSL_ENONE){
|
|
SSLShutdown(sslInterface->mId);
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
rcode = SSLSetBuiltinClientCert(sslInterface->mId, SSL_CLIENTCERT_NINTENDO_0);
|
|
if(rcode != SSL_ENONE){
|
|
SSLShutdown(sslInterface->mId);
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
}
|
|
|
|
theEncryptor->mInitialized = GHTTPTrue;
|
|
theEncryptor->mSessionStarted = GHTTPFalse;
|
|
theEncryptor->mSessionEstablished = GHTTPFalse;
|
|
//theEncryptor->mUseSSLConnect = GHTTPTrue;
|
|
theEncryptor->mEncryptOnBuffer = GHTTPFalse;
|
|
theEncryptor->mEncryptOnSend = GHTTPTrue;
|
|
theEncryptor->mLibSendsHandshakeMessages = GHTTPTrue;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx engine) initialized\r\n");
|
|
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
|
|
// Destroy the engine
|
|
GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
if (theEncryptor != NULL)
|
|
{
|
|
gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface;
|
|
if (sslInterface != NULL)
|
|
{
|
|
SSLShutdown(sslInterface->mId);
|
|
gsifree(sslInterface);
|
|
theEncryptor->mInterface = NULL;
|
|
}
|
|
theEncryptor->mInitialized = GHTTPFalse;
|
|
theEncryptor->mSessionStarted = GHTTPFalse;
|
|
theEncryptor->mSessionEstablished = GHTTPFalse;
|
|
}
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) engine cleanup called\r\n");
|
|
|
|
GSI_UNUSED(connection);
|
|
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
|
|
GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface;
|
|
int result = 0;
|
|
|
|
GS_ASSERT(theEncryptor->mSessionStarted == GHTTPFalse);
|
|
|
|
// Call this only AFTER the socket has been connected to the remote server
|
|
if (!sslInterface->mConnected)
|
|
{
|
|
result = SSLConnect(sslInterface->mId, connection->socket);
|
|
if (result != SSL_ENONE)
|
|
{
|
|
switch(result)
|
|
{
|
|
case SSL_EFAILED:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLConnect failed (SSL_EFAILED)\r\n");
|
|
break;
|
|
case SSL_ESSLID:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLConnect failed (SSL_ESSLID)\r\n");
|
|
break;
|
|
default:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLConnect failed (Unhandled Error)\r\n");
|
|
break;
|
|
}
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
sslInterface->mConnected = GHTTPTrue;
|
|
}
|
|
|
|
GS_ASSERT(sslInterface->mConnected == GHTTPTrue);
|
|
|
|
// begin securing the session
|
|
result = SSLDoHandshake(sslInterface->mId);
|
|
if (result != SSL_ENONE)
|
|
{
|
|
// Check for EWOULDBLOCK conditions
|
|
if (result == SSL_EWANT_READ || result == SSL_EWANT_WRITE)
|
|
return GHIEncryptionResult_Success;
|
|
|
|
switch(result)
|
|
{
|
|
case SSL_EFAILED:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EFAILED)\r\n");
|
|
break;
|
|
case SSL_ESSLID:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESSLID)\r\n");
|
|
break;
|
|
case SSL_ESYSCALL:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESYSCALL)\r\n");
|
|
break;
|
|
case SSL_EZERO_RETURN:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EZERO_RETURN)\r\n");
|
|
break;
|
|
case SSL_EWANT_CONNECT:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EWANT_CONNECT)\r\n");
|
|
break;
|
|
case SSL_EVERIFY_COMMON_NAME:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_COMMON_NAME)\r\n");
|
|
break;
|
|
case SSL_EVERIFY_CHAIN:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_CHAIN)\r\n");
|
|
break;
|
|
case SSL_EVERIFY_ROOT_CA:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_ROOT_CA)\r\n");
|
|
break;
|
|
case SSL_EVERIFY_DATE:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_DATE)\r\n");
|
|
break;
|
|
default:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLDoHandshake failed (Unhandled Error)\r\n");
|
|
break;
|
|
}
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
// Success
|
|
theEncryptor->mSessionStarted = GHTTPTrue;
|
|
theEncryptor->mSessionEstablished = GHTTPTrue;
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Encrypt and send some data
|
|
GHIEncryptionResult ghiEncryptorSslEncryptSend(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * thePlainTextBuffer,
|
|
int thePlainTextLength,
|
|
int * theBytesSentOut)
|
|
{
|
|
gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface;
|
|
int result = 0;
|
|
|
|
result = SSLWrite(sslInterface->mId, thePlainTextBuffer, thePlainTextLength);
|
|
if (result == SSL_EZERO_RETURN)
|
|
{
|
|
// send 0 is not fatal
|
|
*theBytesSentOut = 0;
|
|
}
|
|
else if (result == SSL_EWANT_WRITE)
|
|
{
|
|
// signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS
|
|
*theBytesSentOut = -1;
|
|
}
|
|
else if (result < 0)
|
|
{
|
|
switch(result)
|
|
{
|
|
case SSL_EFAILED:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (SSL_EFAILED)\r\n");
|
|
break;
|
|
case SSL_ESSLID:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESSLID)\r\n");
|
|
break;
|
|
case SSL_EWANT_READ:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_READ)\r\n");
|
|
break;
|
|
case SSL_ESYSCALL:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESYSCALL)\r\n");
|
|
break;
|
|
case SSL_EWANT_CONNECT:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_CONNECT)\r\n");
|
|
break;
|
|
default:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLWrite failed (Unhandled Error)\r\n");
|
|
break;
|
|
}
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
else
|
|
{
|
|
GS_ASSERT(result > 0);
|
|
*theBytesSentOut = result;
|
|
}
|
|
GSI_UNUSED(connection);
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Receive and decrypt some data
|
|
GHIEncryptionResult ghiEncryptorSslDecryptRecv(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
char * theDecryptedBuffer,
|
|
int * theDecryptedLength)
|
|
{
|
|
gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface;
|
|
int result = 0;
|
|
|
|
result = SSLRead(sslInterface->mId, theDecryptedBuffer, *theDecryptedLength);
|
|
if (result == SSL_EZERO_RETURN)
|
|
{
|
|
// receive 0 is not fatal
|
|
*theDecryptedLength = 0;
|
|
}
|
|
else if (result == SSL_EWANT_READ)
|
|
{
|
|
// signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS
|
|
*theDecryptedLength = -1;
|
|
}
|
|
else if (result < 0)
|
|
{
|
|
// Fatal errors
|
|
switch(result)
|
|
{
|
|
case SSL_EFAILED:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (SSL_EFAILED)\r\n");
|
|
break;
|
|
case SSL_ESSLID:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (SSL_ESSLID)\r\n");
|
|
break;
|
|
case SSL_EWANT_WRITE:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_WRITE)\r\n");
|
|
break;
|
|
case SSL_ESYSCALL:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (SSL_ESYSCALL)\r\n");
|
|
break;
|
|
case SSL_EWANT_CONNECT:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_CONNECT)\r\n");
|
|
break;
|
|
default:
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL (RevoEx) SSLRead failed (Unhandled Error)\r\n");
|
|
break;
|
|
}
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
else
|
|
{
|
|
GS_ASSERT(result > 0);
|
|
*theDecryptedLength = result;
|
|
}
|
|
GSI_UNUSED(connection);
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * thePlainTextBuffer,
|
|
int thePlainTextLength,
|
|
char * theEncryptedBuffer,
|
|
int * theEncryptedLength)
|
|
{
|
|
GS_FAIL(); // Should never call this for RevoEx SSL! It uses encrypt on send
|
|
|
|
GSI_UNUSED(connection);
|
|
GSI_UNUSED(theEncryptor);
|
|
GSI_UNUSED(thePlainTextBuffer);
|
|
GSI_UNUSED(thePlainTextLength);
|
|
GSI_UNUSED(theEncryptedBuffer);
|
|
GSI_UNUSED(theEncryptedLength);
|
|
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * theEncryptedBuffer,
|
|
int * theEncryptedLength,
|
|
char * theDecryptedBuffer,
|
|
int * theDecryptedLength)
|
|
{
|
|
GS_FAIL(); // Should never call this for RevoEx SSL! It uses decrypt on recv
|
|
|
|
GSI_UNUSED(connection);
|
|
GSI_UNUSED(theEncryptor);
|
|
GSI_UNUSED(theEncryptedBuffer);
|
|
GSI_UNUSED(theEncryptedLength);
|
|
GSI_UNUSED(theDecryptedBuffer);
|
|
GSI_UNUSED(theDecryptedLength);
|
|
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// *********************** GS SSL ENCRYPTION ENGINE ********************** //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#else
|
|
|
|
#include "../common/gsSSL.h"
|
|
#include "../common/gsSHA1.h"
|
|
#include "../common/gsRC4.h"
|
|
#include "../md5.h"
|
|
|
|
|
|
// Processor for SSL state messages (transparent to application)
|
|
static GHIEncryptionResult ghiEncryptorProcessSSLHandshake(struct GHIConnection * connection,
|
|
struct GHIEncryptor * encryptor,
|
|
GHIBuffer * data);
|
|
|
|
// SSL-ASN.1 lengths are variable length NBO integers
|
|
// we use this utility to make data packing easier
|
|
// example: 61 little-endian(intel) = 61 00 00 00
|
|
// big-endian(network) = 00 00 00 61
|
|
static void ghiEncryptorWriteNBOLength(unsigned char* buf, int value, int size)
|
|
{
|
|
int NBO = (int)htonl(value);
|
|
unsigned char* NBOData = (unsigned char*)&NBO;
|
|
|
|
assert(size <= sizeof(NBO));
|
|
if (size > sizeof(NBO))
|
|
return; // can't write more than 4 bytes!
|
|
|
|
// this won't work if NBO ever changes from big-endian
|
|
memcpy(buf, NBOData+(sizeof(int)-size), (size_t)size);
|
|
}
|
|
static GHTTPBool ghiEncryptorReadNBOLength(GHIBuffer * data, int* value, int size)
|
|
{
|
|
assert(size <= sizeof(*value));
|
|
if (size > sizeof(*value))
|
|
return GHTTPFalse;
|
|
if (GHTTPFalse == ghiReadDataFromBufferFixed(data, ((char*)value)+(sizeof(int)-size), size))
|
|
return GHTTPFalse;
|
|
|
|
*value = (int)htonl(*value);
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
static GHTTPBool ghiEncryptorParseASN1Sequence(GHIBuffer * data, int* lenOut)
|
|
{
|
|
char tempChar = '\0';
|
|
|
|
if (GHTTPFalse == ghiReadDataFromBufferFixed(data, &tempChar, 1))
|
|
return GHTTPFalse;
|
|
if (tempChar != 0x30) // sequence start byte
|
|
return GHTTPFalse;
|
|
|
|
if (GHTTPFalse == ghiReadDataFromBufferFixed(data, &tempChar, 1))
|
|
return GHTTPFalse;
|
|
if ((tempChar & 0x80) == 0x80)
|
|
{
|
|
int tempInt = 0;
|
|
|
|
// length is stored in next (tempChar^0x80) bytes
|
|
tempChar ^= 0x80;
|
|
if (GHTTPFalse == ghiEncryptorReadNBOLength(data, &tempInt, tempChar))
|
|
return GHTTPFalse;
|
|
if (tempInt > (data->len - data->pos))
|
|
return GHTTPFalse;
|
|
|
|
*lenOut = tempInt;
|
|
return GHTTPTrue;
|
|
}
|
|
else
|
|
{
|
|
if ((int)tempChar > (data->len - data->pos))
|
|
return GHTTPFalse;
|
|
|
|
*lenOut = tempChar;
|
|
return GHTTPTrue;
|
|
}
|
|
}
|
|
|
|
static void ghiEncryptorGenerateEncryptionKeys(gsSSL* sslInterface)
|
|
{
|
|
// Use the server random, client random and pre master secret
|
|
// to compute the encryption key.
|
|
|
|
// SSLv3 style
|
|
// master_secret = {
|
|
// MD5(pre_master_secret + SHA1("A"+pre_master_secret+client_random+server_random)) +
|
|
// MD5(pre_master_secret + SHA1("BB"+pre_master_secret+client_random+server_random)) +
|
|
// MD5(pre_master_secret + SHA1("CCC"+pre_master_secret+client_random+server_random))
|
|
// }
|
|
// key_block = {
|
|
// MD5(master_secret + SHA1("A"+master_secret+server_random+client_random)) +
|
|
// MD5(master_secret + SHA1("BB"+master_secret+server_random+client_random)) +
|
|
// MD5(master_secret + SHA1("CCC"+master_secret+server_random+client_random))
|
|
|
|
SHA1Context sha1;
|
|
MD5_CTX md5;
|
|
unsigned char temp[SHA1HashSize];
|
|
|
|
unsigned int randomSize = 32;
|
|
unsigned char keyblock[64]; // todo: support different key sizes
|
|
|
|
// master_secret "A"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"A", 1);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final((unsigned char*)&sslInterface->mastersecret[0*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// master_secret "BB"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"BB", 2);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final((unsigned char*)&sslInterface->mastersecret[1*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// master_secret "CCC"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"CCC", 3);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final((unsigned char*)&sslInterface->mastersecret[2*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// key_block "A"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"A", 1);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final(&keyblock[0*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// key_block "BB"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"BB", 2);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final(&keyblock[1*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// key_block "CCC"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"CCC", 3);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final(&keyblock[2*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// key_block "DDDD"
|
|
SHA1Reset(&sha1);
|
|
SHA1Input(&sha1, (const unsigned char*)"DDDD", 4);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize);
|
|
SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize);
|
|
SHA1Result(&sha1, temp);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE);
|
|
MD5Final(&keyblock[3*GS_CRYPT_MD5_HASHSIZE], &md5);
|
|
|
|
// key_block "EEEEE"
|
|
// key_block "FFFFFF"
|
|
// ... continue if more key material is needed
|
|
|
|
// todo: support different key sizes
|
|
// KEYBLOCK
|
|
// writemac[16], readmac[16], writekey[16], readkey[16], writeIV[0], readIV[0]
|
|
memcpy(sslInterface->clientWriteMACSecret, &keyblock[0], 16);
|
|
memcpy(sslInterface->clientReadMACSecret, &keyblock[16], 16);
|
|
memcpy(sslInterface->clientWriteKey, &keyblock[32], 16);
|
|
memcpy(sslInterface->clientReadKey, &keyblock[48], 16);
|
|
|
|
sslInterface->clientWriteMACLen = 16;
|
|
sslInterface->clientReadMACLen = 16;
|
|
sslInterface->clientWriteKeyLen = 16;
|
|
sslInterface->clientReadKeyLen = 16;
|
|
|
|
// Init the stream cipher
|
|
RC4Init(&sslInterface->sendRC4, (const unsigned char*)sslInterface->clientWriteKey, sslInterface->clientWriteKeyLen);
|
|
RC4Init(&sslInterface->recvRC4, (const unsigned char*)sslInterface->clientReadKey, sslInterface->clientReadKeyLen);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"Generated SSL session keys\r\n");
|
|
}
|
|
|
|
|
|
// Init the engine
|
|
GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
gsSSL* sslInterface = NULL;
|
|
|
|
// There is only one place where this function should be called,
|
|
// and it should check if the engine has been initialized
|
|
assert(theEncryptor->mInitialized == GHTTPFalse);
|
|
assert(theEncryptor->mInterface == NULL);
|
|
|
|
// Make sure the send buffer is large enough for the SSL handshake (handshake is <1k)
|
|
if (connection->sendBuffer.size - connection->sendBuffer.len < sizeof(gsSSLClientHelloMsg))
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
|
|
// allocate the interface (need one per connection)
|
|
theEncryptor->mInterface = gsimalloc(sizeof(gsSSL));
|
|
if (theEncryptor->mInterface == NULL)
|
|
{
|
|
// memory allocation failed
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError,
|
|
"Failed to allocate SSL interface (out of memory: %d bytes)\r\n", sizeof(gsSSL));
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
memset(theEncryptor->mInterface, 0, sizeof(gsSSL));
|
|
sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
|
|
theEncryptor->mInitialized = GHTTPTrue;
|
|
theEncryptor->mSessionStarted = GHTTPFalse;
|
|
theEncryptor->mSessionEstablished = GHTTPFalse;
|
|
theEncryptor->mEncryptOnBuffer = GHTTPTrue;
|
|
theEncryptor->mEncryptOnSend = GHTTPFalse;
|
|
theEncryptor->mLibSendsHandshakeMessages = GHTTPFalse;
|
|
MD5Init(&sslInterface->finishHashMD5);
|
|
SHA1Reset(&sslInterface->finishHashSHA1);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL engine initialized\r\n");
|
|
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Destroy the engine
|
|
GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
if (theEncryptor != NULL)
|
|
{
|
|
gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
if (sslInterface != NULL)
|
|
{
|
|
gsifree(sslInterface);
|
|
theEncryptor->mInterface = NULL;
|
|
}
|
|
theEncryptor->mInitialized = GHTTPFalse;
|
|
theEncryptor->mSessionStarted = GHTTPFalse;
|
|
theEncryptor->mSessionEstablished = GHTTPFalse;
|
|
}
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL engine cleanup called\r\n");
|
|
|
|
GSI_UNUSED(connection);
|
|
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Init the engine
|
|
GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor)
|
|
{
|
|
gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
gsSSLClientHelloMsg helloMsg;
|
|
int i=0;
|
|
|
|
// prepare the client hello
|
|
// 1)
|
|
helloMsg.header.contentType = GS_SSL_CONTENT_HANDSHAKE;
|
|
helloMsg.header.versionMajor = GS_SSL_VERSION_MAJOR;
|
|
helloMsg.header.versionMinor = GS_SSL_VERSION_MINOR;
|
|
|
|
// Set the length of the client hello message (2-byte NBO int, not including record header)
|
|
ghiEncryptorWriteNBOLength(helloMsg.header.lengthNBO, sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg), 2);
|
|
|
|
helloMsg.handshakeType = GS_SSL_HANDSHAKE_CLIENTHELLO;
|
|
helloMsg.versionMajor = GS_SSL_VERSION_MAJOR;
|
|
helloMsg.versionMinor = GS_SSL_VERSION_MINOR;
|
|
|
|
// Set the length of the client hello data (3 byte NBO int)
|
|
// This is the total message length MINUS the SSL record header MINUS four additional header bytes
|
|
ghiEncryptorWriteNBOLength(helloMsg.lengthNBO, sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg)-4, 3);
|
|
//ghttpEncryptorSetNBOBytesFromHBOInt(helloMsg.time, (gsi_u32)current_time(), 4); // 4 byte time value (for randomness)
|
|
ghiEncryptorWriteNBOLength(helloMsg.time, 0, 4); // test code: no randomness
|
|
|
|
// fill in the [rest of the] random
|
|
// Security Note: If a hacker is able to discern the current_time() they may be able to
|
|
// recreate the random bytes and recover the session key.
|
|
Util_RandSeed(current_time());
|
|
for (i=0; i<28; i++)
|
|
{
|
|
#if defined(GS_CRYPT_NO_RANDOM)
|
|
#pragma message ("!!!WARNING: SSL Random disable for debug purposes. SSL not secured!!!")
|
|
helloMsg.random[i] = 0; // test code: no randomness
|
|
#else
|
|
helloMsg.random[i] = (unsigned char)Util_RandInt(0, 0xff);
|
|
#endif
|
|
}
|
|
|
|
// store a copy of the random (used later for key generation)
|
|
memcpy(&sslInterface->clientRandom[0], helloMsg.time, 4);
|
|
memcpy(&sslInterface->clientRandom[4], helloMsg.random, 28);
|
|
|
|
// todo: session resumption
|
|
helloMsg.sessionIdLen = 0;
|
|
|
|
// fill in cipher suite IDs
|
|
helloMsg.cipherSuitesLength = htons(sizeof(gsi_u16)*GS_SSL_NUM_CIPHER_SUITES);
|
|
for (i=0; i < GS_SSL_NUM_CIPHER_SUITES; i++)
|
|
helloMsg.cipherSuites[i] = htons((unsigned short)gsSSLCipherSuites[i].mSuiteID);
|
|
|
|
// there are no standard SSL compression methods
|
|
helloMsg.compressionMethodLen = 1;
|
|
helloMsg.compressionMethodList = 0;
|
|
|
|
// We need to compute a hash of all the handshake messages
|
|
// Add this message to the hash (both MD5 hash and SHA1 hash)
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&helloMsg+sizeof(gsSSLRecordHeaderMsg), sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg));
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&helloMsg+sizeof(gsSSLRecordHeaderMsg), sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg));
|
|
|
|
// Now send it (we already verified the length, so this should not fail)
|
|
if (GHTTPFalse == ghiAppendDataToBuffer(&connection->sendBuffer, (const char*)&helloMsg, sizeof(gsSSLClientHelloMsg)))
|
|
{
|
|
// assert or just return?
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
}
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"GameSpy SSL engine handshake started\r\n");
|
|
|
|
theEncryptor->mSessionStarted = GHTTPTrue;
|
|
return GHIEncryptionResult_Success;
|
|
|
|
}
|
|
|
|
// Encrypt some data
|
|
// - theEncryptedLength is reduced by the length of data written to theEncryptedBuffer
|
|
// So if the encrypted buffer is 255 bytes long and we write 50 additional bytes, we'll return 205.
|
|
GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * thePlainTextBuffer,
|
|
int thePlainTextLength,
|
|
char * theEncryptedBuffer,
|
|
int * theEncryptedLength)
|
|
{
|
|
if (theEncryptor != NULL)
|
|
{
|
|
gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
if (sslInterface == NULL || theEncryptor->mSessionEstablished == GHTTPFalse)
|
|
{
|
|
// not secured yet, send as plain text
|
|
if (thePlainTextLength > *theEncryptedLength)
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
memcpy(theEncryptedBuffer, thePlainTextBuffer, (size_t)thePlainTextLength);
|
|
*theEncryptedLength += thePlainTextLength; // number of bytes written
|
|
}
|
|
else
|
|
{
|
|
// Create an SSL encrypted record
|
|
// The order of operations below is very important.
|
|
// The MAC must be computed before ciphering the plain text because
|
|
// theEncryptedBuffer may be the same memory location as thePlainTextBuffer
|
|
|
|
gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
gsSSLRecordHeaderMsg* header = NULL;
|
|
MD5_CTX md5;
|
|
int pos = 0;
|
|
unsigned short lengthNBO = htons((unsigned short)thePlainTextLength);
|
|
unsigned char MAC[GS_CRYPT_MD5_HASHSIZE];
|
|
|
|
// The SSL record adds a little overhead
|
|
if (*theEncryptedLength < (thePlainTextLength+(int)sizeof(gsSSLRecordHeaderMsg)))
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
|
|
// write the SSL header
|
|
header = (gsSSLRecordHeaderMsg*)theEncryptedBuffer;
|
|
header->contentType = GS_SSL_CONTENT_APPLICATIONDATA;
|
|
header->versionMajor = GS_SSL_VERSION_MAJOR;
|
|
header->versionMinor = GS_SSL_VERSION_MINOR;
|
|
pos += sizeof(gsSSLRecordHeaderMsg);
|
|
|
|
// calculate the MAC
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, sslInterface->clientWriteMACSecret, (unsigned int)sslInterface->clientWriteMACLen);
|
|
MD5Update(&md5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN);
|
|
MD5Update(&md5, sslInterface->sendSeqNBO, sizeof(sslInterface->sendSeqNBO));
|
|
MD5Update(&md5, (unsigned char*)"\x17", 1); // content type application data
|
|
MD5Update(&md5,(unsigned char*)&lengthNBO, sizeof(lengthNBO));
|
|
MD5Update(&md5, (unsigned char*)thePlainTextBuffer, (unsigned int)thePlainTextLength); // **cast-away const**
|
|
MD5Final(MAC, &md5); // first half of MAC
|
|
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, sslInterface->clientWriteMACSecret, (unsigned int)sslInterface->clientWriteMACLen);
|
|
MD5Update(&md5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN);
|
|
MD5Update(&md5, MAC, GS_CRYPT_MD5_HASHSIZE);
|
|
MD5Final(MAC, &md5); // complete MAC
|
|
|
|
// apply stream cipher to data + MAC
|
|
RC4Encrypt(&sslInterface->sendRC4, (const unsigned char*)thePlainTextBuffer, (unsigned char*)&theEncryptedBuffer[pos], thePlainTextLength);
|
|
pos += thePlainTextLength;
|
|
RC4Encrypt(&sslInterface->sendRC4, MAC, (unsigned char*)&theEncryptedBuffer[pos], GS_CRYPT_MD5_HASHSIZE);
|
|
pos += GS_CRYPT_MD5_HASHSIZE;
|
|
|
|
// Now that we know the final length (data+mac+pad), write it into the header
|
|
ghiEncryptorWriteNBOLength(header->lengthNBO, (int)(pos - sizeof(gsSSLRecordHeaderMsg)), 2);
|
|
|
|
// adjust encrypted length
|
|
*theEncryptedLength -= pos;
|
|
|
|
// Update the sequence number for the next message (8-byte, NBO)
|
|
pos = 7; // **changing the semantic of variable "pos"
|
|
do
|
|
{
|
|
//int carry = 0;
|
|
if (sslInterface->sendSeqNBO[pos] == 0xFF) // wraparound means carry
|
|
{
|
|
//carry = 1;
|
|
sslInterface->sendSeqNBO[pos] = 0;
|
|
pos -= 1;
|
|
}
|
|
else
|
|
{
|
|
sslInterface->sendSeqNBO[pos] += 1;
|
|
pos = 0; // end addition
|
|
}
|
|
} while(pos >= 0);
|
|
}
|
|
}
|
|
|
|
GSI_UNUSED(connection);
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
// Decrypt some data
|
|
// - During the handshaking process, this may result in data being appended to the send buffer
|
|
// - Data may be left in the encrypted buffer
|
|
// - theEncryptedLength becomes the length of data read from theEncryptedBuffer
|
|
// - theDecryptedLength becomes the length of data written to theDecryptedBuffer
|
|
GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * theEncryptedBuffer,
|
|
int * theEncryptedLength,
|
|
char * theDecryptedBuffer,
|
|
int * theDecryptedLength)
|
|
{
|
|
gsSSL* sslInterface = NULL;
|
|
int readPos = 0;
|
|
int writePos = 0;
|
|
|
|
// Make sure we have a valid encryptor
|
|
assert(theEncryptor != NULL);
|
|
assert(theEncryptor->mInterface != NULL);
|
|
if (theEncryptor == NULL || theEncryptor->mInterface == NULL)
|
|
{
|
|
// no encryption set? copy as plain text
|
|
memcpy(theDecryptedBuffer, theEncryptedBuffer, (size_t)(*theEncryptedLength));
|
|
*theDecryptedLength = *theEncryptedLength;
|
|
*theEncryptedLength = 0; // no bytes remaining
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
sslInterface = (gsSSL*)theEncryptor->mInterface;
|
|
if (sslInterface == NULL)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
// Read each SSL message from the stream (leave partial messages)
|
|
while(readPos < *theEncryptedLength)
|
|
{
|
|
gsSSLRecordHeaderMsg* header = NULL;
|
|
unsigned short length = 0;
|
|
GHIEncryptionResult result;
|
|
|
|
// make sure we have the complete record header
|
|
if ((*theEncryptedLength-readPos) < sizeof(gsSSLRecordHeaderMsg))
|
|
break;
|
|
header = (gsSSLRecordHeaderMsg*)&theEncryptedBuffer[readPos];
|
|
|
|
// make sure we have the complete record data
|
|
// Warning: Convert the length in two steps to avoid issues with byte order
|
|
// BAD!! -> length = ntohs((header->lengthNBO[0] | (header->lengthNBO[1] << 8)));
|
|
//length = (unsigned short)(header->lengthNBO[0] | (header->lengthNBO[1] << 8));
|
|
memcpy(&length, &header->lengthNBO[0], sizeof(length));
|
|
length = ntohs(length);
|
|
if ( *theEncryptedLength < (readPos + length +(int)sizeof(gsSSLRecordHeaderMsg)))
|
|
break; // wait for more data
|
|
|
|
// if we have to decrypt, make sure there is room in the decrypt buffer
|
|
if (connection->encryptor.mSessionEstablished)
|
|
{
|
|
if ((*theDecryptedLength-writePos) < length)
|
|
{
|
|
*theEncryptedLength = readPos; // bytes read *NOT* bytes remaining
|
|
*theDecryptedLength = writePos; // bytes written
|
|
|
|
if (*theDecryptedLength>0)
|
|
return GHIEncryptionResult_Success;
|
|
else
|
|
return GHIEncryptionResult_BufferTooSmall;
|
|
}
|
|
}
|
|
|
|
//readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
|
|
// process the record data
|
|
switch(header->contentType)
|
|
{
|
|
case GS_SSL_CONTENT_HANDSHAKE:
|
|
{
|
|
GHIBuffer data;
|
|
|
|
// Apply stream cipher if the session has been established
|
|
readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
if (connection->encryptor.mSessionEstablished)
|
|
RC4Encrypt(&sslInterface->recvRC4, (const unsigned char*)&theEncryptedBuffer[readPos], (unsigned char*)&theEncryptedBuffer[readPos], length);
|
|
|
|
ghiInitReadOnlyBuffer(connection, &data, &theEncryptedBuffer[readPos], length);
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"SSL handshake message received\r\n");
|
|
|
|
result = ghiEncryptorProcessSSLHandshake(connection, theEncryptor, &data);
|
|
if (result != GHIEncryptionResult_Success)
|
|
return result; // error!
|
|
|
|
break;
|
|
}
|
|
case GS_SSL_CONTENT_APPLICATIONDATA:
|
|
{
|
|
// make sure there is enough room to receive this data
|
|
if ( (*theDecryptedLength-writePos) < length)
|
|
{
|
|
}
|
|
|
|
// Apply stream cipher if the session has been established
|
|
readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
if (connection->encryptor.mSessionEstablished)
|
|
RC4Encrypt(&sslInterface->recvRC4, (const unsigned char*)&theEncryptedBuffer[readPos], (unsigned char*)&theEncryptedBuffer[readPos], length);
|
|
|
|
// verify MAC and pad
|
|
// verifyMAC();
|
|
|
|
// copy to decrypted buffer so HTTP layer can process
|
|
memcpy(theDecryptedBuffer+writePos, &theEncryptedBuffer[readPos], (size_t)(length - GS_CRYPT_MD5_HASHSIZE));
|
|
writePos += length - GS_CRYPT_MD5_HASHSIZE;
|
|
break;
|
|
}
|
|
|
|
case GS_SSL_CONTENT_CHANGECIPHERSPEC:
|
|
readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
//if(readPos > *theEncryptedLength)
|
|
// _asm int 3;
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug,
|
|
"SSL change cipher spec message received\r\n");
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Notice,
|
|
"SSL: Incoming traffic now encrypted\r\n");
|
|
connection->encryptor.mSessionEstablished = GHTTPTrue;
|
|
break;
|
|
|
|
case GS_SSL_CONTENT_ALERT:
|
|
readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
//if(readPos > *theEncryptedLength)
|
|
// _asm int 3;
|
|
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError,
|
|
"SSL received unhandled ALERT\r\n");
|
|
// server alert
|
|
break;
|
|
|
|
default:
|
|
readPos += sizeof(gsSSLRecordHeaderMsg);
|
|
return GHIEncryptionResult_Error; // unhandled content type
|
|
};
|
|
|
|
readPos += length;
|
|
};
|
|
|
|
// remove read bytes from the stream
|
|
*theEncryptedLength = readPos; // bytes read *NOT* bytes remaining
|
|
*theDecryptedLength = writePos; // bytes written
|
|
|
|
if (*theEncryptedLength < 0)
|
|
return GHIEncryptionResult_Error;
|
|
else
|
|
return GHIEncryptionResult_Success;
|
|
}
|
|
|
|
static GHTTPBool ghiCertificateChainIsValid(gsSSL* sslInterface)
|
|
{
|
|
GSI_UNUSED(sslInterface);
|
|
|
|
return GHTTPTrue;
|
|
}
|
|
|
|
#define CHECK(a) { if (GHTTPFalse == a) return GHIEncryptionResult_Error; }
|
|
|
|
|
|
// Programmer note:
|
|
// The structure of these SSL handshake messages may seem a bit cryptic,
|
|
// due to their variable length data items. Refer to the ASN1/DER encoding guide
|
|
// for tag specifics.
|
|
GHIEncryptionResult ghiEncryptorProcessSSLHandshake(struct GHIConnection * connection,
|
|
struct GHIEncryptor * encryptor,
|
|
GHIBuffer * data)
|
|
{
|
|
// There may be multiple messages within the handshake message
|
|
// length must be completely used, otherwise it's a protocol error
|
|
gsSSL* sslInterface = (gsSSL*)encryptor->mInterface;
|
|
|
|
while(data->pos < data->len)
|
|
{
|
|
// Parse each SSL handshake message (there may be multiple)
|
|
int messageStart = data->pos;
|
|
char messageType = 0;
|
|
CHECK(ghiReadDataFromBufferFixed(data, &messageType, 1));
|
|
|
|
if (messageType == GS_SSL_HANDSHAKE_SERVERHELLO)
|
|
{
|
|
int totalMsgLen = 0; // length of header + data
|
|
int msgDataLen = 0; // length of data
|
|
int tempInt = 0;
|
|
char tempChar = '\0';
|
|
|
|
// make sure we don't have a session already (e.g. dupe hello message)
|
|
if (sslInterface->sessionLen != 0)
|
|
return GHIEncryptionResult_Error; // abort connection
|
|
|
|
CHECK(ghiEncryptorReadNBOLength(data, &msgDataLen, 3));
|
|
|
|
// check reported size against the actual bytes remaining
|
|
if (msgDataLen > (data->len - data->pos))
|
|
return GHIEncryptionResult_Error; // abort connection
|
|
|
|
// skip SSL version
|
|
// (length check not required because we did that above)
|
|
data->pos += 2;
|
|
|
|
// store server random (used for key generation)
|
|
CHECK(ghiReadDataFromBufferFixed(data, (char*)&sslInterface->serverRandom[0], 32));
|
|
|
|
// store session information (length followed by data)
|
|
CHECK(ghiReadDataFromBufferFixed(data, &tempChar, 1));
|
|
CHECK(ghiReadDataFromBufferFixed(data, (char*)sslInterface->sessionData, tempChar));
|
|
sslInterface->sessionLen = (int)tempChar;
|
|
|
|
// store cipher suite
|
|
CHECK(ghiEncryptorReadNBOLength(data, &tempInt, 2));
|
|
sslInterface->cipherSuite = (unsigned short)tempInt;
|
|
|
|
// skip compression algorithms (should always be 0x00 since we don't support any!)
|
|
CHECK(ghiReadDataFromBufferFixed(data, &tempChar, 1));
|
|
if (tempChar != 0x00)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
// add it to the running handshake hash
|
|
totalMsgLen = data->pos - messageStart;
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen);
|
|
}
|
|
else if (messageType == GS_SSL_HANDSHAKE_CERTIFICATE)
|
|
{
|
|
int msgLength = 0; // combined length of the message (size in SSL message header)
|
|
int certListLen = 0; // combined length of all certificates
|
|
int totalMsgLen = 0; // our calculated msg length (for handshake hashing)
|
|
|
|
int certCount = 0;
|
|
int certListEndPos = 0;
|
|
|
|
CHECK(ghiEncryptorReadNBOLength(data, &msgLength, 3));
|
|
CHECK(ghiEncryptorReadNBOLength(data, &certListLen, 3));
|
|
if (msgLength != certListLen + 3)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
// make sure we don't have the certificates already (e.g. dupe message)
|
|
//if (sslInterface->certificateArray != NULL)
|
|
// return GHIEncryptionResult_Error; // abort connection
|
|
|
|
// make sure we have enough data to cover the certificate list
|
|
certListEndPos = data->pos + certListLen;
|
|
if (certListLen > (data->len - data->pos))
|
|
return GHIEncryptionResult_Error;
|
|
|
|
// read the certificates
|
|
while(data->pos < certListEndPos)
|
|
{
|
|
int certLength = 0;
|
|
int certStartPos = 0;
|
|
|
|
int temp = 0;
|
|
int version = 0;
|
|
|
|
// Must start with a 3 byte length
|
|
CHECK(ghiEncryptorReadNBOLength(data, &certLength, 3));
|
|
|
|
// Make sure we have enough data to cover this certificate
|
|
if (certLength > (data->len - data->pos))
|
|
return GHIEncryptionResult_Error; // certificate too big
|
|
|
|
// 0xFFFF is max message size in SSL v3.0, we don't currently support
|
|
// split messages
|
|
if (certLength > 0xFFFF)
|
|
return GHIEncryptionResult_Error;
|
|
|
|
certStartPos = data->pos; // remember this for a shortcut later
|
|
certCount++;
|
|
|
|
// make a copy of the certificate data
|
|
//certCopy = gsimalloc(certLength);
|
|
//if (certCopy == NULL)
|
|
//{
|
|
// gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError,
|
|
// "SSL failed to allocate certificate #%d (out of memory)\r\n", certCount);
|
|
// return GHIEncryptionResult_Error;
|
|
//}
|
|
//memcpy(certCopy, &data[browsePos], certLength);
|
|
//ArrayAppend(sslInterface->certificateArray, &certCopy);
|
|
|
|
// The first certificate holds the server's public key
|
|
if (certCount == 1)
|
|
{
|
|
// X.509 format is rather convoluted. Since we only support
|
|
// one variation anyways, I'm hardcoding the specific values
|
|
// we require. Anything else is a protocol error.
|
|
// 0x30 marks the start of a sequence. next byte is a length field size
|
|
// 0x82 is a length tag, meaning the next two bytes contain the length
|
|
// 0x81 is the same thing, only the next one byte contains the length
|
|
// The other values usually denote required types
|
|
|
|
// Certificate SEQUENCE
|
|
int seqLen = 0;
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
// todo: verify reported length of this sequence
|
|
|
|
// TBSCertificate SEQUENCE
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
// todo: verify reported length of this sequence
|
|
|
|
// EXPLICIT Version (must be one of: 0x03,0x02,0x01)
|
|
if (5 > (data->len - data->pos)) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0xa0) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x03) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x01) return GHIEncryptionResult_Error;
|
|
version = (unsigned char)data->data[data->pos++];
|
|
|
|
// Serial Number (variable length, with 2-byte length field)
|
|
if ((unsigned char)data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error;
|
|
temp = (unsigned char)data->data[data->pos++]; // len of serial number
|
|
if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error;
|
|
data->pos += temp; // skip the serial number
|
|
|
|
// Signature algorithm identifier SEQUENCE
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
data->pos += seqLen; // skip algorithm ID (todo: verify signatures)
|
|
|
|
// Issuer Name
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
data->pos += seqLen; // skip the issuer name sequence
|
|
|
|
// Validity
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
data->pos += seqLen; // skip the validity sequence
|
|
|
|
// Subject Name
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
data->pos += seqLen; // skip the subject name sequence
|
|
|
|
// Subject Public Key Info
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
// AlgorithmIdentifier
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
if (seqLen != 0x0d) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x06) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x09) return GHIEncryptionResult_Error;
|
|
if (0 != memcmp(&data->data[data->pos], gsSslRsaOid, sizeof(gsSslRsaOid)))
|
|
return GHIEncryptionResult_Error; // only RSA certs are supported
|
|
data->pos += sizeof(gsSslRsaOid);
|
|
if ((unsigned char)data->data[data->pos++] != 0x05) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error;
|
|
|
|
// Bitstring (subject public key)
|
|
if (2 > (certListEndPos - data->pos)) return GHIEncryptionResult_Error;
|
|
if ((unsigned char)data->data[data->pos++] != 0x03) return GHIEncryptionResult_Error; // bitstring
|
|
if ((unsigned char)data->data[data->pos++] != 0x81) return GHIEncryptionResult_Error; // 1 byte len field
|
|
if (temp > (certListEndPos - data->pos)) return GHIEncryptionResult_Error;
|
|
temp = (unsigned char)data->data[data->pos++]; // remaining data size (check or ignore)
|
|
|
|
if ((unsigned char)data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error;
|
|
|
|
// Start of the public key modulus
|
|
CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen));
|
|
|
|
// Read out the public key modulus
|
|
if (data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; // integer tag
|
|
if ((data->data[data->pos]&0x80)==0x80) // ASN1 variable length field
|
|
{
|
|
int lensize = data->data[data->pos++]&0x7f;
|
|
if (lensize > 4)
|
|
return GHIEncryptionResult_Error;
|
|
temp = 0;
|
|
while(lensize-- > 0)
|
|
temp = (temp << 8) | (unsigned char)data->data[data->pos++];
|
|
}
|
|
else
|
|
{
|
|
temp = (unsigned char)data->data[data->pos++];
|
|
}
|
|
if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error;
|
|
if (data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error; // ignore bits must be 0x00
|
|
if (temp-1 > GS_LARGEINT_BINARY_SIZE/sizeof(char)) return GHIEncryptionResult_Error;
|
|
sslInterface->serverpub.modulus.mLength = (unsigned int)((temp-1)/GS_LARGEINT_DIGIT_SIZE_BYTES); //-1 to subtract the previous 0x00 byte
|
|
gsLargeIntSetFromMemoryStream(&sslInterface->serverpub.modulus, (const gsi_u8*)&data->data[data->pos], (gsi_u32)temp-1);
|
|
data->pos += temp-1;
|
|
|
|
// Read out the public key exponent
|
|
if (data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; // integer
|
|
if ((data->data[data->pos]&0x80)==0x80)
|
|
{
|
|
int lensize = data->data[data->pos++]&0x7f;
|
|
if (lensize > 4)
|
|
return GHIEncryptionResult_Error;
|
|
temp = 0;
|
|
while(lensize-- > 0)
|
|
temp = (temp << 8) | (unsigned char)data->data[data->pos++];
|
|
}
|
|
else
|
|
{
|
|
temp = (unsigned char)data->data[data->pos++];
|
|
}
|
|
if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error;
|
|
if (temp == 0) return GHIEncryptionResult_Error; // no exponent?
|
|
if (temp > GS_LARGEINT_BINARY_SIZE/sizeof(char)) return GHIEncryptionResult_Error;
|
|
sslInterface->serverpub.exponent.mLength = (unsigned int)(((temp-1)/GS_LARGEINT_DIGIT_SIZE_BYTES)+1); // ceiling of temp/4
|
|
gsLargeIntSetFromMemoryStream(&sslInterface->serverpub.exponent, (const gsi_u8*)&data->data[data->pos], (gsi_u32)temp);
|
|
data->pos += temp;
|
|
}
|
|
|
|
// update the position
|
|
data->pos = certStartPos + certLength;
|
|
|
|
GSI_UNUSED(version);
|
|
}
|
|
if (data->pos != certListEndPos)
|
|
return GHIEncryptionResult_Error; // bytes hanging off the end!
|
|
|
|
// todo: verify certificate chain
|
|
// first certificate is the server's, the rest likely belong to CA
|
|
if (GHTTPFalse == ghiCertificateChainIsValid(sslInterface))
|
|
return GHIEncryptionResult_Error;
|
|
|
|
// add it to the running handshake hash
|
|
totalMsgLen = data->pos - messageStart;
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen);
|
|
}
|
|
else if (messageType == GS_SSL_HANDSHAKE_SERVERHELLODONE)
|
|
{
|
|
// Process the hello done
|
|
// Respond with 3 messages
|
|
// ClientKeyExchange
|
|
// ChangeCipherSpec
|
|
// Finished (final handshake)
|
|
int i=0;
|
|
|
|
gsSSLClientKeyExchangeMsg* clientKeyExchange = NULL;
|
|
gsSSLRecordHeaderMsg* changeCipherSpec = NULL;
|
|
gsSSLRecordHeaderMsg* finalHandshake = NULL;
|
|
|
|
unsigned char temp[7];
|
|
unsigned char hashTempMD5[GS_CRYPT_MD5_HASHSIZE];
|
|
unsigned char hashTempSHA1[GS_CRYPT_SHA1_HASHSIZE];
|
|
int tempInt = 0;
|
|
|
|
// ServerHelloDone has a zero length data field
|
|
CHECK(ghiEncryptorReadNBOLength(data, &tempInt, 3));
|
|
if (tempInt != 0x00) return GHIEncryptionResult_Error;
|
|
|
|
// add it to the running handshake hash
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)(data->pos - messageStart));
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)(data->pos - messageStart));
|
|
|
|
// Make sure there is room in the send buffer for the response messages
|
|
tempInt = (int)(sizeof(gsSSLClientKeyExchangeMsg) + sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES);
|
|
while (connection->sendBuffer.size - connection->sendBuffer.len < tempInt)
|
|
{
|
|
// not enough room in send buffer, try to grow it
|
|
if (GHTTPFalse == ghiResizeBuffer(&connection->sendBuffer, connection->sendBuffer.sizeIncrement))
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
// 1) Client key exchange,
|
|
// create the pre-master-secret
|
|
sslInterface->premastersecret[0] = GS_SSL_VERSION_MAJOR;
|
|
sslInterface->premastersecret[1] = GS_SSL_VERSION_MINOR;
|
|
for (i=2; i<GS_SSL_MASTERSECRET_LEN; i++)
|
|
{
|
|
#if defined(GS_CRYPT_NO_RANDOM)
|
|
#pragma message ("!!!WARNING: SSL Random disable for debug purposes. SSL not secured!!!")
|
|
// Use zero as the random so we can packet sniff for debug
|
|
// warning! : this compromises the SSL security
|
|
sslInterface->premastersecret[i] = 0; // rand()
|
|
#else
|
|
Util_RandSeed(current_time());
|
|
sslInterface->premastersecret[i] = (unsigned char)(Util_RandInt(0, 0x0100)); // range = [0...FF]
|
|
#endif
|
|
}
|
|
|
|
clientKeyExchange = (gsSSLClientKeyExchangeMsg*)&connection->sendBuffer.data[connection->sendBuffer.len];
|
|
connection->sendBuffer.len += sizeof(gsSSLClientKeyExchangeMsg);
|
|
clientKeyExchange->header.contentType = GS_SSL_CONTENT_HANDSHAKE;
|
|
clientKeyExchange->header.versionMajor = GS_SSL_VERSION_MAJOR;
|
|
clientKeyExchange->header.versionMinor = GS_SSL_VERSION_MINOR;
|
|
ghiEncryptorWriteNBOLength(clientKeyExchange->header.lengthNBO, (int)(sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES+4), 2);
|
|
clientKeyExchange->handshakeType = GS_SSL_HANDSHAKE_CLIENTKEYEXCHANGE;
|
|
ghiEncryptorWriteNBOLength(clientKeyExchange->lengthNBO, (int)(sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES), 3);
|
|
// encrypt the preMasterSecret using the server's public key (store result in sendbuffer)
|
|
gsCryptRSAEncryptBuffer(&sslInterface->serverpub, sslInterface->premastersecret,
|
|
GS_SSL_MASTERSECRET_LEN, (unsigned char*)&connection->sendBuffer.data[connection->sendBuffer.len]);
|
|
connection->sendBuffer.len += sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES;
|
|
|
|
// add it to the running handshake hash
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)clientKeyExchange+sizeof(gsSSLRecordHeaderMsg),
|
|
sizeof(gsSSLClientKeyExchangeMsg) - sizeof(gsSSLRecordHeaderMsg) +
|
|
sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)clientKeyExchange+sizeof(gsSSLRecordHeaderMsg),
|
|
sizeof(gsSSLClientKeyExchangeMsg) - sizeof(gsSSLRecordHeaderMsg) +
|
|
sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES);
|
|
|
|
|
|
// 2) change cipher spec
|
|
changeCipherSpec = (gsSSLRecordHeaderMsg*)&connection->sendBuffer.data[connection->sendBuffer.len];
|
|
changeCipherSpec->contentType = GS_SSL_CONTENT_CHANGECIPHERSPEC;
|
|
changeCipherSpec->versionMajor = GS_SSL_VERSION_MAJOR;
|
|
changeCipherSpec->versionMinor = GS_SSL_VERSION_MINOR;
|
|
changeCipherSpec->lengthNBO[0] = 0;
|
|
changeCipherSpec->lengthNBO[1] = 1; // always one byte length
|
|
connection->sendBuffer.len += sizeof(gsSSLRecordHeaderMsg);
|
|
connection->sendBuffer.data[connection->sendBuffer.len++] = 0x01; // always set to 0x01
|
|
// DO NOT add it to the running handshake hash (its content is not GS_SSL_CONTENT_HANDSHAKE)
|
|
|
|
// Calculate the encryption keys
|
|
ghiEncryptorGenerateEncryptionKeys(sslInterface);
|
|
|
|
// 3) final handshake message (encrypted)
|
|
finalHandshake = (gsSSLRecordHeaderMsg*)&connection->sendBuffer.data[connection->sendBuffer.len];
|
|
finalHandshake->contentType = GS_SSL_CONTENT_HANDSHAKE;
|
|
finalHandshake->versionMajor = GS_SSL_VERSION_MAJOR;
|
|
finalHandshake->versionMinor = GS_SSL_VERSION_MINOR;
|
|
finalHandshake->lengthNBO[0] = 0;
|
|
finalHandshake->lengthNBO[1] = 56; // handshake type(1)+handshake lenNBO(3)+SHA1(20)+MD5(16)+MAC(16)
|
|
connection->sendBuffer.len += sizeof(gsSSLRecordHeaderMsg);
|
|
connection->sendBuffer.data[connection->sendBuffer.len++] = GS_SSL_HANDSHAKE_FINISHED;
|
|
ghiEncryptorWriteNBOLength((unsigned char*)&connection->sendBuffer.data[connection->sendBuffer.len], 36, 3);
|
|
connection->sendBuffer.len += 3;
|
|
|
|
|
|
// MD5(master_secret + pad2 + MD5(handshake_messages+"CLNT"+master_secret+pad1))
|
|
// SHA1(master_secret + pad2 + SHA1(handshake_messages+"CLNT"+master_secret+pad1))
|
|
// prepare the final hashes (inner hashes)
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_CLIENT_FINISH_VALUE, 4);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN);
|
|
MD5Final(hashTempMD5, &sslInterface->finishHashMD5);
|
|
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_CLIENT_FINISH_VALUE, 4);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_SHA1_PAD_LEN);
|
|
SHA1Result(&sslInterface->finishHashSHA1, hashTempSHA1);
|
|
|
|
// prepare the final hashes (outer hashes)
|
|
MD5Init(&sslInterface->finishHashMD5);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN);
|
|
MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE);
|
|
MD5Final(hashTempMD5, &sslInterface->finishHashMD5);
|
|
|
|
SHA1Reset(&sslInterface->finishHashSHA1);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN);
|
|
SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_SHA1_PAD_LEN);
|
|
SHA1Input(&sslInterface->finishHashSHA1, hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE);
|
|
SHA1Result(&sslInterface->finishHashSHA1, hashTempSHA1);
|
|
|
|
// copy results into the sendbuffer
|
|
memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempMD5, GS_CRYPT_MD5_HASHSIZE);
|
|
connection->sendBuffer.len += GS_CRYPT_MD5_HASHSIZE;
|
|
memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE);
|
|
connection->sendBuffer.len += GS_CRYPT_SHA1_HASHSIZE;
|
|
|
|
// output the message MAC (hash(MAC_write_secret+pad_2+ hash(MAC_write_secret+pad_1+seq_num+length+content)));
|
|
// Re-using the finishHashMD5 since it has already been allocated
|
|
MD5Init(&sslInterface->finishHashMD5);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->clientWriteMACSecret, GS_CRYPT_MD5_HASHSIZE);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->sendSeqNBO, 8);
|
|
temp[0] = 0x16;
|
|
temp[1] = (unsigned char)((GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4)>>8);
|
|
temp[2] = (unsigned char)((GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4));
|
|
//temp[1] = (unsigned char)(htons(GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE));
|
|
//temp[2] = (unsigned char)(htons(GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4)>>8);
|
|
temp[3] = 0x14; // 20-bytes of data (MD5+SHA1)
|
|
temp[4] = 0x00; // 3-byte length NBO
|
|
temp[5] = 0x00; // ..
|
|
temp[6] = 0x24; // ..
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&temp, 7);
|
|
MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE); // content part 1
|
|
MD5Update(&sslInterface->finishHashMD5, hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE); // content part 2
|
|
MD5Final(hashTempMD5, &sslInterface->finishHashMD5);
|
|
MD5Init(&sslInterface->finishHashMD5); // reset for outer hash
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->clientWriteMACSecret, GS_CRYPT_MD5_HASHSIZE);
|
|
MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN);
|
|
MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE);
|
|
MD5Final(hashTempMD5, &sslInterface->finishHashMD5);
|
|
|
|
memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempMD5, GS_CRYPT_MD5_HASHSIZE);
|
|
connection->sendBuffer.len += GS_CRYPT_MD5_HASHSIZE;
|
|
|
|
// increment sequence each time we send a message
|
|
// ...assume NBO is bigendian for simplicity
|
|
memset(sslInterface->sendSeqNBO, 0, sizeof(sslInterface->sendSeqNBO));
|
|
ghiEncryptorWriteNBOLength(&sslInterface->sendSeqNBO[4], 1, 4);
|
|
|
|
// now encrypt the message (not including record header)
|
|
RC4Encrypt(&sslInterface->sendRC4,
|
|
((unsigned char*)finalHandshake)+sizeof(gsSSLRecordHeaderMsg),
|
|
((unsigned char*)finalHandshake)+sizeof(gsSSLRecordHeaderMsg),
|
|
56);
|
|
}
|
|
else if (messageType == GS_SSL_HANDSHAKE_FINISHED)
|
|
{
|
|
// process server finished and verify hashes
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Notice,
|
|
"SSL: todo - verify server finished hash\r\n");
|
|
data->pos = data->len;
|
|
}
|
|
else
|
|
{
|
|
gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError,
|
|
"SSL received unexpected handshake message type: %d\r\n", messageType);
|
|
return GHIEncryptionResult_Error; // abort connection
|
|
}
|
|
}
|
|
|
|
GSI_UNUSED(connection);
|
|
|
|
if (data->pos == data->len)
|
|
return GHIEncryptionResult_Success;
|
|
else
|
|
return GHIEncryptionResult_Error; // too many or too few bytes, protocol error!
|
|
}
|
|
|
|
GHIEncryptionResult ghiEncryptorSslEncryptSend(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
const char * thePlainTextBuffer,
|
|
int thePlainTextLength,
|
|
int * theBytesSentOut)
|
|
{
|
|
GS_FAIL(); // Should never call this for GameSpy SSL! It uses encrypt on buffer
|
|
|
|
GSI_UNUSED(connection);
|
|
GSI_UNUSED(theEncryptor);
|
|
GSI_UNUSED(thePlainTextBuffer);
|
|
GSI_UNUSED(thePlainTextLength);
|
|
GSI_UNUSED(theBytesSentOut);
|
|
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
GHIEncryptionResult ghiEncryptorSslDecryptRecv(struct GHIConnection * connection,
|
|
struct GHIEncryptor * theEncryptor,
|
|
char * theDecryptedBuffer,
|
|
int * theDecryptedLength)
|
|
{
|
|
GS_FAIL(); // Should never call this for GameSpy SSL! It uses decrypt on buffer
|
|
|
|
GSI_UNUSED(connection);
|
|
GSI_UNUSED(theEncryptor);
|
|
GSI_UNUSED(theDecryptedBuffer);
|
|
GSI_UNUSED(theDecryptedLength);
|
|
|
|
return GHIEncryptionResult_Error;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#endif // encryption engine switch
|
|
|
|
|