/* * Modification History * * 2003-August-21 Jason Rohrer * Created. * * 2003-August-22 Jason Rohrer * Added key-establishment portion of protocol. * Added construction of secure stream objects. * Fixed a few bugs. * Added missing stream start tag. * * 2003-August-23 Jason Rohrer * Fixed a memory leak. * * 2004-January-26 Jason Rohrer * Added partial work-around for an exception-handling bug in older versions * of gcc. */ #include "SecureStreamFactory.h" #include "SecureInputStream.h" #include "SecureOutputStream.h" #include "MUTE/common/CryptoUtils.h" #include "MUTE/common/AESEncryptor.h" #include "MUTE/common/AESDecryptor.h" #include "minorGems/util/stringUtils.h" #include "minorGems/util/SimpleVector.h" #include "minorGems/formats/encodingUtils.h" #include "minorGems/network/p2pParts/protocolUtils.h" #include "minorGems/util/log/AppLog.h" #include char SecureStreamFactory::establishStreams( InputStream *inInputStream, OutputStream *inOutputStream, char *inRSAPublicKey, char *inRSAPrivateKey, InputStream **outSecureInputStream, OutputStream **outSecureOutputStream ) { char *loggerName = "SecureStreamFactory"; *outSecureInputStream = NULL; *outSecureOutputStream = NULL; // send our public key char *publicKeyString = autoSprintf( "PublicKey: %s\nEndPublicKey\n", inRSAPublicKey ); int expectedSent = strlen( publicKeyString ); int numSent = inOutputStream->writeString( publicKeyString ); delete [] publicKeyString; if( numSent != expectedSent ) { AppLog::error( loggerName, "Failed to send our public key" ); return false; } // receive their public key // key is second token (index 1) char *theirPublicKey = readStreamUpToTagAndGetToken( inInputStream, "EndPublicKey", 5000, 1 ); // non-valid RSA keys are handled properly by CryptoUtils // by catching exceptions thrown by Crypto++ // however, in older versions of gcc (2.95 and older), // the across-module exception handling is broken for larger linked apps // so a bad RSA key results in an app abort. // This was being triggered regularly by keys that were showing up as // the string "(null)", which must have been generated at the remote // node when printing a NULL key to the settings file... why would // this happen? // as a partial work-around, reject keys that are too short, which // will include "(null)" key strings. if( theirPublicKey != NULL ) { if( strlen( theirPublicKey ) < 10 ) { // key too short to be a valid RSA key char *logMessage = autoSprintf( "Their public key too short to be valid: " "%s", theirPublicKey ); AppLog::error( loggerName, logMessage ); delete [] logMessage; delete [] theirPublicKey; return false; } } if( theirPublicKey == NULL ) { AppLog::error( loggerName, "Failed to receive their public key" ); return false; } // generate a fresh AES key that we will use for sending char *ourAESKeyHex = CryptoUtils::generateAESKey( CryptoUtils::AES_128_BIT ); // assume that hex encoding produced by CryptoUtils is correct unsigned char *ourRawAESKey = hexDecode( ourAESKeyHex ); int ourRawAESKeyLength = strlen( ourAESKeyHex ) / 2; // encrypt our raw AES key with their public key, producing hex encoding char *ourRawAESKeyEncrypted = CryptoUtils::rsaEncrypt( theirPublicKey, ourRawAESKey, ourRawAESKeyLength ); delete [] ourRawAESKey; delete [] theirPublicKey; if( ourRawAESKeyEncrypted == NULL ) { AppLog::error( loggerName, "Failed to encrypt our AES key with their public RSA key" ); delete [] ourAESKeyHex; return false; } // send our AES key char *ourAESKeyString = autoSprintf( "AESKey: %s\nEndAESKey\n", ourRawAESKeyEncrypted ); delete [] ourRawAESKeyEncrypted; expectedSent = strlen( ourAESKeyString ); numSent = inOutputStream->writeString( ourAESKeyString ); delete [] ourAESKeyString; if( numSent != expectedSent ) { AppLog::error( loggerName, "Failed to send our AES key" ); delete [] ourAESKeyHex; return false; } // receive their AES key // key is second token (index 1) char *theirRawAESKeyEncrypted = readStreamUpToTagAndGetToken( inInputStream, "EndAESKey", 5000, 1 ); if( theirRawAESKeyEncrypted == NULL ) { AppLog::error( loggerName, "Failed to receive their AES key" ); delete [] ourAESKeyHex; return false; } // decrypt their AES key with our private key int theirRawAESKeyLength; unsigned char *theirRawAESKey = CryptoUtils::rsaDecrypt( inRSAPrivateKey, theirRawAESKeyEncrypted, &theirRawAESKeyLength ); delete [] theirRawAESKeyEncrypted; if( theirRawAESKey == NULL || theirRawAESKeyLength != 16 ) { // decryption failed or their key is not 128 bits long if( theirRawAESKey != NULL ) { AppLog::error( loggerName, "Their AES key not 128 bits long" ); delete [] theirRawAESKey; } else { AppLog::error( loggerName, "Failed to decrypt their AES key" ); } delete [] ourAESKeyHex; return false; } char *theirAESKeyHex = hexEncode( theirRawAESKey, theirRawAESKeyLength ); delete [] theirRawAESKey; // send the stream start tag inOutputStream->writeString( "Stream:" ); // read their stream start tag char *upToStreamStart = readStreamUpToTag( inInputStream, "Stream:", 100 ); if( upToStreamStart == NULL ) { AppLog::error( loggerName, "Failed to read the start of their stream" ); delete [] ourAESKeyHex; delete [] theirAESKeyHex; return false; } delete [] upToStreamStart; // now we have both AES keys (one for sending, and one for receiving) // we are done with the key exchange portion of the protocol AESEncryptor *sendingEncryptor = new AESEncryptor( ourAESKeyHex ); delete [] ourAESKeyHex; AESDecryptor *receivingDecryptor = new AESDecryptor( theirAESKeyHex ); delete [] theirAESKeyHex; *outSecureOutputStream = new SecureOutputStream( inOutputStream, sendingEncryptor ); *outSecureInputStream = new SecureInputStream( inInputStream, receivingDecryptor ); return true; }