如何让服务器接受SSL和纯文本(不安全)连接? [英] How to allow a Server to accept both SSL and plain text (insecure) connections?

查看:426
本文介绍了如何让服务器接受SSL和纯文本(不安全)连接?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个可以同时接受安全的SSL和不安全的纯文本连接(向后兼容)的服务器。我的代码是除来自一个不安全的客户端接收的第一传输的数据几乎工作失去在服务器的第一个5个字节(字符)。更多specificially如果我发送一个不安全的连接30个字节,当服务器获取到 OnClientDataReceived()函数,行 INT IRX = nwStream。 EndRead(ASYN); ,那么 IRX = 25 。从客户端传输的任何后续消息包含所有发送的字节/字符如预期。我怀疑是连接的初始假设是一个 SSLStream 可剥头5个字节,然后当它失败了,这5个字节已经从缓冲区中提取,并不再可用。难道的另一种方法任何机构知道我可以采取编写代码,以使服务器可以自动地动态切换?

I am trying to create a server that can accept both secure SSL and insecure plain text connection (for backwards compatibility). My code is almost working except the first transmitted data received from an insecure client loses the first 5 bytes (chars) at the server. More specificially if I transmit 30 bytes on an insecure connection, when the server gets to the OnClientDataReceived() function, the line "int iRx = nwStream.EndRead(asyn);", then iRx = 25. Any subsequent messages transmitted from the client contain all sent bytes/chars as expected. I suspect that the initial assumption of the connection being an SSLStream may be stripping the first 5 bytes and then when it fails, those 5 bytes have already been extracted from the buffer and are no longer available. Does any body know of another approach I could take to write the code so that the server automatically can switch on the fly?

我试图避免做以下内容:

I am trying to avoid doing the following:


  • 要求客户连接使用纯文本的NetworkStream ,然后要求升级到一个SSL流

  • 设置两个 TcpListeners 在两个不同的端口(一个安全,一个用于不安全)

  • Require that a client connect using a plain text NetworkStream and then request to upgrade to an SSL stream
  • Setting up two TcpListeners on two different ports (one for secure, one for insecure)
/// Each client that connects gets an instance of the ConnectedClient class.
Class Pseudo_ConnectedClient
{
    //Properties
    byte[] Buffer; //Holds temporary buffer of read bytes from BeginRead()
    TcpClient TCPClient; //Reference to the connected client
    Socket ClientSocket; //The outer Socket Reference of the connected client
    StringBuilder CurrentMessage; //concatenated chunks of data in buffer until we have a complete message (ends with <ETX>
    Stream Stream; //SSLStream or NetworkStream depending on client
    ArrayList MessageQueue; //Array of complete messages received from client that need to be processed
}

/// When a new client connects (OnClientConnection callback is executed), the server creates the ConnectedClient object and stores its 
/// reference in a local dictionary, then configures the callbacks for incoming data (WaitForClientData)
void OnClientConnection(IAsyncResult result)
{
    TcpListener listener = result.AsyncState as TcpListener;
    TcpClient clnt = null;

    try
    {
        if (!IsRunning) //then stop was called, so don't call EndAcceptTcpClient because it will throw and ObjectDisposedException
            return;

        //Start accepting the next connection...
        listener.BeginAcceptTcpClient(this.onClientConnection, listener);

        //Get reference to client and set flag to indicate connection accepted.
        clnt = listener.EndAcceptTcpClient(result);

        //Add the reference to our ArrayList of Connected Clients
        ConnectedClient conClnt = new ConnectedClient(clnt);
        _clientList.Add(conClnt);

        //Configure client to listen for incoming data
        WaitForClientData(conClnt);
    }
    catch (Exception ex)
    {
        Trace.WriteLine("Server:OnClientConnection: Exception - " + ex.ToString());
    }
}

