Development

Development

Implementing WS-Security with CXF in a WSDL-First Web Service

Security is one of the most common requirements for SOAP-based web services. Several standards exist, among them WS-Security and WS-SecurityPolicy. They can be hard to implement, and they are often ignored in favor of a more ad hoc security standard, most often using password authentication in the message itself and SSL for transport layer security.

Trying to implement these standards recently, I had a very hard time finding a consistent and complete guide for doing so, or even a good explanation of the standards themselves. I did find good information on Glen Mazza's Blog, and my implementation and this tutorial owe much to that information. But that tutorial is based on another one which is in turn based on another one. I found it difficult to filter through the layers to find what was necessary. Thus, I wrote this to provide a more complete and easy to use guide.

This tutorial will try to take you step-by-step through adding a security policy to an existing working web service WSDL as well as adding the additional CXF and Spring configuration necessary to make it work. It will not tell you how to build a CXF web service to start with, or how to configure Spring to make it work.

Tools Needed

  • Maven, 2.2.1 or better
  • JDK 1.6 or better

Code

You can find the code necessary for the tutorial here:

Technologies and Techniques Used

This tutorial uses Apache CXF to provide the backing for a JAX-WS web service which is built WSDL-First.

It uses CXF instead of the Glassfish jaxws-ri implementation or the embedded JDK implementation because I found getting jaxws-ri to do the same thing very cumbersome: it needed to reside in an endorsed standards directory (which puts an installation burden on any system administrators using the product); it requires annotations in the WSDL to work correctly; it requires different annotations for the client and server, so two WSDL versions need maintenance; and it failed with a fatal bug when SOAP faults were returned. CXF exhibited none of these problems, and was easy to integrate with Spring. That said, we generate the JAX-WS and JAXB code with Sun/Oracle's standard tools to make sure they're compliant.

The service is built WSDL-first because I believe that this is the most implementation-independent way of producing a SOAP-based web service, and because I think it gives you better interfaces by forcing you to think of them as services, rather than as java methods. It also allows us to clearly specify the security policy, which makes it easier for service consumers to comply.

This example also uses a multi-module Maven project which separates the WSDL, the generated JAX-WS code, and the service implementation/WAR into separate modules, which allows for easy re-use of the WSDL and/or the generated code.

The tutorial example also uses Spring, and the starting code consists of a complete working web service, packaged as a WAR, configured via Spring. Although various techniques are used to construct the configuration, I won't be explaining the base Maven or Spring configuration in detail.

That said, there are some "tricks" in the code that might cause problems moving this example into an existing web service project:

  • The WSSecurityTutorialJaxWs project uses binding customizations to make the generated code more Java-friendly. These are like any other standard JAX-WS binding customizations, but you should note they exist.
  • The WSSecurityTutorialJaxWs unpacks the WSDL into a temporary directory for generation; it also unpacks the WSDL into the target/classes directory so that it ends up in the final WAR. This is because various tools, including CXF, can load the WSDL from the classpath rather than from the endpoint server, and so it is added to the jar as a convenience.
  • The WSSecurityTutorialWAR module is configured by various files through Spring, using an extension of Spring's property placeholder functionality which will, if necessary, read properties from system property or JNDI env values. There are three tiers of property configuration files: a default one, a deployment one, and a test one. The intent is for the default one (in src/main/resources) to be rolled into the WAR, for the deployment one to be modified and deployed to the deployment server's file system, and its location specified via a system property or JNDI value.
  • SLF4J is used for logging, and configuration files in the META-INF directories of the WAR and test classpaths force CXF to use SLF4J as well.
  • The WAR module also uses TestNG instead of JUnit, which allows us to "group" tests. A normal build will run the "unit" and "local-integration" groups. Adding the "integration-test" profile to the build (e.g., 'mvn clean install -Pintegration-test') executes the "remote-integration" group and uses a plugin to start Tomcat so that the service can be tested running in a container.

Getting Started

