Could not establish secure channel for SSL/TLS wit

2019-08-01 15:54发布

问题:

Disclaimer: Questions with titles like this one are common, but no answer has provided a solution for me so I need to ask it anyway (with a new set of parameters).

Problem

A webservice client endpoint is declared in web.config like this:

<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>

The certificates (client and server certificate) are installed using the "Manage computer certificates" app. They are stored in a .cer file (server certificate) and a .pfx file (client certificate) respectively. They are both stored in "Trusted Root Certification Authorities".

Success

Running the client using the visual studio debug webserver (IIS Express) is successful.

Failure

However, when I try to run it in IIS, I get the error message

Could not establish secure channel for SSL/TLS with authority 'site.com'

Problem solving method

I have tried to create a web api function that lets me know if the server finds the certificates in question. It does. The code looks like

[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;
            }
        }
    }
}

Even if I am running this on IIS, I get an output like this (using console.log in chrome):

So the certs are clearly visible to the IIS, although they are stored in "Trusted Root Certification Authorities". The only way expire dates could have been retrieved is using the store.

回答1:

Enabling CAPI2 log in event log might give you the answer why it Could not create SSL/TLS secure channel. CAPI2 log is by default disabled. When you enable it try to make the request again. There should be some error events that will contain helpful info about the cause.

I would also check (and maybe change) some things:

  • Open WCF endpoint in IE and check if the site is trusted by IS. If it's not find out why. This is first thing you should do.
  • Client certificate (pfx) should be placed in LocalMachine/My (Personal) store. Root CA certificates should be placed in Trusted Root Certification Authorities store (you got that right) and intermediate CA certificates in Intermediate Certification Authorities store
  • Rights to the private key should be given to IIS App Pool that your WCF client runs under. It can be done using certlm.msc tool.
  • Check if private key is usable in the web api method. So check PrivateKey property, sign some hello world data with it etc.