Ejemplo n.º 1
0
        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");
        }
Ejemplo n.º 2
0
        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;
            }
        }
Ejemplo n.º 3
0
        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;
            }
        }
Ejemplo n.º 4
0
        /// <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");
        }
Ejemplo n.º 5
0
        /// <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");
        }
Ejemplo n.º 6
0
        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");
        }
Ejemplo n.º 7
0
        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");
        }