dotnet / wcf

This repo contains the client-oriented WCF libraries that enable applications built on .NET Core to communicate with WCF services.
MIT License
1.7k stars 559 forks source link

netstandard 2.0 - support for TransportWithMessageCredential in BasicHttpsBinding #4469

Open fptkolodziejczyk opened 3 years ago

fptkolodziejczyk commented 3 years ago

Hello, question especially to @mconnew or anyone. I want to ensure that we have support for TransportWithMessageCredential in BasicHttpsBinding (not BasicHttpBinding) in netstandard 2.0.

In this site we can see support for BasicHttpBinding, but I'm guess that we have support also for BasicHttpsBinding. https://github.com/dotnet/wcf/blob/master/release-notes/SupportedFeatures-v2.0.0.md

This code throws NotSupportedPlatformException:

var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.TransportWithMessageCredential)
                {
                };

but this code below doesn't throw exception and looks like everything works. I'm not sure if everything is secured/implemented under hood:

var binding = new BasicHttpsBinding
                {
                    Security =
                    {
                        Mode = BasicHttpsSecurityMode.TransportWithMessageCredential
                    }
                };

This is reference to your source code: https://github.com/dotnet/wcf/blob/release/2.0.0/src/System.Private.ServiceModel/src/System/ServiceModel/BasicHttpsBinding.cs

This is some kind of bug in this code and I can use this code OR BasicHttpsBinding is only partially/not supported? I tried upgrade netstandard 2.0 to netstandard 2.1 but it looks like doesn't help. Upgrade for my case is generally not a good idea because it's a big cost.

fptkolodziejczyk commented 3 years ago

@mconnew any news on this topic? This is very important to my client.

mconnew commented 3 years ago

Sorry for the slow reply, I was taking a much needed break over the Christmas holiday. That's a bug for that binding, but you can still do what you want using BasicHttpBinding, just pass an https address to the channel factory. We have tests to validate that scenario so I know it's working.
The BasicHttpsBinding binding just has the default security mode be Transport, otherwise it's pretty much identical.

fptkolodziejczyk commented 3 years ago

Thank you very much for you response @mconnew :) It was very helpful!

fptkolodziejczyk commented 3 years ago

@mconnew Are you planning to fix this bug BasicHttpsBinding with BasicHttpsSecurityMode.TransportWithMessageCredential in any version of .net standard?

HongGit commented 3 years ago

@imcarolwang could you please take a look and see if you could fix this? In the meantime, we probably should also looking into add test coverage to this (copy the existing test for BasicHttpBinding)

fptkolodziejczyk commented 3 years ago

@mconnew, @HongGit, @imcarolwang I have similar problem (System.PlatformNotSupportedException: 'Configuration files are not supported.') when I don't pass any binding to constructor ApiClient class which inherits after System.ServiceModel.ClientBase.

program.cs

        public ApiClient CreateApiClient()
        {
            var client = new ApiClient();
            client.ClientCredentials.UserName.UserName ="user";
            client.ClientCredentials.UserName.Password = "password";
            return client;
        }

app.config:

   <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="CustomBinding_IApiClient">
                    <security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport" requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
                        <localClientSettings detectReplays="false" />
                        <localServiceSettings detectReplays="false" />
                    </security>
                    <textMessageEncoding messageVersion="Soap11" />
                    <httpsTransport maxReceivedMessageSize="2147483647" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="https://test/api.svc" binding="customBinding" bindingConfiguration="CustomBinding_IApiClient" contract="WsdlService.IApiClient" name="CustomBinding_IApiClient" />
        </client>
    </system.serviceModel>

I think I can see a problem, we probably need change my client constructor and fill Binding and EndpointAddress as configuration files are not supported, right? How to fix this with CustomBinding and UserNameOverTransport? 2021-01-12_16h30_25

And the most important question: when are you going release these fixes and which version of .net core/netstandard? Because is the most proper way for us. Other implementation are not possible for us.

mconnew commented 3 years ago

WCF targets .NET Standard 2.0 so our latest release will work on all currently supported versions of .NET Core.
If you are using a custom binding, you don't need BasicHttpsBinding. I had a look at the PR Carol created to enable BasicHttpsBinding and there was an aspect I hadn't noticed and that was that there's an api missing which would be required for full support. It's the property BasicHttpsSecurity.Message needed to specify the client credential type. I'm not sure if we can release an api change in a patch update, I need to do some investigation.
As I said previously though, you can just use BasicHttpBinding instead. The difference is in the default security mode which you are overriding anyway.
To convert your custom binding, I think this will work:

