Catch-22 prevents streamed TCP WCF service securable by WIF; ruining my Christmas, mental health
Asked Answered
M

1

184

I have a requirement to secure a streamed WCF net.tcp service endpoint using WIF. It should authenticate incoming calls against our token server. The service is streamed because it is designed to transfer large amounts of data n stuff.

This appears to be impossible. And if I can't get around the catch, my Christmas will be ruined and I'll drink myself to death in a gutter while merry shoppers step over my slowly cooling body. Totes serious, you guys.

Why is this impossible? Here's the Catch-22.

On the client, I need to create a channel with the GenericXmlSecurityToken I get from our token server. No problemo.

// people around here hate the Framework Design Guidelines.
var token = Authentication.Current._Token;
var service = base.ChannelFactory.CreateChannelWithIssuedToken(token);
return service.Derp();

Did I say "no problemo"? Problemo. In fact, NullReferenceException style problemo.

"Bro, " I asked the Framework, "do you even null check?" The Framework was silent, so I disassembled and found that

((IChannel)(object)tChannel).
    GetProperty<ChannelParameterCollection>().
    Add(federatedClientCredentialsParameter);

was the source of the exception, and that the GetProperty call was returning null. So, WTF? Turns out that if I turn on Message security and set the client credential type to IssuedToken then this property now exists in the ClientFactory (protip: There is no "SetProperty" equivalent in IChannel, the bastard).

<binding name="OMGWTFLOL22" transferMode="Streamed" >
    <security mode="Message">
        <message clientCredentialType="IssuedToken"/>
    </security>
</binding>

Sweet. No more NREs. However, now my client is faulted at birth (still love him, tho). Digging through WCF diagnostics (protip: make your worst enemies do this after crushing them and driving them before you but right before enjoying the lamentations of their women and children), I see it's because of a security mismatch between the server and client.

The requested upgrade is not supported by 'net.tcp://localhost:49627/MyService'. This could be due to mismatched bindings (for example security enabled on the client and not on the server).

Checking the host's diags (again: crush, drive, read logs, enjoy lamentations), I see this is true

Protocol Type application/ssl-tls was sent to a service that does not support that type of upgrade.

"Well, self," I says, "I'll just turn on Message security on the host!" And I do. If you want to know what it looks like, it's an exact copy of the client config. Look up.

Result: Kaboom.

The binding ('NetTcpBinding','http://tempuri.org/') supports streaming which cannot be configured together with message level security. Consider choosing a different transfer mode or choosing the transport level security.

So, my host cannot be both streamed and secured via tokens. Catch-22.

tl;dr: How can I secure a streamed net.tcp WCF endpoint using WIF???

Maggiore answered 19/12, 2013 at 20:54 Comment(19)
Ok, probably ignorant question here, but does WIF really require Message mode? Transport mode sounds like it would work better with streaming, something like the obviously untested <security mode="Transport" /> <transport clientCredentialType="IssuedToken" /> </security>Texture
TransportWithMessageCredential mode may be another option.Texture
@JoachimIsaksson: Can't use Message security at all.Maggiore
@JoachimIsaksson: Also, <transport clientCredentialType="IssuedToken" /> is invalid.\Maggiore
TMLK, MessageSecurity can sign and encrypt buffered payload, but fumbles when dealing with streams. Have you considered using authenticationMode=IssuedTokenOverTransport?Tenantry
@OnoSendai: What is this IssuedTokenOverTransport you speak of? Searching... FYI, I'll be bountying this question when I can, so if you can write an answer with a sample config of IssuedTokenOverTransport + a streamed endpoint I'll try that out. The holidays (and my drinking myself to death in a gutter) may intervene, unfortunately.Maggiore
Let me see if i can summon some ghosts from the past to help save your holidays then. Some hints here: social.msdn.microsoft.com/Forums/vstudio/en-US/…Tenantry
MSDN: 'The issued token appears at the SOAP layer as either an endorsing supporting token or a bearer token; that is, a token that signs the message signature.' So if I remember this right, Auth token will be at SOAP envelope, instead of message itself. msdn.microsoft.com/en-us/library/aa751836(v=vs.110).aspxTenantry
Looking into that now, btw. If it works, I can supplement an answer submitted by you with code snippets. Need some time to work it up.Maggiore
@Will Not sure but this might help: zamd.net/2008/07/04/federation-over-tcp-streamingMyronmyrrh
@Sheldon thanks for the bounty, but I'm afraid it will be wasted.Maggiore
Any chance you can post a test-case project that others could experiment with?Tompkins
@antiduh: Unfortunately, my prototype is using our API and our STS server, so it would be a decent amount of work to convert it. There are some demos out there that are set up to secure an HTTP endpoint; all you need to do is grab one and then convert the endpoint to streamed net.tcp.Maggiore
I am going to be using up some of my company's partner support time with Microsoft on this. If I am able to find a solution I'll post the relevant info here.Maggiore
Have you considered splitting the service into two calls? One authentication service and another for the streaming portion (that accepts a token returned by the first - which is secured via WIF)Silverweed
@x0n: Actually, that is how we are doing it. The token is in my hand before I call the streamed service. Any method of using that token to secure said stream would be acceptable.Maggiore
@will I answered with clarification as to why what you're doing is fundamentally not possible using ws-security.Silverweed
This question is under discussion in MetaFillister
@Kiquenet just what you see here.Maggiore
S
44

