28 April, 2010

WCF: Restrict which Clients can call your WCF Service methods via X.509 Certificates

Today a colleague asked me how you can prevent anybody but a known client from being able to call your WCF service/service-methods. It goes without saying that we are aiming at controlling the calling machine in this scenario and not so much interested in the calling user. A quite common scenario when doing server-to-server communication in e.g. a B2B situation. Well – it can be accomplished quite securely and robust by using X.509 certificates which identifies the calling machine.

I should mean that it is preferred if the solution to this problem is a completely configured solution instead of a coded solution. No code – just configuration. In this way, the compiled assemblies remain the same and no need for recompilation is needed when you decide to change certificates or other “external” things. This I did not completely succeed with, as the below solution requires a single security-attribute (PrincipalPermission) on the methods that you want protected. As seen in the figure – this service is to be called by 2 different clients; one with a valid certificate and one without a certificate. The last should fail and be thrown away when trying to call the service methods!

Running sample:
You can download a running sample (Proof-Of-Concept) here: X509.POC.zip

Capture

How do we accomplish this scenario?

First – lets focus on the WCF Service that is to be protected.

SERVICE SIDE:
Now – to insist that the Client should identity himself via X.509 certificates to the Service, we need to configure this in the service configuration in the <bindings> configuration section. In the below, a custom configuration is seen called “messageAndCertificateClient”. This mandates that the calling Client should present a Certificate (set in the clientCredentialsType attribute) to identity/authenticate himself to the service.

<configuration>
<
system.serviceModel>
<
services>
<
service name="Server.CalcService" behaviorConfiguration="serviceCredentialsBehavior">
<
endpoint name=""
address="http://localhost/Calculator"
binding="wsHttpBinding"
bindingConfiguration="messageAndCertificateClient"
contract="X509.SharedLib.ICalcService" />
</
service>
</
services>

<
bindings>
<
wsHttpBinding>
<
binding name="messageAndCertificateClient">
<
security mode="Message">
<
message clientCredentialType="Certificate" />
</
security>
</
binding>
</
wsHttpBinding>
</
bindings>

<
behaviors>
<
serviceBehaviors>
<
behavior name="serviceCredentialsBehavior">
<
serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost/Calculator/mex"/>
<
serviceCredentials>
<!--
Service certificate (used for initial handshake and negotiation between C/S)-->
<
serviceCertificate findValue="HabaneroServer"
x509FindType="FindBySubjectName"
storeLocation="LocalMachine"
storeName ="My"/>
<
clientCertificate>
<!--
How should Client certificates be authenticated?-->
<
authentication certificateValidationMode="None"/>
</
clientCertificate>
</
serviceCredentials>
<!--
use AspNetRoles to concatentate CN and thumbPrint -->
<
serviceAuthorization principalPermissionMode="UseAspNetRoles"/>
</
behavior>
</
serviceBehaviors>
</
behaviors>
</
system.serviceModel>
</
configuration>


The server side has a simple method implementation/signature seen below. As seen, the service method (‘Add’) has been attributed with a PrincipalPermissionAttribute (system.security.permissions). This attribute is set with the Name property to “CN=<subject>; <thumbprint>”. This is quite important as the later tag <serviceAuthorization principalPermissionMode=”UseAspNetRoles”/> makes WCF concatenate the incoming Clients certificate data (subject and certificate unique thumbprint) into a semicolon separated string. This string is used for authorization later on by means of simple string comparison. It is important to mention that this PrincipalPermissionAttribute can be applied to the service method level only and does not apply to the service level. You are however in this way able to control access to a very granular level and do not span the service in it’s entirety.

[ServiceContract(Namespace="http://services.ckit.dk/2010/1")]
public interface ICalcService
{
[OperationContract]
int Add(int a, int b);

[OperationContract]
int Subtract(int a, int b);
}



class CalcService : ICalcService
{
#region ICalcService Members

//can only be called if cert = <thumb>
[PrincipalPermission(SecurityAction.Demand, Name = "CN=HabaneroClient; 6cc9da1b64592d1f0ce46824674c833a30993bba")]
public int Add(int a, int b)
{
Console.WriteLine("Service called...");
return a + b;
}

public int Subtract(int a, int b)
{
Console.WriteLine("Service called...");
return a - b;
}

#endregion
}


Now – you might be wondering where did this thumbprint stuff come from? The subject and thumbprint that we want to allow to use our service methods are both coming from the Clients certificate. It is retrieved by opening the Clients certificate in the Certificate Manager (MMC-snapin) and copying them into notepad (this is done on the Client machine!).


image


CLIENT SIDE: We need to setup the client to present certificates to the service. How is this accomplished? The same way anything else is setup when dealing with WCF. It is configured in the Clients configuration file:

 
CLIENT SIDE:
The client service is set up like this, mandating that a Certificate is presented to the (server) service. In addition, an expected identity (HabaneroServer) is set in the dns value setting. The Certificate presented by the client is called HabaneroClient and found in the local Certificate Storage.
<?xml version="1.0" encoding="utf-8"?>
<
configuration>
<
system.serviceModel>
<
client>
<
endpoint name="localEP"
address="http://localhost/Calculator"
binding="wsHttpBinding"
behaviorConfiguration="endpointCredentialsBehavior"
bindingConfiguration="certBehave"
contract="X509.SharedLib.ICalcService">
<
identity>
<!--
Expected identity of server (certificate)-->
<
dns value="HabaneroServer" />
</
identity>
</
endpoint>
</
client>

