none
How to invoke a SOAP service requiring client-side authentication with certificates installed at runtime RRS feed

  • Question

  • I have an application deployed to IIS that needs to invoke a SOAP service. It's using WCF from .NET Framework. That SOAP service requires that requests made be authenticated with a client-side certificate which is given at runtime. Admin users of the application can update the used certificate in a back-office. The goal is for autonomy and the certificate lifecycle management be independent from IIS or the underlying system so using the machine certificate store is not an option. Here's the initial code:

    var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
    var client = new ServiceReference1.myClient(binding, new EndpointAddress(serviceUrl));
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
    var certificate = new X509Certificate2(certificateBinary, certificatePassword);
    client.ClientCredentials.ClientCertificate.Certificate = certificate;
    
    //use the client
    var result = client.myMethod(new ServiceReference1.MethodRequest());

    certificateBinary is the result of loading a PFX file containing the full certificate chain (client certificate, intermediate and root CAs) and certificatePassword the password used to create that file. But the request is rejected by the server. From looking at Wireshark, it seems only the client-certificate is sent. This is different from what happens if we install the PFX on the machine store which works fine.

    So the next step I tried was to install the certificates at runtime. First load them:

    X509Certificate2Collection collection = new X509Certificate2Collection();
    try {
        collection.Import(ssCertificateFile, ssPassword, X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
    }

    Then identify what kind of certificates they are and finally installing them on the current user store:

    private static void InstallCertificates(X509Certificate2Collection clientCerts, X509Certificate2Collection intermediateCAs, X509Certificate2Collection RootCAs) {
        using (X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser)) {
            personalStore.Open(OpenFlags.ReadWrite);
            personalStore.AddRange(clientCerts);
        }
    
        using (X509Store intermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser)) {
            intermediateStore.Open(OpenFlags.ReadWrite);
            intermediateStore.AddRange(intermediateCAs);
        }
    
        using (X509Store trustedCAsStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
            trustedCAsStore.Open(OpenFlags.ReadWrite);
            trustedCAsStore.AddRange(rootCAs);
        }
    }

    This fails when installing the root CAs in trusted root certificate authorities (StoreName.Root) with:

    System.Security.Cryptography.CryptographicException: The request is not supported.
       at System.Security.Cryptography.X509Certificates.X509Store.Add(X509Certificate2 certificate)
       at System.Security.Cryptography.X509Certificates.X509Store.AddRange(X509Certificate2Collection certificates)
       at OutSystems.NssCertificationExtremeXP.CssCertificationExtremeXP.InstallCertificates(CertificatesClassifier certificates)

    so only the client certificate and the intermediate CAs get installed and at runtime apparently this is not enough.

    But if I take the exact same code as it is and run it with in a separate C# project, when installing the root CAs there's a confirmation dialog and if I click OK the certificate is installed.

    From https://stackoverflow.com/questions/4196997/certificate-install-security-warning-workaround, it looks like every time we want to install something in the user Trusted Root Certificate Authorities, that prompt happens and it probably is not supported on the context of a non-GUI usage.

    The problem is that even if I don't install the root CA in the store, I can successfully call the SOAP service when running this stand-alone app, only when running under IIS this fails.

    Does anyone know why this happens and how to solve it?

    Wednesday, May 15, 2019 4:06 PM

All replies

  • Hi PedroGuim, 
    I didn’t encounter this scenario. Generally, I installed the client certificate in the local machine Root CA. 
    As you know, whatever the transport security with a certificate or the message security with a certificate is used in the service. we all need to establish the trust relationship between the client app and server service. namely that we are supposed to run the client app after the client certificate is installed in the server certificate store (certlm.msc or certmgr.msc)
    https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication
    https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-certificate-client
    Do you mean that the client certificate provided in the runtime is not installed in the server certificate store (Root CA), the client application still works well?

    Best Regards

    Abraham

    Thursday, May 16, 2019 6:51 AM
    Moderator
  • Hi Abraham,

    I'll try to answer your question and rephrase some things I wrote, just to make sure we're on the same page:

    I want to use as a client a SOAP service which I don't control. The goal is to be able to consume it from an application deployed on IIS but as a test, I also tried to consume the same SOAP service outside IIS, using a simple console application project.

    With the stand-alone app, I can invoke the service, either with Root CA installed on the user certificate store or not, and it works (I only need the client certificate and the intermediate CAs). In IIS, I installing the Root CA fails, but the client certificate and the intermediate CAs install so if it behaved as the stand-alone app, it should still work.

    The set of certificates used in both cases is the same (comes from the same PFX file) and it's trusted by the external SOAP service. So the problem, as far as I can understand, is that the logic to send the certificate chain is different in IIS and outside it, even ignoring the question of Root CAs.

    Thursday, May 16, 2019 2:30 PM
  • Hi PedroGuim,
    I still could not get your point. here is my understanding in working with certificate on this issue. Wish it is useful to you.
    Your Client application should install the server certificate, and the account run the client application should have the permission to access the private key. In order to guarantee this, the following user should be added.

    As for the client certificate, it is unnecessary to install it in the client side, we only need to provide it in the code.  And guarantee that it is trusted by the server.
    For setting the mode of authenticating client certificate, it is completed by the below section.
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
              <serviceCredentials>
                <clientCertificate>
                  <authentication certificateValidationMode="ChainTrust"/>
                </clientCertificate>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
    

    The default value of CertificateValidationMode is X509CertificateValidationMode.ChainTrust. so the default case is that we should install the client certificate in the Root CA on server side.
    Besides, usually for testing, we use PowerShell to generate a self-signed certificate for two machines. In this case, we need to ensure that the client target framework is above 4.6.2.
    Feel free to let me know if there is anything I can help with.
    Best Regards
    Abraham
    Friday, May 17, 2019 2:58 AM
    Moderator
  • Hello Abraham,

    I'll answer by parts structure the flow.

    Your Client application should install the server certificate, and the account run the client application should have the permission to access the private key. In order to guarantee this, the following user should be added.

    The problem is not the server certificate, that part is working fine.

    As for the client certificate, it is unnecessary to install it in the client side, we only need to provide it in the code.  And guarantee that it is trusted by the server.

    I'm my experience that's not the case - it needs to be installed on the computer certificate store or else it won't work. But my requirements are to avoid doing do that.

    Also, I'm using a client-certificate signed by an intermediate CA, in turn signed by a root CA which is not public, so it's not trusted/installed by default.

    Friday, May 17, 2019 10:37 AM
  • Hi PedroGuim,
    Please refer to the official example,
    https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-certificate-client
    In this example, I could provide the certificate as below form.
    X509Certificate2 cert = new X509Certificate2("31.pfx", "123456");
                client.ClientCredentials.ClientCertificate.Certificate = cert;

    The premise is that I need to put the certificate file in the project and already know the password of the PFX file. In additional, I must guarantee that the above client certificate must be trusted by the server, it is required since the authentication mode of the server to client is ChainTrust.
    <authentication certificateValidationMode="ChainTrust"/>


    Both certificates are created by using Powershell New-SelfsignedCertificate command. so I should ensure the client framework is above 4.6.2.
    If necessary, I could post my complete code. Wish this information are helpful to you.
    Feel free to let me know if there is anything I can help with.
    Best Regards
    Abraham


    Monday, May 20, 2019 3:11 AM
    Moderator