【问题标题】:Service client with Mutual Authentication (2-way client certificate authentication)具有相互身份验证的服务客户端(2 路客户端证书身份验证)
【发布时间】:2014-01-27 23:53:00
【问题描述】:

我正在尝试通过他们的 WSDL 创建到 Web 服务的连接。有人告诉我,该服务的身份验证被描述为通过交换证书进行身份验证的 TLS。我通过 Visual Studio 中的“添加服务引用”生成了客户端。当我发送命令时,我希望在 Wire Shark 中看到“握手”,但我什至看不到正在发送“Client Hello”启动。

这里描述了服务的身份验证: http://en.wikipedia.org/wiki/Transport_Layer_Security#Description

我的客户端是用 c# 编写的

这是我正在运行以测试连接的完整程序(它以 Run() 开头):

public class ClientExample
{
    private const string Url =
        "https://xxxxxxxxx";

    public static void Run()
    {
        ServicePointManager.ServerCertificateValidationCallback += ValidateCertificate;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;

        PerformTest("Clear Cache", GetBinding());
    }

    private static Binding GetBinding()
    {
        var bec = new BindingElementCollection
            {
                new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8),
                new HttpsTransportBindingElement{ RequireClientCertificate = true }
            };
        return new CustomBinding(bec);
    }

    private static void PerformTest(string test, Binding binding)
    {
        try
        {
            Console.ResetColor();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(test);
            Console.ResetColor();

            var client = GetClient(binding);
            SendMessage(client);
        }
        catch (Exception e)
        {
            DisplayError(e);
        }
    }

    private static MyClient GetClient(Binding binding)
    {
        var endpointAddress = new EndpointAddress(Url);

        var client = new MyClient(binding, endpointAddress);

        if (client.ClientCredentials != null)
        {
            client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                      X509FindType.FindBySubjectName,
                                                                      "xxxxxxxxxxxxx");                
        }
        return client;
    }

    private static void SendMessage(ChargePointServiceClient client)
    {
        var response = client.clearCache("xxxxxxxxxxxxx", new ClearCacheRequest());
        Console.WriteLine(ClearCacheDescription(response));
    }

    private static string ClearCacheDescription(ClearCacheStatus response)
    {
        switch (response)
        {
            case ClearCacheStatus.Accepted:
                return "Accepted";
            case ClearCacheStatus.Rejected:
                return "Rejected";
        }

        return "Unkown";
    }

    private static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors)
    {
        switch (sslpolicyerrors)
        {
            case SslPolicyErrors.None:
                return true;
            case SslPolicyErrors.RemoteCertificateChainErrors:
                DisplayWarningMessage("RemoteCertificateChainErrors");
                return false;
            case SslPolicyErrors.RemoteCertificateNameMismatch:
                DisplayWarningMessage("RemoteCertificateNameMismatch");
                return false;
            case SslPolicyErrors.RemoteCertificateNotAvailable:
                DisplayWarningMessage("RemoteCertificateNotAvailable");
                return false;
            default:
                DisplayWarningMessage("Unkown Certificate Validation Error");
                return false;
        }
    }

    private static void DisplayError(Exception exception)
    {
        if (exception == null)
            return;

        Console.BackgroundColor = ConsoleColor.DarkRed;
        Console.ForegroundColor = ConsoleColor.White;
        Console.WriteLine(@"Exception");
        Console.ResetColor();
        Console.WriteLine(exception.Message);

        if (exception.InnerException != null)
            Console.WriteLine();

        DisplayError(exception.InnerException);
    }

    private static void DisplayWarningMessage(string message)
    {
        Console.BackgroundColor = ConsoleColor.DarkYellow;
        Console.ForegroundColor = ConsoleColor.White;
        Console.WriteLine(message);
        Console.ResetColor();
    }
}

我在 app.config 中通过 system.diagnostic 记录了所有网络流量:

<system.diagnostics>
  <sources>
    <source name="System.Net">
      <listeners>
        <add name="System.Net"/>
      </listeners>
    </source>
    <source name="System.Net.Sockets">
      <listeners>
        <add name="System.Net"/>
      </listeners>
    </source>
    <source name="System.Net.Cache">
      <listeners>
        <add name="System.Net"/>
      </listeners>
    </source>
  </sources>
  <switches>
    <add name="System.Net" value="Verbose"/>
    <add name="System.Net.Sockets" value="Verbose"/>
    <add name="System.Net.Cache" value="Verbose"/>
  </switches>
  <sharedListeners>
    <add name="System.Net"
      type="System.Diagnostics.TextWriterTraceListener"
      traceOutputOptions="None"
      initializeData="network.log"
    />
  </sharedListeners>
  <trace autoflush="true"/>
</system.diagnostics>

以下是一些感兴趣的日志行

这确认创建了一个 tls 流:

System.Net Information: 0 : [9040] TlsStream#50727427::.ctor(host=xxxxx, #certs=1)

创建了一个安全通道:

