WCF security interop scenarios

https://msdn.microsoft.com/en-us/netframework/webservicesinterop.aspx has the following case studies which are a great resource for interop scenarios and have been tested with specific vendors.

Resources

Case Studies

Whitepapers

Videos

Blogs

The following are some adhoc scenarios that may be helpful in certain conditions but its recommended to refer the above case studies above first.

Scenario #1: Interop with WCF service/client using mutual certificates.
https://msdn.microsoft.com/en-us/library/ms733102.aspx talks about using WsHttpBinding to implement this in a WCF-WCF scenario and is also an interoperable binding, but this doesn’t work when the other party doesn’t support EncryptedKeys. WsHttpBinding creates a SymmetricSecurityBindingElement which creates a symmetric security key encrypted with the transport certificate of the recipient.
This may result in an error message similar to the following:

Signature verification failed: An error was discovered processing the <wsse:Security> header. (Unsupported KeyInfo type) or Could not locate the security token referenced by key info.

So, we had to switch to a customBinding that does not use the encrypted symmetric key by setting the authenticationMode to “MutualCertificateDuplex”.

Also, other security authentication modes like CertificateOverTransport only sign some SOAP-Headers – not the SOAP-Body. This is the authentication mode used by security mode “TransportWithMessageCredentials” – it is relying on the transport security (SSL channel) to verify that the message was not modified. The SOAP-headers are only signed, because authentication is done by using message-credentials.
https://msdn.microsoft.com/en-us/library/aa751836.aspx lists the various authentication modes available. As you can see this mode provides an Asymmetric security binding element.

MutualCertificateDuplex
With this authentication mode, the client authenticates using an X.509 certificate that appears at the SOAP layer as an endorsing supporting token; that is, a token that signs the message signature. The service is also authenticated using an X.509 certificate. The binding is a AsymmetricSecurityBindingElement returned by the CreateMutualCertificateDuplexBindingElement method. Alternatively, set the authenticationMode attribute to MutualCertificateDuplex.

The following is the binding that worked in this particular scenario.

 <customBinding>
<binding name="myCustomBinding">
<transactionFlow/> 
<security authenticationMode="MutualCertificateDuplex" 
allowSerializedSigningTokenOnReply="true" 
requireSignatureConfirmation="false" 
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10" 
requireDerivedKeys="false"/> <textMessageEncoding messageVersion="Soap11"/> 
<httpsTransport/> 
</binding> 
</customBinding> 
<endpoint address="https://<ServerName>/Service.svc" binding="customBinding" bindingConfiguration="myCustomBinding" 
contract="ServiceReference1.IService" name="CustomBinding_IService" behaviorConfiguration="ClientBehavior"> 
</endpoint> 
<behaviors> 
<endpointBehaviors> 
<behavior name="ClientBehavior"> 
<clientCredentials> 
<clientCertificate findValue="Cohowinery.com" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySubjectName"/> 
<serviceCertificate> 
<defaultCertificate findValue="Contoso.com" storeLocation="CurrentUser" storeName="TrustedPeople" x509FindType="FindBySubjectName"/> 
<authentication revocationMode="NoCheck" certificateValidationMode="None"/> 
</serviceCertificate> 
</clientCredentials> 
</behavior>
</endpointBehaviors>
</behaviors>

Note: You need to have the privateKey for the Client certificate (Cohowinery.com) installed and the identity of the client application needs to have access to the private key.
Service Certificate (Contoso.com) is the certificate for the service, this is usually the SSL certificate.

Scenario #2: Sign the message only and not encrypt it as well.
You can control this using the ProtectionLevel property.
See https://msdn.microsoft.com/en-us/library/aa347791.aspx
For a WCF client, you can change this property for the ServiceContractAttribute/OperationContractAtribute in the proxy generated code (Reference.cs)
The following could be an error message resulting from this:
There is no web service configured to match the incoming request. No pipelines match incoming SOAP request

Scenario #3: Support a specific message encoding version.
https://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messageversion.aspx talks about the various message encoding versions supported by WCF.
Default message version is WS-Addressing 1.0 and SOAP 1.2. If the other party requires the messages to be encoded only in Soap1.1 and if it does not understand the “WS-Addressing” headers, the WS-Addressing headers can be disabled by modifying the messageVersion in the message encoding element of the customBinding.   
          <textMessageEncoding messageVersion="Soap11"/>The following could be an error message resulting from this:
System.ServiceModel.ActionMismatchAddressingException,:The SOAP action specified on the message, '', does not match the HTTP SOAP Action, 'https://tempuri.org/IService/GetData'.

Scenario #4: Support a specific algorithm used for encryption.
Mismatch in the encryption algorithm on client and server can result in error message similar to the following:
System.ServiceModel.Security.MessageSecurityException: The algorithm 'https://www.w3.org/2001/04/xmlenc#rsa-1_5' is not accepted for operation 'AsymmetricKeyWrap' by algorithm suite Basic256

