Always include the boost-package. Otherwise it won't get searched on a second run.
[amule.git] / src / EncryptedStreamSocket.cpp
blob670e37afeea6fcc92659d974ecfce83505b6e8df
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 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.
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(muleSocketFlags 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 (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(GetPeer()));
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)"), GetPeer());
209 //printf("Writing %i bytes of data\n", nBufLen);
210 return CSocketClientProxy::Write(lpBuf, nBufLen);
213 int CEncryptedStreamSocket::Read(void* lpBuf, uint32_t nBufLen)
215 m_nObfusicationBytesReceived = CSocketClientProxy::Read(lpBuf, nBufLen);
216 m_bFullReceive = m_nObfusicationBytesReceived == (uint32)nBufLen;
218 //printf("Read %i bytes on %s, socket %p\n", m_nObfusicationBytesReceived, (const char*) unicode2char(GetPeer()), this);
220 if (m_nObfusicationBytesReceived == (uint32_t)SOCKET_ERROR || m_nObfusicationBytesReceived <= 0) {
221 return m_nObfusicationBytesReceived;
224 switch (m_StreamCryptState) {
225 case ECS_NONE: // disabled, just pass it through
226 return m_nObfusicationBytesReceived;
227 case ECS_PENDING:
228 case ECS_PENDING_SERVER:
229 //printf("Received %i bytes before sending?\n", m_nObfusicationBytesReceived);
230 wxFAIL;
231 //DebugLogError(_T("CEncryptedStreamSocket Received data before sending on outgoing connection"));
232 m_StreamCryptState = ECS_NONE;
233 return m_nObfusicationBytesReceived;
234 case ECS_UNKNOWN: {
235 //printf("Receiving encrypted data on ECS_UNKNOWN\n");
236 uint32_t nRead = 1;
237 bool bNormalHeader = false;
238 switch (((uint8_t*)lpBuf)[0]) {
239 case OP_EDONKEYPROT:
240 case OP_PACKEDPROT:
241 case OP_EMULEPROT:
242 bNormalHeader = true;
243 break;
246 if (!bNormalHeader) {
247 //printf("Not a normal header, negotiating encryption\n");
248 StartNegotiation(false);
249 const uint32 nNegRes = Negotiate((uint8_t*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
250 if (nNegRes == (uint32_t)(-1)) {
251 return 0;
253 nRead += nNegRes;
254 if (nRead != (uint32_t)m_nObfusicationBytesReceived) {
255 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
256 // (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)
257 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (1)"), GetPeer());
258 //printf("On error: encryption\n");
259 OnError(ERR_ENCRYPTION);
261 return 0;
262 } else {
263 // doesn't seem to be encrypted
264 //printf("Encrypted data doesn't seem to be encrypted\n");
265 m_StreamCryptState = ECS_NONE;
267 // if we require an encrypted connection, cut the connection here. This shouldn't happen that often
268 // at least with other up-to-date eMule clients because they check for incompability before connecting if possible
269 if (thePrefs::IsClientCryptLayerRequired()) {
270 // TODO: Remove me when I have been solved
271 // Even if the Require option is enabled, we currently have to accept unencrypted connection which are made
272 // for lowid/firewall checks from servers and other from us selected client. Otherwise, this option would
273 // always result in a lowid/firewalled status. This is of course not nice, but we can't avoid this workaround
274 // until servers and kad completely support encryption too, which will at least for kad take a bit
275 // only exception is the .ini option ClientCryptLayerRequiredStrict which will even ignore test connections
276 // Update: New server now support encrypted callbacks
277 uint32_t ip = GetPeerInt();
278 if (thePrefs::IsClientCryptLayerRequiredStrict() || (!theApp->serverconnect->AwaitingTestFromIP(ip)
279 && !theApp->clientlist->IsKadFirewallCheckIP(ip)) )
281 OnError(ERR_ENCRYPTION_NOTALLOWED);
282 return 0;
283 } else {
284 //AddDebugLogLine(DLP_DEFAULT, false, _T("Incoming unencrypted firewallcheck connection permitted despite RequireEncryption setting - %s"), GetPeer() );
287 return m_nObfusicationBytesReceived; // buffer was unchanged, we can just pass it through
290 case ECS_ENCRYPTING:
291 //printf("Encryption enabled on data receiving, decrypting and passing along\n");
292 // basic obfusication enabled and set, so decrypt and pass along
293 m_pfiReceiveBuffer.RC4Crypt((uint8_t*)lpBuf, (uint8_t*)lpBuf, m_nObfusicationBytesReceived);
294 //DumpMem(lpBuf, m_nObfusicationBytesReceived, wxT("Directly decrypted data:"));
295 return m_nObfusicationBytesReceived;
296 case ECS_NEGOTIATING:{
297 //printf("Negotiating on data receive\n");
298 const uint32_t nRead = Negotiate((uint8_t*)lpBuf, m_nObfusicationBytesReceived);
299 if (nRead == (uint32_t)(-1)) {
300 //printf("-> Encryption read error on negotiation\n");
301 return 0;
302 } else if (nRead != (uint32_t)m_nObfusicationBytesReceived && m_StreamCryptState != ECS_ENCRYPTING) {
303 //printf("-> Too much data, bailing out of negotiation step\n");
304 // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen
305 //DebugLogError(_T("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (2)"), GetPeer());
306 OnError(ERR_ENCRYPTION);
307 return 0;
308 } else if (nRead != (uint32_t)m_nObfusicationBytesReceived && m_StreamCryptState == ECS_ENCRYPTING) {
309 //printf("-> Handshake negotiation finished\n");
310 // we finished the handshake and if we this was an outgoing connection it is allowed (but strange and unlikely) that the client sent payload
311 //DebugLogWarning(_T("CEncryptedStreamSocket: Client %s has finished the handshake but also sent payload on a outgoing connection"), GetPeer());
312 memmove(lpBuf, (uint8_t*)lpBuf + nRead, m_nObfusicationBytesReceived - nRead);
313 return m_nObfusicationBytesReceived - nRead;
314 } else {
315 //printf("-> Negotiation went probably ok\n");
316 return 0;
319 default:
320 wxFAIL;
321 return m_nObfusicationBytesReceived;
325 void CEncryptedStreamSocket::OnSend(int)
327 // if the socket just connected and this is outgoing, we might want to start the handshake here
328 if (m_StreamCryptState == ECS_PENDING || m_StreamCryptState == ECS_PENDING_SERVER){
329 //printf("Starting connection negotiation on OnSend for %s\n", (const char*) unicode2char(GetPeer()));
330 StartNegotiation(true);
331 return;
334 // check if we have negotiating data pending
335 if (!m_pfiSendBuffer.IsEmpty()) {
336 wxASSERT( m_StreamCryptState >= ECS_NEGOTIATING );
337 SendNegotiatingData(NULL, 0);
341 void CEncryptedStreamSocket::CryptPrepareSendData(uint8* pBuffer, uint32 nLen)
343 if (!IsEncryptionLayerReady()) {
344 wxFAIL; // must be a bug
345 return;
347 if (m_StreamCryptState == ECS_UNKNOWN) {
348 //this happens when the encryption option was not set on an outgoing connection
349 //or if we try to send before receiving on an incoming connection - both shouldn't happen
350 m_StreamCryptState = ECS_NONE;
351 //DebugLogError(_T("CEncryptedStreamSocket: Overwriting State ECS_UNKNOWN with ECS_NONE because of premature Send() (%s)"), GetPeer());
353 if (m_StreamCryptState == ECS_ENCRYPTING) {
354 //printf("Preparing crypt data on %s\n", (const char*) unicode2char(GetPeer()));
355 //DumpMem(pBuffer, nLen, wxT("Before crypt prepare:\n"));
356 m_pfiSendBuffer.RC4Crypt(pBuffer, pBuffer, nLen);
357 //DumpMem(pBuffer, nLen, wxT("After crypt prepare:\n"));
361 /* Internals, just for this class (can be raped) */
363 bool CEncryptedStreamSocket::IsEncryptionLayerReady()
365 return ( (m_StreamCryptState == ECS_NONE || m_StreamCryptState == ECS_ENCRYPTING || m_StreamCryptState == ECS_UNKNOWN )
366 && (m_pfiSendBuffer.IsEmpty() || (m_bServerCrypt && m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING)) );
369 void CEncryptedStreamSocket::StartNegotiation(bool bOutgoing)
371 //printf("Starting socket negotiation\n");
372 if (!bOutgoing) {
373 //printf("Incoming connection negotiation on %s\n", (const char*) unicode2char(GetPeer()));
374 m_NegotiatingState = ONS_BASIC_CLIENTA_RANDOMPART;
375 m_StreamCryptState = ECS_NEGOTIATING;
376 m_nReceiveBytesWanted = 4;
377 } else if (m_StreamCryptState == ECS_PENDING) {
378 //printf("Socket is client.pending on negotiation\n");
379 CMemFile fileRequest(29);
380 const uint8_t bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker();
381 fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker);
382 fileRequest.WriteUInt32(m_nRandomKeyPart);
383 fileRequest.WriteUInt32(MAGICVALUE_SYNC);
384 const uint8_t bySupportedEncryptionMethod = ENM_OBFUSCATION; // we do not support any further encryption in this version
385 fileRequest.WriteUInt8(bySupportedEncryptionMethod);
386 fileRequest.WriteUInt8(bySupportedEncryptionMethod); // so we also prefer this one
387 uint8_t byPadding = (uint8_t)(GetRandomUint8() % (thePrefs::GetCryptTCPPaddingLength() + 1));
388 fileRequest.WriteUInt8(byPadding);
389 for (int i = 0; i < byPadding; i++) {
390 fileRequest.WriteUInt8(GetRandomUint8());
393 m_NegotiatingState = ONS_BASIC_CLIENTB_MAGICVALUE;
394 m_StreamCryptState = ECS_NEGOTIATING;
395 m_nReceiveBytesWanted = 4;
397 SendNegotiatingData(fileRequest.GetRawBuffer(), (uint32_t)fileRequest.GetLength(), 5);
398 } else if (m_StreamCryptState == ECS_PENDING_SERVER) {
399 //printf("Socket is server.pending on negotiation\n");
400 CMemFile fileRequest(113);
401 const uint8_t bySemiRandomNotProtocolMarker = GetSemiRandomNotProtocolMarker();
402 fileRequest.WriteUInt8(bySemiRandomNotProtocolMarker);
404 m_cryptDHA.Randomize((CryptoPP::AutoSeededRandomPool&)GetRandomPool(), DHAGREEMENT_A_BITS); // our random a
405 wxASSERT( m_cryptDHA.MinEncodedSize() <= DHAGREEMENT_A_BITS / 8 );
406 CryptoPP::Integer cryptDHPrime((uint8_t*)dh768_p, PRIMESIZE_BYTES); // our fixed prime
407 // calculate g^a % p
408 CryptoPP::Integer cryptDHGexpAmodP = a_exp_b_mod_c(CryptoPP::Integer(2), m_cryptDHA, cryptDHPrime);
409 wxASSERT( m_cryptDHA.MinEncodedSize() <= PRIMESIZE_BYTES );
410 // put the result into a buffer
411 uint8_t aBuffer[PRIMESIZE_BYTES];
412 cryptDHGexpAmodP.Encode(aBuffer, PRIMESIZE_BYTES);
414 fileRequest.Write(aBuffer, PRIMESIZE_BYTES);
415 uint8 byPadding = (uint8)(GetRandomUint8() % 16); // add random padding
416 fileRequest.WriteUInt8(byPadding);
418 for (int i = 0; i < byPadding; i++) {
419 fileRequest.WriteUInt8(GetRandomUint8());
422 m_NegotiatingState = ONS_BASIC_SERVER_DHANSWER;
423 m_StreamCryptState = ECS_NEGOTIATING;
424 m_nReceiveBytesWanted = 96;
426 SendNegotiatingData(fileRequest.GetRawBuffer(), (uint32_t)fileRequest.GetLength(), (uint32_t)fileRequest.GetLength());
427 } else {
428 wxFAIL;
429 m_StreamCryptState = ECS_NONE;
430 return;
434 int CEncryptedStreamSocket::Negotiate(const uint8* pBuffer, uint32 nLen)
436 uint32_t nRead = 0;
437 wxASSERT( m_nReceiveBytesWanted > 0 );
439 //DumpMem(pBuffer, nLen, wxT("Negotiate buffer: "));
441 try {
442 while (m_NegotiatingState != ONS_COMPLETE && m_nReceiveBytesWanted > 0) {
443 if (m_nReceiveBytesWanted > 512) {
444 wxFAIL;
445 return 0;
448 const uint32_t nToRead = std::min(nLen - nRead, m_nReceiveBytesWanted);
449 //printf("Reading %i bytes, add from %i position on %i position\n",nToRead, nRead, (int)m_pfiReceiveBuffer.GetPosition());
450 //DumpMem(pBuffer + nRead, nToRead, wxT("Recv Buffer: "));
451 m_pfiReceiveBuffer.Write(pBuffer + nRead, nToRead);
452 nRead += nToRead;
453 m_nReceiveBytesWanted -= nToRead;
454 if (m_nReceiveBytesWanted > 0) {
455 return nRead;
458 if (m_NegotiatingState != ONS_BASIC_CLIENTA_RANDOMPART && m_NegotiatingState != ONS_BASIC_SERVER_DHANSWER) {
459 // We have the keys, decrypt
460 //printf("We have the keys, so decrypt away on %s\n", (const char*) unicode2char(GetPeer()));
461 m_pfiReceiveBuffer.Encrypt();
464 m_pfiReceiveBuffer.Seek(0);
466 switch (m_NegotiatingState) {
467 case ONS_NONE: // would be a bug
468 wxFAIL;
469 return 0;
470 case ONS_BASIC_CLIENTA_RANDOMPART: {
471 //printf("We are on ONS_BASIC_CLIENTA_RANDOMPART, create the keys on %s\n", (const char*) unicode2char(GetPeer()));
472 // This creates the send/receive keys.
474 uint8_t achKeyData[21];
475 md4cpy(achKeyData, thePrefs::GetUserHash().GetHash());
476 m_pfiReceiveBuffer.Read(achKeyData + 17, 4);
478 achKeyData[16] = MAGICVALUE_REQUESTER;
480 //DumpMem(achKeyData, sizeof(achKeyData), wxT("ach:"));
482 MD5Sum md5(achKeyData, sizeof(achKeyData));
483 //DumpMem(md5.GetRawHash(), 16, wxT("Md5:"));
484 m_pfiReceiveBuffer.SetKey(md5);
486 achKeyData[16] = MAGICVALUE_SERVER;
487 md5.Calculate(achKeyData, sizeof(achKeyData));
488 m_pfiSendBuffer.SetKey(md5);
490 m_NegotiatingState = ONS_BASIC_CLIENTA_MAGICVALUE;
491 m_nReceiveBytesWanted = 4;
492 break;
494 case ONS_BASIC_CLIENTA_MAGICVALUE: {
495 // Check the magic value to confirm encryption works.
496 //printf("Creating magic value on negotiate on %s\n", (const char*) unicode2char(GetPeer()));
498 uint32_t dwValue = m_pfiReceiveBuffer.ReadUInt32();
500 if (dwValue == MAGICVALUE_SYNC) {
501 // yup, the one or the other way it worked, this is an encrypted stream
502 //DEBUG_ONLY( DebugLog(_T("Received proper magic value, clientIP: %s"), GetPeer()) );
503 // set the receiver key
504 //printf("Magic value works on %s\n", (const char*) unicode2char(GetPeer()));
505 m_NegotiatingState = ONS_BASIC_CLIENTA_METHODTAGSPADLEN;
506 m_nReceiveBytesWanted = 3;
507 } else {
508 //printf("Wrong magic value: 0x%x != 0x%x on %s\n",dwValue, MAGICVALUE_SYNC, (const char*)unicode2char(GetPeer()));
509 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value from clientIP %s on a supposly encrytped stream / Wrong Header"), GetPeer());
510 OnError(ERR_ENCRYPTION);
511 return (-1);
513 break;
515 case ONS_BASIC_CLIENTA_METHODTAGSPADLEN: {
516 // Get encryption method and padding.
517 // Might fall back to padding process, but the bytes will be ignored.
518 //printf("Getting encryption method on negotiation\n");
520 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
521 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
523 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
524 //printf("Unsupported encryption method!\n");
525 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Client %s preffered unsupported encryption method (%i)"), GetPeer(), m_dbgbyEncryptionRequested);
528 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
529 m_NegotiatingState = ONS_BASIC_CLIENTA_PADDING;
531 if (m_nReceiveBytesWanted > 0) {
532 // No padding
533 break;
536 /* fall through */
537 case ONS_BASIC_CLIENTA_PADDING: {
538 //printf("Negotiating on padding, completing\n");
539 // ignore the random bytes, send the response, set status complete
540 CMemFile fileResponse(26);
541 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
542 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
543 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
545 const uint8_t byPaddingLen = theApp->serverconnect->AwaitingTestFromIP(GetPeerInt()) ? 16 : (thePrefs::GetCryptTCPPaddingLength() + 1);
546 uint8_t byPadding = (uint8_t)(GetRandomUint8() % byPaddingLen);
548 fileResponse.WriteUInt8(byPadding);
549 for (int i = 0; i < byPadding; i++) {
550 fileResponse.WriteUInt8((uint8_t)rand());
552 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32_t)fileResponse.GetLength());
553 m_NegotiatingState = ONS_COMPLETE;
554 m_StreamCryptState = ECS_ENCRYPTING;
555 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (incoming)"), GetPeer()) );
556 break;
558 case ONS_BASIC_CLIENTB_MAGICVALUE: {
559 //printf("Negotiating on magic value\n");
560 if (m_pfiReceiveBuffer.ReadUInt32() != MAGICVALUE_SYNC) {
561 //DebugLogError(_T("CEncryptedStreamSocket: EncryptedstreamSyncError: Client sent wrong Magic Value as answer, cannot complete handshake (%s)"), GetPeer());
562 OnError(ERR_ENCRYPTION);
563 return (-1);
565 m_NegotiatingState = ONS_BASIC_CLIENTB_METHODTAGSPADLEN;
566 m_nReceiveBytesWanted = 2;
567 break;
569 case ONS_BASIC_CLIENTB_METHODTAGSPADLEN: {
570 //printf("Negotiating on client B pad length\n");
571 m_dbgbyEncryptionMethodSet = m_pfiReceiveBuffer.ReadUInt8();
572 if (m_dbgbyEncryptionMethodSet != ENM_OBFUSCATION) {
573 //DebugLogError( _T("CEncryptedStreamSocket: Client %s set unsupported encryption method (%i), handshake failed"), GetPeer(), m_dbgbyEncryptionMethodSet);
574 OnError(ERR_ENCRYPTION);
575 return (-1);
577 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
578 m_NegotiatingState = ONS_BASIC_CLIENTB_PADDING;
579 if (m_nReceiveBytesWanted > 0) {
580 break;
583 /* fall through */
584 case ONS_BASIC_CLIENTB_PADDING:
585 //printf("Negotiating on client B padding, handshake complete\n");
586 // ignore the random bytes, the handshake is complete
587 m_NegotiatingState = ONS_COMPLETE;
588 m_StreamCryptState = ECS_ENCRYPTING;
589 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (outgoing)"), GetPeer()) );
590 break;
591 case ONS_BASIC_SERVER_DHANSWER: {
592 wxASSERT( !m_cryptDHA.IsZero() );
593 uint8_t aBuffer[PRIMESIZE_BYTES + 1];
594 m_pfiReceiveBuffer.Read(aBuffer, PRIMESIZE_BYTES);
595 CryptoPP::Integer cryptDHAnswer((uint8_t*)aBuffer, PRIMESIZE_BYTES);
596 CryptoPP::Integer cryptDHPrime((uint8_t*)dh768_p, PRIMESIZE_BYTES); // our fixed prime
597 CryptoPP::Integer cryptResult = a_exp_b_mod_c(cryptDHAnswer, m_cryptDHA, cryptDHPrime);
599 m_cryptDHA = 0;
600 //DEBUG_ONLY( ZeroMemory(aBuffer, sizeof(aBuffer)) );
601 wxASSERT( cryptResult.MinEncodedSize() <= PRIMESIZE_BYTES );
603 // create the keys
604 cryptResult.Encode(aBuffer, PRIMESIZE_BYTES);
605 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_REQUESTER;
606 MD5Sum md5(aBuffer, sizeof(aBuffer));
607 m_pfiSendBuffer.SetKey(md5);
608 aBuffer[PRIMESIZE_BYTES] = MAGICVALUE_SERVER;
609 md5.Calculate(aBuffer, sizeof(aBuffer));
610 m_pfiReceiveBuffer.SetKey(md5);
612 m_NegotiatingState = ONS_BASIC_SERVER_MAGICVALUE;
613 m_nReceiveBytesWanted = 4;
614 break;
616 case ONS_BASIC_SERVER_MAGICVALUE: {
617 uint32_t dwValue = m_pfiReceiveBuffer.ReadUInt32();
618 if (dwValue == MAGICVALUE_SYNC) {
619 // yup, the one or the other way it worked, this is an encrypted stream
620 //DebugLog(_T("Received proper magic value after DH-Agreement from Serverconnection IP: %s"), GetPeer());
621 // set the receiver key
622 m_NegotiatingState = ONS_BASIC_SERVER_METHODTAGSPADLEN;
623 m_nReceiveBytesWanted = 3;
624 } else {
625 //DebugLogError(_T("CEncryptedStreamSocket: Received wrong magic value after DH-Agreement from Serverconnection"), GetPeer());
626 OnError(ERR_ENCRYPTION);
627 return (-1);
629 break;
631 case ONS_BASIC_SERVER_METHODTAGSPADLEN:
632 m_dbgbyEncryptionSupported = m_pfiReceiveBuffer.ReadUInt8();
633 m_dbgbyEncryptionRequested = m_pfiReceiveBuffer.ReadUInt8();
634 if (m_dbgbyEncryptionRequested != ENM_OBFUSCATION) {
635 // AddDebugLogLine(DLP_LOW, false, _T("CEncryptedStreamSocket: Server %s preffered unsupported encryption method (%i)"), GetPeer(), m_dbgbyEncryptionRequested);
637 m_nReceiveBytesWanted = m_pfiReceiveBuffer.ReadUInt8();
638 m_NegotiatingState = ONS_BASIC_SERVER_PADDING;
639 if (m_nReceiveBytesWanted > 0) {
640 break;
642 /* fall through */
643 case ONS_BASIC_SERVER_PADDING: {
644 // ignore the random bytes (they are decrypted already), send the response, set status complete
645 CMemFile fileResponse(26);
646 fileResponse.WriteUInt32(MAGICVALUE_SYNC);
647 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
648 fileResponse.WriteUInt8(bySelectedEncryptionMethod);
650 // Server callback connection only allows 16 bytes of padding.
651 uint8_t byPadding = (uint8_t)(GetRandomUint8() % 16);
652 fileResponse.WriteUInt8(byPadding);
654 for (int i = 0; i < byPadding; i++) {
655 fileResponse.WriteUInt8((uint8_t)rand());
658 m_NegotiatingState = ONS_BASIC_SERVER_DELAYEDSENDING;
659 SendNegotiatingData(fileResponse.GetRawBuffer(), (uint32_t)fileResponse.GetLength(), 0, true); // don't actually send it right now, store it in our sendbuffer
660 m_StreamCryptState = ECS_ENCRYPTING;
661 //DEBUG_ONLY( DebugLog(_T("CEncryptedStreamSocket: Finished DH Obufscation handshake with Server %s"), GetPeer()) );
662 break;
664 default:
665 wxFAIL;
667 m_pfiReceiveBuffer.ResetData();
669 return nRead;
670 } catch(...) {
671 // can only be caused by a bug in negationhandling, not by the datastream
672 //error->Delete();
673 //printf("Bug on negotiation?\n");
674 wxFAIL;
675 OnError(ERR_ENCRYPTION);
676 m_pfiReceiveBuffer.ResetData();
677 return (-1);
681 int CEncryptedStreamSocket::SendNegotiatingData(const void* lpBuf, uint32_t nBufLen, uint32_t nStartCryptFromByte, bool bDelaySend)
683 wxASSERT( m_StreamCryptState == ECS_NEGOTIATING || m_StreamCryptState == ECS_ENCRYPTING );
684 wxASSERT( nStartCryptFromByte <= nBufLen );
685 wxASSERT( m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING || !bDelaySend );
686 //printf("Send negotiation data on %s\n", (const char*) unicode2char(GetPeer()));
687 uint8_t* pBuffer = NULL;
688 bool bProcess = false;
689 if (lpBuf != NULL) {
690 pBuffer = new uint8_t[nBufLen];
691 if (pBuffer == NULL) {
692 throw CMuleException(wxT("Memory exception"), wxT("Memory exception on TCP encrypted socket"));
695 if (nStartCryptFromByte > 0) {
696 memcpy(pBuffer, lpBuf, nStartCryptFromByte);
699 if (nBufLen - nStartCryptFromByte > 0) {
700 //printf("Crypting negotiation data on %s starting on byte %i\n", (const char*) unicode2char(GetPeer()), nStartCryptFromByte);
701 //DumpMem(lpBuf, nBufLen, wxT("Pre-encryption:"));
702 m_pfiSendBuffer.RC4Crypt((uint8*)lpBuf + nStartCryptFromByte, pBuffer + nStartCryptFromByte, nBufLen - nStartCryptFromByte);
703 //DumpMem(pBuffer, nBufLen, wxT("Post-encryption:"));
706 if (!m_pfiSendBuffer.IsEmpty()) {
707 // we already have data pending. Attach it and try to send
708 if (m_NegotiatingState == ONS_BASIC_SERVER_DELAYEDSENDING) {
709 m_NegotiatingState = ONS_COMPLETE;
710 } else {
711 wxFAIL;
713 m_pfiSendBuffer.Append(pBuffer, nBufLen);
714 delete[] pBuffer;
715 pBuffer = NULL;
716 nStartCryptFromByte = 0;
717 bProcess = true; // we want to try to send it right now
721 if (lpBuf == NULL || bProcess) {
722 // this call is for processing pending data
723 if (m_pfiSendBuffer.IsEmpty() || nStartCryptFromByte != 0) {
724 wxFAIL;
725 return 0; // or not
727 nBufLen = (uint32)m_pfiSendBuffer.GetLength();
728 pBuffer = m_pfiSendBuffer.Detach();
731 wxASSERT( m_pfiSendBuffer.IsEmpty() );
733 uint32_t result = 0;
734 if (!bDelaySend) {
735 //printf("Writing negotiation data on %s: ", (const char*) unicode2char(GetPeer()));
736 result = CSocketClientProxy::Write(pBuffer, nBufLen);
737 //printf("Wrote %i bytes\n",result);
740 if (result == (uint32_t)SOCKET_ERROR || bDelaySend) {
741 m_pfiSendBuffer.Write(pBuffer, nBufLen);
742 delete[] pBuffer;
743 return result;
744 } else {
745 if (result < nBufLen) {
746 // Store the partial data pending
747 //printf("Partial negotiation pending on %s\n", (const char*) unicode2char(GetPeer()));
748 m_pfiSendBuffer.Write(pBuffer + result, nBufLen - result);
750 delete[] pBuffer;
751 return result;
756 uint8_t CEncryptedStreamSocket::GetSemiRandomNotProtocolMarker() const
758 uint8_t bySemiRandomNotProtocolMarker = 0;
759 bool bOk = false;
760 for (int i = 0; i < 128; i++) {
761 bySemiRandomNotProtocolMarker = GetRandomUint8();
762 switch (bySemiRandomNotProtocolMarker) { // not allowed values
763 case OP_EDONKEYPROT:
764 case OP_PACKEDPROT:
765 case OP_EMULEPROT:
766 break;
767 default:
768 bOk = true;
771 if (bOk) {
772 break;
776 if (!bOk) {
777 // either we have _real_ bad luck or the randomgenerator is a bit messed up
778 wxFAIL;
779 bySemiRandomNotProtocolMarker = 0x01;
781 return bySemiRandomNotProtocolMarker;