FubarDevelopment / FtpServer

Portable FTP server written in .NET
http://fubardevelopment.github.io/FtpServer/
MIT License
473 stars 161 forks source link

How can I block connections without a certificate? (Forced Explicit) #83

Closed mknet3 closed 4 years ago

mknet3 commented 4 years ago

It is possible to block all connections without certificate before credentials are sent by the client?

Is this functionality supported?

fubar-coder commented 4 years ago

You have two choices:

Implicit TLS is usually supported by the FTP clients, but is still a non-standard feature.

Implicit TLS

You could use implicit TLS. You're basically enforcing a TLS control connection from the beginning. Take a look at the following snippet:

services.Decorate<IFtpServer>(
    (ftpServer, serviceProvider) =>
    {
        if (options.Ftps.Implicit)
        {
            var authTlsOptions = serviceProvider.GetRequiredService<IOptions<AuthTlsOptions>>();
            if (authTlsOptions.Value.ServerCertificate != null)
            {
                // Use an implicit SSL connection (without the AUTH TLS command)
                ftpServer.ConfigureConnection += (s, e) =>
                {
                    TlsEnableServerCommandHandler.EnableTlsAsync(
                        e.Connection,
                        authTlsOptions.Value.ServerCertificate,
                        serviceProvider.GetService<ILogger<TlsEnableServerCommandHandler>>(),
                        CancellationToken.None).Wait();
                };
            }
        }

        return ftpServer;
    });

The snippet is called during the dependency injection configuration and does the following things:

This enforces the usage of an encrypted FTP control connection and therefore automatically protects the login.

Warning

Be aware that the "implicit TLS" solution uses Task.Wait, which will most likely create a deadlock when the code is used in a different environment than ASP.NET Core.

Custom FtpLoginStateMachine implementation

EDIT: Some clarification

mknet3 commented 4 years ago

Sorry, but im trying to add implicit connection and this solution is not working to me:

image

  HResult=0x80131500
  Message=One or more errors occurred. (Status must be Running, Stopped, or Paused, but was ReadyToRun.)
  Source=System.Private.CoreLib
  StackTrace:
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Ftps.FtpsConfiguration.<>c__DisplayClass2_1.<AddFtpsHostedService>b__5(Object s, ConnectionEventArgs e) in Ftps\FtpsConfiguration.cs:line 90
   at FubarDev.FtpServer.FtpServer.<AddClientAsync>d__39.MoveNext()

Inner Exception 1:
InvalidOperationException: Status must be Running, Stopped, or Paused, but was ReadyToRun.

should work this solution in 3.0.1 version?

fubar-coder commented 4 years ago

Yes, it should work. I'll take a look at it tomorrow.

fubar-coder commented 4 years ago

3.0.2 is released and should fix the implicit TLS problem.

mknet3 commented 4 years ago

Thanks! It is working, but not with code above... work with next code found in samples:

           services
                .AddSingleton(new ImplicitFtpsControlConnectionStreamAdapterOptions(certificate))
                .AddSingleton<IFtpControlStreamAdapter, ImplicitFtpsControlConnectionStreamAdapter>();

            // Ensure that PROT and PBSZ commands are working.
            services.Decorate<IFtpServer>(
                (ftpServer, _) =>
                {
                    ftpServer.ConfigureConnection += (s, e) =>
                    {
                        var serviceProvider = e.Connection.ConnectionServices;
                        var stateMachine = serviceProvider.GetRequiredService<IFtpLoginStateMachine>();
                        var authTlsMechanism = serviceProvider.GetRequiredService<IEnumerable<IAuthenticationMechanism>>()
                            .Single(x => x.CanHandle("TLS"));
                        stateMachine.Activate(authTlsMechanism);
                    };

                    return ftpServer;
                });

            return services;
        }

        private class ImplicitFtpsControlConnectionStreamAdapterOptions
        {
            public ImplicitFtpsControlConnectionStreamAdapterOptions(X509Certificate2 certificate)
            {
                Certificate = certificate;
            }

            public X509Certificate2 Certificate { get; }
        }

        private class ImplicitFtpsControlConnectionStreamAdapter : IFtpControlStreamAdapter
        {
            private readonly ImplicitFtpsControlConnectionStreamAdapterOptions _options;
            private readonly ISslStreamWrapperFactory _sslStreamWrapperFactory;

            public ImplicitFtpsControlConnectionStreamAdapter(
                ImplicitFtpsControlConnectionStreamAdapterOptions options,
                ISslStreamWrapperFactory sslStreamWrapperFactory)
            {
                _options = options;
                _sslStreamWrapperFactory = sslStreamWrapperFactory;
            }

            /// <inheritdoc />
            public Task<Stream> WrapAsync(Stream stream, CancellationToken cancellationToken)
            {
                return _sslStreamWrapperFactory.WrapStreamAsync(stream, false, _options.Certificate, cancellationToken);
            }
        }
    }
fubar-coder commented 4 years ago

That's strange, but it's good that you solved the problem.

EDIT: I somehow used old code as an example. I don't know why I mixed this up, sorry 😞