Ejemplo n.º 1
0
        public override void FromBytes(byte[] bytes)
        {
            Guard.NotNullOrEmpty(nameof(bytes), bytes);
            Guard.InRangeAndNotNull($"{nameof(bytes)}.{nameof(bytes.Length)}", bytes.Length, 6, 513);

            Ver = new AuthVerField(bytes[0]);

            ULen = new ULenField();
            ULen.FromByte(bytes[1]);

            UName = new UNameField();
            UName.FromBytes(bytes[2..(2 + ULen.Value)]);
Ejemplo n.º 2
0
        public UsernamePasswordRequest(UNameField uName, PasswdField passwd)
        {
            Ver    = AuthVerField.Version1;
            UName  = Guard.NotNull(nameof(uName), uName);
            Passwd = Guard.NotNull(nameof(passwd), passwd);

            var pLen = new PLenField();
            var uLen = new ULenField();

            pLen.FromPasswdField(passwd);
            uLen.FromUNameField(uName);
            PLen = pLen;
            ULen = uLen;
        }
        public override void FromBytes(byte[] bytes)
        {
            Guard.NotNullOrEmpty(nameof(bytes), bytes);
            Guard.InRangeAndNotNull($"{nameof(bytes)}.{nameof(bytes.Length)}", bytes.Length, 6, 513);

            Ver = new AuthVerField();
            Ver.FromByte(bytes[0]);

            ULen = new ULenField();
            ULen.FromByte(bytes[1]);

            UName = new UNameField();
            UName.FromBytes(bytes.Skip(2).Take(ULen.Value).ToArray());

            PLen = new PLenField();
            PLen.FromByte(bytes[1 + ULen.Value]);
            int expectedPlenValue = bytes.Length - 3 + ULen.Value;

            if (PLen.Value != expectedPlenValue)
            {
                throw new FormatException($"{nameof(PLen)}.{nameof(PLen.Value)} must be {nameof(bytes)}.{nameof(bytes.Length)} - 3 + {nameof(ULen)}.{nameof(ULen.Value)} = {expectedPlenValue}. Actual: {PLen.Value}.");
            }
            Passwd.FromBytes(bytes.Skip(3 + ULen.Value).ToArray());
        }
Ejemplo n.º 4
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[] receiveBuffer = await SendRequestAsync(new VersionMethodRequest(methods), 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(password: identity);
                var usernamePasswordRequest = new UsernamePasswordRequest(uName, passwd);

                receiveBuffer = await SendRequestAsync(usernamePasswordRequest, 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("<");
        }
        /// <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.");
                }
            }
        }
Ejemplo n.º 6
0
 public ULenField(UNameField uName)
 {
     ByteValue = (byte)uName.ToBytes().Length;
 }
Ejemplo n.º 7
0
 public void FromUNameField(UNameField uName)
 {
     ByteValue = (byte)uName.ToBytes().Length;
 }