NetCoreServer icon indicating copy to clipboard operation
NetCoreServer copied to clipboard

Feature-Request: TCP-Server with UPGRADABLE connection

Open ststeiger opened this issue 2 years ago • 0 comments

I wanted to ask if it's somehow possible to (add the ability to) upgrade a TCP connection to a TLS connection (on a TCP-Server). (basically, at any point in time, not necessarely at the start of the connection)

There is this feature in SMTP/IMAP/POP3.

When connecting to port 465/whatever the client will/can upgrade its connection to SSL (upon request).

The thing is, email can connect unsecured via TCP, and then upgrade the connection to TLS on request. Not just on SMTP, but on IMAP/POP3 as well . I'm trying to implement a pop3/imap server.

Example of what I mean: https://github.com/ststeiger/SmtpServer/blob/master/Src/SmtpServer/IO/SecurableDuplexPipe.cs#L42

Bellow the relevant part of what I mean. Here, it uses SNI (host-header) based TLS-certificate-selection. Thus the use of StreamExtended in NetStandard2, because there is no SNI-callback-based certificate selection in NetStandard2. ) That is to say, please also consider the use of multiple domain names on the same IP.
(and each domain has a different SSL-Certificate).

        /// <summary>
        /// Upgrade to a secure pipeline.
        /// </summary>
        /// <param name="certificate">The X509Certificate used to authenticate the server.</param>
        /// <param name="protocols">The value that represents the protocol used for authentication.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task that asynchronously performs the operation.</returns>
        public async Task UpgradeAsync(ServerCertificateSelectionCallback certificate, SslProtocols protocols, CancellationToken cancellationToken = default)
        {
            // https://docs.microsoft.com/en-us/dotnet/standard/frameworks
#if NET
            SslStream stream = new SslStream(_stream, true);

            System.Net.Security.SslServerAuthenticationOptions sslOptions =
                new System.Net.Security.SslServerAuthenticationOptions
                {
                    // ServerCertificate = certificate,
                    ServerCertificateSelectionCallback = new System.Net.Security.ServerCertificateSelectionCallback(certificate),
                    CertificateRevocationCheckMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.Offline,
                    EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12
                }
            ;
            
            await stream.AuthenticateAsServerAsync(sslOptions);
#else

            StreamExtended.DefaultBufferPool bufferPool = new StreamExtended.DefaultBufferPool();

            StreamExtended.Network.CustomBufferedStream yourClientStream =
                new StreamExtended.Network.CustomBufferedStream(_stream, bufferPool, 4096);

            StreamExtended.ClientHelloInfo clientSslHelloInfo =
                await StreamExtended.SslTools.PeekClientHello(yourClientStream, bufferPool);


            string sniHostName = null;

            //will be null if no client hello was received (not a SSL connection)
            if (clientSslHelloInfo != null)
            {
                sniHostName = clientSslHelloInfo.Extensions?.FirstOrDefault(x => x.Key == "server_name").Value?.Data;
            }

            if (string.IsNullOrWhiteSpace(sniHostName))
                sniHostName = _clientAddress.MapToIPv4().ToString();

            System.Net.Security.SslStream stream = new System.Net.Security.SslStream(yourClientStream, true);
            X509Certificate cert = certificate(stream, sniHostName);
            await stream.AuthenticateAsServerAsync(cert, false, protocols, true).ConfigureAwait(false);
#endif


            _stream = stream;
            
            Input = PipeReader.Create(_stream);
            Output = PipeWriter.Create(_stream);
        }

ststeiger avatar Jun 23 '22 09:06 ststeiger