System.Net Information: 0 : [9040] SecureChannel#11159819::.ctor(hostname=xxxxxxx, #clientCertificates=1, encryptionPolicy=RequireEncryption)
System.Net Information: 0 : [9040] Enumerating security packages:
System.Net Information: 0 : [9040]     Negotiate
System.Net Information: 0 : [9040]     NegoExtender
System.Net Information: 0 : [9040]     Kerberos
System.Net Information: 0 : [9040]     NTLM
System.Net Information: 0 : [9040]     TSSSP
System.Net Information: 0 : [9040]     pku2u
System.Net Information: 0 : [9040]     Schannel
System.Net Information: 0 : [9040]     Microsoft Unified Security Protocol Provider
System.Net Information: 0 : [9040]     LiveSSP
System.Net Information: 0 : [9040]     WDigest
System.Net Information: 0 : [9040]     CREDSSP
System.Net Information: 0 : [9040] SecureChannel#11159819 - Attempting to restart the session using the user-provided certificate: [Version]

不确定为什么要查找私钥:

System.Net Information: 0 : [9040] SecureChannel#11159819 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [9040] SecureChannel#11159819 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [9040] SecureChannel#11159819 - Locating the private key for the certificate: [Version]

在日志中的这一点上,我看到证书已被交换。不幸的是,钢丝鲨没有证实这一点......

现在我的程序验证服务证书并开始处理

System.Net Information: 0 : [9040] SecureChannel#11159819 - Remote certificate was verified as valid by the user.
System.Net Information: 0 : [9040] ProcessAuthentication(Protocol=Ssl3, Cipher=Rc4 128 bit strength, Hash=Sha1 160 bit strength, Key Exchange=RsaKeyX 2048 bit strength).

然后我看到交换了一些加密数据并发送了我的清除缓存命令

我收到来自服务的加密响应,但该消息指示出现故障。我认为这是因为服务不接受身份验证

System.Net Error: 0 : [9040] Exception in HttpWebRequest#46890055::GetResponse - The remote server returned an error: (500) Internal Server Error..

这是我在wireshark中使用的过滤器(两个IP都是不是我的服务)

(ip.src == xxx.xxx.xxx.xx or ip.dst == xxx.xxx.xxx.xx) and ssl.handshake

另外,我使用的证书没有私钥。我认为我不需要 TLS 文档中的任何一个。

所以我的问题是为什么我在运行程序时在wireshark中看不到Client Hello/Server Hello,或者我应该如何配置客户端绑定来启动客户端hello?

(我标记 wcf 是因为我认为 wcf 专业人员可能知道我的问题的答案。我的解决方案将独立于 wcf,因为我无法控制服务绑定)

【问题讨论】:

    标签: c# wcf ssl


    【解决方案1】:

    听起来您所描述的是在 SSL/TLS 中使用客户端证书身份验证。在这种情况下,您使用的客户端证书肯定需要一个私钥,这是完成 SSL/TLS 握手所必需的。

    注意,在配置客户端时,需要告诉绑定使用客户端证书进行传输认证:

    <security mode="Transport">
        <transport clientCredentialType="Certificate" />
    </security>
    

    然后告诉它如何使用 ClientCertificate 行为找到您要使用的证书:

    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientCertificateBehavior">
          <clientCredentials>
            <clientCertificate findValue="CN=clienttempcert" storeLocation="CurrentUser"
              storeName="My" x509FindType="FindBySubjectDistinguishedName" />
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    

    请注意,您指定密钥的证书必须具有客户端可以加载的私钥(但是,服务根本不需要私钥)。

    【讨论】:

    • 这是我不清楚的部分。我被告知通过浏览器下载证书,我下载的证书没有私钥。当我打开证书时,它显示“此证书用于以下目的:* 承受远程计算机的身份。* 向远程计算机证明您的身份。”对我来说,似乎我只是将证书提供给服务以证明身份,然后通过我提供的 wiki 链接中描述的步骤 4 进行加密。
    • 我假设浏览器在访问 https 站点时具有相同的机制。浏览器没有私钥,但通信最终会被加密。
    • 您从哪里下载证书?这是来自证书颁发机构吗?通常,如果您使用证书颁发机构生成新证书,CA 不会获取或生成私钥,该私钥仅在发出请求的机器上生成,然后当您使用该证书时,密钥会与证书匹配将其下载回同一台机器。
    • 我正在使用 Firefox 访问我尝试与之通信的站点 (https)。如果单击挂锁,您可以查看证书并将其导出。我不是从 CA 获得它或执行 CSR。当我被告知这是我应该如何获得证书时,我有点困惑。使用具有私钥的 .pfx 文件会容易得多...
    • 您说的是服务器证书,但您说站点/服务需要 客户端 端证书。根本不是一回事。
    【解决方案2】:

    获取 PFX,并将其安装到服务器证书。到个人商店或当前商店,本地机器商店等。

    然后在 asp.net web.config 中使用它并使用 findbyThumbPrint。

    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientCertificateBehavior">
          <clientCredentials>
            <clientCertificate findValue="yourthumprinthere" storeLocation="CurrentUser" or PersonalStore
              storeName="My" x509FindType="FindByThumbprint" />
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-09
      • 2013-10-07
      • 1970-01-01
      • 2022-06-23
      • 1970-01-01
      • 1970-01-01
      • 2015-12-26
      • 2018-02-12
      相关资源
      最近更新 更多