WCF has gotchas in a few areas with streaming (I'm looking at you, MTOM1) due to a fundamental issue in how it fails to perform preauthentication the way most people would think that should work (it only affects subsequent requests for that channel, not the first request) Ok, so this is not exactly your issue but please follow along as I will get to yours at the end. Normally the HTTP challenge works like this:

  1. client hits server anonymously
  2. server says, sorry, 401, I need authentication
  3. client hits server with authentication token
  4. server accepts.

Now, if you ever try to enable MTOM streaming on an WCF endpoint on the server, it will not complain. But, when you configure it on the client proxy (as you should, they must match bindings) it will explode in a fiery death. The reason for this is that the above sequence of events that WCF is trying to prevent is this:

  1. client streams 100MB file to server anonymously in a single POST
  2. server says sorry, 401, I need authentication
  3. client again streams 100MB file to server with an authentication header
  4. server accepts.

Notice that you just sent 200MB to the server when you only needed to send 100MB. Well, this is the problem. The answer is to send the authentication on the first attempt but this is not possible in WCF without writing a custom behaviour. Anyway, I digress.

Your Problem

First up, let me tell you that what you're trying is impossible2. Now, in order for you to stop spinning your wheels, let me tell you why:

It strikes me that you are now wandering in a similar class of problem. If you enable message level security, the client must load the entire stream of data into memory before it can actually close out the message with the usual hash function and xml signature required by ws-security. If it has to read the entire stream to sign the single message (which is not really a message, but it's a single continuous stream) then you can see the problem here. WCF will have to stream it once "locally" to compute the message security, then stream it again to send it to the server. This is clearly a silly thing, so WCF does not permit message level security for streaming data.

So, the simple answer here is that you should send the token either as a parameter to the initial web service, or as a SOAP header and use a custom behaviour to validate it. You cannot use WS-Security to do this. Frankly, this is not just a WCF issue - I cannot see how it could practically work for any other stacks.

Solving the MTOM Problem

This is just for an example how I solved my MTOM streaming issue for basic authentication, so perhaps you could take the guts of this and implement something similar for your issue. The crux of it is that in order to enable your custom message inspector, you have to disable all notion of security on the client proxy (it remains enabled on the server,) apart from transport level (SSL):

this._contentService.Endpoint.Behaviors.Add(
    new BasicAuthenticationBehavior(
        username: this.Settings.HttpUser,
        password: this.Settings.HttpPass));
var binding = (BasicHttpBinding)this._contentService.Endpoint.Binding;
binding.Security.Mode = BasicHttpSecurityMode.Transport; // SSL only            
binding.Security.Transport.ClientCredentialType = 
   HttpClientCredentialType.None; // Do not provide

Note that I have turned off transport security here because I will be providing that myself using a message inspector and custom behaviour:

internal class BasicAuthenticationBehavior : IEndpointBehavior
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationBehavior(string username, string password)
    {
        this._username = username;
        this._password = password;
    }
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        var inspector = new BasicAuthenticationInspector(
            this._username, this._password);
        clientRuntime.MessageInspectors.Add(inspector);
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
}

internal class BasicAuthenticationInspector : IClientMessageInspector
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationInspector(string username, string password)
    {
        this._username = username;
        this._password = password;
    }

    public void AfterReceiveReply(ref Message reply,
        object correlationState) { }

    public object BeforeSendRequest(ref Message request,
        IClientChannel channel)
    {
        // we add the headers manually rather than using credentials 
        // due to proxying issues, and with the 101-continue http verb 
        var authInfo = Convert.ToBase64String(
            Encoding.Default.GetBytes(this._username + ":" + this._password));

        var messageProperty = new HttpRequestMessageProperty();
        messageProperty.Headers.Add("Authorization", "Basic " + authInfo);
        request.Properties[HttpRequestMessageProperty.Name] = messageProperty;

        return null;
    }
}

So, this example is for anyone who is suffering with the MTOM issue, but also as a skeleton for you to implement something similar to authenticate your token generated by the primary WIF-secured token service.

Hope this helps.

(1) Large Data and Streaming

(2) Message Security in WCF (see "disadvantages.")

Silverweed answered 5/1, 2014 at 17:12 Comment(1)
MTOM and Basic Authorization, and MTOM and OAuth2 ?Bickel

© 2022 - 2024 — McMap. All rights reserved.