/// <summary>
        /// IsolateSOCKSAuth must be on (on by default)
        /// https://www.torproject.org/docs/tor-manual.html.en
        /// https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n35
        /// </summary>
        /// <param name="identity">Isolates streams by identity. If identity is empty string, it won't isolate stream.</param>
        internal async Task HandshakeAsync(string identity)
        {
            if (TorSocks5EndPoint is null)
            {
                return;
            }

            Guard.NotNull(nameof(identity), identity);

            MethodsField methods = string.IsNullOrWhiteSpace(identity)
                                ? new MethodsField(MethodField.NoAuthenticationRequired)
                                : new MethodsField(MethodField.UsernamePassword);

            var sendBuffer = new VersionMethodRequest(methods).ToBytes();

            var receiveBuffer = await SendAsync(sendBuffer, 2).ConfigureAwait(false);

            var methodSelection = new MethodSelectionResponse();

            methodSelection.FromBytes(receiveBuffer);
            if (methodSelection.Ver != VerField.Socks5)
            {
                throw new NotSupportedException($"SOCKS{methodSelection.Ver.Value} not supported. Only SOCKS5 is supported.");
            }
            if (methodSelection.Method == MethodField.NoAcceptableMethods)
            {
                // https://www.ietf.org/rfc/rfc1928.txt
                // If the selected METHOD is X'FF', none of the methods listed by the
                // client are acceptable, and the client MUST close the connection.
                DisposeTcpClient();
                throw new NotSupportedException("Tor's SOCKS5 proxy does not support any of the client's authentication methods.");
            }
            if (methodSelection.Method == MethodField.UsernamePassword)
            {
                // https://tools.ietf.org/html/rfc1929#section-2
                // Once the SOCKS V5 server has started, and the client has selected the
                // Username / Password Authentication protocol, the Username / Password
                // subnegotiation begins. This begins with the client producing a
                // Username / Password request:
                var username = identity;
                var password = identity;
                var uName    = new UNameField(username);
                var passwd   = new PasswdField(password);
                var usernamePasswordRequest = new UsernamePasswordRequest(uName, passwd);
                sendBuffer = usernamePasswordRequest.ToBytes();

                Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
                receiveBuffer = await SendAsync(sendBuffer, 2).ConfigureAwait(false);

                var userNamePasswordResponse = new UsernamePasswordResponse();
                userNamePasswordResponse.FromBytes(receiveBuffer);
                if (userNamePasswordResponse.Ver != usernamePasswordRequest.Ver)
                {
                    throw new NotSupportedException($"Authentication version {userNamePasswordResponse.Ver.Value} not supported. Only version {usernamePasswordRequest.Ver} is supported.");
                }

                if (!userNamePasswordResponse.Status.IsSuccess())                 // Tor authentication is different, this will never happen;
                {
                    // https://tools.ietf.org/html/rfc1929#section-2
                    // A STATUS field of X'00' indicates success. If the server returns a
                    // `failure' (STATUS value other than X'00') status, it MUST close the
                    // connection.
                    DisposeTcpClient();
                    throw new InvalidOperationException("Wrong username and/or password.");
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Do the authentication part of Tor's SOCKS5 protocol.
        /// </summary>
        /// <param name="isolateStream">Whether random username/password should be used for authentication and thus effectively create a new Tor circuit.</param>
        /// <remarks>Tor process must be started with enabled <c>IsolateSOCKSAuth</c> option. It's ON by default.</remarks>
        /// <seealso href="https://www.torproject.org/docs/tor-manual.html.en"/>
        /// <seealso href="https://linux.die.net/man/1/tor">For <c>IsolateSOCKSAuth</c> option explanation.</seealso>
        /// <seealso href="https://gitweb.torproject.org/torspec.git/tree/socks-extensions.txt#n35"/>
        public async Task HandshakeAsync(bool isolateStream = false, CancellationToken cancellationToken = default)
        {
            Logger.LogDebug($"> {nameof(isolateStream)}={isolateStream}");

            // https://github.com/torproject/torspec/blob/master/socks-extensions.txt
            // The "NO AUTHENTICATION REQUIRED" (SOCKS5) authentication method [00] is
            // supported; and as of Tor 0.2.3.2 - alpha, the "USERNAME/PASSWORD"(SOCKS5)
            // authentication method[02] is supported too, and used as a method to
            // implement stream isolation.As an extension to support some broken clients,
            // we allow clients to pass "USERNAME/PASSWORD" authentication message to us
            // even if no authentication was selected.Furthermore, we allow
            // username / password fields of this message to be empty. This technically
            // violates RFC1929[4], but ensures interoperability with somewhat broken
            // SOCKS5 client implementations.
            var methods = new MethodsField(isolateStream ? MethodField.UsernamePassword : MethodField.NoAuthenticationRequired);

            byte[] sendBuffer    = new VersionMethodRequest(methods).ToBytes();
            byte[] receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize : 2, cancellationToken).ConfigureAwait(false);

            var methodSelection = new MethodSelectionResponse(receiveBuffer);

            if (methodSelection.Ver != VerField.Socks5)
            {
                throw new NotSupportedException($"SOCKS{methodSelection.Ver.Value} not supported. Only SOCKS5 is supported.");
            }
            else if (methodSelection.Method == MethodField.NoAcceptableMethods)
            {
                // https://www.ietf.org/rfc/rfc1928.txt
                // If the selected METHOD is X'FF', none of the methods listed by the
                // client are acceptable, and the client MUST close the connection.
                DisposeTcpClient();
                throw new NotSupportedException("Tor's SOCKS5 proxy does not support any of the client's authentication methods.");
            }
            else if (methodSelection.Method == MethodField.UsernamePassword)
            {
                // https://tools.ietf.org/html/rfc1929#section-2
                // Once the SOCKS V5 server has started, and the client has selected the
                // Username / Password Authentication protocol, the Username / Password
                // sub-negotiation begins. This begins with the client producing a
                // Username / Password request:
                var identity = RandomString.CapitalAlphaNumeric(21);
                var uName    = new UNameField(uName: identity);
                var passwd   = new PasswdField(passwd: identity);
                var usernamePasswordRequest = new UsernamePasswordRequest(uName, passwd);
                sendBuffer = usernamePasswordRequest.ToBytes();

                Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
                receiveBuffer = await SendAsync(sendBuffer, receiveBufferSize : 2, cancellationToken).ConfigureAwait(false);

                var userNamePasswordResponse = new UsernamePasswordResponse(receiveBuffer);

                if (userNamePasswordResponse.Ver != usernamePasswordRequest.Ver)
                {
                    throw new NotSupportedException($"Authentication version {userNamePasswordResponse.Ver.Value} not supported. Only version {usernamePasswordRequest.Ver} is supported.");
                }

                if (!userNamePasswordResponse.Status.IsSuccess())                 // Tor authentication is different, this will never happen;
                {
                    // https://tools.ietf.org/html/rfc1929#section-2
                    // A STATUS field of X'00' indicates success. If the server returns a
                    // `failure' (STATUS value other than X'00') status, it MUST close the
                    // connection.
                    DisposeTcpClient();
                    throw new InvalidOperationException("Wrong username and/or password.");
                }
            }

            Logger.LogDebug("<");
        }