views:

480

answers:

2

My team is developing a number of WPF plug-ins for a 3rd party thick client application. The WPF plug-ins use WCF to consume web services published by a number of TIBCO services. The thick client application maintains a separate central data store and uses a proprietary API to access the data store. The thick client and WPF plug-ins are due to be deployed onto 10,000 workstations. Our customer wants to keep the certificate used by the thick client in the central data store so that they don't need to worry about re-issuing the certificate (current re-issue cycle takes about 3 months) and also have the opportunity to authorise the use of the certificate. The proposed architecture offers a form of shared secret / authentication between the central data store and the TIBCO services.

Whilst I don’t necessarily agree with the proposed architecture our team is not able to change it and must work with what’s been provided.

Basically our client wants us to build into our WPF plug-ins a mechanism which retrieves the certificate from the central data store (which will be allowed or denied based on roles in that data store) into memory then use the certificate for creating the SSL connection to the TIBCO services. No use of the local machine's certificate store is allowed and the in memory version is to be discarded at the end of each session.

So the question is does anyone know if it is possible to pass an in-memory certificate to a WCF (.NET 3.5) service for SSL transport level encryption?

Note: I had asked a similar question (here) but have since deleted it and re-asked it with more information.

+4  A: 

It is possible. We do something similar with Mutual Certificate Auth - the service certificate and in some cases the client certificate are picked up from a central authority as part of an auto-discovery/single-sign-on mechanism.

It's not entirely clear in what context the certificate will be used, but in all cases what you need to do is define your own behavior and behavior element deriving from the particular behavior/element in the System.ServiceModel.Description namespace that takes the certificate. I'll assume for the time being that it's a client credential. First you have to write the behaviour, which goes something like this:

public class MyCredentials : ClientCredentials
{
    public override void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime behavior)
    {
        // Assuming GetCertificateFromNetwork retrieves from CDS
        ClientCertificate.Certificate = GetCertificateFromNetwork();
    }

    protected override ClientCredentials CloneCore()
    {
        // ...
    }
}

Now you need to create an element that can go in the XML configuration:

public class MyCredentialsExtensionElement : ClientCredentialsElement
{
    protected override object CreateBehavior()
    {
        return new MyCredentials();
    }

    public override Type BehaviorType
    {
        get { return typeof(MyCredentials); }
    }

    // Snip other overrides like Properties
}

After this you can add the policy to your WCF config:

<behaviors>
    <endpointBehaviors>
        <behavior name="MyEndpointBehavior">
            <myCredentials/>
        </behavior>
    </endpointBehaviors>
</behaviors>

Edit: Almost forgot to mention, you need to register the extension:

<system.serviceModel>
    <extensions>
        <behaviorExtensions>
            <add name="myCredentials"
                 type="MyAssembly.MyCredentialsExtensionElement, MyAssembly,
                       Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </behaviorExtensions>
    </extensions>
</system.serviceModel>

Hope that helps. If you need more details on the arrangement of all of these classes and what's going on behind the scenes, try reading Extending WCF with Custom Behaviors.

Aaronaught
Thank you so much for your suggestion. I'll let you know how I get on.
Kane
+2  A: 

Hi Guys,

I'm the guy who got Kane (our SO lackey!) to ask the original question. I thought I'd finally create an account and post our findings / results / experiences in regards to the answer posted by Aaronaught (so any credit to him above).

We tried adding a custom behaviour as suggested above and setting the behaviourConfiguration on the endpoint configuration element to use it. We couldn't get the code to fire at all so ended up going with a programmatic approach.

As we had a wrapper class set up to build a ClientBase object we used our existing creation functions to add the behaviour after building all the other parts of the ClientBase.

We ran into a few issues doing this also, namely that a ClientCredentials behaviour was already being defined for our ClientBase authenticating with a Username and Password rather than our Certificate + Username and Password. So we removed the existing behaviour programmatically before adding our new certificate based behaviour (with the Username and Password injected) as a temporary measure for testing. Still no dice, our behaviour was being constructed and ApplyClientBehavior was being fired but the service was still falling over when Invoke was called (we never got the real Exception due to a bunch of using statements that were difficult to refactor out).

We then decided instead of removing the existing ClientCredentials behaviour that we would just inject our certificate into it before letting the whole lot procede as normal. Third times a charm and it's all up and working now.

I'd like to thank Aaronaught (and I would vote up if I could!) for putting us on the right trail and providing a well thought out and useful answer.

Heres a small code snippet of it up and running (using a test .CRT file).

     protected override ClientBase<TChannel> CreateClientBase(string endpointConfigurationName)
    {
        ClientBase<TChannel> clientBase = new ClientBase<TChannel>(endpointConfigurationName); // Construct yours however you want here

        // ...

        ClientCredentials credentials = clientBase.Endpoint.Behaviors.Find<ClientCredentials>();

        X509Certificate2 certificate = new X509Certificate2();
        byte[] rawCertificateData = File.ReadAllBytes(@"C:\Path\To\YourCert.crt");
        certificate.Import(rawCertificateData);

        credentials.ClientCertificate.Certificate = certificate;

        return clientBase;
    }

As another side note, as part of testing we removed all our certificates from the local machine store, this actually caused a problem using Fiddler. Fiddler didn't detect our client certificate because it was purely in memory and not in the the trusted store. If we added it back in to the trusted store then Fiddler started to play nice again.

Thanks again.

jcfox
Thanks for the answer jules :)
Kane
Hmm, very puzzling that you couldn't get the behavior to run. Did you maybe forget to configure the endpoint with the behavior? Anyway, looks like you guys found a workaround, so all's well that ends well!
Aaronaught
Yeah, we configured the endpoint to use the behaviour but could only get it to trigger when adding it in programmatically. It was a bit bizarre but we didn't pursue it further as we took a bit of a change in direction.
jcfox