none
Using certificates for both message and transport RRS feed

  • Question

  • Hi

    I'm working on WCF client accessing remote Web Service. This service needs authentication using certificates for both SOAP message and HTTPS transport. wsHttpBinding doesn't allow security mode for both message and transport, so I adapted Mahesh's solusion presented here: 

    https://blogesh.wordpress.com/2009/10/08/separate-certificates-for-transport-and-message-security-in-wcf/

    The transport works well but there is no <wsse:security> header in created SOAP message. So SecurityNegotiationException appears with InnerException : "Missing wsse:Security header in request".

    Both certificates are installed in LocalMachine repository in Trusted Root Certification Authorities and are valid.

    Any help is appreciated

    Below is my code:

    App.config
    ----------
     <system.serviceModel>
        <bindings>
          <customBinding>
            <binding name="RecipeWSCustomBinding">
              <transactionFlow />
              <security authenticationMode="SecureConversation" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
                <secureConversationBootstrap authenticationMode="MutualSslNegotiated" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10" />
              </security>
              <textMessageEncoding />
              <httpsTransport requireClientCertificate="true" authenticationScheme="Negotiate" useDefaultWebProxy="true" manualAddressing="false" />
            </binding>
          </customBinding>
        </bindings>

        <extensions>
          <behaviorExtensions>
            <add name="clientCredentialsExtension" type="RecipeWSC.ClientCredentialsExtensionElement, RecipeWSC" />
          </behaviorExtensions>
        </extensions>
        
        <behaviors>
          <endpointBehaviors>
            <behavior name="SecureMessageAndTransportBehavior">
              <clientCredentialsExtension>
                <!--This cert is used for signing the message-->
                <clientCertificate findValue="(subject)"
                                   storeLocation="LocalMachine"
                                   storeName="Root"
                                   x509FindType="FindBySubjectDistinguishedName"
                                  />
                <!--This cert is used for the transport-->
                <transportCertificate findValue="(subject)"
                                   storeLocation="LocalMachine"
                                   storeName="Root"
                                   x509FindType="FindBySubjectDistinguishedName"
                                  />
              </clientCredentialsExtension>
            </behavior>
          </endpointBehaviors>
        </behaviors>

        <client>
          <endpoint address="https://service_address"
              behaviorConfiguration="SecureMessageAndTransportBehavior"
              binding="customBinding" bindingConfiguration="RecipeWSCustomBinding"
              contract="RecipeService.RecipeWS" name="RecipeWS">
          </endpoint>
        </client>
      </system.serviceModel>

    Custom ClientCredentials class
    ----------------------------
    /// 
      /// Class that extends Client Credentials so that the certificate for the
      /// Transport layer encryption can be separate
      /// 
      public class MyClientCredentials : ClientCredentials
      {
        /// <summary>
        /// The X509 Certificate that is to be used for https.
        /// </summary>
        public X509Certificate2 TransportCertificate { get; set; }

        public MyClientCredentials(ClientCredentials existingCredentials)
          : base(existingCredentials)
        {
        }

        protected MyClientCredentials(MyClientCredentials other)
          : base(other)
        {
          TransportCertificate = other.TransportCertificate;
        }

        protected override ClientCredentials CloneCore()
        {
          return new MyClientCredentials(this);
        }

        public override SecurityTokenManager CreateSecurityTokenManager()
        {
          return new MyClientCredentialsSecurityTokenManager(this);
        }

        public void SetTransportCertificate(string subjectName, StoreLocation storeLocation, StoreName storeName)
        {
          SetTransportCertificate(storeLocation, storeName, X509FindType.FindBySubjectDistinguishedName, subjectName);
        }

        public void SetTransportCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType x509FindType, string subjectName)
        {
          TransportCertificate = FindCertificate(storeLocation, storeName, x509FindType, subjectName);
        }

        private static X509Certificate2 FindCertificate(StoreLocation location, StoreName name, X509FindType findType, string findValue)
        {
          X509Store store = new X509Store(name, location);
          try
          {
            store.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection col = store.Certificates.Find(findType, findValue, true);
            return  col.Count > 0 ? col[0] : null; // return first certificate found
          }
          finally
          {
            store.Close();
          }
        }
      }

    ClientCredentialsSecurityTokenManager derived class
    ---------------------------------------------------

    internal class MyClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager
      {
        MyClientCredentials credentials;

        public MyClientCredentialsSecurityTokenManager(MyClientCredentials credentials)
            : base(credentials)
        {
          this.credentials = credentials;
        }

        public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement requirement)
        {
          SecurityTokenProvider result = null;

          if (requirement.Properties.ContainsKey(ServiceModelSecurityTokenRequirement.TransportSchemeProperty) && requirement.TokenType == SecurityTokenTypes.X509Certificate)
          {
            result = new X509SecurityTokenProvider(this.credentials.TransportCertificate);
          }
          else if (requirement.KeyUsage == SecurityKeyUsage.Signature && requirement.TokenType == SecurityTokenTypes.X509Certificate)
          {
            result = new X509SecurityTokenProvider(this.credentials.ClientCertificate.Certificate);
          }
          else
          {
            result = base.CreateSecurityTokenProvider(requirement);
          }
          return result;
        }
      }

    Configuration handler for <clientCredentialsExtension> element in app.config
    ----------------------

    public class ClientCredentialsExtensionElement : ClientCredentialsElement
    {
    ConfigurationPropertyCollection properties;

    public override Type BehaviorType
    {
    get
    {
    return typeof(MyClientCredentials);
    }
    }

    [ConfigurationProperty("transportCertificate")]
    public X509InitiatorCertificateClientElement TransportCertificate
    {
    get
    {
    return base["transportCertificate"] as X509InitiatorCertificateClientElement;
    }
    }

    protected override ConfigurationPropertyCollection Properties
    {
    get
    {
    if (this.properties == null)
    {
    ConfigurationPropertyCollection properties = base.Properties;
    properties.Add(new ConfigurationProperty(
    "transportCertificate",
    typeof(X509InitiatorCertificateClientElement),
    null, null, null,
    ConfigurationPropertyOptions.None));
    this.properties = properties;
    }
    return this.properties;
    }
    }

    protected override object CreateBehavior()
    {
    MyClientCredentials creds = new MyClientCredentials(base.CreateBehavior() as ClientCredentials);

    PropertyInformationCollection properties = ElementInformation.Properties;

    creds.SetTransportCertificate(TransportCertificate.StoreLocation,
    TransportCertificate.StoreName,
    TransportCertificate.X509FindType,
    TransportCertificate.FindValue);

    base.ApplyConfiguration(creds);
    return creds;
    }
    }
    Thursday, August 31, 2017 9:24 AM