/// WaitForClientData registers the AsyncCallback to handle incoming data from a client (OnClientDataReceieved).  
/// If a certificate has been provided, then it listens for clients to connect on an SSLStream and configures the 
/// BeginAuthenticateAsServer callback.  If no certificate is provided, then it only sets up a NetworkStream 
/// and prepares for the BeginRead callback.
private void WaitForClientData(ConnectedClient clnt)
{
    if (!IsRunning) return; //Then stop was called, so don't do anything

    SslStream sslStream = null;

    try
    {
        if (_pfnClientDataCallBack == null) //then define the call back function to invoke when data is received from a connected client
            _pfnClientDataCallBack = new AsyncCallback(OnClientDataReceived);

        NetworkStream nwStream = clnt.TCPClient.GetStream();

        //Check if we can establish a secure connection
        if (this.SSLCertificate != null) //Then we have the ability to make an SSL connection (SSLCertificate is a X509Certificate2 object)
        {
            if (this.certValidationCallback != null)
                sslStream = new SslStream(nwStream, true, this.certValidationCallback);
            else
                sslStream = new SslStream(nwStream, true);

            clnt.Stream = sslStream;

            //Start Listening for incoming (secure) data
            sslStream.BeginAuthenticateAsServer(this.SSLCertificate, false, SslProtocols.Default, false, onAuthenticateAsServer, clnt);
        }
        else //No certificate available to make a secure connection, so use insecure (unless not allowed)
        {
            if (this.RequireSecureConnection == false) //Then we can try to read from the insecure stream
            {
                clnt.Stream = nwStream;

                //Start Listening for incoming (unsecure) data
                nwStream.BeginRead(clnt.Buffer, 0, clnt.Buffer.Length, _pfnClientDataCallBack, clnt);
            }
            else //we can't do anything - report config problem
            {
                throw new InvalidOperationException("A PFX certificate is not loaded and the server is configured to require a secure connection");
            }
        }
    }
    catch (Exception ex)
    {
        DisconnectClient(clnt);
    }
}

/// OnAuthenticateAsServer first checks if the stream is authenticated, if it isn't it gets the TCPClient's reference 
/// to the outer NetworkStream (client.TCPClient.GetStream()) - the insecure stream and calls the BeginRead on that.  
/// If the stream is authenticated, then it keeps the reference to the SSLStream and calls BeginRead on it.
private void OnAuthenticateAsServer(IAsyncResult result)
{
    ConnectedClient clnt = null;
    SslStream sslStream = null;

    if (this.IsRunning == false) return;

    try
    {
        clnt = result.AsyncState as ConnectedClient;
        sslStream = clnt.Stream as SslStream;

        if (sslStream.IsAuthenticated)
            sslStream.EndAuthenticateAsServer(result);
        else //Try and switch to an insecure connections
        {
            if (this.RequireSecureConnection == false) //Then we are allowed to accept insecure connections
            {
                if (clnt.TCPClient.Connected)
                    clnt.Stream = clnt.TCPClient.GetStream();
            }
            else //Insecure connections are not allowed, close the connection
            {
                DisconnectClient(clnt);
            }
        }
    }
    catch (Exception ex)
    {
        DisconnectClient(clnt);
    }

    if( clnt.Stream != null) //Then we have a stream to read, start Async read
        clnt.Stream.BeginRead(clnt.Buffer, 0, clnt.Buffer.Length, _pfnClientDataCallBack, clnt);
}

/// OnClientDataReceived callback is triggered by the BeginRead async when data is available from a client.  
/// It determines if the stream (as assigned by OnAuthenticateAsServer) is an SSLStream or a NetworkStream 
/// and then reads the data out of the stream accordingly.  The logic to parse and process the message has 
/// been removed because it isn't relevant to the question.
private void OnClientDataReceived(IAsyncResult asyn)
{
    try
    {
        ConnectedClient connectClnt = asyn.AsyncState as ConnectedClient;

        if (!connectClnt.TCPClient.Connected) //Then the client is no longer connected >> clean up
        {
            DisconnectClient(connectClnt);
            return;
        }

        Stream nwStream = null;
        if( connectClnt.Stream is SslStream) //Then this client is connected via a secure stream
            nwStream = connectClnt.Stream as SslStream;
        else //this is a plain text stream
            nwStream = connectClnt.Stream as NetworkStream;

        // Complete the BeginReceive() asynchronous call by EndReceive() method which
        // will return the number of characters written to the stream by the client
        int iRx = nwStream.EndRead(asyn); //Returns the numbers of bytes in the read buffer
        char[] chars = new char[iRx];   

        // Extract the characters as a buffer and create a String
        Decoder d = ASCIIEncoding.UTF8.GetDecoder();
        d.GetChars(connectClnt.Buffer, 0, iRx, chars, 0);

        //string data = ASCIIEncoding.ASCII.GetString(buff, 0, buff.Length);
        string data = new string(chars);

        if (iRx > 0) //Then there was data in the buffer
        {
            //Append the current packet with any additional data that was already received
            connectClnt.CurrentMessage.Append(data);

            //Do work here to check for a complete message
            //Make sure two complete messages didn't get concatenated in one transmission (mobile devices)
            //Add each message to the client's messageQueue
            //Clear the currentMessage
            //Any partial messsage at the end of the buffer needs to be added to the currentMessage

            //Start reading again
            nwStream.BeginRead(connectClnt.Buffer, 0, connectClnt.Buffer.Length, OnClientDataReceived, connectClnt);
        }
        else //zero-length packet received - Disconnecting socket
        {
            DisconnectClient(connectClnt);
        }                
    }
    catch (Exception ex)
    {
        return;
    }
}