This can be configured using the defaultAlgorithmSuite property of the security binding element in a customBinding.
The available options are https://msdn.microsoft.com/en-us/library/system.servicemodel.security.securityalgorithmsuite.aspx
Also, WCF traces for the request and response can be used to see what is the current encryption method being used e.g.
<e:EncryptionMethod Algorithm="https://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"> <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" xmlns="https://www.w3.org/2000/09/xmldsig#"></DigestMethod> </e:EncryptionMethod>

Scenario #5: Disabling WS-Security signature confirmation.

Setting requireSignatureConfirmation to true in WCF can lead to the following error if the other party doesn’t have WS-Security signature confirmation enabled.

<Exception> <ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 </ExceptionType> <Message>Not all the signatures in the request message were confirmed in the reply message.</Message> <StackTrace> at System.ServiceModel.Security.MessageSecurityProtocol.CheckSignatureConfirmation(ReceiveSecurityHeader securityHeader, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout) at System.ServiceModel.Channels.TransactionRequestChannelGeneric`1.Request(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type) </StackTrace> <ExceptionString>System.ServiceModel.Security.MessageSecurityException: Not all the signatures in the request message were confirmed in the reply message.</ExceptionString> </Exception>

Scenario #6: Serializing tokens in the request/response.
If server has included the public key of the server certificate in the response, it could lead to the following error on the client:
Cannot find a token authenticator for the 'System.IdentityModel.Tokens.X509SecurityToken' token type. Tokens of that type cannot be accepted according to current security settings.

This can be addressed by setting allowSerializedSigningTokenOnReply="true".
https://blogs.msdn.com/b/hongmeig/archive/2006/12/06/mutualcertificate-with-server-x509-cert-in-the-response.aspx talks about this.

Sceanrio#7: Allowing unsecured response.

Hotfix https://support.microsoft.com/kb/971493 provides this functionality.

Windows Communication Foundation (WCF) does not have the functionality to send secured messages and then receive unsecured responses, or to send unsecured messages and receive secured responses. The hotfix that is described in this article adds a new enableUnsecuredResponse attribute. The default value of the enableUnsecuredResponse attribute is false. If you set the value of this attribute to true, the following new features are enabled in WCF:

  • WCF clients can accept unsecured responses even if the outgoing messages are secured by using the Secure Socket Layer (SSL) protocol to sign the message body.
  • WCF services can send unsecured responses that have no security header in SOAP envelopes even if the request is secured.

Note The enableUnsecuredResponse attribute only applies only to the CustomBinding type.

Here is a config snippet to use this attribute.
<customBinding> <binding name="myCustomBinding"> <security authenticationMode="MutualCertificateDuplex" enableUnsecuredResponse="true"/> <textMessageEncoding messageVersion="Soap11"/> <httpsTransport requireClientCertificate="false" /> </binding></customBinding>

Scenario#8: Using separate certificates for signing and encryption

In a mutual certificate scenario, WCF expects the response to be signed with the service certificate, if it’s not the following error message is thrown
<ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType> <Message>The incoming message was signed with a token which was different from what used to encrypt the body. This was not expected.</Message> <StackTrace> at System.ServiceModel.Security.TokenTracker.RecordToken(SecurityToken token) at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout) at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader) at System.ServiceModel.Security.StrictModeSecurityHeaderElementInferenceEngine.ExecuteProcessingPasses(ReceiveSecurityHeader securityHeader, XmlDictionaryReader reader) at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout) at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

If the service certificate is the same on the server side, then check to see if the exported service certificate (installed on the client) has the same thumbprint as the service certificate. It may be an issue of a corrupt certificate.
If the server indeed uses a different certificate for signing, you may need to implement the solution described in https://msdn.microsoft.com/en-us/library/ms729856.aspx.
https://webservices20.blogspot.com/2010/09/wcf-server-signs-response-with.html is a good reference on this.

Scenario#9: Passing UserName token as a supporting token.
https://blogs.msdn.com/b/sajay/archive/2006/12/12/passing-a-username-as-a-supporting-token.aspx has a WCF client and WCF service sample for this.
For an interop scenario, where the symmetric security might be an issue (as described in Scenario#1 above), you can also use the code from https://blogs.msdn.com/b/distributedservices/archive/2010/06/14/wcf-interoperability-guidelines-1-reference-style-of-a-primary-signing-token-inside-a-response.aspx.
If there is a mismatch in the format of the primary signing token, Dhruba talks about how to write a custom channel to insert a <SecurityTokenReference> element to encapsulate the X509Data in the above blog.
Also, in https://blogs.msdn.com/b/dhrubach/archive/2010/06/18/10027513.aspx he talks about a possible error when the primary signing token is not inside of a BinarySecurityToken.

 <Exception> 
<ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType> 
<Message>
Cannot resolve KeyInfo for verifying signature: 
KeyInfo 'SecurityKeyIdentifier ( IsReadOnly = False, Count = 1, Clause[0] = X509IssuerSerialKeyIdentifierClause(Issuer = 'CN=XXXX', Serial = '665497663279805416273') ) ', 
available tokens 'SecurityTokenResolver ( TokenCount = 0, ) '.
</Message> 
<StackTrace> 
at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.ResolveSignatureToken(SecurityKeyIdentifier keyIdentifier, SecurityTokenResolver resolver, Boolean isPrimarySignature) 
at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.VerifySignature(SignedXml signedXml, Boolean isPrimarySignature, SecurityHeaderTokenResolver resolver, Object signatureTarget, String id) 
at System.ServiceModel.Security.ReceiveSecurityHeader.ProcessPrimarySignature(SignedXml signedXml, Boolean isFromDecryptedSource) 
at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader) at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout) 
at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) 
at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) 
at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

Scenario #10: Message protection order.
If Message protection order doesn’t match between the two parties, you may get the following error:
Digest verification failed for Reference '#Body-c9e6c885-4a67-437d-b513-98a7a51168ce'.This can be set with the following property:
https://msdn.microsoft.com/en-us/library/system.servicemodel.security.messageprotectionorder.aspx

Sceanario#11: Ordering of elements in the security header.
In some scenarios, you may need to change the default ordering of the elements in the security header. The default is Strict.
If you cannot make the other party to generate security header with the same element order as WCF expects, you can change the layout to Lax.
        securityHeaderLayout="Strict/Lax/LaxTimestampFirst/LaxTimestampLast"One possible error that can result from this is the following:
<Exception> <ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType> <Message>The security header element 'Timestamp' with the 'Timestamp-26' id must be signed.</Message> <StackTrace> at System.ServiceModel.Security.ReceiveSecurityHeaderElementManager.EnsureAllRequiredSecurityHeaderTargetsWereProtected() at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout, ChannelBinding channelBinding, ExtendedProtectionPolicy extendedProtectionPolicy) at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout) at System.ServiceModel.Channels.TransactionRequestChannelGeneric`1.Request(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type) at TestClient.ServiceReference1.AccountWebService.getParticipantInfo(getParticipantInfo request) at TestClient.ServiceReference1.AccountWebServiceClient.TestClient.ServiceReference1.AccountWebService.getParticipantInfo(getParticipantInfo request) at TestClient.ServiceReference1.AccountWebServiceClient.getParticipantInfo(String CampaignCode, String CentraxID) at TestClient.Program.Main(String[] args) at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() </StackTrace> <ExceptionString>System.ServiceModel.Security.MessageSecurityException: The security header element 'Timestamp' with the 'Timestamp-26' id must be signed.</ExceptionString> </Exception>