var sbe = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
sbe.MessageSecurityVersion = MessageSeccurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
var encoder = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
var transport = new HttpsTransportBindingElement();
transport.MaxReceivedMessageSize = 2147483647;
var binding = new CustomBinding(sbe, encoder, transport);

For the security binding element, you don't need to set the algorithm suite, the require derivied keys, include timestamp or detect replays as you are using the default in all those cases. You should be able to use this custom binding without any further changes being released as everything in your custom binding is supported.

fptkolodziejczyk commented 3 years ago

@mconnew We have two seperate problems for completely different servers: 1.First one with asmx endpoint https://github.com/dotnet/wcf/issues/4469#issue-770672651 I tried use workaround with BasicHttpsBinding(BasicHttpsSecurityMode.Transport) but it doesn't work. The message from asmx service is: System.ServiceModel.FaultException: 'Server was unable to process request. ---> The request is not signed with an acceptable signature.' (error thrown for svc.ProcessMessage)

This is example code which I'm trying to use:

    public class ApiTest
    {
        public Table RunWcfRequest(string url, string login, string password, TableRequest request)
        {
            using (var svc = GetService(url, login, password))
            {
                var xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(request.XML);
                var requestId = Guid.NewGuid().ToString();
                var element = xmlDoc.DocumentElement;
                var payload = new Payload
                {
                    content = new[]
                    {
                        new Content
                        {
                            id = requestId,
                            Any = element
                        }
                    }
                };

                var manifest = new PayloadManifest
                {
                    manifest = new[]
                    {
                        new Manifest
                        {
                            element = element?.LocalName,
                            namespaceURI = element?.NamespaceURI,
                            contentID = requestId
                        }
                    }
                };
                svc.ProcessMessage(ref manifest, ref payload);

                var response = payload.content[0].Any;

                return request.ProcessResponse(response);
            }
        }

        private ApiClient GetService(string url, string login, string password)
        {
            var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport)
            {
                MaxReceivedMessageSize = int.MaxValue
            };

            var client = new ApiClient(binding, new EndpointAddress(url));

            client.ClientCredentials.UserName.UserName = login;
            client.ClientCredentials.UserName.Password = password;

            client.Endpoint.EndpointBehaviors.Add(new MessageInspectorBehavior());

            return client;
        }
    }

Original code have BasicHttpsSecurityMode.TransportWithMessageCredential. We can't use certificates for connection as server of asmx web service is external company. And I think we still need use TransportWithMessageCredential because we don't want decrease security, and we can wait some time for fix, but question is how long.
Do you know any workaround for my code? Maybe we should rewrite our code to gain the same effect. Or maybe this error is because server doesn't allow on connection with BasicHttpsSecurityMode.Transport? Even so, we still need TransportWithMessageCredential.

2.Second one with svc endpoint https://github.com/dotnet/wcf/issues/4469#issuecomment-758658304 We have problems with custom binding but it looks like this following code doesn't works for us (error is thrown for contract method which is not on this code):

    private static ApiClient CreateApiClient()
        {

            var sbe = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
            sbe.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
            var encoder = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
            var transport = new HttpsTransportBindingElement {MaxReceivedMessageSize = 2147483647};
            var binding = new CustomBinding(sbe, encoder, transport);

            var client = new ApiClient(binding, new EndpointAddress("https://test.com/Api.svc"));
            client.ClientCredentials.UserName.UserName = "<username>";
            client.ClientCredentials.UserName.Password = "<password>";

            return client;
        }

Error messages: -Main message: "System.ServiceModel.Security.MessageSecurityException: 'An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.'" -Inner exception: "FaultException: An error occurred when processing the security tokens in the message.", "InvalidSecurityToken"

mconnew commented 3 years ago

For your first problem, do you have it working using WCF on .NET Framework? Or are you using a web services client on .NET Framework and now trying to use WCF as web services client isn't supported? Someone else was trying to do this and they were use WSE to sign the body with the web services client and it hit a code path not enabled, but could easily be enabled. It just wasn't one that WCF clients normally use. I suspect you're hitting this case.

For your second problem, on .NET Framework, can you do the following and use a debugger to examine the contents of the CustomBinding.Elements and compare it with what the above shows in a debugger?

var customBinding = new CustomBinding("CustomBinding_IApiClient");