什么工作:




  • 如果服务器没有证书,一个的NetworkStream只用,所有字节从客户端收到的所有邮件。

  • 如果服务器确实有一个证书(一种SSLStream是安装程序)和一个安全的连接可以建立。(使用HTTPS网页浏览器://)和收到的所有邮件的完整邮件


    • 如果服务器确实有证书(一个 SSLStream 的设置),不安全的连接从客户端,当从客户端收到的第一条消息做,代码没有正确地检测 SSLStream 未经过身份验证,然后切换到的NetworkStream 的TcpClient >。然而,当 EndRead 被称为上的NetworkStream 的第一个消息,前5个字符(字节)从失踪被送出,但只适用于第一消息的消息。其余所有的消息都是完整的,只要的TcpClient 连接。如果客户端断开连接,然后重新连接时,第一条消息被裁剪,那么以后所有的消息都好了起来。

    • If the server does have a certificate (an SSLStream is setup) and an insecure connection is made from a client, when the first message is received from that client, the code does correctly detect the SSLStream is not authenticated and switches to the NetworkStream of the TCPClient. However, when EndRead is called on that NetworkStream for the first message, the first 5 chars (bytes) are missing from the message that was sent, but only for the first message. All remaining messages are complete as long as the TCPClient is connected. If the client disconnects and then reconnects, the first message is clipped, then all subsequent messages are good again.

    是什么原因造成这些第一5个字节的裁剪,我怎么能避免呢?

    What is causing those first 5 bytes to be clipped, and how can I avoid it?

    我的项目是目前使用.NET v3.5版本...我想留在这个版本不加紧4.0如果我能避免它。

    My project is currently using .NET v3.5... I would like to remain at this version and not step up to 4.0 if I can avoid it.

    后续问题

    达的回答下面确实让我保留这些失踪的5个字节,但是,我宁愿坚持使用的BeginRead EndRead 在我的代码,以避免阻塞的方法。是否有良好的教程,呈现出最佳实践时倍率(ING)这些?更具体地讲,如何与的IAsyncResult 对象的工作。我得到我需要补充的是存储在缓冲区RestartableStream任何内容,然后通过落到内流(基地)得到休息和恢复的托拉尔。但由于的IAsyncResult 对象是一个自定义类,我想不通,我可以返回前与内流的结合RestartableStream的爱好者的通用方法。我是否需要,也能实现的BeginRead(),所以我知道用户想要的内容存储到缓存?我想其他的解决方案,因为被丢弃的字节问题只是与客户端的第一个消息(之后我知道是否使用它作为一个 SSLStream 的NetworkStream ),将通过直接调用RestartableStream(暂时阻断代码)阅读()的方法来处理第一条消息,那么以后所有的邮件使用异步回调就像我现在做阅读的内容。

    Damien's answer below does allow me to retain those missing 5 bytes, however, I would prefer to stick with the BeginRead and EndRead methods in my code to avoid blocking. Are there any good tutorials showing a 'best practices' when override(ing) these? More specifically, how to work with the IAsyncResult object. I get that I would need to add any content that is stored in the RestartableStream buffers, then fall through to the inner stream (base) to get the rest and return the toral. But since the IAsyncResult object is a custom class, I can't figure out the generic way that I can combine the buffs of RestartableStream with those of the inner stream before returning. Do I need to also implement BeginRead() so that I know the buffers the user wants the content stored into? I guess the other solution is, since the dropped bytes problem is only with the first message from the client (after that I know whether to use it as a SSLStream or a NetworkStream), would be to handle that first message by directly calling the Read() method of RestartableStream (temporarily blocking the code), then for all future messages use the Async callbacks to Read the contents as I do now.

    推荐答案

    好吧,我认为最好的你能做的就是把自己的类在 SslStream 的NetworkStream ,你实现一些定制的缓冲。我做对以下的的测试,但我推荐几个你把在生产(也可能是一些更加健壮的错误处理)之前。我的认为的我避免任何4.0或4.5isms:

    Okay, I think the best you can do is place your own class in between SslStream and NetworkStream where you implement some customized buffering. I've done a few tests on the below, but I'd recommend a few more before you put in in production (and probably some more robust error handling). I think I've avoided any 4.0 or 4.5isms:

      public sealed class RestartableReadStream : Stream
      {
        private Stream _inner;
        private List<byte[]> _buffers;
        private bool _buffering;
        private int? _currentBuffer = null;
        private int? _currentBufferPosition = null;
        public RestartableReadStream(Stream inner)
        {
          if (!inner.CanRead) throw new NotSupportedException(); //Don't know what else is being expected of us
          if (inner.CanSeek) throw new NotSupportedException(); //Just use the underlying streams ability to seek, no need for this class
          _inner = inner;
          _buffering = true;
          _buffers = new List<byte[]>();
        }
    
        public void StopBuffering()
        {
          _buffering = false;
          if (!_currentBuffer.HasValue)
          {
            //We aren't currently using the buffers
            _buffers = null;
            _currentBufferPosition = null;
          }
        }
    
        public void Restart()
        {
          if (!_buffering) throw new NotSupportedException();  //Buffering got turned off already
          if (_buffers.Count == 0) return;
          _currentBuffer = 0;
          _currentBufferPosition = 0;
        }
    
        public override int Read(byte[] buffer, int offset, int count)
        {
          if (_currentBuffer.HasValue)
          {
            //Try to satisfy the read request from the current buffer
            byte[] rbuffer = _buffers[_currentBuffer.Value];
            int roffset = _currentBufferPosition.Value;
            if ((rbuffer.Length - roffset) <= count)
            {
              //Just give them what we have in the current buffer (exhausting it)
              count = (rbuffer.Length - roffset);
              for (int i = 0; i < count; i++)
              {
                buffer[offset + i] = rbuffer[roffset + i];
              }
    
              _currentBuffer++;
              if (_currentBuffer.Value == _buffers.Count)
              {
                //We've stopped reading from the buffers
                if (!_buffering)
                  _buffers = null;
                _currentBuffer = null;
                _currentBufferPosition = null;
              }
              return count;
            }
            else
            {
              for (int i = 0; i < count; i++)
              {
                buffer[offset + i] = rbuffer[roffset + i];
              }
              _currentBufferPosition += count;
              return count;
            }
          }
          //If we reach here, we're currently using the inner stream. But may be buffering the results
          int ncount = _inner.Read(buffer, offset, count);
          if (_buffering)
          {
            byte[] rbuffer = new byte[ncount];
            for (int i = 0; i < ncount; i++)
            {
              rbuffer[i] = buffer[offset + i];
            }
            _buffers.Add(rbuffer);
          }
          return ncount;
        }
    
        public override bool CanRead
        {
          get { return true; }
        }
    
        public override bool CanSeek
        {
          get { return false; }
        }
    
        public override bool CanWrite
        {
          get { return false; }
        }
    
        //No more interesting code below here
    
        public override void Flush()
        {
          throw new NotSupportedException();
        }
    
        public override long Length
        {
          get { throw new NotSupportedException(); }
        }
    
        public override long Position
        {
          get
          {
            throw new NotSupportedException();
          }
          set
          {
            throw new NotSupportedException();
          }
        }
    
        public override long Seek(long offset, SeekOrigin origin)
        {
          throw new NotSupportedException();
        }
    
        public override void SetLength(long value)
        {
          throw new NotSupportedException();
        }
    
        public override void Write(byte[] buffer, int offset, int count)
        {
          throw new NotSupportedException();
        }
      }
    



    用法:

    Usage:

    构造一个 RestartableReadStream 在你的的NetworkStream 。传递实例 SslStream 。如果您认为SSL是错误的方式做事,叫重新启动(),然后无论你想再次使用它。你甚至可以尝试两个以上策略(调用重新启动()各一间)。

    Construct a RestartableReadStream around your NetworkStream. Pass that instance to SslStream. If you decide that SSL was the wrong way to do things, call Restart() and then use it again however you want to. You can even try more than two strategies (calling Restart() between each one).

    一旦你哪个策略(例如SSL或者非SSL)落户是正确的,调用 StopBuffering()。一旦它的完成重放它有任何可用的缓冲区,它会恢复到刚打电话在其内流。如果你的的通话 StopBuffering ,那么整个历史,从流中读取数据将被保留在 _buffers 列表中,这可能会增加相当多的内存压力。

    Once you've settled on which strategy (e.g. SSL or non-SSL) is correct, call StopBuffering(). Once it's finished replaying any buffers it had available, it will revert to just calling Read on its inner stream. If you don't call StopBuffering, then the entire history of reads from the stream will be kept in the _buffers list, which could add quite a bit of memory pressure.

    请注意,以上都不是特别占多线程访问。但是,如果你有多个线程调用阅读()上的单个流(尤其是一个基于网络),我不希望任何理智呢。

    Note that none of the above particularly accounts for multi-threaded access. But if you've got multiple threads calling Read() on a single stream (especially one that's network based), I wouldn't expect any sanity anyway.

    这篇关于如何让服务器接受SSL和纯文本(不安全)连接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