Sceanario#12: Incorrect version of security header. Having an incorrect messageSecurityVersion in the client binding can lead to the following error:
In this particular case, the server was sending a response with the following KeyIdentifier(note that this is WS-Security 1.1 version) and the client’s messageSercurityVersion was set to WS-Security 1.0.

 messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"

<wsse:SecurityTokenReference xmlns:wsu="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-18679393"> <wsse:KeyIdentifier EncodingType="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="https://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1"><ValueRemoved></wsse:KeyIdentifier> </wsse:SecurityTokenReference><Exception> <ExceptionType>System.Xml.XmlException, System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType> <Message>Cannot read KeyIdentifierClause from element 'KeyIdentifier' with namespace 'https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'. Custom KeyIdentifierClauses require custom SecurityTokenSerializers, please refer to the SDK for examples.</Message> <StackTrace> at System.ServiceModel.Security.WSSecurityJan2004.SecurityTokenReferenceJan2004ClauseEntry.ReadKeyIdentifierClauseCore(XmlDictionaryReader reader) at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierClauseCore(XmlReader reader) at System.IdentityModel.Selectors.SecurityTokenSerializer.ReadKeyIdentifierClause(XmlReader reader) at System.ServiceModel.Security.XmlDsigSep2000.KeyInfoEntry.ReadKeyIdentifierCore(XmlDictionaryReader reader) at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierCore(XmlReader reader) at System.IdentityModel.Selectors.SecurityTokenSerializer.ReadKeyIdentifier(XmlReader reader) at System.IdentityModel.Signature.ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager) at System.IdentityModel.SignedXml.ReadFrom(XmlDictionaryReader reader) at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.ReadSignatureCore(XmlDictionaryReader signatureReader) at System.ServiceModel.Security.ReceiveSecurityHeader.ReadSignature(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer) at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader) at System.ServiceModel.Security.StrictModeSecurityHeaderElementInferenceEngine.ExecuteProcessingPasses(ReceiveSecurityHeader securityHeader, XmlDictionaryReader reader) at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout, ChannelBinding channelBinding, ExtendedProtectionPolicy extendedProtectionPolicy) at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout) at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout) at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs) at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type) </StackTrace> <ExceptionString>System.Xml.XmlException: Cannot read KeyIdentifierClause from element 'KeyIdentifier' with namespace 'https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'. Custom KeyIdentifierClauses require custom SecurityTokenSerializers, please refer to the SDK for examples.</ExceptionString> </Exception>