SSL认证适用于localhost,但不适用于计算机名称或IP

时间:2015-12-14 10:21:29

标签: c# sockets ssl https

我们在服务器上运行了一个Web应用程序,它通过XDomainRequest发布了http请求(因为IE9)。

有许多客户端计算机通过套接字侦听器监视端口上的控制台应用程序。客户端打开Web应用程序 IE9浏览器,当他们点击链接时,网页会发送如下请求:

" https://localhost:portNumber/applicationName/doSomething" " https://computerName:portNumber/applicationName/doSomething" " https://ipAddress:portNumber/applicationName/doSomething"

第二个和第三个请求用于控制其他用户的应用程序。计算机。

问题是如果请求带有localhost,则控制台应用程序不会有关于读取传入数据和发送响应的问题。但 如果请求附带计算机名称或IP地址,则浏览器会显示认证警告并希望用户单击 "继续浏览此网站(不推荐)"链接。

我们想通过代码创建三个不同的证书。 但即使使用其中三个的sslstream也是可能的,我们无法决定选择真正的认证,因为我们首先进行认证然后接收数据。因此,当我们捕获传入请求时,必须已完成身份验证。

另一种方法是强制套接字侦听器或sslstream表示所有这三个请求,就好像它们是localhost一样。因此,对于每一个,身份验证将作为localhost。但我无法找到实际的方法。

这是代码。我给出了代码,因为可能有一些错误的SslStream用法。

using System;
using System.Net.Sockets;
using System.Net;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
using System.IO;
using System.Net.Security;
using System.Security.Authentication;
using System.Threading;
using System.Text;

namespace StackOverFlowProject
{
    class StackOverFlowSample
    {
        private static ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
        private static X509Certificate _cert = null;

        static void Main(string[] args)
        {
            StackOverFlowSample stackOverFlowSample = new StackOverFlowSample();
            stackOverFlowSample.StartListening();
        }

