1

免责声明:像这样标题的问题很常见,但没有答案为我提供解决方案,所以无论如何我都需要问它(使用一组新参数)。

问题

在 web.config 中声明了一个 webservice 客户端端点,如下所示:

<behaviors>
  <endpointBehaviors>
    <behavior name="bankid">
      <clientCredentials>
        <clientCertificate findValue="FP Testcert 2"
          storeLocation="LocalMachine"
          storeName="Root"
          x509FindType="FindBySubjectName"/>
        <serviceCertificate>
          <defaultCertificate findValue="Test BankID SSL Root CA v1 Test"
            storeLocation="LocalMachine"
            storeName="Root"
            x509FindType="FindBySubjectName"/>
        <authentication certificateValidationMode="None"
          revocationMode="NoCheck"
          trustedStoreLocation="LocalMachine"/>
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

使用“管理计算机证书”应用程序安装证书(客户端和服务器证书)。它们分别存储在 .cer 文件(服务器证书)和 .pfx 文件(客户端证书)中。它们都存储在“受信任的根证书颁发机构”中。

成功

使用 Visual Studio 调试网络服务器 (IIS Express) 运行客户端是成功的。

失败

但是,当我尝试在 IIS 中运行它时,我收到错误消息

无法为具有权限“site.com”的 SSL/TLS 建立安全通道

问题解决方法

我试图创建一个 web api 函数,让我知道服务器是否找到了有问题的证书。确实如此。代码看起来像

[HttpGet]
[Route("Debug/certs")]
public CertsOutput certs()
{
    var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);

    certStore.Open(OpenFlags.ReadOnly);

    var config = System.Web.Configuration.WebConfigurationManager
      .OpenWebConfiguration("~");
    var group = ServiceModelSectionGroup.GetSectionGroup(config);
    var endPointBehaviors = group.Behaviors.EndpointBehaviors;
    var endpointBehavior = endPointBehaviors[0];

    var ClientCredential = (ClientCredentialsElement) endpointBehavior[0];

    var clientCert = ClientCredential.ClientCertificate;
    var serverCert = ClientCredential.ServiceCertificate.DefaultCertificate;

    var result = new CertsOutput
    {
        clientCert = new CertsOutput.Cert
        {
            FindValue = clientCert.FindValue,
            StoreName = clientCert.StoreName.ToString(),
            StoreLocation = clientCert.StoreLocation.ToString(),
            FindType = clientCert.X509FindType.ToString()
        },

        serverCert = new CertsOutput.Cert
        {
            FindValue = serverCert.FindValue,
            StoreName = serverCert.StoreName.ToString(),
            StoreLocation = serverCert.StoreLocation.ToString(),
            FindType = serverCert.X509FindType.ToString()
        }
    };

    return result;
}

public class CertsOutput
{
    public Cert clientCert { get; set; }
    public Cert serverCert { get; set; }

    public class Cert
    {
        public string FindValue { get; set; }
        public string StoreName { get; set; }
        public string StoreLocation { get; set; }
        public string FindType { get; set; }

        public string Expiration => Certificate?.GetExpirationDateString() 
           ?? "Cant find cert";

        X509Certificate _certificate = null;
        private X509Certificate Certificate
        {
            get
            {
                if (_certificate != null)
                    return _certificate;

                StoreName storeNameEnum;
                switch(StoreName)
                {
                    case "My":
                        storeNameEnum = System_StoreName.My;
                        break;
                    case "Root":
                        storeNameEnum = System_StoreName.Root;
                        break;
                    default:
                        throw new Exception("Unknown store name: " + StoreName);
                }

                StoreLocation storeLocationEnum;
                switch(StoreLocation)
                {
                    case "LocalMachine":
                        storeLocationEnum = System_StoreLocation.LocalMachine;
                        break;
                    case "CurrentUser":
                        storeLocationEnum = System_StoreLocation.CurrentUser;
                        break;
                    default:
                        throw new Exception("Unknown store location: " + StoreLocation);
                }

                var certStore = new X509Store(storeNameEnum, storeLocationEnum);

                certStore.Open(OpenFlags.ReadOnly);

                var certCollection = certStore.Certificates.Find
                    (X509FindType.FindBySubjectName, FindValue, validOnly:false);

                certStore.Close();

                var result = certCollection[0];
                _certificate = result;

                return result;
            }
        }
    }
}

即使我在 IIS 上运行它,我也会得到这样的输出(在 chrome 中使用 console.log):

输出显示 IIS 实际上可以使用证书

因此证书对 IIS 是清晰可见的,尽管它们存储在“受信任的根证书颁发机构”中。可以检索过期日期的唯一方法是使用商店。

4

1 回答 1

2

在事件日志中启用 CAPI2 日志可能会给您答案,为什么Could not create SSL/TLS secure channel.默认情况下禁用 CAPI2 日志。当您启用它时,请尝试再次发出请求。应该有一些错误事件将包含有关原因的有用信息。

CAPI2 事件日志

我还会检查(也许会改变)一些事情:

  • 在 IE 中打开 WCF 端点并检查该站点是否受 IS 信任。如果没有找出原因。这是你应该做的第一件事。
  • 客户端证书 (pfx) 应放置在LocalMachine/My(个人)存储中。根 CA 证书应放置在受信任的根证书颁发机构存储中(您说得对),中间 CA 证书应放置在中间证书颁发机构存储中
  • 应将私钥的权限授予运行 WCF 客户端的 IIS 应用程序池。可以使用certlm.msc工具来完成。
  • 检查私钥是否在 web api 方法中可用。所以检查PrivateKey属性,用它签署一些 hello world 数据等。
于 2017-11-10T08:18:44.997 回答