This will cause CustomBinding to load the binding from config and populate the Elements property with each of the components. If everything matches up correctly, can you capture a fiddler trace of the request for both .NET Framework and Core. You will either need to use some dummy temporary credentials or you will need to redact the username and password as the UserName profile sends the password in plain text (it might encode it, but it's easily reversible).

fptkolodziejczyk commented 3 years ago

@mconnew

Re 1: We're migrating code, all libraries which are using net472 are converted for now to netstandard2.0. This WCF code (ASMX client) is from library which is converted to netstandard2.0 and we have support for both frameworks: <TargetFrameworks>netstandard2.0;net472</TargetFramework >

And we have old API's in which we have net472, we just migrating API endpoints to new projects with .net core and web api. These new API's are using common libraries with netstandard2.0 and net472. And for some time we need old and new API's as some code doesn't work for now, i.e. these two WCF clients.

I made some tests. For old code with net framework I can't use BasicHttpsBinding(BasicHttpsSecurityMode.Transport) because I have also error: : 'Server was unable to process request. ---> The request is not signed with an acceptable signature.' It probably means that asmx server allow only on BasicHttpsBinding(BasicHttpsSecurityMode.TransportWithMessageCredential) and this code works for now on production.

We probably need wait on fix with BasicHttpsBinding(BasicHttpsSecurityMode.TransportWithMessageCredential) for netstandard. I don't understand thread with WSE, you probably asking if I'm trying to run this code on .net framework? This code actually works from long time (Net framework 472 with asmx client). Do you have any idea how to workaround this? In the end, we don't want to decrease security.

Re 2: This case is different. I can see 'svc' endpoint in browser and I can open 'svc?wsdl' file, but I can't connect to this wcf even with old .net framework code. This is possible that for old code is restriction and I can't use this code because I'm outside US OR maybe some firewall. I have errors: System.ServiceModel.ServerTooBusyException: 'The HTTP service located at https://<test.com>/Api.svc is unavailable. This could be because the service is too busy or because no endpoint was found listening at the specified address. Please ensure that the address is correct and try accessing the service again later.' Inner exception: WebException: The remote server returned an error: (503) Server Unavailable.

For new code with .net core I also have errors but different: System.ServiceModel.Security.MessageSecurityException: 'An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.' Inner Exception: FaultException: An error occurred when processing the security tokens in the message.

During creating constructor of connection for ApiClient I can find following differences (important in my opinion) in debug (this code doesn't throw any exceptions): -Channel for .net core is null, for Net Framework is correctly created -State for .net core is Faulted, for Net Framework is Created -Endpoint for .net core has: Address=error CS0103: The name '_address' does not exist in the current context System.ServiceModel.Description.ServiceEndpoint

Differences in fiddler when constructor of connection for ApiClient is ok and throws for both cases (old and new code) during calling method to API (I think this doesn't help as my old code doesn't work now): a).Net core: 2021-01-18_22h22_14 2021-01-18_22h21_38 b)Net framework: 2021-01-18_22h47_40 2021-01-18_22h48_18

I can't use this: var customBinding = new CustomBinding("CustomBinding_IApiClient"); You don't support endpoint configuration name via constructor: https://user-images.githubusercontent.com/73098587/104335248-80d28b00-54f3-11eb-8f1d-f2a8fb17a71d.png

Do you need any more specific tests/fiddler tracing? I think rest of Fiddler tracing is pretty similar.

fptkolodziejczyk commented 3 years ago

@mconnew which release date is for this fix https://github.com/dotnet/wcf/pull/4486 ?

fptkolodziejczyk commented 3 years ago

@mconnew Do you have any update/approximate date when 5.0.2 will be released? (it doesn't have to be exactly)

mconnew commented 3 years ago

@fptkolodziejczyk, for some reason I thought this would need an api change which can't happen in a patch release. But I just looked at the PR again and I'm not seeing one so I think I was confusing issues. I've merged the change and will discuss with @HongGit about when we want to release an update.

fptkolodziejczyk commented 3 years ago

@mconnew Ok, thanks for update. Any ideas to: https://github.com/dotnet/wcf/issues/4469#issuecomment-762488973 Re.2 ?

mconnew commented 2 years ago

@fptkolodziejczyk, in regard to Re.2, I don't have enough information to say what's going on. The server is returning a 503 error, so there's something the server didn't like about the request. You would need to contact the service owner to get assistance debugging tha. If the service is running WCF, you can also use svc?SingleWsdl in a browser and save the file to disk. Then you can point all the tooling such as svcutil to the save file. If it isn't WCF and can't use the SingleWsdl option, you can download the base wsdl, then examine it in a text editor and find all the imports specified, then go and download each of the imports. You might need to fix up the import statements in the wsdl if they are absolute paths, but once you've done that, then you can point the code generation tooling at the downloaded files. It's less than ideal, but without knowing the root cause, it's the only suggestion I have.

As for CustomBinding with an endpoint configuration name, that's because we can't support app.config in .NET Core as we rely heavily on a lot of stuff in the machine.config file. You need to configure your CustomBinding in code. If you are having difficulty translating your app.config configuration into the code equivalent, you can provide your config here and I can help you convert it.