        private void StartListening()
        {
            GetCertificate();

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

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

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

                    Console.WriteLine("Socket listener is running. Waiting for requests...");

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

        private static void GetCertificate()
        {
            byte[] pfxData = File.ReadAllBytes(Application.StartupPath + @"\" + "localhost.pfx");

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


        private void AcceptCallback(IAsyncResult result)
        {
            Socket listener = null;
            Socket handler = null;
            StateObject state = null;
            SslStream sslStream = null;

            _manualResetEvent.Set();

            listener = (Socket)result.AsyncState;

            handler = listener.EndAccept(result);

            state = new StateObject();

            if (handler.RemoteEndPoint != null)
            {
                state.clientIP = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();
            }

            sslStream = new SslStream(new NetworkStream(handler, true));
            sslStream.AuthenticateAsServer(_cert, false, SslProtocols.Tls, true);

            sslStream.ReadTimeout = 100000;
            sslStream.WriteTimeout = 100000;

            state.workStream = sslStream;

            if (state.workStream.IsAuthenticated)
            {
                state.workStream.BeginRead(state.buffer, 0, StateObject.BufferSize, ReceiveCallback, state);
            }

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

        private void ReceiveCallback(IAsyncResult result)
        {
            StateObject stateObject = null;
            SslStream sslStreamReader = null;

            byte[] byteData = null;

            stateObject = (StateObject)result.AsyncState;
            sslStreamReader = stateObject.workStream;

            int byteCount = sslStreamReader.EndRead(result);

            Decoder decoder = Encoding.UTF8.GetDecoder();
            char[] chars = new char[decoder.GetCharCount(stateObject.buffer, 0, byteCount)];
            decoder.GetChars(stateObject.buffer, 0, byteCount, chars, 0);
            stateObject.sb.Append(chars);

            if (byteCount > 0)
            {
                stateObject.totalReceivedBytes += byteCount;

                string[] lines = stateObject.sb.ToString().Split('\n');

                if (lines[lines.Length - 1] != "<EOF>")
                {
                    // We didn't receive all data. Continue reading...
                    sslStreamReader.BeginRead(stateObject.buffer, 0, stateObject.buffer.Length, new AsyncCallback(ReceiveCallback), stateObject);
                }
                else
                {
                    Console.WriteLine("We received all data. Sending response...");

                    byteData = Encoding.UTF8.GetBytes("Hello! I received your request!");

                    string httpHeaders = "HTTP/1.1" + "\r\n"
                                    + "Cache-Control: no-cache" + "\r\n"
                                    + "Access-Control-Allow-Origin: *" + "\r\n"
                                    + "\r\n";

                    byte[] byteHttpHeaders = Encoding.UTF8.GetBytes(httpHeaders);

                    byte[] concat = new byte[byteHttpHeaders.Length + byteData.Length];
                    Buffer.BlockCopy(byteHttpHeaders, 0, concat, 0, byteHttpHeaders.Length);
                    Buffer.BlockCopy(byteData, 0, concat, byteHttpHeaders.Length, byteData.Length);

                    stateObject.sslStreamReader = sslStreamReader;

                    sslStreamReader.BeginWrite(concat, 0, concat.Length, new AsyncCallback(SendCallback), stateObject);
                }
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            SslStream sslStreamSender = null;

            StateObject stateObject = (StateObject)ar.AsyncState;

            sslStreamSender = stateObject.sslStreamReader;
            sslStreamSender.EndWrite(ar);

            Console.WriteLine(stateObject.totalReceivedBytes.ToString() + " bytes sent to " + stateObject.clientIP + " address");

            sslStreamSender.Close();
            sslStreamSender.Dispose();
        }

    }

    public class StateObject
    {
        public SslStream workStream = null;

        public SslStream sslStreamReader = null;

        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public StringBuilder sb = new StringBuilder();

        public string clientIP = "";

        public int totalReceivedBytes = 0;
    }
}

4 个答案:

答案 0 :(得分:5)

你的安全人员是对的。你试图实现这一目标的方式不适用于SSL。

如果您有证书,则设置为对一个CN进行身份验证。所以,对于一个过于简单的例子。谷歌有证书。它会对https://*.google.com进行身份验证。这意味着任何google.com请求都会生成有效证书。而且你的浏览器很开心。

现在打开命令提示符,ping google.com。抓住IP地址(在我的情况下,它出现了216.58.210.14)。输入https://216.58.210.14。您的浏览器抱怨该网站不安全等。原因是服务器可能与服务于您之前的请求的服务器相同,但根据证书,您获得该服务器的方式无效,因为CN不是谷歌。 com,但是IP地址。

因此,如果您有需要连接到(例如)127.0.0.1,10.92.1.4和myserver.com的服务,您将需要一个对每种情况都有效的证书。

答案 1 :(得分:2)

您遇到的证书警告实际上是名称不匹配错误,表示SSL证书中的公用名(域名)与用于访问网站/服务器的URL /地址不匹配。

https://www.sslshopper.com/ssl-certificate-name-mismatch-error.html

在您的使用场景中,您可能希望从localhost和ip地址转换为支持利用计算机名称的简单域模型。 (例如computerName.someDomain.com

然后,您可以获得可用于验证进程间通信的通配符证书(例如*.someDomain.com)。

https://www.sslshopper.com/best-ssl-wildcard-certificate.html

答案 2 :(得分:1)

我不确定你的"https://computerName:portNumber/applicationName/doSomething"意味着什么。因此,不确定您是否正确使用证书,或者您在代码中访问或使用的路径/连接是否正确。 https://technet.microsoft.com/en-us/library/dd891009.aspx

它可以访问任何不限于TCP的协议。 https://technet.microsoft.com/en-us/library/cc784450(v=ws.10).aspx

  • 通配符SSL证书允许管理员使用单个SSL证书在同一域上保护无限数量的子域。向* .yourdomain.com颁发WildCard SSL证书,因为必须为每个子域购买大量SSL证书,并且只需要使用一个IP地址,从而节省成本。
  • 典型的SSL证书仅保护一个完全限定的域名。统一通信证书允许您在一个证书中分配多个主机名(称为主题备用名称或SAN)。为了实现全面的灵活性,多域SSL证书允许使用主题备用名称(SAN)字段,因此您可以使用单个SSL证书保护多达100个不同的域名,子域或公共IP地址,并且只需要一个IP地址。

**该值必须与您访问的主机名或域名完全匹配。否则,在导入和信任证书后,您仍会收到浏览器证书错误。例如:如果您之前使用http://101.10.10.1:9000/访问该网站,并且新证书的公用名是&#34; mydomain&#34;,则快捷方式应更新为http://mydomain:9000/;如果您为mydomain:9000创建了证书。其次,使用您将通过其处理服务器的主机名或CNAME。这是非常重要的。如果您的Web服务器的真实主机名是mysubdomain.mydomain.com,但是人们将使用www.mydomain.com来解决该框,那么使用后一个名称来回答“公共名称”(CNAME)问题。

**检查您是否能够使用openssl s_time -connect remote.host:443进行连接(使用特定的ssl命令进行故障排除,我引用openssl)或openssl s_time -connect remote.host:443 -www /test.html -new **如果您没有可供您使用的启用SSL的Web服务器,则可以使用s_server选项模拟一个。

# on one host, set up the server (using default port 4433) openssl s_server -cert mycerti.pem -www # on second host (or even the same one), run s_time openssl s_time -connect mydomain:9001 -www / -new -ssl3

# on second host (or even the same one), run s_time openssl s_time -connect mydomain:9001 -www / -new -ssl3 **检查证书是否在使用中? $ openssl s_client -connect [ip_or_dns_name]:[port] $ man s_client

答案 3 :(得分:0)

我建议的解决方案是假设:

  • 网站托管在IIS

  • 您可以通过网络访问应用程序,而不会出现任何问题。

  • 您无需访问互联网上的webapp(需要获得认证机构的认可)。

尝试:

  1. 通过IIS创建自签名证书,如:

    我。打开IIS,选择服务器证书,如: enter image description here

    II。创建自签名证书,如: enter image description here

    III。配置网站使用该证书,如:

    • 选择网站,点击绑定。

    • 点击编辑https

    • 的绑定
  2. enter image description here

    IV。编辑网站绑定以使用您创建的证书:

    enter image description here

    现在您可以使用计算机名访问您的应用程序。

    您可以为互联网的域证书重复相同的过程,但这需要由某些经过认证的机构注册/提供的证书。

    请注意,现在使用可以在本地和&amp ;;上访问计算机名称的应用程序在网络上。由于为单个主机名/共域颁发证书,因此在本地计算机上也使用机器名而不是localhost。