none
Need advise on how to sign request's soap body using WCF

    Question

  • Hello.   Please skip to the questions at the bottom of this post if you don't think you'll need all this introductory info.

    I need to write a client using WCF that will consume a non-.NET web service that uses SOAP 1.1 and WS-Security 1.0 over SSL.  The only requirement is that the request's soap body is signed.  The reply, on the other hand, is not signed.  It doesn't have any soap headers, in fact.

    So, I went and created the proxy class using svcutil.  The binding used in the generated config was basicHttpBinding and the security mode was Transport (because it was an https url).  The effect was that it would send the soap request over https without a digital signature.

    I tried making all sorts of changes to hopefully make it sign the soap body but nothing would work for me.  Here is the latest version of my config file (I added the text in blue and modified the text in red):

    <configuration>
        <system.serviceModel>
        <behaviors>
          <endpointBehaviors>
            <behavior name="endpointCredentialsBehavior">
              <clientCredentials>
                <clientCertificate findValue="some_value_here"
                   storeLocation="CurrentUser"
                  x509FindType="FindBySubjectName" />
              </clientCredentials >
            </behavior>
          </endpointBehaviors>
        </behaviors>
            <bindings>
                <basicHttpBinding>
                    <binding name="ITransactionProcessor" closeTimeout="00:01:00"
                        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                        allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                        <security mode="TransportWithMessageCredential">
                            <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                            <message clientCredentialType="Certificate" algorithmSuite="Default" />
                        </security>
                    </binding>
                </basicHttpBinding>
            </bindings>
            <client>
                <endpoint address="
    https://rest_of_url_here"
                    behaviorConfiguration="endpointCredentialsBehavior"
                    binding="basicHttpBinding" bindingConfiguration="ITransactionProcessor"
                    contract="ITransactionProcessor" name="portXML" />
            </client>
        </system.serviceModel>
    </configuration>

    These changes, sadly, only made it sign the Timestamp element in the headers, not the soap body.  :(

    Here are my questions:

    1. How do I make it sign the SOAP body?  Is there some place I can specify which elements to sign?  I read somewhere that the SOAP body is automatically signed but I'm not seeing it happening.

    2. With this configuration, I expectedly get a fault from the web service since it expects the soap body to be signed.  However, the other problem is that .NET throws some exception (which escapes me right now), saying that the reply did not have any security headers.  Apparently, it expects two-way security.  How do I make it such that it will not expect the reply to have security in it?

    3. I noticed that when I switch to wsHttpBinding, the soap request uses SOAP 1.2 namespaces, rendering it useless for me at this point.  Is that expected behavior?  Does basicHttpBinding use SOAP 1.1 and wsHttpBinding use SOAP 1.2?  Or can I mix and match?  If so, how?

    4. Should I look into custom binding?  Will it allow me to sign the soap body in the requests and not expect the reply to be signed?  I have not tried it but I've looked at the documentation.  I was looking for something about being able to specify which elements to sign but I couldn't find anything.  So, I'm reluctant to try it.

    Thanks.  I'd really appreciate any help.

     

    Thursday, November 23, 2006 5:35 AM

Answers

All replies

  • Oops, just wanted to correct the spelling error.  I need advice, not advise.  No big deal, I know.  But I'd feel better. :)

     

    Thursday, November 23, 2006 5:43 AM
  • You should set the protection level on the message you want to sign

    You can use a wsHttpBinding and set the service behavior use a service certificate and it will use the cert to sign or sign and encrypt you message.

    http://msdn2.microsoft.com/en-gb/library/aa347791.aspx

    Thanks
    Sajay

    Thursday, November 23, 2006 4:37 PM
  • Hello, Sajay.  I tried what I think you were suggesting.

    svcutil created a class called inputMessageIn:

    [System.ServiceModel.MessageContractAttribute(IsWrapped=false, ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
    public partial class inputMessageIn

    {...}

    I added the ProtectionLevel part but that did not take effect.  It's still only signing the timestamp in the header.  I would like it to sign the SOAP body.  Is ProtectionLevel meant for client-side proxy classes too?  The documentation is rather vague.

    Also, I cannot use wsHttpBinding as it uses SOAP 1.2.  The web service only accepts SOAP 1.1 at this time.

    Thanks.  And again, I'd really appreciate your or anyone's help.

    Tuesday, November 28, 2006 1:17 AM
  • The thread http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=953014&SiteID=1 has some information regarding your problem.

    Pedro Felix
    Tuesday, November 28, 2006 9:06 AM
    Moderator
  • Thanks, Pedro.  Were you able to implement what you wanted?  Would you mind sharing with me (if you still remember) what your config file and/or source looked like?  While I could start to embark on a customBinding route per Michelle's 2nd suggestion in her last post, it would be nice if I didn't have to start completely in the dark.

    Btw, I think I need Transport security as I need to communicate via SSL.  At the same time, I need to sign the outgoing SOAP body.  The reply, on the other hand, will not have any signature or any WS-Security headers for that matter.

    Thanks again!

    Tuesday, November 28, 2006 7:32 PM
  • Hello.

    Unfortunately, I don't see any other way of doing what you want without implementing a custom binding element, the way Michele suggested.
    It is possible to express your requirements using WS-Policy. However, as discussed in the referenced thread, WCF does not seems to support it.

    Sorry.
    Pedro Felix
    Wednesday, November 29, 2006 10:17 AM
    Moderator
  • Hello mteverest,

     

    I have similar issues for a situation connecting to a WSE 2 Service.

    They require SSL plus a signed body.  I have tried everything but cant seem to be able to create a proxy to consume the service.

    I would have thought that WCF would be able to talk to a Microsoft Service that cant be any more than two years old.

     

    Did you ever resolve this issue - nobody seems to want to go near this type of query.

     

    Regards,

    Edmund.

     

    Tuesday, May 15, 2007 11:00 PM
  • I like to second that, I am also in need of a solution for creating a WCF client to interact with a service that required SSL + the soap body signed.

    Regards,
    Wim.
    Wednesday, October 01, 2008 2:43 PM
  • Hi,

     

    The soap body must be signed using what kind of credential (e.g. certificate, windows)?

    Is the message pattern one-way?

    Is the body of request also signed?

    Regards

    Pedro Félix

     

     

    Wednesday, October 01, 2008 2:56 PM
    Moderator
  • The soap body must be signed using a certificate. The message pattern is two way, but only the request message needs to be signed. I already add an unsigned timestamp on my response message as WCF wants this.

    For an other of the services I need a .NET client for, it is also required I add an extra SOAP header element which in turn also has to be signed.

    Regards,
    Wim.
    Thursday, October 02, 2008 7:02 AM
  • Unfortunately, I don't know of any easy method to acomplish the desired goal.

     

    I've though of two solutions:

    1) Use an AsymmetricBindingElement created using SecurityBindingElement.CreateCertificateSignatureBindingElement, hovewer this only works with one-way messages

     

    2) Use an TransportSecurityBindingElement created using  SecurityBindintElement.CreateCertificateOverTransportBindingElement. However, with this only some header elements get signed by the certificate and not the body.

     

    A possible solution would be to add a binding element that creates a protocol channel that signs the body. However, I suspect that this is not an easy task.

     

    Regards

    Pedro Félix

    Friday, October 03, 2008 3:22 PM
    Moderator
  • Well, there might be a way to acomplish your goal.

     

    1) Create a custom binding with both:

    a) an HttpsTransportBindingElement - this will give you transport level security

    b) an AsymmetricSecurityBindingElement with an X509SecurityTokenParameters - this will give you message level security

     

    2) Annotate the operation contract with [OperationContract(ProtectionLevel=ProtectionLevel.Sign)] so that the message is only signed (not encrypted).

     

    HTH

    Pedro Félix 

     

    Friday, October 03, 2008 3:48 PM
    Moderator
  • Hello,

    I tried the custom binding and it works, thanks !

    The only issue I am resolving right now is that my j2ee webservice has to returned a signed timestamp as required by WCF.
    Before when using just transport security and skipping the message integrity check in my j2ee service I only returned an unsigned timestamp.

    For another possible issue, I have to sign an additional SOAP header for another service, but as I figure, this should be possible with the custom client ContractBehavior I have used for setting the operation contracts.

    But anyway, thanks for the help.
    Monday, October 06, 2008 8:54 AM
  • I was a bit to quick in shouting that I got it to work apparantly.

    I have an issue right now in validating the signed response message. The response message is now signed with the private key matching the certificate I specified as ServiceCertificate.DefaultCertificate on the WCF client.
    As I understand this certificate will be used for encryption of the request message ( which is turned off in my case ) and for verification that the response is signed with the private key corresponding to that certificate. Is this a right interpretation ?

    Yet I am running into a MessageSecurityException on validating the response security headers in the WCF client. 
    The error message is in dutch but it should translate to something like:
        System.ServiceModel.Security.MessageSecurityException: The incoming message is signed with a different token than the token by which the main message is coded. This is an unexpected error.

    Googling around gave me no clues as to my error in my setup.

    Any ideas ?
    Tuesday, October 07, 2008 9:18 AM
  • Hi,

     

    1) Are you sure that the certificate defined in the DefaultCertificate is the one used by the service

     

    2) Could you enable client-side trace log and post the traced exceptions, including the inner exceptions.

     

    3) You could also enable client-side message log and analyze the response message, namely the used security token.

     

    HTH

    Pedro Félix

     

    • Proposed as answer by roma48 Tuesday, December 01, 2009 6:59 PM
    Tuesday, October 07, 2008 9:25 AM
    Moderator
  • I am using the AsymmetricSecrityBindingElement created by SecurityBindingElement.CreateMutualCertificateDuplexBindingElement by the way.
    Also with AllowSerializedSigningTokenOnReply set to true.
    Tuesday, October 07, 2008 9:30 AM
  • Hey

    1) Yes, just double checked with the keystore used in my j2ee service.

    2)

    Test Case Failures:
    1) safe_online_sdk_dotnet.test.cs.TestIdMapping.TestMethod : System.ServiceModel.Security.MessageSecurityException : Het binnenkomende bericht is ondertekend met een ander token dan het token waarmee de hoofdtekst gecodeerd is. Dit is een onverwachte fout.

    Server stack trace:
    bij System.ServiceModel.Security.TokenTracker.RecordToken(SecurityToken token)
    bij System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)
    bij System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)
    bij System.ServiceModel.Security.StrictModeSecurityHeaderElementInferenceEngine.ExecuteProcessingPasses(ReceiveSecurityHeader securityHeader, XmlDictionaryReader reader)
    bij System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)
    bij System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message& message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
    bij System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message& message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
    bij System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
    bij System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout)
    bij System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
    bij System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
    bij System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
    bij System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
    bij System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
    bij System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

    Exception rethrown at [0]:
    bij System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
    bij System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
    bij IdMappingWSNamespace.NameIdentifierMappingPort.NameIdentifierMappingQuery(NameIdentifierMappingQueryRequest request)
    bij IdMappingWSNamespace.NameIdentifierMappingPortClient.IdMappingWSNamespace.NameIdentifierMappingPort.NameIdentifierMappingQuery(NameIdentifierMappingQueryRequest request) in c:\Users\devel\Csharp\safe-online-sdk-dotnet\target\services\SafeOnlineNameIdentifierMappingWebService.cs:regel 4983
    bij IdMappingWSNamespace.NameIdentifierMappingPortClient.NameIdentifierMappingQuery(NameIDMappingRequestType NameIDMappingRequest) in c:\Users\devel\Csharp\safe-online-sdk-dotnet\target\services\SafeOnlineNameIdentifierMappingWebService.cs:regel 4990
    bij safe_online_sdk_dotnet.IdMappingClientImpl.getUserId(String username) in c:\Users\devel\Csharp\safe-online-sdk-dotnet\src\main\cs\IdMappingClientImpl.cs:regel 51
    bij safe_online_sdk_dotnet.test.cs.TestIdMapping.TestMethod() in c:\Users\devel\Csharp\safe-online-sdk-dotnet\safe-online-sdk-dotnet-test\src\test\cs\TestIdMapping.cs:regel 21

    3) Trying to figure out how to achieve this programmatically at the moment

    Tuesday, October 07, 2008 9:54 AM
  • Hi,

     

    2) I'm sorry, but Dutch does not belongs to the set of know languages

     

    3) Use service config editor to enable message tracing. Don't forget to enable full message tracing at transport level.

     

    Regards

    Pedro Félix

     

    Tuesday, October 07, 2008 10:34 AM
    Moderator
  • 2) The error would translate to:

    System.ServiceModel.Security.MessageSecurityException: The incoming message is signed with a different token than the token by which the main message is coded. This is an unexpected error.

    Sadly enough I am running on Vista Home Basic which has no support to change to display language ...

    3) I am not able to use the service config editor. I am using SharpDevelop where I develop all the clients programatically. I am using svcutil in my project file to create the service and client ports. I generated a config with logging enabled, added the logging part to the generaed config by SvcUtil and then turned on /mergeConfig but when running my Nunit tests for the clients, logging is still not eanbled.

    Basically quite stuck, are there any other pointers to delve a bit deeper into finding the issue ? Its only 4 days since I have dived into the .NET environment to create .NET clients for our j2ee services.

    Regards,
    Wim.
    Tuesday, October 07, 2008 12:23 PM
  • Hi

     

    1) The following code snippet shows how to enable message logging

    Code Snippet

        <system.diagnostics>
            <sources>
                <source name="System.ServiceModel" switchValue="Verbose,ActivityTracing"
                    propagateActivity="true">
                    <listeners>
                        <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                            <filter type="" />
                        </add>
                        <add name="ServiceModelTraceListener">
                            <filter type="" />
                        </add>
                    </listeners>
                </source>
                <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
                    <listeners>
                        <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                            <filter type="" />
                        </add>
                        <add name="ServiceModelMessageLoggingListener">
                            <filter type="" />
                        </add>
                    </listeners>
                </source>
            </sources>
            <sharedListeners>
                <add initializeData="<path to trace log file>"
                    type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                    name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
                    <filter type="" />
                </add>
                <add initializeData="<path to message log file>"
                    type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                    name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
                    <filter type="" />
                </add>
            </sharedListeners>
        </system.diagnostics>
        <system.serviceModel>
            <diagnostics>
                <messageLogging logEntireMessage="true" logMalformedMessages="true"
                    logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="true" />
            </diagnostics>
        </system.serviceModel>

     

     

    2) I would try the following

    a) Analyse the trace log for thrown exceptions, namely the inner exceptions

    b) Analyse the token value or the token reference present in the response message and compare it with the used certificate

     

     

    HTH

    Pedro Félix

    Tuesday, October 07, 2008 1:21 PM
    Moderator
  • Thanks for the configuration example but I had found that part out. The issue I am having is not knowing how to "glue" this to my client port implementation.

    I generate the ports using SvcUtil, and then use it to implement my client as followed :


    ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(WCFUtil.OlasCertificateValidationCallback);

    string address = "https://" + location + "/foo-ws/idmapping";
    EndpointAddress remoteAddress = new EndpointAddress(address);

    this.client = new NameIdentifierMappingPortClient(new FooBinding(), remoteAddress);

    X509Certificate2 certificate = new X509Certificate2(pfxFilename, password);
    this.client.ClientCredentials.ClientCertificate.Certificate = certificate;

    X509Certificate2 serviceCertificate = new X509Certificate2("C:\\Users\\devel\\Csharp\\foo.crt", "secret");
    this.client.ClientCredentials.ServiceCertificate.DefaultCertificate = serviceCertificate;
    // To override the validation for our self-signed test certificates
    this.client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;

    this.client.Endpoint.Contract.ProtectionLevel = ProtectionLevel.Sign;
    this.client.Endpoint.Behaviors.Add(new LoggingBehavior());


    Not really sure how I can glue in the configuration to enable message logging
    Tuesday, October 07, 2008 2:08 PM
  • Just add the above configuration to your app.config (or create one if it does no texists)

     

    Pedro Félix

     

    Tuesday, October 07, 2008 2:36 PM
    Moderator
  • Hey,

    I didn't manage to get the logging working but I found out it simply did not work because indeed the service certificate was wrong ... Seems I made a stupid mistake twice before ...

    So now the binding is working, my j2ee service signs both timestamp and body and my WCF client accepts this.

    Thanks for the great help!

    Regards,
    Wim.
    Wednesday, October 08, 2008 11:43 AM
  • Hey,

    I am having one unresolved issue with one of my services where a custom SOAP header is inserted, which in turn needs to be signed.

    Adding the custom header I have figured out. But signing it is not successfull yet.
    I followed a blog from Nicholas Allen regarding this and created a custom IContractBehavior and added it to the client's endpoint contracts behaviors.
    The behavior tries to add the soap header to the signature as follows :

    public void AddBindingParameters(ContractDescription contractDescription,
    ServiceEndpoint endpoint,
    BindingParameterCollection bindingParameters)
    {
    Console.WriteLine("add TargetIdentity SOAP header to signature parts");
    ChannelProtectionRequirements requirements = bindingParameters.Find<ChannelProtectionRequirements>();
    MessagePartSpecification targetIdentityPart =
    new MessagePartSpecification(new XmlQualifiedName("TargetIdentity", "urn:libertyTongue Tiedb:2005-11"));
    requirements.OutgoingSignatureParts.AddParts(targetIdentityPart);
    }

    Yet at my j2eee service, the TargetIdentity SOAP header is not included in the signature.


    Any ideas ?


    Regards,
    Wim.
    Wednesday, October 08, 2008 3:06 PM
  • Hi again,

     

    You should add the message part specification to the IncomingSignatureParts and not to the OutgoingSignatureParts.

    The MSDN docs state that

    a) IncomingSignatureParts - Gets a collection of message parts that are signed for messages from client to server

    b) OutgoingSignatureParts - Gets a collection of message parts that are signed for messages from server to client

     

    The Incoming/Outgoing names apparently refer to the server side, however you are doing a client.

     

    HTH

    Pedro Félix

    Wednesday, October 08, 2008 5:53 PM
    Moderator
  • Thanks, my last webservice is now also interoperable with .NET

    Appreciated your help Pedro.

    Kind regards,
    Wim.
    Thursday, October 09, 2008 8:21 AM
  • I'm glad your problems are completly solved. Interoperability issues regarding security aspects tend to be hard to solve.

     

    Regards

    Pedro Félix

    Thursday, October 09, 2008 12:47 PM
    Moderator
  • Hi, how did you combined this two things. I have tried to, but I'm getting exception that this is only for OneWay web operation...

    Thanks

    Mirec
    Sunday, November 30, 2008 9:59 AM
  • 2) For the search engines, the error message from the English resources would be: System.ServiceModel.Security.MessageSecurityException: The incoming message was signed with a token which was different from what used to encrypt the body.  This was not expected.
    Tuesday, March 31, 2009 8:48 PM