private int Negotiate(byte[] pBuffer, int offset, int nLen) { int nRead = 0; Debug.Assert(receiveBytesWanted_ > 0); try { while (negotiatingState_ != NegotiatingStateEnum.ONS_COMPLETE && receiveBytesWanted_ > 0) { if (receiveBytesWanted_ > 512) { Debug.Assert(false); return(0); } if (fiReceiveBuffer_ == null) { byte[] pReceiveBuffer = new byte[512]; // use a fixed size buffer fiReceiveBuffer_ = MpdObjectManager.CreateSafeMemFile(pReceiveBuffer); } int nToRead = Math.Min(Convert.ToInt32(nLen) - nRead, Convert.ToInt32(receiveBytesWanted_)); fiReceiveBuffer_.Write(pBuffer, nRead, nToRead); nRead += nToRead; receiveBytesWanted_ -= Convert.ToUInt32(nToRead); if (receiveBytesWanted_ > 0) { return(nRead); } uint nCurrentBytesLen = (uint)fiReceiveBuffer_.Position; if (negotiatingState_ != NegotiatingStateEnum.ONS_BASIC_CLIENTA_RANDOMPART && negotiatingState_ != NegotiatingStateEnum.ONS_BASIC_SERVER_DHANSWER) { // don't have the keys yet byte[] pCryptBuffer = fiReceiveBuffer_.Buffer; MuleUtilities.RC4Crypt(pCryptBuffer, pCryptBuffer, nCurrentBytesLen, rc4ReceiveKey_); } fiReceiveBuffer_.SeekToBegin(); switch (negotiatingState_) { case NegotiatingStateEnum.ONS_NONE: // would be a bug Debug.Assert(false); return(0); case NegotiatingStateEnum.ONS_BASIC_CLIENTA_RANDOMPART: { Debug.Assert(rc4ReceiveKey_ == null); byte[] achKeyData = new byte[21]; MpdUtilities.Md4Cpy(achKeyData, MuleApplication.Instance.Preference.UserHash); achKeyData[16] = Convert.ToByte(MuleConstants.MAGICVALUE_REQUESTER); fiReceiveBuffer_.Read(achKeyData, 17, 4); // random key part sent from remote client MD5 md5 = MD5.Create(); rc4ReceiveKey_ = MuleUtilities.RC4CreateKey(md5.ComputeHash(achKeyData), 16); achKeyData[16] = Convert.ToByte(MuleConstants.MAGICVALUE_SERVER); rc4SendKey_ = MuleUtilities.RC4CreateKey(md5.ComputeHash(achKeyData), 16); negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_CLIENTA_MAGICVALUE; receiveBytesWanted_ = 4; break; } case NegotiatingStateEnum.ONS_BASIC_CLIENTA_MAGICVALUE: { uint dwValue = fiReceiveBuffer_.ReadUInt32(); if (dwValue == MuleConstants.MAGICVALUE_SYNC) { // yup, the one or the other way it worked, this is an encrypted stream //DEBUG_ONLY( MpdUtilities.DebugLog(("Received proper magic value, clientIP: %s"), DbgGetIPString()) ); // set the receiver key negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_CLIENTA_METHODTAGSPADLEN; receiveBytesWanted_ = 3; } else { MpdUtilities.DebugLogError(("CEncryptedStreamSocket: Received wrong magic value from clientIP %s on a supposly encrytped stream / Wrong Header"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); return(-1); } break; } case NegotiatingStateEnum.ONS_BASIC_CLIENTA_METHODTAGSPADLEN: DbgByEncryptionSupported = fiReceiveBuffer_.ReadUInt8(); DbgByEncryptionRequested = fiReceiveBuffer_.ReadUInt8(); if (DbgByEncryptionRequested != Convert.ToByte(EncryptionMethodsEnum.ENM_OBFUSCATION)) { MpdUtilities.AddDebugLogLine(EDebugLogPriority.DLP_LOW, false, ("CEncryptedStreamSocket: Client %s preffered unsupported encryption method (%i)"), DbgGetIPString(), DbgByEncryptionRequested); } receiveBytesWanted_ = fiReceiveBuffer_.ReadUInt8(); negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_CLIENTA_PADDING; if (receiveBytesWanted_ > 0) { break; } else { goto case NegotiatingStateEnum.ONS_BASIC_CLIENTA_PADDING; } case NegotiatingStateEnum.ONS_BASIC_CLIENTA_PADDING: { // ignore the random bytes, send the response, set status complete SafeMemFile fileResponse = MpdObjectManager.CreateSafeMemFile(26); fileResponse.WriteUInt32(MuleConstants.MAGICVALUE_SYNC); byte bySelectedEncryptionMethod = Convert.ToByte(EncryptionMethodsEnum.ENM_OBFUSCATION); // we do not support any further encryption in this version, so no need to look which the other client preferred fileResponse.WriteUInt8(bySelectedEncryptionMethod); IPEndPoint remoteEp = RemoteEndPoint as IPEndPoint; byte byPaddingLen = MuleApplication.Instance.ServerConnect.AwaitingTestFromIP(BitConverter.ToUInt32(remoteEp.Address.GetAddressBytes(), 0)) ? Convert.ToByte(16) : Convert.ToByte(MuleApplication.Instance.Preference.CryptTCPPaddingLength + 1); byte byPadding = Convert.ToByte(MpdUtilities.GetRandomUInt8() % byPaddingLen); fileResponse.WriteUInt8(byPadding); for (int i = 0; i < byPadding; i++) { fileResponse.WriteUInt8(MpdUtilities.GetRandomUInt8()); } SendNegotiatingData(fileResponse.Buffer, (uint)fileResponse.Length); negotiatingState_ = NegotiatingStateEnum.ONS_COMPLETE; streamCryptState_ = StreamCryptStateEnum.ECS_ENCRYPTING; //DEBUG_ONLY( MpdUtilities.DebugLog(("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (incoming)"), DbgGetIPString()) ); break; } case NegotiatingStateEnum.ONS_BASIC_CLIENTB_MAGICVALUE: { if (fiReceiveBuffer_.ReadUInt32() != MuleConstants.MAGICVALUE_SYNC) { MpdUtilities.DebugLogError(("CEncryptedStreamSocket: EncryptedstreamSyncError: Client sent wrong Magic Value as answer, cannot complete handshake (%s)"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); return(-1); } negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_CLIENTB_METHODTAGSPADLEN; receiveBytesWanted_ = 2; break; } case NegotiatingStateEnum.ONS_BASIC_CLIENTB_METHODTAGSPADLEN: { DbgByEncryptionMethodSet = fiReceiveBuffer_.ReadUInt8(); if (DbgByEncryptionMethodSet != Convert.ToByte(EncryptionMethodsEnum.ENM_OBFUSCATION)) { MpdUtilities.DebugLogError(("CEncryptedStreamSocket: Client %s set unsupported encryption method (%i), handshake failed"), DbgGetIPString(), DbgByEncryptionMethodSet); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); return(-1); } receiveBytesWanted_ = fiReceiveBuffer_.ReadUInt8(); negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_CLIENTB_PADDING; if (receiveBytesWanted_ > 0) { break; } else { goto case NegotiatingStateEnum.ONS_BASIC_CLIENTB_PADDING; } } case NegotiatingStateEnum.ONS_BASIC_CLIENTB_PADDING: // ignore the random bytes, the handshake is complete negotiatingState_ = NegotiatingStateEnum.ONS_COMPLETE; streamCryptState_ = StreamCryptStateEnum.ECS_ENCRYPTING; //DEBUG_ONLY( MpdUtilities.DebugLog(("CEncryptedStreamSocket: Finished Obufscation handshake with client %s (outgoing)"), DbgGetIPString()) ); break; case NegotiatingStateEnum.ONS_BASIC_SERVER_DHANSWER: { Debug.Assert(cryptDHA_ != new BigInteger(0)); byte[] aBuffer = new byte[MuleConstants.PRIMESIZE_BYTES + 1]; fiReceiveBuffer_.Read(aBuffer, 0, Convert.ToInt32(MuleConstants.PRIMESIZE_BYTES)); BigInteger cryptDHAnswer = new BigInteger(aBuffer, (int)MuleConstants.PRIMESIZE_BYTES); BigInteger cryptDHPrime = new BigInteger(dh768_p_, (int)MuleConstants.PRIMESIZE_BYTES); // our fixed prime BigInteger cryptResult = cryptDHAnswer.modPow(cryptDHA_, cryptDHPrime); cryptDHA_ = 0; Array.Clear(aBuffer, 0, aBuffer.Length); // create the keys Array.Copy(cryptResult.getBytes(), aBuffer, MuleConstants.PRIMESIZE_BYTES); aBuffer[MuleConstants.PRIMESIZE_BYTES] = Convert.ToByte(MuleConstants.MAGICVALUE_REQUESTER); MD5 md5 = MD5.Create(); rc4SendKey_ = MuleUtilities.RC4CreateKey(md5.ComputeHash(aBuffer), 16); aBuffer[MuleConstants.PRIMESIZE_BYTES] = Convert.ToByte(MuleConstants.MAGICVALUE_SERVER); rc4ReceiveKey_ = MuleUtilities.RC4CreateKey(md5.ComputeHash(aBuffer), 16); negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_SERVER_MAGICVALUE; receiveBytesWanted_ = 4; break; } case NegotiatingStateEnum.ONS_BASIC_SERVER_MAGICVALUE: { uint dwValue = fiReceiveBuffer_.ReadUInt32(); if (dwValue == MuleConstants.MAGICVALUE_SYNC) { // yup, the one or the other way it worked, this is an encrypted stream MpdUtilities.DebugLog(("Received proper magic value after DH-Agreement from Serverconnection IP: %s"), DbgGetIPString()); // set the receiver key negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_SERVER_METHODTAGSPADLEN; receiveBytesWanted_ = 3; } else { MpdUtilities.DebugLogError(("CEncryptedStreamSocket: Received wrong magic value after DH-Agreement from Serverconnection"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); return(-1); } break; } case NegotiatingStateEnum.ONS_BASIC_SERVER_METHODTAGSPADLEN: DbgByEncryptionSupported = fiReceiveBuffer_.ReadUInt8(); DbgByEncryptionRequested = fiReceiveBuffer_.ReadUInt8(); if (DbgByEncryptionRequested != Convert.ToByte(EncryptionMethodsEnum.ENM_OBFUSCATION)) { MpdUtilities.AddDebugLogLine(EDebugLogPriority.DLP_LOW, false, ("CEncryptedStreamSocket: Server %s preffered unsupported encryption method (%i)"), DbgGetIPString(), DbgByEncryptionRequested); } receiveBytesWanted_ = fiReceiveBuffer_.ReadUInt8(); negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_SERVER_PADDING; if (receiveBytesWanted_ > 16) { MpdUtilities.AddDebugLogLine(EDebugLogPriority.DLP_LOW, false, ("CEncryptedStreamSocket: Server %s sent more than 16 (%i) padding bytes"), DbgGetIPString(), receiveBytesWanted_); } if (receiveBytesWanted_ > 0) { break; } else { goto case NegotiatingStateEnum.ONS_BASIC_SERVER_PADDING; } case NegotiatingStateEnum.ONS_BASIC_SERVER_PADDING: { // ignore the random bytes (they are decrypted already), send the response, set status complete SafeMemFile fileResponse = MpdObjectManager.CreateSafeMemFile(26); fileResponse.WriteUInt32(MuleConstants.MAGICVALUE_SYNC); byte bySelectedEncryptionMethod = Convert.ToByte(EncryptionMethodsEnum.ENM_OBFUSCATION); // we do not support any further encryption in this version, so no need to look which the other client preferred fileResponse.WriteUInt8(bySelectedEncryptionMethod); byte byPadding = (byte)(MpdUtilities.GetRandomUInt8() % 16); fileResponse.WriteUInt8(byPadding); for (int i = 0; i < byPadding; i++) { fileResponse.WriteUInt8(MpdUtilities.GetRandomUInt8()); } negotiatingState_ = NegotiatingStateEnum.ONS_BASIC_SERVER_DELAYEDSENDING; SendNegotiatingData(fileResponse.Buffer, (uint)fileResponse.Length, 0, true); // don't actually send it right now, store it in our sendbuffer streamCryptState_ = StreamCryptStateEnum.ECS_ENCRYPTING; MpdUtilities.DebugLog(("CEncryptedStreamSocket: Finished DH Obufscation handshake with Server %s"), DbgGetIPString()); break; } default: Debug.Assert(false); break; } fiReceiveBuffer_.SeekToBegin(); } fiReceiveBuffer_ = null; return(nRead); } catch (Exception) { Debug.Assert(false); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); fiReceiveBuffer_ = null; return(-1); } }
public override int Receive(byte[] lpBuf, int offset, int nBufLen, SocketFlags nFlags) { obfuscationBytesReceived_ = base.Receive(lpBuf, offset, nBufLen, nFlags); fullReceive_ = obfuscationBytesReceived_ == (uint)nBufLen; if (obfuscationBytesReceived_ == SOCKET_ERROR || obfuscationBytesReceived_ <= 0) { return(obfuscationBytesReceived_); } switch (streamCryptState_) { case StreamCryptStateEnum.ECS_NONE: // disabled, just pass it through return(obfuscationBytesReceived_); case StreamCryptStateEnum.ECS_PENDING: case StreamCryptStateEnum.ECS_PENDING_SERVER: Debug.Assert(false); MpdUtilities.DebugLogError(("CEncryptedStreamSocket Received data before sending on outgoing connection")); streamCryptState_ = StreamCryptStateEnum.ECS_NONE; return(obfuscationBytesReceived_); case StreamCryptStateEnum.ECS_UNKNOWN: { int nRead = 1; bool bNormalHeader = false; switch (lpBuf[offset]) { case MuleConstants.PROTOCOL_EDONKEYPROT: case MuleConstants.PROTOCOL_PACKEDPROT: case MuleConstants.PROTOCOL_EMULEPROT: bNormalHeader = true; break; } if (!bNormalHeader) { StartNegotiation(false); int nNegRes = Negotiate(lpBuf, offset + nRead, obfuscationBytesReceived_ - nRead); if (nNegRes == (-1)) { return(0); } nRead += nNegRes; if (nRead != obfuscationBytesReceived_) { // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen // (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) MpdUtilities.DebugLogError(("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (1)"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); } return(0); } else { // doesn't seems to be encrypted streamCryptState_ = StreamCryptStateEnum.ECS_NONE; // if we require an encrypted connection, cut the connection here. This shouldn't happen that often // at least with other up-to-date eMule clients because they check for incompability before connecting if possible if (MuleApplication.Instance.Preference.IsClientCryptLayerRequired) { // TODO: Remove me when i have been solved // Even if the Require option is enabled, we currently have to accept unencrypted connection which are made // for lowid/firewall checks from servers and other from us selected client. Otherwise, this option would // always result in a lowid/firewalled status. This is of course not nice, but we can't avoid this walkarround // untill servers and kad completely support encryption too, which will at least for kad take a bit // only exception is the .ini option ClientCryptLayerRequiredStrict which will even ignore test connections // Update: New server now support encrypted callbacks IPEndPoint remote = RemoteEndPoint as IPEndPoint; uint address = BitConverter.ToUInt32(remote.Address.GetAddressBytes(), 0); if (MuleApplication.Instance.Preference.IsClientCryptLayerRequiredStrict || (!MuleApplication.Instance.ServerConnect.AwaitingTestFromIP(address) && !MuleApplication.Instance.ClientList.IsKadFirewallCheckIP(address))) { MpdUtilities.AddDebugLogLine(EDebugLogPriority.DLP_DEFAULT, false, ("Rejected incoming connection because Obfuscation was required but not used %s"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION_NOTALLOWED)); return(0); } else { MpdUtilities.AddDebugLogLine(EDebugLogPriority.DLP_DEFAULT, false, ("Incoming unencrypted firewallcheck connection permitted despite RequireEncryption setting - %s"), DbgGetIPString()); } } return(obfuscationBytesReceived_); // buffer was unchanged, we can just pass it through } } case StreamCryptStateEnum.ECS_ENCRYPTING: // basic obfuscation enabled and set, so decrypt and pass along MuleUtilities.RC4Crypt(lpBuf, offset, lpBuf, offset, Convert.ToUInt32(obfuscationBytesReceived_), rc4ReceiveKey_); return(obfuscationBytesReceived_); case StreamCryptStateEnum.ECS_NEGOTIATING: { int nRead = Negotiate(lpBuf, offset, obfuscationBytesReceived_); if (nRead == (-1)) { return(0); } else if (nRead != obfuscationBytesReceived_ && streamCryptState_ != StreamCryptStateEnum.ECS_ENCRYPTING) { // this means we have more data then the current negotiation step required (or there is a bug) and this should never happen MpdUtilities.DebugLogError(("CEncryptedStreamSocket: Client %s sent more data then expected while negotiating, disconnecting (2)"), DbgGetIPString()); OnError(Convert.ToInt32(EMSocketErrorCodeEnum.ERR_ENCRYPTION)); return(0); } else if (nRead != (uint)obfuscationBytesReceived_ && streamCryptState_ == StreamCryptStateEnum.ECS_ENCRYPTING) { // we finished the handshake and if we this was an outgoing connection it is allowed (but strange and unlikely) that the client sent payload MpdUtilities.DebugLogWarning(("CEncryptedStreamSocket: Client %s has finished the handshake but also sent payload on a outgoing connection"), DbgGetIPString()); Array.Copy(lpBuf, offset + nRead, lpBuf, offset + 0, obfuscationBytesReceived_ - nRead); return(obfuscationBytesReceived_ - nRead); } else { return(0); } } default: Debug.Assert(false); return(obfuscationBytesReceived_); } }