如何将 PFX 证书文件应用于 SslStream 套接字侦听器?

How to apply PFX certificate file to SslStream socket listener?

我有一个多线程套接字侦听器。它监听一个端口。它从 HTTP 网站接收数据并根据他们的请求发送响应。效果不错。

现在我想对 HTTPS 网站做同样的事情。所以我将其更改为能够通过 SslStream 而不是 socket 进行读写。

网站所有者给了我一个 pfx 文件和一个密码。我将其放入本地证书商店的受信任证书列表中。

已成功从商店中检索到认证文件。但是在调用 AuthenticateAsServer 方法后,我的处理程序套接字似乎是空的。所以我在 ReceiveCallBack 方法中看不到任何数据。

如何将 pfx 证书应用到我的 SslStream 套接字?

这是我的代码:

using System;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.IO;

namespace SslStreamTestApp
{
    public class SslStreamSocketListener
    {
        private static readonly ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
        private readonly string pfxSerialNumber = "12345BLABLABLA";

        X509Certificate _cert = null;

        private void GetCertificate()
        {
            X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);

            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection certificateCollection = store.Certificates.Find(X509FindType.FindBySerialNumber, pfxSerialNumber, true);

            if (certificateCollection.Count == 1)
            {
                _cert = certificateCollection[0];
            }
        }

        private void StartListening()
        {
            GetCertificate();

            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 9002);

            if (localEndPoint != null)
            {
                Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                if (listener != null)
                {
                    listener.Bind(localEndPoint);
                    listener.Listen(5);

                    Console.WriteLine("Socket listener is running...");

                    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                }
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            _manualResetEvent.Set();

            Socket listener = (Socket)ar.AsyncState;

            Socket handler = listener.EndAccept(ar);

            SslStream stream = new SslStream(new NetworkStream(handler, true));
            stream.AuthenticateAsServer(_cert, false, System.Security.Authentication.SslProtocols.Ssl3, true);

            StateObject state = new StateObject();
            state.workStream = stream;

            stream.BeginRead(state.buffer, 0, StateObject.BufferSize, ReceiveCallback, state);

            listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
        }

        private void ReceiveCallback(IAsyncResult result)
        {
            StateObject state = (StateObject)result.AsyncState;
            SslStream stream = state.workStream;

            // Check how many bytes received
            int byteCount = stream.EndRead(result);

            Decoder decoder = Encoding.UTF8.GetDecoder();

            char[] chars = new char[decoder.GetCharCount(state.buffer, 0, byteCount)];

            decoder.GetChars(state.buffer, 0, byteCount, chars, 0);

            state.sb.Append(chars);

            string check = state.sb.ToString();

            if (state.sb.ToString().IndexOf("<EOF>") == -1 && byteCount != 0)
            {
                // We didn't receive all data. Continue receiving...
                stream.BeginRead(state.buffer, 0, state.buffer.Length, new AsyncCallback(ReceiveCallback), stream);
            }
            else
            {
                string[] lines = state.sb.ToString().Split('\n');

                // send test message to client
                byte[] test = Encoding.UTF8.GetBytes("\"infoMessage\":\"test\"");
                stream.BeginWrite(test, 0, test.Length, WriteAsyncCallback, stream);
            }
        }

        private void WriteAsyncCallback(IAsyncResult ar)
        {
            SslStream sslStream = (SslStream)ar.AsyncState;

            Console.WriteLine("Sending message to client...");

            try
            {
                sslStream.EndWrite(ar);
            }
            catch (IOException)
            {
                Console.WriteLine("Unable to complete send.");
            }
            finally
            {
                sslStream.Close();
            }
        }
    }

    public class StateObject
    {
        public SslStream workStream = null;
        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public StringBuilder sb = new StringBuilder();
    }
}

编辑: 我想我对 SSL 逻辑有一些疑问。 PFX 文件属于 HTTPS 网站。但是请求来自 HTTPS 到我的套接字侦听器。然后我的套接字侦听器发送响应。在这种情况下,我是服务器还是客户端?我将使用 AuthenticateAsServer 还是 AuthenticateAsClient?

如果我调用 AuthenticateAsServer,它不会抛出错误。它毫无问题地通过了身份验证。但是我无法通过我的 StateObject 的 SslStream 将处理程序套接字中的数据传送到 ReceiveCallBack 方法。

您可以直接加载 pfx 文件:

byte[] pfxData = File.ReadAllBytes("myfile.pfx");

cert = new X509Certificate2(pfxData, "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

如果您从证书存储区加载它,那么它很重要 该密钥可以为应用程序导出。

信息:SSL 3.0 版不再安全。 也许这就是为什么你无法建立连接。 Chrome 并且 Firefox 禁用了支持。

提示:编写一个简单的 SSL 客户端来发送一些 "Hello World" 字节。

我使用 pfx 格式的自签名证书测试了您的代码 通过 c# 客户端和 chrome 浏览器。 两个客户端都可以连接和 send/receive 数据。

我唯一改变的是将 SSLProtocol 设置为 TLS

stream.AuthenticateAsServer(_cert, false, System.Security.Authentication.SslProtocols.Tls, true);

信息:HTTPS 是基于 tcp/tls 连接的 HTTP 协议。您有一个没有 http 协议的原始 tcp/tls 连接。

提示:不要忘记打开防火墙端口 9002!