Upstream tarball 20080414
[amule.git] / src / EncryptedStreamSocket.cpp
blob690fb5f0912ae3937cc4ad87c8f1357f9c0d6824
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 /* Basic Obfuscated Handshake Protocol Client <-> Client:
27 -Keycreation:
28 - Client A (Outgoing connection):
29 Sendkey: Md5(<UserHashClientB 16><MagicValue34 1><RandomKeyPartClientA 4>) 21
30 Receivekey: Md5(<UserHashClientB 16><MagicValue203 1><RandomKeyPartClientA 4>) 21
31 - Client B (Incomming connection):
32 Sendkey: Md5(<UserHashClientB 16><MagicValue203 1><RandomKeyPartClientA 4>) 21
33 Receivekey: Md5(<UserHashClientB 16><MagicValue34 1><RandomKeyPartClientA 4>) 21
34 NOTE: First 1024 Bytes are discarded
36 - Handshake
37 -> The handshake is encrypted - except otherwise noted - by the Keys created above
38 -> Handshake is blocking - do not start sending an answer before the request is completly received (this includes the random bytes)
39 -> EncryptionMethod = 0 is Obfusication and the only supported right now
40 Client A: <SemiRandomNotProtocolMarker 1[Unencrypted]><RandomKeyPart 4[Unencrypted]><MagicValue 4><EncryptionMethodsSupported 1><EncryptionMethodPreferred 1><PaddingLen 1><RandomBytes PaddingLen%max256>
41 Client B: <MagicValue 4><EncryptionMethodsSelected 1><PaddingLen 1><RandomBytes PaddingLen%max256>
42 -> The basic handshake is finished here, if an additional/different EncryptionMethod was selected it may continue negotiating details for this one
44 - Overhead: 18-48 (~33) Bytes + 2 * IP/TCP Headers per Connection
46 - Security for Basic Obfusication:
47 - Random looking stream, very limited protection against passive eavesdropping single connections
49 - Additional Comments:
50 - RandomKeyPart is needed to make multiple connections between two clients look different (but still random), since otherwise the same key
51 would be used and RC4 would create the same output. Since the key is a MD5 hash it doesnt weakens the key if that part is known
52 - Why DH-KeyAgreement isn't used as basic obfusication key: It doesn't offers substantial more protection against passive connection based protocol identification, it has about 200 bytes more overhead,
53 needs more CPU time, we cannot say if the received data is junk, unencrypted or part of the keyagreement before the handshake is finished without loosing the complete randomness,
54 it doesn't offers substantial protection against eavesdropping without added authentification
56 Basic Obfuscated Handshake Protocol Client <-> Server:
57 - RC4 Keycreation:
58 - Client (Outgoing connection):
59 Sendkey: Md5(<S 96><MagicValue34 1>) 97
60 Receivekey: Md5(<S 96><MagicValue203 1>) 97
61 - Server (Incomming connection):
62 Sendkey: Md5(<S 96><MagicValue203 1>) 97
63 Receivekey: Md5(<S 96><MagicValue34 1>) 97
65 NOTE: First 1024 Bytes are discarded
67 - Handshake
68 -> The handshake is encrypted - except otherwise noted - by the Keys created above
69 -> Handshake is blocking - do not start sending an answer before the request is completly received (this includes the random bytes)
70 -> EncryptionMethod = 0 is Obfusication and the only supported right now
72 Client: <SemiRandomNotProtocolMarker 1[Unencrypted]><G^A 96 [Unencrypted]><RandomBytes 0-15 [Unencrypted]>
73 Server: <G^B 96 [Unencrypted]><MagicValue 4><EncryptionMethodsSupported 1><EncryptionMethodPreferred 1><PaddingLen 1><RandomBytes PaddingLen>
74 Client: <MagicValue 4><EncryptionMethodsSelected 1><PaddingLen 1><RandomBytes PaddingLen> (Answer delayed till first payload to save a frame)
77 -> The basic handshake is finished here, if an additional/different EncryptionMethod was selected it may continue negotiating details for this one
79 - Overhead: 206-251 (~229) Bytes + 2 * IP/TCP Headers Headers per Connectionon
81 - DH Agreement Specifics: sizeof(a) and sizeof(b) = 128 Bits, g = 2, p = dh768_p (see below), sizeof p, s, etc. = 768 bits
83 #include "EncryptedStreamSocket.h"
84 #include "amule.h"
85 #include "Logger.h"
86 #include "Preferences.h"
87 #include "ServerConnect.h"
88 #include "RC4Encrypt.h"
89 #include "MemFile.h"
90 #include "ClientList.h"
91 #include "RandomFunctions.h"
93 #include <algorithm>
95 #include <common/MD5Sum.h>
96 #include <protocol/Protocols.h>
98 #define MAGICVALUE_REQUESTER 34 // modification of the requester-send and server-receive key
99 #define MAGICVALUE_SERVER 203 // modification of the server-send and requester-send key
100 #define MAGICVALUE_SYNC 0x835E6FC4 // value to check if we have a working encrypted stream
101 #define DHAGREEMENT_A_BITS 128
103 #define PRIMESIZE_BYTES 96
104 static unsigned char dh768_p[]={
105 0xF2,0xBF,0x52,0xC5,0x5F,0x58,0x7A,0xDD,0x53,0x71,0xA9,0x36,
106 0xE8,0x86,0xEB,0x3C,0x62,0x17,0xA3,0x3E,0xC3,0x4C,0xB4,0x0D,
107 0xC7,0x3A,0x41,0xA6,0x43,0xAF,0xFC,0xE7,0x21,0xFC,0x28,0x63,
108 0x66,0x53,0x5B,0xDB,0xCE,0x25,0x9F,0x22,0x86,0xDA,0x4A,0x91,
109 0xB2,0x07,0xCB,0xAA,0x52,0x55,0xD4,0xF6,0x1C,0xCE,0xAE,0xD4,
110 0x5A,0xD5,0xE0,0x74,0x7D,0xF7,0x78,0x18,0x28,0x10,0x5F,0x34,
111 0x0F,0x76,0x23,0x87,0xF8,0x8B,0x28,0x91,0x42,0xFB,0x42,0x68,
112 0x8F,0x05,0x15,0x0F,0x54,0x8B,0x5F,0x43,0x6A,0xF7,0x0D,0xF3,
115 // winsock2.h already defines it
116 #ifdef SOCKET_ERROR
117 #undef SOCKET_ERROR
118 #endif
119 #define SOCKET_ERROR (-1)
121 CEncryptedStreamSocket::CEncryptedStreamSocket(wxSocketFlags flags, const CProxyData *proxyData) : CSocketClientProxy(flags, proxyData)
123 m_StreamCryptState = thePrefs::IsClientCryptLayerSupported() ? ECS_UNKNOWN : ECS_NONE;
124 m_NegotiatingState = ONS_NONE;
125 m_nObfusicationBytesReceived = 0;
126 m_bFullReceive = true;
127 m_dbgbyEncryptionSupported = 0xFF;
128 m_dbgbyEncryptionRequested = 0xFF;
129 m_dbgbyEncryptionMethodSet = 0xFF;
130 m_nReceiveBytesWanted = 0;
131 m_EncryptionMethod = ENM_OBFUSCATION;
132 m_nRandomKeyPart = 0;
133 m_bServerCrypt = false;
136 CEncryptedStreamSocket::~CEncryptedStreamSocket()
142 /* External interface */
144 void CEncryptedStreamSocket::SetConnectionEncryption(bool bEnabled, const uint8* pTargetClientHash, bool bServerConnection){
145 if (m_StreamCryptState != ECS_UNKNOWN && m_StreamCryptState != ECS_NONE){
146 if (!m_StreamCryptState == ECS_NONE || bEnabled) {
147 wxASSERT( false );
149 return;
152 if (bEnabled && pTargetClientHash != NULL && !bServerConnection){
153 m_StreamCryptState = ECS_PENDING;
154 // create obfuscation keys, see on top for key format
156 // use the crypt random generator
157 m_nRandomKeyPart = GetRandomUint32();
159 uint8 achKeyData[21];
160 md4cpy(achKeyData, pTargetClientHash);
161 PokeUInt32(achKeyData + 17, m_nRandomKeyPart);
163 achKeyData[16] = MAGICVALUE_REQUESTER;
164 MD5Sum md5(achKeyData, sizeof(achKeyData));
165 m_pfiSendBuffer.SetKey(md5);
167 achKeyData[16] = MAGICVALUE_SERVER;
168 md5.Calculate(achKeyData, sizeof(achKeyData));
169 m_pfiReceiveBuffer.SetKey(md5);
171 } else if (bServerConnection && bEnabled) {
172 //printf("->Server crypt\n");
173 m_bServerCrypt = true;
174 m_StreamCryptState = ECS_PENDING_SERVER;
175 } else {
176 wxASSERT( !bEnabled );
177 m_StreamCryptState = ECS_NONE;
181 /* Internals, common to base class */
183 // unfortunatly sending cannot be made transparent for the derived class, because of WSA_WOULDBLOCK
184 // together with the fact that each byte must pass the keystream only once
185 int CEncryptedStreamSocket::Write(const void* lpBuf, wxUint32 nBufLen){
186 //printf("Starting write for %s\n", (const char*) unicode2char(DbgGetIPString()));
187 if (!IsEncryptionLayerReady()) {
188 wxASSERT(0);
189 return 0;
190 } else if (m_bServerCrypt && m_StreamCryptState == ECS_ENCRYPTING && !m_pfiSendBuffer.IsEmpty()){
191 wxASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING );
192 // handshakedata was delayed to put it into one frame with the first paypload to the server
193 // do so now with the payload attached
194 int nRes = SendNegotiatingData(lpBuf, nBufLen, nBufLen);
195 wxASSERT( nRes != SOCKET_ERROR );
196 (void)nRes;
197 return nBufLen; // report a full send, even if we didn't for some reason - the data is now in our buffer and will be handled later
198 } else if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) {
199 wxASSERT(0);
202 if (m_StreamCryptState == ECS_UNKNOWN) {
203 //this happens when the encryption option was not set on a outgoing connection
204 //or if we try to send before receiving on a incoming connection - both shouldn't happen
205 m_StreamCryptState = ECS_NONE;
206 //DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), DbgGetIPString());
209 //printf("Writing %i bytes of data\n", nBufLen);
210 CSocketClientProxy::Write(lpBuf, nBufLen);
211 return CSocketClientProxy::LastCount();
214 int CEncryptedStreamSocket::Read(void* lpBuf, wxUint32 nBufLen) {
215 CSocketClientProxy::Read(lpBuf, nBufLen);
216 m_nObfusicationBytesReceived = CSocketClientProxy::LastCount();
217 m_bFullReceive = m_nObfusicationBytesReceived == (uint32)nBufLen;
219 //printf("Read %i bytes on %s, socket %p\n", m_nObfusicationBytesReceived, (const char*) unicode2char(DbgGetIPString()), this);
221 if (m_nObfusicationBytesReceived == SOCKET_ERROR || m_nObfusicationBytesReceived <= 0){
222 return m_nObfusicationBytesReceived;
225 switch (m_StreamCryptState) {
226 case ECS_NONE: // disabled, just pass it through
227 return m_nObfusicationBytesReceived;
228 case ECS_PENDING:
229 case ECS_PENDING_SERVER:
230 //printf("Received %i bytes before sending?\n", m_nObfusicationBytesReceived);
231 wxASSERT( false );
232 //DebugLogError(_T("CEncryptedStreamSocket Received data before sending on outgoing connection"));
233 m_StreamCryptState = ECS_NONE;
234 return m_nObfusicationBytesReceived;
235 case ECS_UNKNOWN: {
236 //printf("Receiving encrypted data on ECS_UNKNOWN\n");
237 uint32 nRead = 1;
238 bool bNormalHeader = false;
239 switch (((uint8*)lpBuf)[0]){
240 case OP_EDONKEYPROT:
241 case OP_PACKEDPROT:
242 case OP_EMULEPROT:
243 bNormalHeader = true;
244 break;
247 if (!bNormalHeader) {
248 //printf("Not a normal header, negotiating encryption\n");
249 StartNegotiation(false);
250 const uint32 nNegRes = Negotiate((uint8*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
251 if (nNegRes == (-1)) {
252 return 0;
254 nRead += nNegRes;
255 if (nRead != (uint32)m_nObfusicationBytesReceived){
256 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
257 // (note: even if it just finished the handshake here, there still can be no data left, since the other client didnt received our response yet)
258 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (1)"), DbgGetIPString());
259 //printf("On error: encryption\n");
260 OnError(ERR_ENCRYPTION);
262 return 0;
263 } else {
264 // doesn't seems to be encrypted
265 //printf("Encrypted data doesn't seem to be encrypted\n");
266 m_StreamCryptState = ECS_NONE;
268 // if we require an encrypted connection, cut the connection here. This shouldn't happen that often
269 // at least with other up-to-date eMule clients because they check for incompability before connecting if possible
270 if (thePrefs::IsClientCryptLayerRequired()){
271 // TODO: Remove me when i have been solved
272 // Even if the Require option is enabled, we currently have to accept unencrypted connection which are made
273 // for lowid/firewall checks from servers and other from us selected client. Otherwise, this option would
274 // always result in a lowid/firewalled status. This is of course not nice, but we can't avoid this walkarround
275 // untill servers and kad completely support encryption too, which will at least for kad take a bit
276 // only exception is the .ini option ClientCryptLayerRequiredStrict which will even ignore test connections
277 // Update: New server now support encrypted callbacks
278 amuleIPV4Address address;
279 GetPeer(address);
280 uint32 ip = StringIPtoUint32(address.IPAddress());
281 if (thePrefs::IsClientCryptLayerRequiredStrict() || (!theApp->serverconnect->AwaitingTestFromIP(ip)
282 && !theApp->clientlist->IsKadFirewallCheckIP(ip)) )
284 OnError(ERR_ENCRYPTION_NOTALLOWED);
285 return 0;
286 } else {
287 //AddDebugLogLine(DLP_DEFAULT, false, _T("Incoming unencrypted firewallcheck connection permitted despite RequireEncryption setting - %s"), DbgGetIPString() );
292 return m_nObfusicationBytesReceived; // buffer was unchanged, we can just pass it through
295 case ECS_ENCRYPTING:
296 //printf("Encryption enabled on data receiving, decrypting and passing along\n");
297 // basic obfusication enabled and set, so decrypt and pass along
298 m_pfiReceiveBuffer.RC4Crypt((uint8*)lpBuf, (uint8*)lpBuf, m_nObfusicationBytesReceived);
299 //DumpMem(lpBuf, m_nObfusicationBytesReceived, wxT("Directly decrypted data:"));
300 return m_nObfusicationBytesReceived;
301 case ECS_NEGOTIATING:{
302 //printf("Negotiating on data receive\n");
303 const uint32 nRead = Negotiate((uint8*)lpBuf, m_nObfusicationBytesReceived);
304 if (nRead == (-1)) {
305 //printf("-> Encryption read error on negotiation\n");
306 return 0;
307 } else if (nRead != (uint32)m_nObfusicationBytesReceived && m_StreamCryptState != ECS_ENCRYPTING) {
308 //printf("-> Too much data, bailing out of negotiation step\n");
309 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
310 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (2)"), DbgGetIPString());
311 OnError(ERR_ENCRYPTION);
312 return 0;
313 } else if (nRead != (uint32)m_nObfusicationBytesReceived && m_StreamCryptState == ECS_ENCRYPTING){
314 //printf("-> Handshake negotiation finished\n");
315 // we finished the handshake and if we this was an outgoing connection it is allowed (but strange and unlikely) that the client sent payload
316 //DebugLogWarning(_T("CEncryptedStreamSocket: Client %s has finished the handshake but also sent payload on a outgoing connection"), DbgGetIPString());
317 memmove(lpBuf, (uint8*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
318 return m_nObfusicationBytesReceived - nRead;
319 } else {
320 //printf("-> Negotiation went probably ok\n");
321 return 0;
324 default:
325 wxASSERT( false );
326 return m_nObfusicationBytesReceived;
330 void CEncryptedStreamSocket::OnSend(int) {
332 // if the socket just connected and this is outgoing, we might want to start the handshake here
333 if (m_StreamCryptState == ECS_PENDING || m_StreamCryptState == ECS_PENDING_SERVER){
334 //printf("Starting connection negotiation on OnSend for %s\n", (const char*) unicode2char(DbgGetIPString()));
335 StartNegotiation(true);
336 return;
339 // check if we have negotiating data pending
340 if (!m_pfiSendBuffer.IsEmpty()){
341 wxASSERT( m_StreamCryptState >= ECS_NEGOTIATING );
342 SendNegotiatingData(NULL, 0);
346 void CEncryptedStreamSocket::CryptPrepareSendData(uint8* pBuffer, uint32 nLen){
347 if (!IsEncryptionLayerReady()){
348 wxASSERT( false ); // must be a bug
349 return;
351 if (m_StreamCryptState == ECS_UNKNOWN){
352 //this happens when the encryption option was not set on a outgoing connection
353 //or if we try to send before receiving on a incoming connection - both shouldn't happen
354 m_StreamCryptState = ECS_NONE;
355 //DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), DbgGetIPString());
357 if (m_StreamCryptState == ECS_ENCRYPTING) {
358 //printf("Preparing crypt data on %s\n", (const char*) unicode2char(DbgGetIPString()));
359 //DumpMem(pBuffer, nLen, wxT("Before crypt prepare:\n"));
360 m_pfiSendBuffer.RC4Crypt(pBuffer, pBuffer, nLen);
361 //DumpMem(pBuffer, nLen, wxT("After crypt prepare:\n"));
365 /* Internals, just for this class (can be raped) */
367 bool CEncryptedStreamSocket::IsEncryptionLayerReady(){
368 return ( (m_StreamCryptState == ECS_NONE || m_StreamCryptState == ECS_ENCRYPTING || m_StreamCryptState == ECS_UNKNOWN )
369 && (m_pfiSendBuffer.IsEmpty() || (m_bServerCrypt && m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING)) );
373 void CEncryptedStreamSocket::StartNegotiation(bool bOutgoing)
375 //printf("Starting socket negotiation\n");
376 if (!bOutgoing){
377 //printf("Incoming connection negotiation on %s\n", (const char*) unicode2char(DbgGetIPString()));
378 m_NegotiatingState = ONS_BASIC_CLIENTA_RANDOMPART;
379 m_StreamCryptState = ECS_NEGOTIATING;
380 m_nReceiveBytesWanted = 4;
381 } else if (m_StreamCryptState == ECS_PENDING) {
382 //printf("Socket is client.pending on negotiation\n");
383 CMemFile fileRequest(29);
384 const uint8 bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker();
385 fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker);
386 fileRequest.WriteUInt32(m_nRandomKeyPart);
387 fileRequest.WriteUInt32(MAGICVALUE_SYNC);
388 const uint8 bySupportedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version
389 fileRequest.WriteUInt8(bySupportedEncryptionMethod);
390 fileRequest.WriteUInt8(bySupportedEncryptionMethod); // so we also prefer this one
391 uint8 byPadding = (uint8)(GetRandomUint8() % (thePrefs::GetCryptTCPPaddingLength() + 1));
392 fileRequest.WriteUInt8(byPadding);
393 for (int i = 0; i < byPadding; i++) {
394 fileRequest.WriteUInt8(GetRandomUint8());
397 m_NegotiatingState = ONS_BASIC_CLIENTB_MAGICVALUE;
398 m_StreamCryptState = ECS_NEGOTIATING;
399 m_nReceiveBytesWanted = 4;
401 SendNegotiatingData(fileRequest.GetRawBuffer(), (uint32)fileRequest.GetLength(), 5);
402 } else if (m_StreamCryptState == ECS_PENDING_SERVER) {
403 //printf("Socket is server.pending on negotiation\n");
404 CMemFile fileRequest(113);
405 const uint8 bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker();
406 fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker);
408 m_cryptDHA.Randomize((CryptoPP::AutoSeededRandomPool&)GetRandomPool(), DHAGREEMENT_A_BITS); // our random a
409 wxASSERT( m_cryptDHA.MinEncodedSize() <= DHAGREEMENT_A_BITS / 8 );
410 CryptoPP::Integer cryptDHPrime((byte*)dh768_p, PRIMESIZE_BYTES); // our fixed prime
411 // calculate g^a % p
412 CryptoPP::Integer cryptDHGexpAmodP = a_exp_b_mod_c(CryptoPP::Integer(2), m_cryptDHA, cryptDHPrime);
413 wxASSERT( m_cryptDHA.MinEncodedSize() <= PRIMESIZE_BYTES );
414 // put the result into a buffer
415 uint8 aBuffer[PRIMESIZE_BYTES];
416 cryptDHGexpAmodP.Encode(aBuffer, PRIMESIZE_BYTES);
418 fileRequest.Write(aBuffer, PRIMESIZE_BYTES);
419 uint8 byPadding = (uint8)(GetRandomUint8() % 16); // add random padding
420 fileRequest.WriteUInt8(byPadding);
422 for (int i = 0; i < byPadding; i++) {
423 fileRequest.WriteUInt8(GetRandomUint8());
426 m_NegotiatingState = ONS_BASIC_SERVER_DHANSWER;
427 m_StreamCryptState = ECS_NEGOTIATING;
428 m_nReceiveBytesWanted = 96;
430 SendNegotiatingData(fileRequest.GetRawBuffer(), (uint32)fileRequest.GetLength(), (uint32)fileRequest.GetLength());
431 } else {
432 wxASSERT( false );
433 m_StreamCryptState = ECS_NONE;
434 return;
438 int CEncryptedStreamSocket::Negotiate(const uint8* pBuffer, uint32 nLen){
439 uint32 nRead = 0;
440 wxASSERT( m_nReceiveBytesWanted > 0 );
442 //DumpMem(pBuffer, nLen, wxT("Negotiate buffer: "));
444 try{
445 while (m_NegotiatingState != ONS_COMPLETE && m_nReceiveBytesWanted > 0){
446 if (m_nReceiveBytesWanted > 512){
447 wxASSERT( false );
448 return 0;
451 const uint32 nToRead = std::min(nLen - nRead, m_nReceiveBytesWanted);
452 //printf("Reading %i bytes, add from %i position on %i position\n",nToRead, nRead, (int)m_pfiReceiveBuffer.GetPosition());
453 //DumpMem(pBuffer + nRead, nToRead, wxT("Recv Buffer: "));
454 m_pfiReceiveBuffer.ResetData();
455 m_pfiReceiveBuffer.Write(pBuffer + nRead, nToRead);
456 nRead += nToRead;
457 m_nReceiveBytesWanted -= nToRead;
458 if (m_nReceiveBytesWanted > 0) {
459 return nRead;
462 if (m_NegotiatingState != ONS_BASIC_CLIENTA_RANDOMPART && m_NegotiatingState != ONS_BASIC_SERVER_DHANSWER) {
463 // We have the keys, decrypt
464 //printf("We have the keys, so decrypt away on %s\n", (const char*) unicode2char(DbgGetIPString()));
465 m_pfiReceiveBuffer.Encrypt();
468 m_pfiReceiveBuffer.Seek(0);
470 switch (m_NegotiatingState){
471 case ONS_NONE: // would be a bug
472 wxASSERT( false );
473 return 0;
474 case ONS_BASIC_CLIENTA_RANDOMPART: {
475 //printf("We are on ONS_BASIC_CLIENTA_RANDOMPART, create the keys on %s\n", (const char*) unicode2char(DbgGetIPString()));
476 // This creates the send/receive keys.
478 uint8 achKeyData[21];
479 md4cpy(achKeyData, thePrefs::GetUserHash().GetHash());
480 m_pfiReceiveBuffer.Read(achKeyData + 17, 4);
482 achKeyData[16] = MAGICVALUE_REQUESTER;
484 //DumpMem(achKeyData, sizeof(achKeyData), wxT("ach:"));
486 MD5Sum md5(achKeyData, sizeof(achKeyData));
487 //DumpMem(md5.GetRawHash(), 16, wxT("Md5:"));
488 m_pfiReceiveBuffer.SetKey(md5);
490 achKeyData[16] = MAGICVALUE_SERVER;
491 md5.Calculate(achKeyData, sizeof(achKeyData));
492 m_pfiSendBuffer.SetKey(md5);
494 m_NegotiatingState = ONS_BASIC_CLIENTA_MAGICVALUE;
495 m_nReceiveBytesWanted = 4;
496 break;
499 case ONS_BASIC_CLIENTA_MAGICVALUE: {
500 // Check the magic value to confirm encryption works.
501 //printf("Creating magic value on negotiate on %s\n", (const char*) unicode2char(DbgGetIPString()));
503 uint32 dwValue = m_pfiReceiveBuffer.ReadUInt32();
505 if (dwValue == MAGICVALUE_SYNC){
506 // yup, the one or the other way it worked, this is an encrypted stream
507 //DEBUG_ONLY( DebugLog(_T("Received proper magic value, clientIP: %s"), DbgGetIPString()) );
508 // set the receiver key
509 //printf("Magic value works on %s\n", (const char*) unicode2char(DbgGetIPString()));
510 m_NegotiatingState = ONS_BASIC_CLIENTA_METHODTAGSPADLEN;
511 m_nReceiveBytesWanted = 3;
512 } else {
513 //printf("Wrong magic value: 0x%x != 0x%x on %s\n",dwValue, MAGICVALUE_SYNC, (const char*)unicode2char(DbgGetIPString()));
514 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value from clientIP %s on a supposly encrytped stream / Wrong Header"), DbgGetIPString());
515 OnError(ERR_ENCRYPTION);
516 return (-1);
518 break;
520 case ONS_BASIC_CLIENTA_METHODTAGSPADLEN: {
522 // Get encryption method and padding.
523 // Might fall back to padding process, but the bytes will be ignored.
524 //printf("Getting encryption method on negotiation\n");
526 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
527 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
529 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
530 //printf("Unsupported encryption method!\n");
531 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Client %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested);
534 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
536 m_NegotiatingState = ONS_BASIC_CLIENTA_PADDING;
538 if (m_nReceiveBytesWanted > 0) {
539 // No padding
540 break;
544 case ONS_BASIC_CLIENTA_PADDING:{
545 //printf("Negotiating on padding, completing\n");
546 // ignore the random bytes, send the response, set status complete
547 CMemFile fileResponse(26);
548 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
549 const uint8 bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred
550 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
552 amuleIPV4Address address;
553 GetPeer(address);
554 const uint8 byPaddingLen = theApp->serverconnect->AwaitingTestFromIP(StringIPtoUint32(address.IPAddress())) ? 16 : (thePrefs::GetCryptTCPPaddingLength() + 1);
555 uint8 byPadding = (uint8)(GetRandomUint8() % byPaddingLen);
557 fileResponse.WriteUInt8(byPadding);
558 for (int i = 0; i < byPadding; i++) {
559 fileResponse.WriteUInt8((uint8)rand());
561 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32)fileResponse.GetLength());
562 m_NegotiatingState = ONS_COMPLETE;
563 m_StreamCryptState = ECS_ENCRYPTING;
564 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (incoming)"), DbgGetIPString()) );
565 break;
568 case ONS_BASIC_CLIENTB_MAGICVALUE:{
569 //printf("Negotiating on magic value\n");
570 if (m_pfiReceiveBuffer.ReadUInt32() != MAGICVALUE_SYNC){
571 //DebugLogError(_T("CEncryptedStreamSocket: EncryptedstreamSyncError: Client sent wrong Magic Value as answer, cannot complete handshake (%s)"), DbgGetIPString());
572 OnError(ERR_ENCRYPTION);
573 return (-1);
575 m_NegotiatingState = ONS_BASIC_CLIENTB_METHODTAGSPADLEN;
576 m_nReceiveBytesWanted = 2;
577 break;
579 case ONS_BASIC_CLIENTB_METHODTAGSPADLEN:{
580 //printf("Negotiating on client B pad length\n");
581 m_dbgbyEncryptionMethodSet = m_pfiReceiveBuffer.ReadUInt8();
582 if (m_dbgbyEncryptionMethodSet != ENM_OBFUSCATION){
583 //DebugLogError( _T("CEncryptedStreamSocket: Client %s set unsupported encryption method (%i), handshake failed"), DbgGetIPString(), m_dbgbyEncryptionMethodSet);
584 OnError(ERR_ENCRYPTION);
585 return (-1);
587 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
588 m_NegotiatingState = ONS_BASIC_CLIENTB_PADDING;
589 if (m_nReceiveBytesWanted > 0) {
590 break;
593 case ONS_BASIC_CLIENTB_PADDING:
594 //printf("Negotiating on client B padding, handshake complete\n");
595 // ignore the random bytes, the handshake is complete
596 m_NegotiatingState = ONS_COMPLETE;
597 m_StreamCryptState = ECS_ENCRYPTING;
598 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (outgoing)"), DbgGetIPString()) );
599 break;
600 case ONS_BASIC_SERVER_DHANSWER:{
601 wxASSERT( !m_cryptDHA.IsZero() );
602 uint8 aBuffer[PRIMESIZE_BYTES + 1];
603 m_pfiReceiveBuffer.Read(aBuffer, PRIMESIZE_BYTES);
604 CryptoPP::Integer cryptDHAnswer((byte*)aBuffer, PRIMESIZE_BYTES);
605 CryptoPP::Integer cryptDHPrime((byte*)dh768_p, PRIMESIZE_BYTES); // our fixed prime
606 CryptoPP::Integer cryptResult = a_exp_b_mod_c(cryptDHAnswer, m_cryptDHA, cryptDHPrime);
608 m_cryptDHA = 0;
609 //DEBUG_ONLY( ZeroMemory(aBuffer, sizeof(aBuffer)) );
610 wxASSERT( cryptResult.MinEncodedSize() <= PRIMESIZE_BYTES );
612 // create the keys
613 cryptResult.Encode(aBuffer, PRIMESIZE_BYTES);
614 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_REQUESTER;
615 MD5Sum md5(aBuffer, sizeof(aBuffer));
616 m_pfiSendBuffer.SetKey(md5);
617 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_SERVER;
618 md5.Calculate(aBuffer, sizeof(aBuffer));
619 m_pfiReceiveBuffer.SetKey(md5);
621 m_NegotiatingState = ONS_BASIC_SERVER_MAGICVALUE;
622 m_nReceiveBytesWanted = 4;
623 break;
625 case ONS_BASIC_SERVER_MAGICVALUE:{
626 uint32 dwValue = m_pfiReceiveBuffer.ReadUInt32();
627 if (dwValue == MAGICVALUE_SYNC){
628 // yup, the one or the other way it worked, this is an encrypted stream
629 //DebugLog(_T("Received proper magic value after DH-Agreement from Serverconnection IP: %s"), DbgGetIPString());
630 // set the receiver key
631 m_NegotiatingState = ONS_BASIC_SERVER_METHODTAGSPADLEN;
632 m_nReceiveBytesWanted = 3;
633 } else {
634 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value after DH-Agreement from Serverconnection"), DbgGetIPString());
635 OnError(ERR_ENCRYPTION);
636 return (-1);
638 break;
640 case ONS_BASIC_SERVER_METHODTAGSPADLEN:
641 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
642 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
643 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
644 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Server %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested);
646 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
647 m_NegotiatingState = ONS_BASIC_SERVER_PADDING;
648 if (m_nReceiveBytesWanted > 0) {
649 break;
651 case ONS_BASIC_SERVER_PADDING:{
652 // ignore the random bytes (they are decrypted already), send the response, set status complete
653 CMemFile fileResponse(26);
654 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
655 const uint8 bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred
656 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
658 // Server callback connection only allows 16 bytes of padding.
659 uint8 byPadding = (uint8)(GetRandomUint8() % 16);
660 fileResponse.WriteUInt8(byPadding);
662 for (int i = 0; i < byPadding; i++) {
663 fileResponse.WriteUInt8((uint8)rand());
666 m_NegotiatingState = ONS_BASIC_SERVER_DELAYEDSENDING;
667 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32)fileResponse.GetLength(), 0, true); // don't actually send it right now, store it in our sendbuffer
668 m_StreamCryptState = ECS_ENCRYPTING;
669 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished DH Obufscation handshake with Server %s"), DbgGetIPString()) );
670 break;
672 default:
673 wxASSERT( false );
677 m_pfiReceiveBuffer.ResetData();
678 return nRead;
679 } catch(...){
680 // can only be caused by a bug in negationhandling, not by the datastream
681 //error->Delete();
682 //printf("Bug on negotiation?\n");
683 wxASSERT( false );
684 OnError(ERR_ENCRYPTION);
685 m_pfiReceiveBuffer.ResetData();
686 return (-1);
691 int CEncryptedStreamSocket::SendNegotiatingData(const void* lpBuf, uint32 nBufLen, uint32 nStartCryptFromByte, bool bDelaySend){
692 wxASSERT( m_StreamCryptState == ECS_NEGOTIATING || m_StreamCryptState == ECS_ENCRYPTING );
693 wxASSERT( nStartCryptFromByte <= nBufLen );
694 wxASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING || !bDelaySend );
695 //printf("Send negotiation data on %s\n", (const char*) unicode2char(DbgGetIPString()));
696 uint8* pBuffer = NULL;
697 bool bProcess = false;
698 if (lpBuf != NULL) {
699 pBuffer = new uint8[nBufLen];
700 if (pBuffer == NULL) {
701 throw CMuleException(wxT("Memory exception"), wxT("Memory exception on TCP encrypted socket"));
704 if (nStartCryptFromByte > 0) {
705 memcpy(pBuffer, lpBuf, nStartCryptFromByte);
708 if (nBufLen - nStartCryptFromByte > 0) {
709 //printf("Crypting negotiation data on %s starting on byte %i\n", (const char*) unicode2char(DbgGetIPString()), nStartCryptFromByte);
710 //DumpMem(lpBuf, nBufLen, wxT("Pre-encryption:"));
711 m_pfiSendBuffer.RC4Crypt((uint8*)lpBuf + nStartCryptFromByte, pBuffer + nStartCryptFromByte, nBufLen - nStartCryptFromByte);
712 //DumpMem(pBuffer, nBufLen, wxT("Post-encryption:"));
715 if (!m_pfiSendBuffer.IsEmpty()) {
716 // we already have data pending. Attach it and try to send
717 if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) {
718 m_NegotiatingState = ONS_COMPLETE;
719 } else {
720 wxASSERT( false );
722 m_pfiSendBuffer.Append(pBuffer, nBufLen);
723 delete[] pBuffer;
724 pBuffer = NULL;
725 nStartCryptFromByte = 0;
726 bProcess = true; // we want to try to send it right now
730 if (lpBuf == NULL || bProcess){
731 // this call is for processing pending data
732 if (m_pfiSendBuffer.IsEmpty() || nStartCryptFromByte != 0){
733 wxASSERT( false );
734 return 0; // or not
736 nBufLen = (uint32)m_pfiSendBuffer.GetLength();
737 pBuffer = m_pfiSendBuffer.Detach();
740 wxASSERT( m_pfiSendBuffer.IsEmpty() );
742 uint32 result = 0;
743 if (!bDelaySend) {
744 //printf("Writing negotiation data on %s: ", (const char*) unicode2char(DbgGetIPString()));
745 CSocketClientProxy::Write(pBuffer, nBufLen);
746 result = CSocketClientProxy::LastCount();
747 //printf("Wrote %i bytes\n",result);
750 if (result == (uint32)SOCKET_ERROR || bDelaySend){
751 m_pfiSendBuffer.Write(pBuffer, nBufLen);
752 delete[] pBuffer;
753 return result;
754 } else {
755 if (result < nBufLen){
756 // Store the partial data pending
757 //printf("Partial negotiation pending on %s\n", (const char*) unicode2char(DbgGetIPString()));
758 m_pfiSendBuffer.Write(pBuffer + result, nBufLen - result);
760 delete[] pBuffer;
761 return result;
765 wxString CEncryptedStreamSocket::DbgGetIPString(){
766 amuleIPV4Address address;
767 GetPeer(address);
768 return address.IPAddress();
771 uint8 CEncryptedStreamSocket::GetSemiRandomNotProtocolMarker() const{
772 uint8 bySemiRandomNotProtocolMarker = 0;
773 bool bOk = false;
774 for (int i = 0; i < 128; i++){
775 bySemiRandomNotProtocolMarker = GetRandomUint8();
776 switch (bySemiRandomNotProtocolMarker) { // not allowed values
777 case OP_EDONKEYPROT:
778 case OP_PACKEDPROT:
779 case OP_EMULEPROT:
780 break;
781 default:
782 bOk = true;
785 if (bOk) {
786 break;
790 if (!bOk){
791 // either we have _real_ bad luck or the randomgenerator is a bit messed up
792 wxASSERT( false );
793 bySemiRandomNotProtocolMarker = 0x01;
795 return bySemiRandomNotProtocolMarker;