All replies

  • Hi mlssoft,

    >> I'm working on WCF client accessing remote Web Service. This service needs authentication using certificates for both SOAP message and HTTPS transport.

    What is the web service type? How did you configure it to use message and transport authentication?

    It would be helpful if you could share us more information about your web service.

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, September 1, 2017 5:12 AM
  • Hi Edward,

    This is arbitrary web service (not REST-compliant) produced probably using Java framework. This is secured on HTTPS transport level by both server and client certificates (CertificateRequest is called by the server) and also on message level (SOAP message signing).

    As you see in app.config in my first post, I use customBinding and extension of ClientCredentials class as extended behaviour in which I put transport credentials. Securing transport works well, securing the SOAP message doesn't work.

    Have you got any idea ?

    Best regards

    Henryk

    Friday, September 1, 2017 8:48 AM
  • Hi mlssoft,

    Do you mean you want to consume a web service which is developed by Java?

    Did you use VS or svcutil.exe to generate client code and configuration?

    Do you know the expected SOAP Request? I would suggest you try to check the difference between the expected SOAP and current SOAP Request.

    In general, the wsse:security will be added by custom binding with security.

    Which message security did you use? After checking your provided link, the authenticationmode is MutualCertificate and you configuration seems to be SecureConversation. Could you try to make a test with MutualCertificate?

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, September 4, 2017 5:58 AM
  • Hi Edward, thanks for your answer.

    I don't know the framework a web service is developed in but I guess that because it is one of Polish healthcare central services it is developed by Java.

    I created the code and configuration file using Visual Studio 2015 by importing WSDL delivered by healthcare organisation.

    I compared the SOAP generated by my app to SOAP generated by SoapUI (the organisation delivered us test project) and my SOAP is deprived wsse:security.

    According to your suggestion I tried AuthenticationMode=MutualCertificate. I did't have the oryginal service certificate so I used the certificate installled in Windows certificate store as a parent in certification path of my client WSS certificate. I set ServiceCertificate.SetDefaultCertificate in ClientCredentials behavior. Unfortunately it doesn't work - I received a message: "General security error(WSSecurityEngine: Callback supplied no password for: ...)". I think I should have a service certificate delivered immediately from the organisation. Am I right ?

    Best regards

    Henryk (mlssoft)

     

     

    Thursday, September 7, 2017 10:17 AM
  • Hi mlssoft,

    >> I don't know the framework a web service is developed in but I guess that because it is one of Polish healthcare central services it is developed by Java.

    Do you mean you consume Java Service from .net client?

    >> I compared the SOAP generated by my app to SOAP generated by SoapUI (the organisation delivered us test project) and my SOAP is deprived wsse:security.

    Could you share us the wsse:security missing part? I suggest you try to refer below link to implement custom binding to add wsse:security header.

    #How to make WCF Client conform to specific WS-Security - sign UsernameToken and SecurityTokenReference

    https://stackoverflow.com/questions/12832213/how-to-make-wcf-client-conform-to-specific-ws-security-sign-usernametoken-and

    Best Regards,

    Edward


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, September 8, 2017 2:51 AM