Upstream tarball 10101
[amule.git] / src / EncryptedStreamSocket.cpp
blobdec6cba803987f58a3633add347badbea739370c
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-2008 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 (Incoming 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 completely received (this includes the random bytes)
39 -> EncryptionMethod = 0 is Obfuscation and the only supported method 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 Obfuscation:
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 doesn't weaken the key if that part is known
52 - Why DH-KeyAgreement isn't used as basic obfuscation key: It doesn't offer 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 losing the complete randomness,
54 it doesn't offer 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 (Incoming 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 completely received (this includes the random bytes)
70 -> EncryptionMethod = 0 is Obfuscation and the only supported method 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 Connection
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()
140 /* External interface */
142 void CEncryptedStreamSocket::SetConnectionEncryption(bool bEnabled, const uint8_t* pTargetClientHash, bool bServerConnection)
144 if (m_StreamCryptState != ECS_UNKNOWN && m_StreamCryptState != ECS_NONE) {
145 if (!m_StreamCryptState == ECS_NONE || bEnabled) {
146 wxFAIL;
148 return;
151 if (bEnabled && pTargetClientHash != NULL && !bServerConnection) {
152 m_StreamCryptState = ECS_PENDING;
153 // create obfuscation keys, see on top for key format
155 // use the crypt random generator
156 m_nRandomKeyPart = GetRandomUint32();
158 uint8 achKeyData[21];
159 md4cpy(achKeyData, pTargetClientHash);
160 PokeUInt32(achKeyData + 17, m_nRandomKeyPart);
162 achKeyData[16] = MAGICVALUE_REQUESTER;
163 MD5Sum md5(achKeyData, sizeof(achKeyData));
164 m_pfiSendBuffer.SetKey(md5);
166 achKeyData[16] = MAGICVALUE_SERVER;
167 md5.Calculate(achKeyData, sizeof(achKeyData));
168 m_pfiReceiveBuffer.SetKey(md5);
170 } else if (bServerConnection && bEnabled) {
171 //printf("->Server crypt\n");
172 m_bServerCrypt = true;
173 m_StreamCryptState = ECS_PENDING_SERVER;
174 } else {
175 wxASSERT( !bEnabled );
176 m_StreamCryptState = ECS_NONE;
180 /* Internals, common to base class */
182 // unfortunately sending cannot be made transparent for the derived class, because of WSA_WOULDBLOCK
183 // together with the fact that each byte must pass the keystream only once
184 int CEncryptedStreamSocket::Write(const void* lpBuf, uint32_t nBufLen)
186 //printf("Starting write for %s\n", (const char*) unicode2char(DbgGetIPString()));
187 if (!IsEncryptionLayerReady()) {
188 wxFAIL;
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 wxFAIL;
202 if (m_StreamCryptState == ECS_UNKNOWN) {
203 //this happens when the encryption option was not set on an outgoing connection
204 //or if we try to send before receiving on an 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, uint32_t nBufLen)
216 CSocketClientProxy::Read(lpBuf, nBufLen);
217 m_nObfusicationBytesReceived = CSocketClientProxy::LastCount();
218 m_bFullReceive = m_nObfusicationBytesReceived == (uint32)nBufLen;
220 //printf("Read %i bytes on %s, socket %p\n", m_nObfusicationBytesReceived, (const char*) unicode2char(DbgGetIPString()), this);
222 if (m_nObfusicationBytesReceived == (uint32_t)SOCKET_ERROR || m_nObfusicationBytesReceived <= 0) {
223 return m_nObfusicationBytesReceived;
226 switch (m_StreamCryptState) {
227 case ECS_NONE: // disabled, just pass it through
228 return m_nObfusicationBytesReceived;
229 case ECS_PENDING:
230 case ECS_PENDING_SERVER:
231 //printf("Received %i bytes before sending?\n", m_nObfusicationBytesReceived);
232 wxFAIL;
233 //DebugLogError(_T("CEncryptedStreamSocket Received data before sending on outgoing connection"));
234 m_StreamCryptState = ECS_NONE;
235 return m_nObfusicationBytesReceived;
236 case ECS_UNKNOWN: {
237 //printf("Receiving encrypted data on ECS_UNKNOWN\n");
238 uint32_t nRead = 1;
239 bool bNormalHeader = false;
240 switch (((uint8_t*)lpBuf)[0]) {
241 case OP_EDONKEYPROT:
242 case OP_PACKEDPROT:
243 case OP_EMULEPROT:
244 bNormalHeader = true;
245 break;
248 if (!bNormalHeader) {
249 //printf("Not a normal header, negotiating encryption\n");
250 StartNegotiation(false);
251 const uint32 nNegRes = Negotiate((uint8_t*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
252 if (nNegRes == (uint32_t)(-1)) {
253 return 0;
255 nRead += nNegRes;
256 if (nRead != (uint32_t)m_nObfusicationBytesReceived) {
257 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
258 // (note: even if it just finished the handshake here, there still can be no data left, since the other client didn't receive our response yet)
259 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (1)"), DbgGetIPString());
260 //printf("On error: encryption\n");
261 OnError(ERR_ENCRYPTION);
263 return 0;
264 } else {
265 // doesn't seem to be encrypted
266 //printf("Encrypted data doesn't seem to be encrypted\n");
267 m_StreamCryptState = ECS_NONE;
269 // if we require an encrypted connection, cut the connection here. This shouldn't happen that often
270 // at least with other up-to-date eMule clients because they check for incompability before connecting if possible
271 if (thePrefs::IsClientCryptLayerRequired()) {
272 // TODO: Remove me when I have been solved
273 // Even if the Require option is enabled, we currently have to accept unencrypted connection which are made
274 // for lowid/firewall checks from servers and other from us selected client. Otherwise, this option would
275 // always result in a lowid/firewalled status. This is of course not nice, but we can't avoid this workaround
276 // until servers and kad completely support encryption too, which will at least for kad take a bit
277 // only exception is the .ini option ClientCryptLayerRequiredStrict which will even ignore test connections
278 // Update: New server now support encrypted callbacks
279 amuleIPV4Address address;
280 GetPeer(address);
281 uint32_t ip = StringIPtoUint32(address.IPAddress());
282 if (thePrefs::IsClientCryptLayerRequiredStrict() || (!theApp->serverconnect->AwaitingTestFromIP(ip)
283 && !theApp->clientlist->IsKadFirewallCheckIP(ip)) )
285 OnError(ERR_ENCRYPTION_NOTALLOWED);
286 return 0;
287 } else {
288 //AddDebugLogLine(DLP_DEFAULT, false, _T("Incoming unencrypted firewallcheck connection permitted despite RequireEncryption setting - %s"), DbgGetIPString() );
291 return m_nObfusicationBytesReceived; // buffer was unchanged, we can just pass it through
294 case ECS_ENCRYPTING:
295 //printf("Encryption enabled on data receiving, decrypting and passing along\n");
296 // basic obfusication enabled and set, so decrypt and pass along
297 m_pfiReceiveBuffer.RC4Crypt((uint8_t*)lpBuf, (uint8_t*)lpBuf, m_nObfusicationBytesReceived);
298 //DumpMem(lpBuf, m_nObfusicationBytesReceived, wxT("Directly decrypted data:"));
299 return m_nObfusicationBytesReceived;
300 case ECS_NEGOTIATING:{
301 //printf("Negotiating on data receive\n");
302 const uint32_t nRead = Negotiate((uint8_t*)lpBuf, m_nObfusicationBytesReceived);
303 if (nRead == (uint32_t)(-1)) {
304 //printf("-> Encryption read error on negotiation\n");
305 return 0;
306 } else if (nRead != (uint32_t)m_nObfusicationBytesReceived && m_StreamCryptState != ECS_ENCRYPTING) {
307 //printf("-> Too much data, bailing out of negotiation step\n");
308 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
309 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (2)"), DbgGetIPString());
310 OnError(ERR_ENCRYPTION);
311 return 0;
312 } else if (nRead != (uint32_t)m_nObfusicationBytesReceived && m_StreamCryptState == ECS_ENCRYPTING) {
313 //printf("-> Handshake negotiation finished\n");
314 // we finished the handshake and if we this was an outgoing connection it is allowed (but strange and unlikely) that the client sent payload
315 //DebugLogWarning(_T("CEncryptedStreamSocket: Client %s has finished the handshake but also sent payload on a outgoing connection"), DbgGetIPString());
316 memmove(lpBuf, (uint8_t*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
317 return m_nObfusicationBytesReceived - nRead;
318 } else {
319 //printf("-> Negotiation went probably ok\n");
320 return 0;
323 default:
324 wxFAIL;
325 return m_nObfusicationBytesReceived;
329 void CEncryptedStreamSocket::OnSend(int)
331 // if the socket just connected and this is outgoing, we might want to start the handshake here
332 if (m_StreamCryptState == ECS_PENDING || m_StreamCryptState == ECS_PENDING_SERVER){
333 //printf("Starting connection negotiation on OnSend for %s\n", (const char*) unicode2char(DbgGetIPString()));
334 StartNegotiation(true);
335 return;
338 // check if we have negotiating data pending
339 if (!m_pfiSendBuffer.IsEmpty()) {
340 wxASSERT( m_StreamCryptState >= ECS_NEGOTIATING );
341 SendNegotiatingData(NULL, 0);
345 void CEncryptedStreamSocket::CryptPrepareSendData(uint8* pBuffer, uint32 nLen)
347 if (!IsEncryptionLayerReady()) {
348 wxFAIL; // must be a bug
349 return;
351 if (m_StreamCryptState == ECS_UNKNOWN) {
352 //this happens when the encryption option was not set on an outgoing connection
353 //or if we try to send before receiving on an 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()
369 return ( (m_StreamCryptState == ECS_NONE || m_StreamCryptState == ECS_ENCRYPTING || m_StreamCryptState == ECS_UNKNOWN )
370 && (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_t bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker();
385 fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker);
386 fileRequest.WriteUInt32(m_nRandomKeyPart);
387 fileRequest.WriteUInt32(MAGICVALUE_SYNC);
388 const uint8_t 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_t byPadding = (uint8_t)(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_t)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_t 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_t 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_t)fileRequest.GetLength(), (uint32_t)fileRequest.GetLength());
431 } else {
432 wxFAIL;
433 m_StreamCryptState = ECS_NONE;
434 return;
438 int CEncryptedStreamSocket::Negotiate(const uint8* pBuffer, uint32 nLen)
440 uint32_t nRead = 0;
441 wxASSERT( m_nReceiveBytesWanted > 0 );
443 //DumpMem(pBuffer, nLen, wxT("Negotiate buffer: "));
445 try {
446 while (m_NegotiatingState != ONS_COMPLETE && m_nReceiveBytesWanted > 0) {
447 if (m_nReceiveBytesWanted > 512) {
448 wxFAIL;
449 return 0;
452 const uint32_t nToRead = std::min(nLen - nRead, m_nReceiveBytesWanted);
453 //printf("Reading %i bytes, add from %i position on %i position\n",nToRead, nRead, (int)m_pfiReceiveBuffer.GetPosition());
454 //DumpMem(pBuffer + nRead, nToRead, wxT("Recv Buffer: "));
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 wxFAIL;
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_t 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;
498 case ONS_BASIC_CLIENTA_MAGICVALUE: {
499 // Check the magic value to confirm encryption works.
500 //printf("Creating magic value on negotiate on %s\n", (const char*) unicode2char(DbgGetIPString()));
502 uint32_t dwValue = m_pfiReceiveBuffer.ReadUInt32();
504 if (dwValue == MAGICVALUE_SYNC) {
505 // yup, the one or the other way it worked, this is an encrypted stream
506 //DEBUG_ONLY( DebugLog(_T("Received proper magic value, clientIP: %s"), DbgGetIPString()) );
507 // set the receiver key
508 //printf("Magic value works on %s\n", (const char*) unicode2char(DbgGetIPString()));
509 m_NegotiatingState = ONS_BASIC_CLIENTA_METHODTAGSPADLEN;
510 m_nReceiveBytesWanted = 3;
511 } else {
512 //printf("Wrong magic value: 0x%x != 0x%x on %s\n",dwValue, MAGICVALUE_SYNC, (const char*)unicode2char(DbgGetIPString()));
513 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value from clientIP %s on a supposly encrytped stream / Wrong Header"), DbgGetIPString());
514 OnError(ERR_ENCRYPTION);
515 return (-1);
517 break;
519 case ONS_BASIC_CLIENTA_METHODTAGSPADLEN: {
520 // Get encryption method and padding.
521 // Might fall back to padding process, but the bytes will be ignored.
522 //printf("Getting encryption method on negotiation\n");
524 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
525 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
527 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
528 //printf("Unsupported encryption method!\n");
529 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Client %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested);
532 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
533 m_NegotiatingState = ONS_BASIC_CLIENTA_PADDING;
535 if (m_nReceiveBytesWanted > 0) {
536 // No padding
537 break;
540 case ONS_BASIC_CLIENTA_PADDING: {
541 //printf("Negotiating on padding, completing\n");
542 // ignore the random bytes, send the response, set status complete
543 CMemFile fileResponse(26);
544 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
545 const uint8_t bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred
546 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
548 amuleIPV4Address address;
549 GetPeer(address);
550 const uint8_t byPaddingLen = theApp->serverconnect->AwaitingTestFromIP(StringIPtoUint32(address.IPAddress())) ? 16 : (thePrefs::GetCryptTCPPaddingLength() + 1);
551 uint8_t byPadding = (uint8_t)(GetRandomUint8() % byPaddingLen);
553 fileResponse.WriteUInt8(byPadding);
554 for (int i = 0; i < byPadding; i++) {
555 fileResponse.WriteUInt8((uint8_t)rand());
557 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32_t)fileResponse.GetLength());
558 m_NegotiatingState = ONS_COMPLETE;
559 m_StreamCryptState = ECS_ENCRYPTING;
560 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (incoming)"), DbgGetIPString()) );
561 break;
563 case ONS_BASIC_CLIENTB_MAGICVALUE: {
564 //printf("Negotiating on magic value\n");
565 if (m_pfiReceiveBuffer.ReadUInt32() != MAGICVALUE_SYNC) {
566 //DebugLogError(_T("CEncryptedStreamSocket: EncryptedstreamSyncError: Client sent wrong Magic Value as answer, cannot complete handshake (%s)"), DbgGetIPString());
567 OnError(ERR_ENCRYPTION);
568 return (-1);
570 m_NegotiatingState = ONS_BASIC_CLIENTB_METHODTAGSPADLEN;
571 m_nReceiveBytesWanted = 2;
572 break;
574 case ONS_BASIC_CLIENTB_METHODTAGSPADLEN: {
575 //printf("Negotiating on client B pad length\n");
576 m_dbgbyEncryptionMethodSet = m_pfiReceiveBuffer.ReadUInt8();
577 if (m_dbgbyEncryptionMethodSet != ENM_OBFUSCATION) {
578 //DebugLogError( _T("CEncryptedStreamSocket: Client %s set unsupported encryption method (%i), handshake failed"), DbgGetIPString(), m_dbgbyEncryptionMethodSet);
579 OnError(ERR_ENCRYPTION);
580 return (-1);
582 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
583 m_NegotiatingState = ONS_BASIC_CLIENTB_PADDING;
584 if (m_nReceiveBytesWanted > 0) {
585 break;
588 case ONS_BASIC_CLIENTB_PADDING:
589 //printf("Negotiating on client B padding, handshake complete\n");
590 // ignore the random bytes, the handshake is complete
591 m_NegotiatingState = ONS_COMPLETE;
592 m_StreamCryptState = ECS_ENCRYPTING;
593 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (outgoing)"), DbgGetIPString()) );
594 break;
595 case ONS_BASIC_SERVER_DHANSWER: {
596 wxASSERT( !m_cryptDHA.IsZero() );
597 uint8_t aBuffer[PRIMESIZE_BYTES + 1];
598 m_pfiReceiveBuffer.Read(aBuffer, PRIMESIZE_BYTES);
599 CryptoPP::Integer cryptDHAnswer((byte*)aBuffer, PRIMESIZE_BYTES);
600 CryptoPP::Integer cryptDHPrime((byte*)dh768_p, PRIMESIZE_BYTES); // our fixed prime
601 CryptoPP::Integer cryptResult = a_exp_b_mod_c(cryptDHAnswer, m_cryptDHA, cryptDHPrime);
603 m_cryptDHA = 0;
604 //DEBUG_ONLY( ZeroMemory(aBuffer, sizeof(aBuffer)) );
605 wxASSERT( cryptResult.MinEncodedSize() <= PRIMESIZE_BYTES );
607 // create the keys
608 cryptResult.Encode(aBuffer, PRIMESIZE_BYTES);
609 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_REQUESTER;
610 MD5Sum md5(aBuffer, sizeof(aBuffer));
611 m_pfiSendBuffer.SetKey(md5);
612 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_SERVER;
613 md5.Calculate(aBuffer, sizeof(aBuffer));
614 m_pfiReceiveBuffer.SetKey(md5);
616 m_NegotiatingState = ONS_BASIC_SERVER_MAGICVALUE;
617 m_nReceiveBytesWanted = 4;
618 break;
620 case ONS_BASIC_SERVER_MAGICVALUE: {
621 uint32_t dwValue = m_pfiReceiveBuffer.ReadUInt32();
622 if (dwValue == MAGICVALUE_SYNC) {
623 // yup, the one or the other way it worked, this is an encrypted stream
624 //DebugLog(_T("Received proper magic value after DH-Agreement from Serverconnection IP: %s"), DbgGetIPString());
625 // set the receiver key
626 m_NegotiatingState = ONS_BASIC_SERVER_METHODTAGSPADLEN;
627 m_nReceiveBytesWanted = 3;
628 } else {
629 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value after DH-Agreement from Serverconnection"), DbgGetIPString());
630 OnError(ERR_ENCRYPTION);
631 return (-1);
633 break;
635 case ONS_BASIC_SERVER_METHODTAGSPADLEN:
636 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
637 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
638 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
639 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Server %s preffered unsupported encryption method (%i)"), DbgGetIPString(), m_dbgbyEncryptionRequested);
641 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
642 m_NegotiatingState = ONS_BASIC_SERVER_PADDING;
643 if (m_nReceiveBytesWanted > 0) {
644 break;
646 case ONS_BASIC_SERVER_PADDING: {
647 // ignore the random bytes (they are decrypted already), send the response, set status complete
648 CMemFile fileResponse(26);
649 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
650 const uint8_t bySelectedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version, so no need to look which the other client preferred
651 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
653 // Server callback connection only allows 16 bytes of padding.
654 uint8_t byPadding = (uint8_t)(GetRandomUint8() % 16);
655 fileResponse.WriteUInt8(byPadding);
657 for (int i = 0; i < byPadding; i++) {
658 fileResponse.WriteUInt8((uint8_t)rand());
661 m_NegotiatingState = ONS_BASIC_SERVER_DELAYEDSENDING;
662 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32_t)fileResponse.GetLength(), 0, true); // don't actually send it right now, store it in our sendbuffer
663 m_StreamCryptState = ECS_ENCRYPTING;
664 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished DH Obufscation handshake with Server %s"), DbgGetIPString()) );
665 break;
667 default:
668 wxFAIL;
670 m_pfiReceiveBuffer.ResetData();
672 return nRead;
673 } catch(...) {
674 // can only be caused by a bug in negationhandling, not by the datastream
675 //error->Delete();
676 //printf("Bug on negotiation?\n");
677 wxFAIL;
678 OnError(ERR_ENCRYPTION);
679 m_pfiReceiveBuffer.ResetData();
680 return (-1);
684 int CEncryptedStreamSocket::SendNegotiatingData(const void* lpBuf, uint32_t nBufLen, uint32_t nStartCryptFromByte, bool bDelaySend)
686 wxASSERT( m_StreamCryptState == ECS_NEGOTIATING || m_StreamCryptState == ECS_ENCRYPTING );
687 wxASSERT( nStartCryptFromByte <= nBufLen );
688 wxASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING || !bDelaySend );
689 //printf("Send negotiation data on %s\n", (const char*) unicode2char(DbgGetIPString()));
690 uint8_t* pBuffer = NULL;
691 bool bProcess = false;
692 if (lpBuf != NULL) {
693 pBuffer = new uint8_t[nBufLen];
694 if (pBuffer == NULL) {
695 throw CMuleException(wxT("Memory exception"), wxT("Memory exception on TCP encrypted socket"));
698 if (nStartCryptFromByte > 0) {
699 memcpy(pBuffer, lpBuf, nStartCryptFromByte);
702 if (nBufLen - nStartCryptFromByte > 0) {
703 //printf("Crypting negotiation data on %s starting on byte %i\n", (const char*) unicode2char(DbgGetIPString()), nStartCryptFromByte);
704 //DumpMem(lpBuf, nBufLen, wxT("Pre-encryption:"));
705 m_pfiSendBuffer.RC4Crypt((uint8*)lpBuf + nStartCryptFromByte, pBuffer + nStartCryptFromByte, nBufLen - nStartCryptFromByte);
706 //DumpMem(pBuffer, nBufLen, wxT("Post-encryption:"));
709 if (!m_pfiSendBuffer.IsEmpty()) {
710 // we already have data pending. Attach it and try to send
711 if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) {
712 m_NegotiatingState = ONS_COMPLETE;
713 } else {
714 wxFAIL;
716 m_pfiSendBuffer.Append(pBuffer, nBufLen);
717 delete[] pBuffer;
718 pBuffer = NULL;
719 nStartCryptFromByte = 0;
720 bProcess = true; // we want to try to send it right now
724 if (lpBuf == NULL || bProcess) {
725 // this call is for processing pending data
726 if (m_pfiSendBuffer.IsEmpty() || nStartCryptFromByte != 0) {
727 wxFAIL;
728 return 0; // or not
730 nBufLen = (uint32)m_pfiSendBuffer.GetLength();
731 pBuffer = m_pfiSendBuffer.Detach();
734 wxASSERT( m_pfiSendBuffer.IsEmpty() );
736 uint32_t result = 0;
737 if (!bDelaySend) {
738 //printf("Writing negotiation data on %s: ", (const char*) unicode2char(DbgGetIPString()));
739 CSocketClientProxy::Write(pBuffer, nBufLen);
740 result = CSocketClientProxy::LastCount();
741 //printf("Wrote %i bytes\n",result);
744 if (result == (uint32_t)SOCKET_ERROR || bDelaySend) {
745 m_pfiSendBuffer.Write(pBuffer, nBufLen);
746 delete[] pBuffer;
747 return result;
748 } else {
749 if (result < nBufLen) {
750 // Store the partial data pending
751 //printf("Partial negotiation pending on %s\n", (const char*) unicode2char(DbgGetIPString()));
752 m_pfiSendBuffer.Write(pBuffer + result, nBufLen - result);
754 delete[] pBuffer;
755 return result;
759 wxString CEncryptedStreamSocket::DbgGetIPString()
761 amuleIPV4Address address;
762 GetPeer(address);
763 return address.IPAddress();
766 uint8_t CEncryptedStreamSocket::GetSemiRandomNotProtocolMarker() const
768 uint8_t bySemiRandomNotProtocolMarker = 0;
769 bool bOk = false;
770 for (int i = 0; i < 128; i++) {
771 bySemiRandomNotProtocolMarker = GetRandomUint8();
772 switch (bySemiRandomNotProtocolMarker) { // not allowed values
773 case OP_EDONKEYPROT:
774 case OP_PACKEDPROT:
775 case OP_EMULEPROT:
776 break;
777 default:
778 bOk = true;
781 if (bOk) {
782 break;
786 if (!bOk) {
787 // either we have _real_ bad luck or the randomgenerator is a bit messed up
788 wxFAIL;
789 bySemiRandomNotProtocolMarker = 0x01;
791 return bySemiRandomNotProtocolMarker;