Wcf: EnableUnsecuredResponse flag not supported

Created on 27 May 2019  Â·  14Comments  Â·  Source: dotnet/wcf

Hello, I am migrating an application which contains some WCF services calls from .NET Full to .Net Core. Unfortunately, one of these services could not be implemented because it requires a Custom Binding with the following security element:

securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityElement.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic128;
securityElement.IncludeTimestamp = true;
securityElement.LocalClientSettings.DetectReplays = false;
securityElement.LocalServiceSettings.DetectReplays = false;
securityElement.EnableUnsecuredResponse = true;

EnableUnsecuredResponse flag is not available in .Net Core and without setting that flag I get a well known exception TransportSecurityBindingElement.BuildChannelFactoryCore is not supported.
I didn't find any valid workaround for that. It would be great to have this feature supported in the next release.

Backlog bug

All 14 comments

Adding @mconnew
@MaxiPigna if you are able to provide a simple client and server for this scenario it would assist with adding support for the flag.

From @mconnew …

I just had a quick search for anything about that property and I believe the issue is down to the security header being written on the wire like:

    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="true"></wsse:Security>

vs

  <wsse:Security soapenv:mustUnderstand="true" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"/>

The first one has a security header with content which is zero length, the second one has no content. It’s semantically the different between having a list of child nodes which is empty vs the list itself being null. Apparently WCF trips up if it gets something it’s not expecting.

We are doing some additional investigating and gathering of info to make sure we have correctly identified the issue. That being the case a possible workaround would be to create a custom message encoder that would either modify the security header or remove it altogether.

A code fix is probably the right way to go but it may not make it into the 3.0 release time frame.

After some additional investigation it looks like the issue is the difference between having no security header at all versus having a security header with no content.

From @mconnew the short-term workaround is to

create a modified message encoder and add that header in with no contents.

We will consider what the correct long-term fix is for a future release.
Let us know if there is anything else we can do.

Moving to future work.

I see that TransportSecurityBindingElement have the EnableUnsecuredResponse flag, but it;s not being propagated by 'TransportSecurityBindingElement.Clone' method. (Because it calls 'new TransportSecurityBindingElement(this)' . This constructor does not initialize the EnableUnsecuredResponse property.

@maheshnlrb, I was just going to suggest you submit a pull request, then saw you already have!

Hello, finally I solved by following @mconnew's suggestion. This is a sample code provided by
@mconnew to implement a Custom Message Encoder by wrapping an existing Message Encoder. It has been very helpful! 😊

@MaxiPigna glad @mconnew was able to help. Let us know if there is anything else we can do.

Custom Message Encoder

@MaxiPigna Can you provide an specific example of how you ended up implement the missing property please?

@MaxiPigna Can you provide an specific example of how you ended up implement the missing property please?

@echavez087 my previous message contains the link with the example. For semplicity, here it is:
https://github.com/dotnet/wcf/files/3307783/CustomMessageEncoder.zip

@MaxiPigna Can you provide an specific example of how you ended up implement the missing property please?

@echavez087 my previous message contains the link with the example. For semplicity, here it is:
https://github.com/dotnet/wcf/files/3307783/CustomMessageEncoder.zip

Thanks for your reply @MaxiPigna I did take a look at the sample, I just was't able to figure out how to add the missing property using that sample. I would really appreciate if you could help me figure it out. Sorry I don't have much experience with WCF. Thanks.

Basically the problem was that the response message didn't contain a security header.

From @mconnew …

I just had a quick search for anything about that property and I believe the issue is down to the security header being written on the wire like:

    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="true"></wsse:Security>

vs

  <wsse:Security soapenv:mustUnderstand="true" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"/>

The first one has a security header with content which is zero length, the second one has no content. It’s semantically the different between having a list of child nodes which is empty vs the list itself being null. Apparently WCF trips up if it gets something it’s not expecting.

We are doing some additional investigating and gathering of info to make sure we have correctly identified the issue. That being the case a possible workaround would be to create a custom message encoder that would either modify the security header or remove it altogether.

A code fix is probably the right way to go but it may not make it into the 3.0 release time frame.

The workaround proposed was to manually add that header (an empty value is enough) to the response when reading the message. So, if you look at the CustomMessageEncoderFactory you see that the two ReadMessage methods before returning the message, they add an empty security header.

private Message AddSecurityHeader(Message message)
{
        var header = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "", true);
        message.Headers.Add(header);
        return message;
}

AddSecurityHeader

@MaxiPigna thank you very much for the detailed explanation it helped me to understand much better. :)

@MaxiPigna I tried your suggestion but got the error,

"The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding. "

If I use any other security mode the server tells me I have an invalid username and password.

// https://github.com/dotnet/wcf/issues/2219
MethodInfo method = typeof(XmlSerializer).GetMethod("set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
method.Invoke(null, new object[] { 1 });

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
SubmitCustomerData submitCustomerData = JsonConvert.DeserializeObject<SubmitCustomerData>(requestBody);

var binding = new System.ServiceModel.BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential;

var customBinding = new CustomBinding(binding);
var elements = customBinding.Elements;
var i = -1;
for (i = 0; i < elements.Count; i++)
{
    if (typeof(MessageEncodingBindingElement).IsAssignableFrom(elements[i].GetType()))
        break;
}
var mebe = (MessageEncodingBindingElement)elements[i];
elements[i] = new CustomMessageEncodingBindingElement(mebe);

// https://community.workday.com/sites/default/files/file-hosting/productionapi/Revenue_Management/v35.0/Revenue_Management.wsdl
EndpointAddress address = new EndpointAddress("https://our_own_implementation_address/Revenue_Management/v35.0");

Revenue_ManagementPortClient rm = new Revenue_ManagementPortClient(customBinding, address);
rm.ClientCredentials.UserName.UserName = "username";
rm.ClientCredentials.UserName.Password = "password";

where

public partial class Revenue_ManagementPortClient : System.ServiceModel.ClientBase<Revenue_ManagementPort>

If I set a breakpoint at return message; in your CustomMessageEncoderFactory.AddSecurityHeader(Message message) method, the message at that point is the correct response from the server.. it's like the client sees the added Security header and rejects the whole thing.
If I don't add the Security header via your encoder, then I get the error that started this whole issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mconnew picture mconnew  Â·  6Comments

Adam-Tg picture Adam-Tg  Â·  5Comments

Petermarcu picture Petermarcu  Â·  4Comments

DotNetPart picture DotNetPart  Â·  6Comments

hongdai picture hongdai  Â·  5Comments