static async Task <EncryptorResult> DoCheckIncomingConnectionAsync(IConnection connection, EncryptionTypes encryption, EngineSettings settings, InfoHash[] sKeys) { var allowedEncryption = (settings?.AllowedEncryption ?? EncryptionTypes.All) & encryption; var supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header); var supportsRC4Full = allowedEncryption.HasFlag(EncryptionTypes.RC4Full); var supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText); // If the connection is incoming, receive the handshake before // trying to decide what encryption to use var buffer = new byte[HandshakeMessage.HandshakeLength]; await NetworkIO.ReceiveAsync(connection, buffer, 0, buffer.Length, null, null, null).ConfigureAwait(false); HandshakeMessage message = new HandshakeMessage(); message.Decode(buffer, 0, buffer.Length); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { if (supportsPlainText) { return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, message)); } } else if (supportsRC4Header || supportsRC4Full) { // The data we just received was part of an encrypted handshake and was *not* the BitTorrent handshake var encSocket = new PeerBEncryption(sKeys, EncryptionTypes.All); await encSocket.HandshakeAsync(connection, buffer, 0, buffer.Length); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } // As the connection was encrypted, the data we got from the initial Receive call will have // been consumed during the crypto handshake process. Now that the encrypted handshake has // been established, we should ensure we read the data again. var data = encSocket.InitialData?.Length > 0 ? encSocket.InitialData : null; if (data == null) { data = buffer; await NetworkIO.ReceiveAsync(connection, data, 0, data.Length, null, null, null); encSocket.Decryptor.Decrypt(data); } message.Decode(data, 0, data.Length); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, message)); } } connection.Dispose(); throw new EncryptionException("Invalid handshake received and no decryption works"); }
protected async ReusableTask ReceiveMessage(byte[] buffer, int length) { if (length == 0) { return; } if (initialBuffer != null) { int toCopy = Math.Min(initialBufferCount, length); Array.Copy(initialBuffer, initialBufferOffset, buffer, 0, toCopy); initialBufferOffset += toCopy; initialBufferCount -= toCopy; if (toCopy == initialBufferCount) { initialBufferCount = 0; initialBufferOffset = 0; initialBuffer = Array.Empty <byte> (); } if (toCopy != length) { await NetworkIO.ReceiveAsync(socket, buffer, toCopy, length - toCopy, null, null, null).ConfigureAwait(false); bytesReceived += length - toCopy; } } else { await NetworkIO.ReceiveAsync(socket, buffer, 0, length, null, null, null).ConfigureAwait(false); bytesReceived += length; } }
protected async ReusableTask ReceiveMessageAsync(SocketMemory buffer) { if (buffer.Length == 0) { return; } if (!initialBuffer.IsEmpty) { int toCopy = Math.Min(initialBuffer.Length, buffer.Length); initialBuffer.Span.Slice(0, toCopy).CopyTo(buffer.AsSpan()); initialBuffer = initialBuffer.Slice(toCopy); if (toCopy != buffer.Length) { await NetworkIO.ReceiveAsync(socket, buffer.Slice(toCopy, buffer.Length - toCopy), null, null, null).ConfigureAwait(false); bytesReceived += buffer.Length - toCopy; } } else { await NetworkIO.ReceiveAsync(socket, buffer, null, null, null).ConfigureAwait(false); bytesReceived += buffer.Length; } }
/// <summary> /// Read data from the socket until the byte string in syncData is read, or until syncStopPoint /// is reached (in that case, there is an EncryptionError). /// (Either "3 A->B: HASH('req1', S)" or "4 B->A: ENCRYPT(VC)") /// </summary> /// <param name="syncData">Buffer with the data to synchronize to</param> /// <param name="syncStopPoint">Maximum number of bytes (measured from the total received from the socket since connection) to read before giving up</param> protected async ReusableTask SynchronizeAsync(byte[] syncData, int syncStopPoint) { // The strategy here is to create a window the size of the data to synchronize and just refill that until its contents match syncData int filled = 0; using (NetworkIO.BufferPool.Rent(syncData.Length, out SocketMemory synchronizeWindow)) { while (bytesReceived < syncStopPoint) { int received = syncData.Length - filled; await NetworkIO.ReceiveAsync(socket, synchronizeWindow.Slice(filled, received), null, null, null).ConfigureAwait(false); bytesReceived += received; bool matched = true; for (int i = 0; i < syncData.Length && matched; i++) { matched &= syncData[i] == synchronizeWindow.AsSpan()[i]; } if (matched) // the match started in the beginning of the window, so it must be a full match { await DoneSynchronizeAsync().ConfigureAwait(false); return; } else { // See if the current window contains the first byte of the expected synchronize data // No need to check synchronizeWindow[0] as otherwise we could loop forever receiving 0 bytes int shift = -1; for (int i = 1; i < syncData.Length && shift == -1; i++) { if (synchronizeWindow.AsSpan()[i] == syncData[0]) { shift = i; } } if (shift > 0) { filled = syncData.Length - shift; // Shuffle everything left by 'shift' (the first good byte) and fill the rest of the window for (int i = shift; i < synchronizeWindow.Length; i++) { synchronizeWindow.AsSpan()[i - shift] = synchronizeWindow.AsSpan()[i]; } } else { // The start point we thought we had is actually garbage, so throw away all the data we have filled = 0; } } } } throw new EncryptionException("Couldn't synchronise 1"); }
/// <summary> /// Read data from the socket until the byte string in syncData is read, or until syncStopPoint /// is reached (in that case, there is an EncryptionError). /// (Either "3 A->B: HASH('req1', S)" or "4 B->A: ENCRYPT(VC)") /// </summary> /// <param name="syncData">Buffer with the data to synchronize to</param> /// <param name="syncStopPoint">Maximum number of bytes (measured from the total received from the socket since connection) to read before giving up</param> protected async Task Synchronize(byte[] syncData, int syncStopPoint) { // The strategy here is to create a window the size of the data to synchronize and just refill that until its contents match syncData int filled = 0; var synchronizeWindow = new byte[syncData.Length]; while (bytesReceived < syncStopPoint) { int received = synchronizeWindow.Length - filled; await NetworkIO.ReceiveAsync(socket, synchronizeWindow, filled, received, null, null, null).ConfigureAwait(false); bytesReceived += received; bool matched = true; for (int i = 0; i < synchronizeWindow.Length && matched; i++) { matched &= syncData[i] == synchronizeWindow[i]; } if (matched) // the match started in the beginning of the window, so it must be a full match { await doneSynchronize().ConfigureAwait(false); return; } else { // See if the current window contains the first byte of the expected synchronize data // No need to check synchronizeWindow[0] as otherwise we could loop forever receiving 0 bytes int shift = -1; for (int i = 1; i < synchronizeWindow.Length && shift == -1; i++) { if (synchronizeWindow[i] == syncData[0]) { shift = i; } } // The current data is all useless, so read an entire new window of data if (shift > 0) { filled = synchronizeWindow.Length - shift; // Shuffle everything left by 'shift' (the first good byte) and fill the rest of the window Buffer.BlockCopy(synchronizeWindow, shift, synchronizeWindow, 0, synchronizeWindow.Length - shift); } else { // The start point we thought we had is actually garbage, so throw away all the data we have filled = 0; } } } throw new EncryptionException("Couldn't synchronise 1"); }
static async Task <byte[]> CheckEncryptionAsync(PeerId id, int bytesToReceive, InfoHash[] sKeys, CancellationToken token) { IConnection connection = id.Connection; var allowedEncryption = (id.Engine?.Settings.AllowedEncryption ?? EncryptionTypes.All) & id.Peer.Encryption; var supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header); var supportsRC4Full = allowedEncryption.HasFlag(EncryptionTypes.RC4Full); var supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText); // If the connection is incoming, receive the handshake before // trying to decide what encryption to use if (connection.IsIncoming) { var buffer = new byte[bytesToReceive]; await NetworkIO.ReceiveAsync(connection, buffer, 0, bytesToReceive, null, null, null).ConfigureAwait(false); HandshakeMessage message = new HandshakeMessage(); message.Decode(buffer, 0, buffer.Length); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { if (supportsPlainText) { id.Encryptor = id.Decryptor = PlainTextEncryption.Instance; return(buffer); } } else if (supportsRC4Header || supportsRC4Full) { // The data we just received was part of an encrypted handshake and was *not* the BitTorrent handshake var encSocket = new PeerBEncryption(sKeys, EncryptionTypes.All); await encSocket.HandshakeAsync(connection, buffer, 0, buffer.Length); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } id.Decryptor = encSocket.Decryptor; id.Encryptor = encSocket.Encryptor; return(encSocket.InitialData?.Length > 0 ? encSocket.InitialData : null); } } else { if ((id.Engine.Settings.PreferEncryption || !supportsPlainText) && (supportsRC4Header || supportsRC4Full)) { var encSocket = new PeerAEncryption(id.TorrentManager.InfoHash, allowedEncryption); await encSocket.HandshakeAsync(connection); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } id.Decryptor = encSocket.Decryptor; id.Encryptor = encSocket.Encryptor; return(encSocket.InitialData?.Length > 0 ? encSocket.InitialData : null); } else if (supportsPlainText) { id.Encryptor = id.Decryptor = PlainTextEncryption.Instance; return(null); } } throw new EncryptionException("Invalid handshake received and no decryption works"); }
static async ReusableTask <EncryptorResult> DoCheckIncomingConnectionAsync(IConnection connection, IList <EncryptionType> preferredEncryption, InfoHash[] sKeys) { bool supportsRC4Header = preferredEncryption.Contains(EncryptionType.RC4Header); bool supportsRC4Full = preferredEncryption.Contains(EncryptionType.RC4Full); bool supportsPlainText = preferredEncryption.Contains(EncryptionType.PlainText); // If the connection is incoming, receive the handshake before // trying to decide what encryption to use var message = new HandshakeMessage(); using (NetworkIO.BufferPool.Rent(HandshakeMessage.HandshakeLength, out ByteBuffer buffer)) { await NetworkIO.ReceiveAsync(connection, buffer, 0, HandshakeMessage.HandshakeLength, null, null, null).ConfigureAwait(false); message.Decode(buffer.Data, 0, HandshakeMessage.HandshakeLength); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { if (supportsPlainText) { return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, message)); } } else if (supportsRC4Header || supportsRC4Full) { // The data we just received was part of an encrypted handshake and was *not* the BitTorrent handshake // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor await MainLoop.SwitchToThreadpool(); var encSocket = new PeerBEncryption(sKeys, preferredEncryption); await encSocket.HandshakeAsync(connection, buffer.Data, 0, HandshakeMessage.HandshakeLength).ConfigureAwait(false); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } // As the connection was encrypted, the data we got from the initial Receive call will have // been consumed during the crypto handshake process. Now that the encrypted handshake has // been established, we should ensure we read the data again. byte[] data = encSocket.InitialData?.Length > 0 ? encSocket.InitialData : null; if (data == null) { await NetworkIO.ReceiveAsync(connection, buffer, 0, HandshakeMessage.HandshakeLength, null, null, null).ConfigureAwait(false); data = buffer.Data; encSocket.Decryptor.Decrypt(data, 0, HandshakeMessage.HandshakeLength); } message.Decode(data, 0, HandshakeMessage.HandshakeLength); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, message)); } } } connection.Dispose(); throw new EncryptionException("Invalid handshake received and no decryption works"); }