none
HOWTO Use SslStream and NegotiateStream in same TcpListener. RRS feed

  • Question

  • Please help,

    I am trying to add NTLM to my web server app, which is based off TcpListener, to have both SSL and NTLM.
    I've succeded with adding SSL, but now I need NTLM, and sadly, there is no documentation on how to have both.
    Furthermore, I've not found a clean sample on even how to apply the NTLM, even if you do not have SSL.

    To keep the issue in focus, below I have extracted code from my server, which works greate with and without SSL. But it is missing the NTLM, and I do not know what to do to add it in.

    Please help me get the answer to the code at the 2 spots commented with: // What do we do here???


    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Reflection;
    using System.Security.Authentication;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Principal;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    
    namespace MyNameSpace
    {
      /*----------------------------------------------------------------------------------------*\
      |                                                                                          |
      \*----------------------------------------------------------------------------------------*/
    
      static class Program
      {
        /*----------------------------------------------------------------------------------------*\
        |                                                                                          |
        \*----------------------------------------------------------------------------------------*/
    
        static void Main()
        {
          string csHostName = Dns.GetHostName().Trim();
          byte[] zbCertificate;
          zbCertificate = File.ReadAllBytes("Path to some SSL Certificate");
          SslCertificate srcCertificate = new SslCertificate
          {
            Type = SslCertificate.CertificateTypes.X509Certificate2,
            Data = new X509Certificate2
            (
              zbCertificate, "Encryption String to work with SSL Certificate"
            ),
            ClientCertificateRequired = false,
            CheckCertificateRevocation = false
          };
    
          HttpServer HttpServer_MyServer = new HttpServer
          {
            HostName = HostName,
            Port = 50000,
            Certificate = srcCertificate,
            ReuseAddress = bReuseAddress
          };
          HttpServer_MyServer.RunListener();
        }
      }
    
      /*----------------------------------------------------------------------------------------*\
      |                                                                                          |
      \*----------------------------------------------------------------------------------------*/
    
      public class HttpServer
      {
        public string HostName;
        public int Port = 50000;
        public TcpListener TcpListener_This;
        public bool ReuseAddress { get; set; } = false;
        public SslCertificate Certificate { get; set; } = null;
    
        /*----------------------------------------------------------------------------------------*\
        |                                                                                          |
        \*----------------------------------------------------------------------------------------*/
    
        public void RunListener()
        {
          IPAddress ip = Dns.GetHostAddresses(HostName).Where
          (
            address => address.AddressFamily == AddressFamily.InterNetwork
          ).First();
          TcpListener_This = new TcpListener(ip, (int) Port);
          bool bIsActive = true;
    
          while (bIsActive)
          {
            TcpClient TcpClient_This = TcpListener_This.AcceptTcpClient();
            HttpProcessor HttpProcessor_This = new HttpProcessor(TcpClient_This, this);
            Thread Thread_That = new Thread(new ThreadStart(HttpProcessor_This.Process));
    
            if (!ThreadApartmentState.IsNull())
            {
              Thread_That.SetApartmentState((ApartmentState) ThreadApartmentState);
            }
    
            Thread_That.Start();
            Thread.Sleep(1);
          }
        }
      }
    
      /*----------------------------------------------------------------------------------------*\
      |                                                                                          |
      \*----------------------------------------------------------------------------------------*/
    
      public class HttpProcessor
      {
        public TcpClient Client { get; set; } = null;
        public HttpServer Server { get; set; }
        public Stream Stream_Input;
        public SslStream SslStream_Input = null;
        public BinaryWriter BinaryWriter_Output;
    
        public HttpProcessor(TcpClient TcpClient_Param, HttpServer HttpServer_Param)
        {
          Client = TcpClient_Param;
          Server = HttpServer_Param;
        }
    
        /*------------------------------------------------------------------------------------------*\
        |                                                                                            |
        \*------------------------------------------------------------------------------------------*/
    
        public override void Process()
        {
          // Note, ultimately NTLM and its negotiation/handhake goes to functionality in SECUR32.dll.
          // The SECUR32.dll function AcceptSecurityContext function in particular handles the
          // process. This process is possibly wrapped by System.Net.Security.NegotiateStream, but
          // below
          //
          // What do we do here, to get an System.Security.Principal.WindowsIdentity object, that
          // we can use to impersonate the NTLM authenitcated user.
    
          WindowsIdentity wiAuthenticatedUser = null;
    
          if (Server.Certificate != null)
          {
            SslStream_Input = new SslStream(Client.GetStream());
            SslStream_Input.AuthenticateAsServer
            (
              Certificate.X509Certificate,
              Certificate.ClientCertificateRequired,
              Certificate.SslProtocols,
              Certificate.CheckCertificateRevocation
            );
            Stream_Input = new BufferedStream(SslStream_Input);
            BinaryWriter_Output = new BinaryWriter(SslStream_Input);
    
            // MISSING NTLM AUTHENTICATION WITH SSL....
            //
            // What do we do here???
            // How do we get wiAuthenticatedUser populated...
            //
            // This point would do the NTLM handshake like above, but the trick here is that the SSL
            // stream has had to kick in 1st, to deal with the SSL encryption.
            //
            // Note, this sample code works, up to this point for SSL.
            //
            // Something extra needs to happen here, to ensure the SSL Stream is worked-with, qand not broken.
          }
          else
          {
            Stream_Input = new BufferedStream(Client.GetStream());
            BinaryWriter_Output = new BinaryWriter(new BufferedStream(Client.GetStream()));
    
            // MISSING NTLM AUTHENTICATION WITH NO SSL....
            //
            // What do we do here???
            // How do we get wiAuthenticatedUser populated...
            //
            // Note, NTLM has a handshake process, which will require returning 401, and waiting
            // for the client to follow the negotiation handshake, with possibly, challenges from the
            // server to the client.
          }
    
          // At this point, we have a Input and Output stream, we can use to get all HTTP parameters
          // and any HTTP posted data, process as desired, and return HTTP response as desired.
          //
          // For this demo, we'll just return HTTP 200.
    
          BinaryWriter_Output.Write("HTTP/1.0 200 OK\n");
          BinaryWriter_Output.Write("Content-Type: " + content_type + "\n");
          BinaryWriter_Output.Write("Connection: close\n");
          BinaryWriter_Output.Write("\n");
        }
      }
    
      /*----------------------------------------------------------------------------------------*\
      |                                                                                          |
      \*----------------------------------------------------------------------------------------*/
    
      public class SslCertificate
      {
        /*----------------------------------------------------------------------------------------*\
        |                                                                                          |
        \*----------------------------------------------------------------------------------------*/
    
        public enum CertificateTypes
        {
          [CSC.Names(new string[] { "X509Certificate" })]
          X509Certificate,
    
          [CSC.Names(new string[] { "X509Certificate2" })]
          X509Certificate2
        }
    
        public CertificateTypes Type { get; set; }
        public object Data { get; set; }
        public bool ClientCertificateRequired { get; set; } = true;
        public SslProtocols SslProtocols { get; set; } =  SslProtocols.Default;
        public bool CheckCertificateRevocation { get; set; } = true;
        public X509Certificate X509Certificate
        {
          get
          {
            if (Data.IsNull()) return null;
            X509Certificate xcerRET = null;
            ML.IGNORE(() => xcerRET = (X509Certificate) Data);
            return xcerRET;
          }
        }
      }
    }
    

    Monday, July 30, 2018 8:28 PM

All replies

  • Hi J-S-B,

    Thank you for posting here.

    For your question, could I know why you want to use both of them? Based on my search, I do not find the example with both of them. NegotiateStream is an implementation of AuthenticatedStream (as SslStream is). It is similar. 

    If you want make both of them, try to make two examples and then make a choice to choose which one you want to use.

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, August 1, 2018 8:46 AM
    Moderator
  • Hello Wendy, and thanks for the reply.

    Sorry to have such a delayed reply.

    To answer your question, it does not matter to me whether we have to used both or not, just that I need SSL and WIA/NTLM/Active-Directory security, and to my knowledge, AuthenitcatedStream does not do SSL, and likewise, SslStream, does not do the authentication to WIA/NTLM/Active-Directory.

    I know the above as I've implemented the SslStream, and it does not begin the process for authentication, which does the authentication handshake with the client, which evidently is a back and forth from server to client on the same stream, until the authentication is processed.

    Tuesday, September 11, 2018 4:51 PM