You can download the starting code here. If you unzip that, you should be able to CD, on the command line, into the WSSecurityTutorialParent module and execute "mvn clean install -Pintegration-test" successfully. If not, you have something wrong with your environment, and you will have to diagnose it before you can continue.

Altering the WSDL

To begin, you have to decide what the service's security policy will actually be, and modify the WSDL to specify it.

Aside from the specifications themselves, there seems to be precious little information about the security specification standard (WS-SecurityPolicy) available. Some information can be found http://wso2.org/library/3132 herehere, and here.

Basically to declare a security policy for your web service, you have to define the policy using the http://schemas.xmlsoap.org/ws/2004/09/policy (wsp) and http://schemas.xmlsoap.org/ws/2005/07/securitypolicy (sp) schemas in your WSDL, and then attach the policy declarations to the service, operation, and/or input/output bindings that you want controlled by that policy.

A policy is declared with the "WS-Policy" schema/vocabulary (http://schemas.xmlsoap.org/ws/2004/09/policy wsp), and looks like this, basically:

WS-Policy Declaration

<wsp:Policy wsu:Id="UniqueIdentifier">
    <wsp:ExactlyOne>
        ...
    </wsp:ExactlyOne>
</wsp:Policy>

Inside the policy declaration, which in itself doesn't define what the policy is, you need to add security policy declarations. These are defined by the (http://schemas.xmlsoap.org/ws/2005/07/securitypolicy sp) schema, and there are a large number of variations, as defined in the specification linked above.

Basically, for our tutorial, we want to require that the body and custom headers of our messages are signed with a X.509 certificate (for source authentication), and that the body of our messages is encrypted with an X.509 certificate (for message privacy).

A policy to encrypt an input or output message is pretty simple, and looks basically like this:

WS-SecurityPolicy Input/Output Declaration

<wsp:Policy wsu:Id="InputOutputUniqueIdentifier">
	<wsp:ExactlyOne>
		<wsp:All>
			<sp:EncryptedParts>
				<sp:Body />
			</sp:EncryptedParts>
			<sp:SignedParts>
				<sp:Body />
				<sp:Header Namespace="http://example.com/tutotial/"/>
			</sp:SignedParts>
		</wsp:All>
	</wsp:ExactlyOne>
</wsp:Policy>

This says any operation whose input or output is linked to InputOutputUniqueIdentifier must have an encrypted body and must have a signed body and headers (the signed headers are all in the given namespace).

In theory we could require that the headers also be encrypted, but there is a CXF bug which prevents this from working (CXF-3452; also see related CXF-3453).

We then need to declare, for the entire service binding, how the input/output binding will take place (what kinds of tokens, how the tokens are exchanged, etc.). The options here are complex, and aside from the rather opaque specification, there's not much explanatory documentation available.

WS-SecurityPolicy Binding Policy Declaration

<wsp:Policy wsu:Id="UniqueBindingPolicyIdentifier">
	<wsp:ExactlyOne>
		<wsp:All>
			<sp:AsymmetricBinding>
				<wsp:Policy>
					<sp:InitiatorToken>
						<wsp:Policy>
							<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">
								<wsp:Policy>
									<sp:WssX509V3Token11 />
								</wsp:Policy>
							</sp:X509Token>
						</wsp:Policy>
					</sp:InitiatorToken>
					<sp:RecipientToken>
						<wsp:Policy>
							<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never">
								<wsp:Policy>
									<sp:WssX509V3Token11 />
									<sp:RequireIssuerSerialReference />
								</wsp:Policy>
							</sp:X509Token>
						</wsp:Policy>
					</sp:RecipientToken>
					<sp:Layout>
						<wsp:Policy>
							<sp:Strict />
						</wsp:Policy>
					</sp:Layout>
					<sp:IncludeTimestamp />
					<sp:OnlySignEntireHeadersAndBody />
					<sp:AlgorithmSuite>
						<wsp:Policy>
							<sp:Basic128 />
						</wsp:Policy>
					</sp:AlgorithmSuite>
					<sp:EncryptSignature />
				</wsp:Policy>
			</sp:AsymmetricBinding>
			<sp:Wss11>
				<wsp:Policy>
					<sp:MustSupportRefIssuerSerial />
				</wsp:Policy>
			</sp:Wss11>
		</wsp:All>
	</wsp:ExactlyOne>
</wsp:Policy>

This says an AsymmetricBinding will be used (asymmetric or public/private keys rather than symmetric encryption); the initiator must always include an X.509 token; the return message will also be signed/encrypted with an X.509 certificate, but the token itself will not be included and instead an issuer serial # reference will be included. Additionally, strict header layout is used; a timestamp is included and messages will be rejected if the timestamp is too far out-of-date (to avoid replay attacks); only complete headers and bodies must be signed rather than child elements of either; the "Basic128" algorithm suite is used; the signature itself must be encrypted; and the caller must support issuer serial references.

If we wanted to include a further layer of security for message transport, or wanted to use transport encryption instead of message-level encryption, we could add something like:

HTTPS Transport Policy Declaration

<sp:TransportToken>
	<wsp:Policy>
		<sp:HttpsToken />
	</wsp:Policy>
</sp:TransportToken>


So to implement these assertions, you should do the following:

Add to the attributes of your wsdl:definitions element:

  • xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  • xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  • xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"

I also added, for editor convenience:

Add the complete set of declarations to your WSDL (I added them as the last elements in the WSDL):

Complete Tutorial Binding Assertion

<wsp:Policy wsu:Id="TutorialBindingPolicy">
	<wsp:ExactlyOne>
		<wsp:All>
			<sp:AsymmetricBinding>
				<wsp:Policy>
					<sp:InitiatorToken>
						<wsp:Policy>
							<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">
								<wsp:Policy>
									<sp:WssX509V3Token11 />
								</wsp:Policy>
							</sp:X509Token>
						</wsp:Policy>
					</sp:InitiatorToken>
					<sp:RecipientToken>
						<wsp:Policy>
							<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never">
								<wsp:Policy>
									<sp:WssX509V3Token11 />
									<sp:RequireIssuerSerialReference />
								</wsp:Policy>
							</sp:X509Token>
						</wsp:Policy>
					</sp:RecipientToken>
					<sp:Layout>
						<wsp:Policy>
							<sp:Strict />
						</wsp:Policy>
					</sp:Layout>
					<sp:IncludeTimestamp />
					<sp:OnlySignEntireHeadersAndBody />
					<sp:AlgorithmSuite>
						<wsp:Policy>
							<sp:Basic128 />
						</wsp:Policy>
					</sp:AlgorithmSuite>
					<sp:EncryptSignature />
				</wsp:Policy>
			</sp:AsymmetricBinding>
			<sp:Wss11>
				<wsp:Policy>
					<sp:MustSupportRefIssuerSerial />
				</wsp:Policy>
			</sp:Wss11>
		</wsp:All>
	</wsp:ExactlyOne>
</wsp:Policy>
<wsp:Policy wsu:Id="TutorialInputBindingPolicy">
	<wsp:ExactlyOne>
		<wsp:All>
			<sp:EncryptedParts>
				<sp:Body />
			</sp:EncryptedParts>
			<sp:SignedParts>
				<sp:Body />
				<sp:Header Namespace="http://example.com/tutotial/"/>
			</sp:SignedParts>
		</wsp:All>
	</wsp:ExactlyOne>
</wsp:Policy>
<wsp:Policy wsu:Id="TutorialOutputBindingPolicy">
	<wsp:ExactlyOne>
		<wsp:All>
			<sp:EncryptedParts>
				<sp:Body />
			</sp:EncryptedParts>
			<sp:SignedParts>
				<sp:Body />
				<sp:Header Namespace="http://example.com/tutotial/"/>
			</sp:SignedParts>
		</wsp:All>
	</wsp:ExactlyOne>
</wsp:Policy>

You then must "reference" the policy declarations where you want them used. To each wsdl:binding element where the binding policy should apply, add:

Binding Policy Reference

<wsp:PolicyReference URI="#TutorialBindingPolicy" />

For each input element where the policy should apply, add:

Input Policy Reference

<wsp:PolicyReference URI="#TutorialInputBindingPolicy"/>

For each output element where the policy should apply, add:

Output Policy Reference

<wsp:PolicyReference URI="#TutorialOutputBindingPolicy"/>

So, for instance, the tutorial's code:

Complete Tutorial Binding

<wsdl:binding name="TutorialWebServiceSOAP" type="tns:TutorialWebService">
	<wsp:PolicyReference URI="#TutorialBindingPolicy" />
	<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
	<wsdl:operation name="sendTutorialMessage">
		<soap:operation soapAction="http://example.com/tutotial/sendTutorialMessage" />
		<wsdl:input>
			<wsp:PolicyReference URI="#TutorialInputBindingPolicy"/>
			<soap:body use="literal" parts="parameters" />
			<soap:header use="literal" part="source" message="tns:TutorialRequest"/>
		</wsdl:input>
		<wsdl:output>
			<wsp:PolicyReference URI="#TutorialOutputBindingPolicy"/>
			<soap:body use="literal" parts="response"/>
			<soap:header use="literal" part="acknowledgment" message="tns:TutorialResponse"/>
		</wsdl:output>
		<soap:address location="http://localhost/" />
	</wsdl:port>
</wsdl:service>

Implementing the Binding

Now you need to get CXF to read, enforce, and support the binding on the server and client. In our example, the server is the end-result WAR of the WAR module, and the client example is the integration test cases in that module.

Dependencies

To do this, you will need to add additional CXF dependencies: one to support WS-Policy, one to support WS-Security, and one as an encryption provider.

In the tutorial example, the Parent module controls the versions, exclusions, etc. of all dependencies, so to the dependencyManagement element of the Parent POM, add:

New Dependency Management Entries

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-ws-security</artifactId>
	<version>${cxf.version}</version>
	<exclusions>
		<exclusion>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-ws-policy</artifactId>
	<version>${cxf.version}</version>
	<exclusions>
		<exclusion>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk16</artifactId>
	<version>${bouncycastle.version}</version>
	<exclusions>
		<exclusion>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>

And to the WAR module's POM:

New WAR Dependency Entries

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-ws-security</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-ws-policy</artifactId>
</dependency>
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk16</artifactId>
</dependency>

New Spring Configuration Files

These new dependencies allow CXF to process the policy declarations and the new headers. To activate them, you need to load the CXF Spring configuration files for those new CXF modules. So, to the WAR's web.xml you should add, right under the existing classpath:META-INF/cxf/cxf-servlet.xml entry:

New Spring Files

classpath:META-INF/cxf/cxf-extension-policy.xml
classpath:META-INF/cxf/cxf-extension-ws-security.xml

And to your client, right after classpath*:/META-INF/cxf/cxf-extension-http.xml, you should add the same two XML files. For the tutorial, this is done in the ContextConfigurations attribute of TutorialWebServiceTest.java.

I found, when experimenting with this, that the CXF configuration files are sensitive to the order in which they are loaded by Spring – so the order specified above for the two new files, and where they are placed relative to existing CXF configurations, seems to be important.

Generate Certificates

Unless you have existing X.509 certificates for your client and server, you are going to have to generate new ones. Of course, for a production scenario, you should have issuer-signed certificates from a recognized authority such as Verisign, but for testing and development, and for this tutorial, self-signed certificates can be used. You can use the Java keytool for this; you will need to create two keystores (client and server), generate a client key and a server key, export the public keys, and import the public keys into the opposite number's keystore. A script to do this is here:

generate-keys.sh

#!/bin/bash

# Set the values we'll use for the generation
read -p"Server Key Alias?" serverkeyalias
read -p"Server Key Password?" serverkeypassword
read -p"Server Keystore Password?" serverstorepassword
read -p"Server Keystore File Name?" serverkeystorename

read -p"Client Key Alias?" clientkeyalias
read -p"Client Key Password?" clientkeypassword
read -p"Client Keystore Password?" clientstorepassword
read -p"Client Keystore File Name?" clientkeystorename

# Generate the server and client keys
keytool -genkey -alias $serverkeyalias -keyalg RSA -sigalg SHA1withRSA -keypass $serverkeypassword -storepass $serverstorepassword -keystore $serverkeystorename -dname "cn=localhost"
keytool -genkey -alias $clientkeyalias -keyalg RSA -sigalg SHA1withRSA -keypass $clientkeypassword -storepass $clientstorepassword -keystore $clientkeystorename -dname "cn=clientuser"

# Export the client key and import it to the server keystore
keytool -export -rfc -keystore $clientkeystorename -storepass $clientstorepassword -alias $clientkeyalias -file $clientkeyalias.cer
keytool -import -trustcacerts -keystore $serverkeystorename -storepass $serverstorepassword -alias $clientkeyalias -file $clientkeyalias.cer -noprompt
rm $clientkeyalias.cer

# Export the server key and import it to the client keystore
keytool -export -rfc -keystore $serverkeystorename -storepass $serverstorepassword -alias $serverkeyalias -file $serverkeyalias.cer
keytool -import -trustcacerts -keystore $clientkeystorename -storepass $clientstorepassword -alias $serverkeyalias -file $serverkeyalias.cer -noprompt
rm $serverkeyalias.cer

Of course you should note or remember the necessary passwords; you will need them later.

These keystores need to be placed where the server or client can read them. For the tutorial, the client keystore goes into src/test/resources, and the server one goes into src/main/springconfig/local. You will later need to tell the client and server, via Spring properties, where these are.

Create a CallbackHandler

To get passwords for specific keys, CXF uses an implementation of javax.security.auth.callback.CallbackHandler. If you don't already have one, you will need to create one. Create a new java class that implements javax.security.auth.callback.CallbackHandler that handles callbacks of type org.apache.ws.security.WSPasswordCallback. For example:

KeystorePasswordCallback.java

package com.example.tutorial.ws.security;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

/**
 * Really callback for key passwords.  Configure it with a map
 * of key-alias-to-password mappings.  Obviously this could
 * be extended to encrypt or obfuscate these passwords if desired.
 */
public class KeystorePasswordCallback implements CallbackHandler
{

    private Map<String, String> passwords = new HashMap<String, String>();

    /**
     * {@inheritDoc}
     * 
     * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
     */
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
    {
        for (Callback callback : callbacks)
        {
            if (callback instanceof WSPasswordCallback)
            {
                WSPasswordCallback pc = (WSPasswordCallback)callback;
    
                String pass = passwords.get(pc.getIdentifier());
                if (pass != null)
                {
                    pc.setPassword(pass);
                    return;
                }
            }
        }
    }

    /**
     * @return the passwords
     */
    public Map<String, String> getPasswords()
    {
        return passwords;
    }

    /**
     * @param passwords the passwords to set
     */
    public void setPasswords(Map<String, String> passwords)
    {
        this.passwords = passwords;
    }
    
}

Configure the Service

Next, you will need to configure the web service to handle WS-Security. Assuming you already have a CXF service defined in a Spring configuration file, you need to add:

  • The CallbackHandler you just created, with necessary passwords
  • A series of properties for the keystore to be used by the service
  • The key alias to be used for signing

To do this to the tutorial code, find cxf-service-config.xml, and add:

cxf-service-config.xml Additions

<bean id="keystorePasswordCallback" class="com.example.tutorial.ws.security.KeystorePasswordCallback">
	<property name="passwords">
		<map>
			<entry key="${wss.keyAlias}" value="${wss.keyPassword}"/>
		</map>
	</property>
</bean>

<util:properties id="keystoreProperties">
	<prop key="org.apache.ws.security.crypto.provider">org.apache.ws.security.components.crypto.Merlin</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.type">${wss.keystoreType}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.password">${wss.keystorePassword}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.alias">${wss.keyAlias}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.file">${wss.keystorePath}</prop>
</util:properties>

These define a password callback, with a key alias entry and password, and the properties to manage the keystore. Note that all these entries are defined Spring property tokens; you will define these soon.

And to the existing jaxws:endpoint/jaxws:properties in that file, add:

cxf-service-config.xml Additions

<entry key="ws-security.callback-handler" value-ref="keystorePasswordCallback"/>
<entry key="ws-security.encryption.properties" value-ref="keystoreProperties"/>
<entry key="ws-security.signature.properties" value-ref="keystoreProperties"/>
<entry key="ws-security.encryption.username" value="useReqSigCert"/>

The entry useReqSigCert tells CXF to "encrypt the response with the same certificate that signed the request".

In this example we also use the same keystore properties for encryption and signature; if you have separate key and trust stores, you can create separate properties with different values.

Because most of the entries above are Spring property tokens, we need to enter the correct values into the property file that's being used by Spring to store these values. For the tutorial, add to TutorialDeploymentPropertyPlaceholders.properties:

TutorialDeploymentPropertyPlaceholders.properties

wss.keyAlias=the key alias you generated the service key with
wss.keyPassword=the key password you generated the service key with
wss.keystoreType=jks
wss.keystorePassword=the key store password you generated the service key with
wss.keystorePath=${configDirectory}/the name you gave the service keystore

Configure the Client

The client configuration is essentially the same, with very minor changes. For the tutorial:

war-spring-test.xml Additions

<bean id="keystorePasswordCallback" class="com.example.tutorial.ws.security.KeystorePasswordCallback">
	<property name="passwords">
		<map>
			<entry key="${wss.keyAlias}" value="${wss.keyPassword}"/>
		</map>
	</property>
</bean>

<util:properties id="keystoreProperties">
	<prop key="org.apache.ws.security.crypto.provider">org.apache.ws.security.components.crypto.Merlin</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.type">${wss.keystoreType}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.password">${wss.keystorePassword}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.keystore.alias">${wss.keyAlias}</prop>
	<prop key="org.apache.ws.security.crypto.merlin.file">${wss.keystorePath}</prop>
</util:properties>

...

<jaxws:client ...

	<jaxws:properties>
		<entry key="ws-security.callback-handler" value-ref="keystorePasswordCallback"/>		
		<entry key="ws-security.encryption.properties" value-ref="keystoreProperties"/>
		<entry key="ws-security.signature.properties" value-ref="keystoreProperties"/>
		<entry key="ws-security.encryption.username" value="${serverKeyAlias}"/>
	</jaxws:properties>
...	

Note that this one uses a specific key alias for the "username".

Then to the properties file:

TutorialTestPropertyPlaceholders.properties

wss.keyAlias=the alias you used to generate the client key
wss.keyPassword=the key password you used to generate the client key
wss.keystoreType=jks
wss.keystorePassword=the store password you used to generate the client key
wss.keystorePath=${configDirectory}/the name you gave to the client keystore
wss.serverKeyAlias=the server key alias you used to generate the server key

Run and Test

This (should) be it. You should be able now to run the service and test its encryption functionality. The tutorial code has logging interceptors turned on so you can see the encrypted and signed messages.

Notes about the Encrypted Messages

Hopefully, if everything works, the exchanged messages should look much like this:

An Encrypted Message

<soap:Envelope
	xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
	<soap:Header>
		<ns2:message-source
			xmlns="http://example.com/tutotial/types/"
			xmlns:ns2="http://example.com/tutotial/"
			xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
			message-identifier="SYSTEM FAILURE"
			system-identifier="test"
			wsu:Id="Id-1337947189"/>
		<wsse:Security
			xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
			soap:mustUnderstand="1">
			<wsse:BinarySecurityToken
				xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
				xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
				EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
				ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
				wsu:Id="CertId-ACAFC43C228502A539130230131374311">MIIBoTCCAQqgAwIBAgIETSyeWTANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpjbGllbnR1c2VyMB4XDTExMDExMTE4MTU1M1oXDTExMDQxMTE4MTU1M1owFTETMBEGA1UEAxMKY2xpZW50dXNlcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgtoyaaP/nPzb7aW9VlRJTGDJENKMy87kewpN2z3TxMdzxsaFxQxtnnW+/iw+9kPAoEWQhFDIO7SG1VCEQrTfrefQ5b2fZZkeEKpMAc/Ls1BQxR7REUlBH7AhDNEu00tAhd0Rg7DUdIHhwI1phkEgasK13t7XxGMuzjb3MxdV5ZkCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA+9VHcnZK2DAbkbNAdur/u6hPSGQz3s1l0ZK+WpKkRrSMh7P/eNZM8lDZnhJbjdyroU1u2X8DIgasQ+CCoHqSltwOpo75VrRCNbjBYATL+SEpU8zh37zO8jQVe4Bte6AAFQ1zFPpEAqgSVgxhNtXLPDrLVoos2svONEqd9wa4XuA==</wsse:BinarySecurityToken>
			<wsu:Timestamp
				xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
				wsu:Id="Timestamp-9">
				<wsu:Created>2011-04-08T22:21:53.743Z</wsu:Created>
				<wsu:Expires>2011-04-08T22:26:53.743Z</wsu:Expires>
			</wsu:Timestamp>
			<xenc:EncryptedKey
				xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
				Id="EncKeyId-ACAFC43C228502A539130230131375015">
				<xenc:EncryptionMethod
					Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
				<ds:KeyInfo
					xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
					<wsse:SecurityTokenReference
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
						<ds:X509Data>
							<ds:X509IssuerSerial>
								<ds:X509IssuerName>CN=localhost</ds:X509IssuerName>
								<ds:X509SerialNumber>1294769753</ds:X509SerialNumber>
							</ds:X509IssuerSerial>
						</ds:X509Data>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
				<xenc:CipherData>
					<xenc:CipherValue>Kr5zeACNhaKl+INqWlI7moEbdSp1o8q7w0RUTTESxAnc9cKrjw1JPyM7VclXSIKOyqUQ81HbeypdiVUKNXMtpxUIcpGxQAtVeDec8nYZgNfBA6LRUh2xMe8QEf43UVKxS9MCvepS+J3tjhjSB4KJLR0mz15Ii0Gx/FJdjBt+RDM=</xenc:CipherValue>
				</xenc:CipherData>
				<xenc:ReferenceList>
					<xenc:DataReference
						URI="#EncDataId-11"/>
					<xenc:DataReference
						URI="#EncDataId-12"/>
				</xenc:ReferenceList>
			</xenc:EncryptedKey>
			<xenc:EncryptedData
				xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
				Id="EncDataId-12"
				Type="http://www.w3.org/2001/04/xmlenc#Element">
				<xenc:EncryptionMethod
					Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
				<ds:KeyInfo
					xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
					<wsse:SecurityTokenReference
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
						<wsse:Reference
							xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
							URI="#EncKeyId-ACAFC43C228502A539130230131375015"/>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
				<xenc:CipherData>
					<xenc:CipherValue>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
VdTKcYaNG4PnAbay</xenc:CipherValue>
				</xenc:CipherData>
			</xenc:EncryptedData>
		</wsse:Security>
	</soap:Header>
	<soap:Body
		xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
		wsu:Id="Id-1857134841">
		<xenc:EncryptedData
			xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
			Id="EncDataId-11"
			Type="http://www.w3.org/2001/04/xmlenc#Content">
			<xenc:EncryptionMethod
				Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
			<ds:KeyInfo
				xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
				<wsse:SecurityTokenReference
					xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
					<wsse:Reference
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
						URI="#EncKeyId-ACAFC43C228502A539130230131375015"/>
				</wsse:SecurityTokenReference>
			</ds:KeyInfo>
			<xenc:CipherData>
				<xenc:CipherValue>1QzMDBtcLlhUOwAeI++PCtUZS+fRwiVly4kOBl+7pNcsFhCudYaySmaKnb1v1Qyh0kmdPxMBjMmD
ZlKTSQESwyDYxlBzu+UxQJi/ovJ85m+k/MJHNvvsuXMwS7VPPGERvnUDr+tfngWrnhKMdx3hE25t
TIsSkCzd89/OPfc9xtrtKxMse1JrJhHaDB62xo4JRD7/dSsS2Wh4vQHhlNipm/w/Yf89vSp35kne
DnqdqmJf49jfefiRgo4HfzAv5MAZpgZ25ngzJK7bnuf5oCEUdauWEgvIviJnU1UT6wApvyYeldby
aeacc3YrfOFxtON2dyXnoPAXRRAyNUabDeuagNGVjZoyKfhmmTiPpjXVNrtXePppkCEXq46yKH9i
hmW728l95VBGaUbS3J/405+ywrr4H5pl6ypMMAY5L/dnk449BpQ3XU/W6JH7UgkmhMwSZMwdt0du
HvNcS/UpW/gaOREynPTV8EcEKscPty3LM8c00uW59sVIYaYEnED1K68zFmejcHcQj2RwtuSd/6dL
Ao5u7JtM1OmcQ6LT5YddMHQPnThZTGxVQWimGPoU+089UXUavcIX5nMY/PUY1ISuzVlvRFw+aEwC
Us7Iq05a9F8ZsbQVq7I20qPZGSouDIbPn5rpHEmQf56wB2k1bS/RqrTqaOXUADlhwWSWfpizT07F
k0QMlOWpyxqJ1q8mnxjllFqVjuu2QulLgyI+ee3cKzh8Wi0tVwh4wqX+XIXLV0Q+IJ1gs6j7lTAU
nXzaqNgYbVG2cIk70V+OyKeXh2Og9Z8gBpB09hULj3SRdIeYpuWrsvWdrDbunKl00OBPsbaZSbcg
pK4/PfNN5II8hVEvF6Fn6V0DkGP9i4c0lT0H52E=</xenc:CipherValue>
			</xenc:CipherData>
		</xenc:EncryptedData>
	</soap:Body>
</soap:Envelope>

The most notable change between this and a "normal" SOAP message is the wsse:Security header and the blocks of xenc:CipherData. I found several things worth noting, because nothing I had read explained how this worked:

  • The wsse:Security element in the header contains the information needed to decrypt and verify the message.
  • The wsse:BinarySecurityToken element contains the actual token data
  • The wsu:Timestamp element contains our requested timestamp, in this case expiring in 5 minutes. Messages sent after the expiration date should fail.
  • The xenc:EncryptedKey element contains information about the key that was actually used to encrypt the message. It contains the token reference for the encrypting key, which in the case of the above message is the public key of the server. It also contains a xenc:CipherValue element which, as I understand it, is a 128-Bit symmetric key, encrypted with the public key of the server. This 128-Bit key is used to encrypt the message; only the randomly generated symmetric key included here is used to encrypt the message. This provides the speed of symmetric encryption coupled with the security of PPK encryption by exchanging random, single-use keys encrypted with the public key of the recipient.
  • It also contains a reference list of elements which it should be used to decrypt.
  • The first xenc:EncryptedData element contains the signature for the message, encrypted with the given symmetric key.
  • The second xenc:EncryptedData contains the body of the message, encrypted with the given symmetric key.

Completed Source

You can download the completed tutorial source here.

comments powered by Disqus