<
behaviors>
<
endpointBehaviors>
<
behavior name="endpointCredentialsBehavior">
<
clientCredentials>
<!--
Identity to present to service-->
<
clientCertificate findValue="HabaneroClient"
storeLocation="LocalMachine"
x509FindType="FindBySubjectName"/>
<
serviceCertificate>
<!--
How will the Client validate the Servers certificate?-->
<
authentication certificateValidationMode="None"/>
</
serviceCertificate>
</
clientCredentials>
</
behavior>
</
endpointBehaviors>
</
behaviors>

<
bindings>
<
wsHttpBinding>
<
binding name="certBehave" >
<
security mode="Message">
<
message clientCredentialType="Certificate" />
</
security>
</
binding>
</
wsHttpBinding>
</
bindings>
</
system.serviceModel>
</
configuration>

As seen above, the Client configuration is more or less a copy of the Server side configuration. It mandates that the Client presents a certificate as authentication to the service.

ASP.NET/Silverlight: How to pass credentials to Silverlight control

I have an embedded Silverlight application (SL) in my website. This SL calls some backend WCF services; services which require authentication (usr/pwd) to be used.

I was kind of thinking; that the SL application embedded in my aspx-page did not have a clue that it was running in the context of an aspx-page. That way – I needed to provide some means of “shared token” to be passed from the hosting aspx-page into the SL application upon initialization, to make the SL control call the WCF-services in an authenticated way.

image

Well – well, it turns out that “someone” has been thinking about this problem beforehand. Here goes:

First of all – my SL application resides on an aspx page that is secured by the <location> tag in the ASP.NET application hosting the SL application. That way – to access the SL application in the first place; you need to login to the ASP.NET application via conventional FORMS login. Now – the really clever thing here is, that every call that goes from the SL application is fed through the network stack of the hosting browser. In this way every call from the SL application is attributed by the browser before it leaves the Clients PC. And this also applies to session tokens.

So the session token obtained when logging into the hosting ASP.NET application, is applied to the SL calls going back to the WCF-service on the backend automatically! Pretty darn clever.

To use this on the server side (WCF-service), you do need to set the AspNetCompatibilityRequirements attribute to gain acccess to the HttpContext in the WCF-service. Otherwise it will not work.

[ServiceContract(Namespace = "something here")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class HelloService
{
[OperationContract]
public string SayHello()
{
string usr = HttpContext.Current.User.Identity.Name;
return string.Format("Hello, {0}", usr);
}
}


Notes to consider:
1) You need to have the AspNetCompatibilityRequirements attribute applied to the service
2) The WCF service must reside within the hosting ASP.NET application to use the same session token.


Technorati Tags: ,,

19 April, 2010

Streaming images to/from website

If you want to “disguise” the origin of your image files or just want more control of the way images are sent to the Client; you can resolve to streaming the images instead of providing them via conventional disk load. By interacting with this process, you are able to control in a more granular way how the images are resized, tagged or any other operation you want to perform before delivering the images to the client. Maybe you simply want to add a watermark before sending them to the client browser? The below 2 images are both coming from a generic httphandler serving these images to the .aspx page.

image

How is this accomplished?

The way this is accomplished is this. I have a simple web application (Imageserver.POC) here.

image As seen in the figure, the project consists of 3 important files:
1) DisplayPage.aspx (conventional aspx-page)
2) ImageProvider.ashx (generic http-handler)
3) ResponseStreamer.cs (handles pushing image to client)

The displayPage.aspx is a conventional .aspx page with embedded image controls (see below). Note the way the ImageUrl is formatted (pointing to the ImageProvider.ashx):

image

By referencing the ImageProvider in this way – the ImageControls (Image1 and Image2) are not bound directly to a physical file, but instead to a httphandler serving the request.

ImageProvider.ashx (httphandler)

The ImageProvider.ashx is very simple in this example. It implements the IHttpHandler interface and has one method (ProcessRequest). In the example it only checks if a querystring exists (a more advanced scenario would most likely be added here). If the querystring count > 0, it serves the photo called “photo.jpg” to the client. And in the above example, the Client is an “<asp:image” control ;-).

image 

The ImageProvider uses a helper class (ResponseStreamer) to actually push the image to the client via the Response object. This class is seen below:

ResponseStreamer.cs:

image

You can most likely come up with additional processing and checks to make before serving the image to the Client, but all control is in the hands of the server developer by using the above method.

02 April, 2010

WCF: Prevent circular reference problems

Having a collection that contains elements pointing back to their parents, you are almost certain to run into a serialization problem.

image

The problem is, that the child points back to it’s parent. Both child and parent carries a collection of children and the serializer runs into an infinite loop of elements => you have a serialization problem!

image

This was previously solved by a custom serializer, but now (3.5 SP1/3.0 SP2) you can cope the problem by adding the attribute ‘IsReference’ to the datacontract of the type in question (see above). This prevents the endless looping and marks the (in the above case) folder as a reference only. In this way -  the ‘Parent’ folder object is only stored as a reference and NOT a full ‘folder’ object.

InRiver: Not loading your extensions?

(You really need to in the loop to appreciate the issue this post addresses). Man, I've been fighting this problem for hours before I ...