Membrane can exchange signed and encrypted SOAP messages with Apache CXF. Interoperability is tested with CXF, SOAP 1.1 and SOAP 1.2.
This example connects a CXF client to a SOAP service through Membrane. CXF signs and encrypts the request. Membrane decrypts it and verifies the signature before forwarding it to the service. On the way back, Membrane signs and encrypts the response, and CXF decrypts and verifies it.
The SOAP body and timestamp are signed, then the body content is encrypted. The gateway requires both signature references and body encryption. Decryption runs before signature verification so that the signature is checked against the original body.
Prepare the following PKCS#12 files. Each private key must be an RSA key with its certificate chain. Exchange public certificates between the two applications.
| Application | File | Contents |
|---|---|---|
| Membrane | membrane.p12 | Private key and certificate under alias membrane |
| Membrane | membrane-trust.p12 | CXF certificate under alias cxf |
| CXF | cxf.p12 | Private key and certificate under alias cxf |
| CXF | cxf-trust.p12 | Membrane certificate under alias membrane |
The private key signs outgoing messages and decrypts incoming messages. The peer's certificate verifies incoming signatures and encrypts outgoing messages. Replace changeit in the examples with your store and key passwords. The examples use the same password for the store and its private key.
Place these two properties files and the CXF PKCS#12 files on the application classpath. Keep the private-key store separate from the peer truststore so that signature verification trusts the configured peer certificates.
cxf-keys.properties:
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.wss4j.crypto.merlin.keystore.type=PKCS12
org.apache.wss4j.crypto.merlin.keystore.file=cxf.p12
org.apache.wss4j.crypto.merlin.keystore.password=changeit
org.apache.wss4j.crypto.merlin.keystore.alias=cxf
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.wss4j.crypto.merlin.keystore.type=PKCS12
org.apache.wss4j.crypto.merlin.keystore.file=cxf.p12
org.apache.wss4j.crypto.merlin.keystore.password=changeit
org.apache.wss4j.crypto.merlin.keystore.alias=cxf
cxf-trust.properties:
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.wss4j.crypto.merlin.truststore.type=PKCS12
org.apache.wss4j.crypto.merlin.truststore.file=cxf-trust.p12
org.apache.wss4j.crypto.merlin.truststore.password=changeit
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin
org.apache.wss4j.crypto.merlin.truststore.type=PKCS12
org.apache.wss4j.crypto.merlin.truststore.file=cxf-trust.p12
org.apache.wss4j.crypto.merlin.truststore.password=changeit
The properties files provide store passwords; the callback handler provides the private-key password. See the WSS4J configuration reference for the store properties and the CXF WS-Security documentation for Spring interceptor configuration.
Add these entries to your API's flow and configure its target to point to your SOAP service. Place the two Membrane store files where the configured paths resolve. The request is forwarded to the service after WS-Security validation; the service does not need WS-Security for this setup.
flow:
- request:
- wsSecurity:
keystore:
location: membrane.p12
type: PKCS12
password: changeit
keyAlias: membrane
keyPassword: changeit
truststore:
location: membrane-trust.p12
type: PKCS12
password: changeit
validate:
- decrypt:
requiredReferences:
- by: BODY
type: CONTENT
- timestamp: {}
- signature:
requiredReferences:
- by: BODY
- by: TIMESTAMP
- response:
- wsSecurity:
keystore:
location: membrane.p12
type: PKCS12
password: changeit
keyAlias: membrane
keyPassword: changeit
truststore:
location: membrane-trust.p12
type: PKCS12
password: changeit
secure:
- timestamp:
ttl: PT5M
- signature:
signatureAlgorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
digestAlgorithm: http://www.w3.org/2001/04/xmlenc#sha256
canonicalizationAlgorithm: http://www.w3.org/2001/10/xml-exc-c14n#
securityTokenReference: {}
references:
- by: BODY
- by: TIMESTAMP
- encrypt:
recipientAlias: cxf
dataEncryptionAlgorithm: http://www.w3.org/2009/xmlenc11#aes256-gcm
keyTransportAlgorithm: http://www.w3.org/2009/xmlenc11#rsa-oaep
references:
- by: BODY
type: CONTENT
flow:
- request:
- wsSecurity:
keystore:
location: membrane.p12
type: PKCS12
password: changeit
keyAlias: membrane
keyPassword: changeit
truststore:
location: membrane-trust.p12
type: PKCS12
password: changeit
validate:
- decrypt:
requiredReferences:
- by: BODY
type: CONTENT
- timestamp: {}
- signature:
requiredReferences:
- by: BODY
- by: TIMESTAMP
- response:
- wsSecurity:
keystore:
location: membrane.p12
type: PKCS12
password: changeit
keyAlias: membrane
keyPassword: changeit
truststore:
location: membrane-trust.p12
type: PKCS12
password: changeit
secure:
- timestamp:
ttl: PT5M
- signature:
signatureAlgorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
digestAlgorithm: http://www.w3.org/2001/04/xmlenc#sha256
canonicalizationAlgorithm: http://www.w3.org/2001/10/xml-exc-c14n#
securityTokenReference: {}
references:
- by: BODY
- by: TIMESTAMP
- encrypt:
recipientAlias: cxf
dataEncryptionAlgorithm: http://www.w3.org/2009/xmlenc11#aes256-gcm
keyTransportAlgorithm: http://www.w3.org/2009/xmlenc11#rsa-oaep
references:
- by: BODY
type: CONTENT
Membrane uses SHA-256 and MGF1-SHA256 for this RSA-OAEP algorithm, and identifies the encryption certificate by thumbprint by default. The securityTokenReference setting selects the direct certificate reference used by CXF.
Add org.apache.cxf:cxf-rt-ws-security to your application using the same dependency version as its other CXF modules. Load the following XML into the Spring application context that manages your CXF client. Replace com.example.soap.OrderService with your generated service interface and the address with your Membrane API URL.
The keyPasswordCallback bean represents your application's password callback handler. Replace com.example.security.KeyPasswordCallback with your implementation of javax.security.auth.callback.CallbackHandler. It must supply the private-key password for alias cxf through WSPasswordCallback for signing and decryption. This application-specific class is required; it is not supplied by CXF. If your application already defines a suitable callback bean, reference that bean instead.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="keyPasswordCallback"
class="com.example.security.KeyPasswordCallback"/>
<bean id="wsSecurityOut"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Signature Timestamp Encrypt"/>
<entry key="user" value="cxf"/>
<entry key="signatureUser" value="cxf"/>
<entry key="encryptionUser" value="membrane"/>
<entry key="passwordCallbackRef" value-ref="keyPasswordCallback"/>
<entry key="signaturePropFile" value="cxf-keys.properties"/>
<entry key="encryptionPropFile" value="cxf-trust.properties"/>
<entry key="signatureKeyIdentifier" value="DirectReference"/>
<entry key="signatureAlgorithm"
value="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<entry key="signatureDigestAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#sha256"/>
<entry key="signatureC14nAlgorithm"
value="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<entry key="signatureParts"
value="{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp"/>
<entry key="encryptionKeyIdentifier" value="Thumbprint"/>
<entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2009/xmlenc11#aes256-gcm"/>
<entry key="encryptionKeyTransportAlgorithm"
value="http://www.w3.org/2009/xmlenc11#rsa-oaep"/>
<entry key="encryptionDigestAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#sha256"/>
<entry key="encryptionMGFAlgorithm"
value="http://www.w3.org/2009/xmlenc11#mgf1sha256"/>
<entry key="encryptionParts"
value="{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
</map>
</constructor-arg>
</bean>
<bean id="wsSecurityIn"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Signature Timestamp Encrypt"/>
<entry key="passwordCallbackRef" value-ref="keyPasswordCallback"/>
<entry key="signatureVerificationPropFile" value="cxf-trust.properties"/>
<entry key="decryptionPropFile" value="cxf-keys.properties"/>
</map>
</constructor-arg>
</bean>
<bean id="coverage"
class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker">
<property name="signBody" value="true"/>
<property name="signTimestamp" value="true"/>
<property name="encryptBody" value="true"/>
</bean>
<jaxws:client id="orderClient"
serviceClass="com.example.soap.OrderService"
address="http://localhost:2000/orders">
<jaxws:outInterceptors>
<ref bean="wsSecurityOut"/>
</jaxws:outInterceptors>
<jaxws:inInterceptors>
<ref bean="wsSecurityIn"/>
<ref bean="coverage"/>
</jaxws:inInterceptors>
</jaxws:client>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="keyPasswordCallback"
class="com.example.security.KeyPasswordCallback"/>
<bean id="wsSecurityOut"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Signature Timestamp Encrypt"/>
<entry key="user" value="cxf"/>
<entry key="signatureUser" value="cxf"/>
<entry key="encryptionUser" value="membrane"/>
<entry key="passwordCallbackRef" value-ref="keyPasswordCallback"/>
<entry key="signaturePropFile" value="cxf-keys.properties"/>
<entry key="encryptionPropFile" value="cxf-trust.properties"/>
<entry key="signatureKeyIdentifier" value="DirectReference"/>
<entry key="signatureAlgorithm"
value="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<entry key="signatureDigestAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#sha256"/>
<entry key="signatureC14nAlgorithm"
value="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<entry key="signatureParts"
value="{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp"/>
<entry key="encryptionKeyIdentifier" value="Thumbprint"/>
<entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2009/xmlenc11#aes256-gcm"/>
<entry key="encryptionKeyTransportAlgorithm"
value="http://www.w3.org/2009/xmlenc11#rsa-oaep"/>
<entry key="encryptionDigestAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#sha256"/>
<entry key="encryptionMGFAlgorithm"
value="http://www.w3.org/2009/xmlenc11#mgf1sha256"/>
<entry key="encryptionParts"
value="{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
</map>
</constructor-arg>
</bean>
<bean id="wsSecurityIn"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Signature Timestamp Encrypt"/>
<entry key="passwordCallbackRef" value-ref="keyPasswordCallback"/>
<entry key="signatureVerificationPropFile" value="cxf-trust.properties"/>
<entry key="decryptionPropFile" value="cxf-keys.properties"/>
</map>
</constructor-arg>
</bean>
<bean id="coverage"
class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker">
<property name="signBody" value="true"/>
<property name="signTimestamp" value="true"/>
<property name="encryptBody" value="true"/>
</bean>
<jaxws:client id="orderClient"
serviceClass="com.example.soap.OrderService"
address="http://localhost:2000/orders">
<jaxws:outInterceptors>
<ref bean="wsSecurityOut"/>
</jaxws:outInterceptors>
<jaxws:inInterceptors>
<ref bean="wsSecurityIn"/>
<ref bean="coverage"/>
</jaxws:inInterceptors>
</jaxws:client>
</beans>
Use the Spring bean orderClient as your SOAP client. Keep the action string Signature Timestamp Encrypt in the shown order: WSS4J creates the timestamp before signing and places the signature after it in the security header. The coverage checker requires a signed body, a signed timestamp, and encrypted body content. It also requires signatures on WS-Addressing ReplyTo and FaultTo headers when present.
The XML example uses SOAP 1.1. For SOAP 1.2, add bindingId="http://www.w3.org/2003/05/soap/bindings/HTTP/" to jaxws:client and replace http://schemas.xmlsoap.org/soap/envelope/ with http://www.w3.org/2003/05/soap-envelope in both signatureParts and encryptionParts. The timestamp namespace stays the same. Membrane handles either SOAP envelope version.
To protect the connection from Membrane to a CXF service, move Membrane's secure configuration to the request flow and its validate configuration to the response flow, retaining the same stores and aliases. In CXF, replace the jaxws:client element with a service endpoint and reference the same security beans:
<bean id="orderService" class="com.example.soap.OrderServiceImpl"/>
<jaxws:endpoint id="orderEndpoint"
implementor="#orderService"
address="/orders">
<jaxws:inInterceptors>
<ref bean="wsSecurityIn"/>
<ref bean="coverage"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="wsSecurityOut"/>
</jaxws:outInterceptors>
</jaxws:endpoint>
<bean id="orderService" class="com.example.soap.OrderServiceImpl"/>
<jaxws:endpoint id="orderEndpoint"
implementor="#orderService"
address="/orders">
<jaxws:inInterceptors>
<ref bean="wsSecurityIn"/>
<ref bean="coverage"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="wsSecurityOut"/>
</jaxws:outInterceptors>
</jaxws:endpoint>
Replace the implementation class with your service implementation. The endpoint address is relative to your CXF servlet; configure Membrane's target with the full service URL. For SOAP 1.2, set the endpoint's bindingId and update the part namespaces as described above. Membrane signs and encrypts requests for CXF; CXF signs and encrypts responses for Membrane.