WCF: Message Security with a Java Client
The aim is to secure communication between two systems using mutual certificates. The server side is a .NET Windows Service (i.e. a self-hosted WCF service) and the client side a Java application. This is one of the common scenarios documented on the WCF MSDN site and uses WS-Security and X.509 certificates. However, the example is only for .NET so it's worth showing the full picture; along with using self-signed certs which we can create for development purposes.
First, the server side steps:
The Code
Create the WCF contract and implementation:
[ServiceContract(Namespace="http://test.server.com/sampleservice")]
public interface ISampleServiceContract {
[OperationContract]
string Message();
}
public class SampleServiceContract : ISampleServiceContract {
public string Message() {
return "Hello from the server!";
}
}
In the service start, fire up the WCF ServiceHost:
var serviceHost = new ServiceHost(typeof(SampleServiceContract));
serviceHost.Open();
The Config
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SampleServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
<serviceCredentials>
<serviceCertificate findValue="server.test.com"
storeLocation="CurrentUser"
storeName="TrustedPeople"
x509FindType="FindBySubjectName"/>
<clientCertificate><authentication certificateValidationMode="PeerTrust" /></clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFSecurity.Server.SampleServiceContract" behaviorConfiguration="SampleServiceBehavior">
<host>
<baseAddresses><add baseAddress="http://localhost:8000/sampleservice"/></baseAddresses>
</host>
<endpoint binding="wsHttpBinding" bindingConfiguration="InteropCertificateBinding" contract="ISampleServiceContract">
<identity><dns value="server.test.com"/></identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="InteropCertificateBinding">
<security mode="Message">
<message algorithmSuite="Basic128"
clientCredentialType="Certificate"
negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
A few points of note. The serviceCertificate tells WCF where to find the server certificate - see below about creating this. As this is a test, the domain's don't match, so I use the identity/dns elements to specify the domain manually. Lastly, Java doesn't support the WCF default encryption algorithm, so change it to Basic128.
Creating the server certificate
It's fairly simple to create the self-signed certificate using makecert:
makecert -r -pe -n "CN=server.test.com" -b 01/01/2012 -e 01/01/2014 -ss my -sr currentuser -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
As this is self-signed, we need to copy it manually trust it. Open an mmc console (Start -> Run -> mmc), File -> Add/Remove Snap-in, Add..., pick Certificates then Add, leave My user account selected and Finish.
Open Certificates - Current User / Personal / Certificates and the newly created server.test.com should be listed. Copy and paste the server.test.com cert to the Trusted People node.
The application should now start successfully. Browse to the endpoint as configured above to confirm you can view the WSDL.
The Client
The Java side is built using Netbeans. Download the "Java EE" version.
Certs
Let's begin by creating the client certificate and create a truststore for the server certificate. In a similar fashion to makecert for .NET, the keytool utility can generate our test cert:
keytool -genkey -alias wcf-client -keyalg RSA -keystore keystore.jks
This will then prompt for the various certificate attributes. Fill it out so the cert looks like:
Is CN=client.test.com, OU=Client, O=Test.com, L=London, ST=UK, C=UK correct?
[no]:
Next, we need the server certificate which we want to trust. So back in the mmc console, right-click the server.test.com cert and All Tasks / Export. Do not export the private key, leave as DER encoded, and call the file server.test.com.cer. This needs importing into a Java truststore:
keytool -import -alias wcf-server -file server.test.com.cer -keystore truststore.jks
Type yes to confirm that it should be trusted.
The last thing is to do the reverse: the client cert should be exported and added to the server's cert store so that the client is trusted. Export the client cert from the Java keystore like so:
keytool -export -alias wcf-client -keystore keystore.jks -rfc -file client.test.com.cer
Copy this file to the server and in the mmc console, under Personal / Certificates, right-click and select All Tasks / Import. Pick the exported client certificate file and complete the import wizard. Copy and paste the certificate into the Trusted People node (as this is a self-signed cert).
The Code
Open NetBeans and create a new Java Application: WcfClient. Right-click on the project and create a new Web Service Client. Enter the service WSDL URL - http://server:8000/sampleservice?wsdl. Select the default Package and leave the Client Style as JAX-WS Style (this option may not exist, in which case this is the default anyway).
Right-click on the sampleservice node under Web Service References to configure the security. Enter the keystore details:

And the truststore details:

Now we just need to invoke the service. With WcfClient.java open and the current active tab, expand the sampleservice tree to the method level and drag the Message node into the editor. This will auto-create a message method. Simply call the method and output the result from the main:
public static void main(String[] args) {
System.out.println(message());
}
private static String message() {
wcfclient.SampleServiceContract service = new wcfclient.SampleServiceContract();
wcfclient.ISampleServiceContract port = service.getWSHttpBindingISampleServiceContract();
return port.message();
}
Finally, make sure the METRO 2.0 library has been added to the project, then simply compile and run. Hopefully "Hello from the server!" should be printed.
