Titel-Logo
Projektstudien
TraceLogger
Basics of Cryptography
Custom JBossAS Login
SOAP Webservice
The Idea
Technologies
Project Structure
Service Definition
XML Signature
A (very) short primer
Keytool
SignatureHandler
VerificationHandler
Installation and Test
Downloads
Role Based Access Control
Preface

It is required that clients digitally sign their price requests. Missing digital signatures or signatures which cannot be validated by the service lead to a fault response. The Organization for the Advancement of Structured Information Standards has specified a standard adressing Web Service Security, see OASIS Web Services Security (WS-Security). The indicated specification uses, for example, the W3C XML Signature recommendation to achieve security goals like message content integrity. It is however completely natural to avoid such heavy-weight specifications like WS-Security and to use directly XML Signature since the Java (and .NET) platform provides an implementation of this W3C recommendation.

By doing so the service interoperability is somewhat reduced because signing client stubs cannot be generated automatically from our service definition because the service definition doesn't include policies about the application of digital signatures. Instead, we are relying on so called SOAPHandlers to create a XML signature of the message payload. Thus we have to implement and to add a SignatureHandler to the process chain of the client and an appropriate VerificationHandler to the process chain of the service. On the other hand our approach follows the separation of concerns principle and makes it easier to reason about the security of the applied digital signature. There exists several attacks, especially so called XML signature wrapping attacks, on WS-Security (and naively applied XML Signatures). See the Analysis of Signature Wrapping Attacks and Countermeasures and XML Signature Element Wrapping Attacks and Countermeasures for some details.

XML Signature - A (very) short primer

XML Signatures may be applied to certain portions of XML documents. These to be signed XML resources must be referenced by Unique Resource Identifiers (URIs). The XML Signature Processor will compute cryptographic message digests over these referenced resources. The references (together with the computed digests), the specified message digest and signature algorithms as well as some additional required transformations like Canonicalization are parts of the SignedInfo element. The actual SignatureValue will be created over this SignedInfo element. The top-level Signature element consists of these SignedInfo and SignatureValue elements. That is the signature generation is a two step process. First the message digests of the referenced resources will be computed and then the actual signature will be generated over these digests. Additionally, a KeyInfo can be added to the Signature element. This optional KeyInfo element may contain information to retrieve the public key needed to validate the digital signature. See this overview and subsequent example from the official XML Signature recommendation.

The Java Development Kit provides an implementation of the XML Signature recommendation. See the Java XML Digital Signature API Specification and The XML Digital Signature API Reference and Tutorial for further information. In the next sections, I will present some code pieces used within the SignatureHandler and VerificationHandler.

[Top]

Keytool

First we have to generate a key pair for the web service client. We will be using keytool, the Key and Certificate Management Tool of the JDK. However, this distribution as provided within the Downloads section comes with two preconfigured keystores. The keystore of the client, mykeystore.jks, contains the key pair whereas certificates.jks contains the certificate authenticating the linked public key of the client. Note that the following shown keytool commands assume that the bin-directory of the JDK is referenced by the PATH environment variable.

$ keytool -genkeypair -dname "cn=Christof Reichardt, L=Rodgau, ST=Hessen, c=DE" -alias Tester -keyalg RSA -keypass changeit -keystore mykeystore.jks -storepass changeit -validity 180

The preceding command creates a key pair with the specified X.500 Distinguished Name "cn=Christof Reichardt, L=Rodgau, ST=Hessen, c=DE" identified by the alias "Tester" and adds it to the mykeystore.jks. If the file mykeystore.jks doesn't exist, it will be created. The self-signed certificate should be considered valid for 180 days. The following command exports the certificate linked with the key entry identified by the alias "Tester" from the mykeystore.jks into a file named certificate.bin

$ keytool -exportcert -alias Tester -file certificate.bin -keystore mykeystore.jks -storepass changeit

Next we have to import this certificate into the keystore used by the service to authenticate the price requests:

$ keytool -importcert -alias Tester -file certificate.bin -keystore certificates.jks -keypass changeit -storepass changeit -v

The mykeystore.jks remains on the client side whereas the certificates.jks must be accessible by the service. Within a real world scenario the certificate of the client might be authenticated by a certificate authority (CA) like DigiCert. By doing so the validity of the client's certificate can be verified by the public key of the CA.

[Top]

The SignatureHandler

Handlers can be added programmatically on the client side by accessing the handler chain via the BindingProvider. In order to get such a BindingProvider we have to instantiate the service interface at first:

1 URL url = new URL("http://127.0.1.1:8080/SecurityPriceService-server-0.0.1-SNAPSHOT/securitypriceservice?wsdl");
2 QName qName = new QName("http://securitypriceservice.jaxws.christofreichardt.de/", "SecurityPriceService");
3 SecurityPriceService securityPriceService = new SecurityPriceService(url, qName);
4 SecurityPriceServicePortType securityPriceServicePort = securityPriceService.getPort(SecurityPriceServicePortType.class);

The SecurityPriceServicePortType instance can be casted to the desired BindingProvider which in turn is used to add the SignatureHandler to the handler chain:

5 BindingProvider bindingProvider = (BindingProvider) securityPriceServicePort;
6 List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
7 handlerChain.add(new SignatureHandler());
8 bindingProvider.getBinding().setHandlerChain(handlerChain);

Such a (client) handler will be typically called twice during a request/response cycle. The first invocation takes place when the SOAP message is outbound and the second invocation takes place when it is inbound. In order to add functionality to the process chain we must implement the boolean handleMessage(C context) method. A certain property can be used to determine the message direction:

1 public boolean handleMessage(SOAPMessageContext soapMessageContext) {
2 boolean outbound = (boolean) soapMessageContext.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
3 if (outbound) {
4 SOAPMessage soapMessage = soapMessageContext.getMessage();
5 try {
6 SOAPHeader soapHeader = soapMessage.getSOAPHeader();
7 addSignature(soapHeader);
8 }
9 catch ( ... ) {
10 ...
11 }
12 }
13 return true;
14 }

Next we can explore how the actual signature will be created and appended to the SOAP header:

1 private void addSignature(Element soapHeader) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MarshalException, XMLSignatureException {
2 XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
3 DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod(DigestMethod.SHA256, null);
4 Reference reference = xmlSignatureFactory.newReference("#xpointer(/S:Envelope/S:Body)", digestMethod);
5 CanonicalizationMethod canonicalizationMethod = xmlSignatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null);
6 SignatureMethod signatureMethod = xmlSignatureFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null);
7 SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));
8 XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, null);
9 DOMSignContext domSignContext = new DOMSignContext(this.privateKeyEntry.getPrivateKey(), soapHeader);
10 domSignContext.setURIDereferencer(new SoapBodyDereferencer());
11 domSignContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
12 xmlSignature.sign(domSignContext);
13 List<?> references = xmlSignature.getSignedInfo().getReferences();
14 traceReferences(references);
15 }

We get a XMLSignatureFactory instance in line two. A XMLSignatureFactory is needed to create and to assemble the several objects which make up a XMLSignature. First we use the XMLSignatureFactory to request the SHA-256 digest algorithm required for the checksum calculation of the referenced resources. Since we want to sign the whole SOAP body only one such reference is created in line four. The given XPointer fragment #xpointer(/S:Envelope/S:Body) denotes a so called same document reference. Note that the enclosed XPath expression is merely a hint for third parties because we will make use of a custom URI resolver (line 10). This SoapBodyDereferencer doesn't really evaluate the given XPointer fragment but validates only its presence and resolves thereupon the SOAP body. A canonicalization algorithm is chosen in line five. Canonicalization is needed because logical equivalent XML documents may have different DOM representations. E.g. intermediary node processors may change the DOM representation but maintain the logical equivalence. The algorithms to compute the actual signature over the digested reference are requested in line 6 (RSA with SHA-256). The SignedInfo object is assembled in line seven. The actual XMLSignature is put together in line eight (we leave out any KeyInfo). The DOMSignContext specifies the private key and the location in the DOM tree where an XMLSignature object is to be marshalled when generating the signature (line nine). We demand the caching of the references in line eleven. This enables the tracing of the referenced input streams after the signature generation which is useful for debugging purposes. The actual signature generation is requested in line twelve.

How does an actual XMLSignature over a SOAP body might look like? Consider this SecurityPriceServiceRequest (I don't give a textual representation to avoid Canonicalization issues). The Signed SecurityPriceServiceRequest contains a so called detached signature within its SOAP header (contrary to enveloped or enveloping signatures).

[Top]

The VerificationHandler

The handler chain on the server side cannot be accessed programmatically unlike the handler chain on the client side. Someone can use an annotation on the service implementation class to indicate a XML resource file containing the handler chain definition. That is the handler chain is fixed at runtime.

1 <?xml version="1.0" encoding="UTF-8"?>
2 <handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
3 <handler-chain>
4 <handler>
5 <handler-name>VerificationHandler</handler-name>
6 <handler-class>de.christofreichardt.jaxws.securitypriceservice.VerificationHandler</handler-class>
7 </handler>
8 <handler>
9 <handler-name>SchemaValidationHandler</handler-name>
10 <handler-class>de.christofreichardt.jaxws.securitypriceservice.SchemaValidationHandler</handler-class>
11 </handler>
12 </handler-chain>
13 </handler-chains>

Besides the VerificationHandler we are seeing a second handler responsible for the validation of the incoming message against the XML schema. Handlers on the server side will be called twice too. The first invocation takes place when the message is inbound. At that time we are required to verify the signature. Missing or invalid signatures as well as missing certificates for the given user id lead to a ProtocolException.

1 public boolean handleMessage(SOAPMessageContext soapMessageContext) {
2 boolean outbound = (boolean) soapMessageContext.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
3 SOAPMessage soapMessage = soapMessageContext.getMessage();
4 if (!outbound) {
5 KeyStore keyStore = retrieveKeystore(soapMessageContext);
6 try {
7 SOAPHeader soapHeader = soapMessage.getSOAPHeader();
8 Node signature = soapHeader.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
9 if (signature == null)
10 throw new ProtocolException("No Signature element found.");
11 PublicKey publicKey = fetchPublicKey(soapMessage, keyStore);
12 XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
13 DOMValidateContext domValidateContext = new DOMValidateContext(publicKey, signature);
14 domValidateContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
15 domValidateContext.setURIDereferencer(new ServerSoapBodyDereferencer());
16 XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
17 boolean validated = xmlSignature.validate(domValidateContext);
18 traceReferences(xmlSignature.getSignedInfo().getReferences(), domValidateContext);
19 if (!validated)
20 throw new ProtocolException("Invalid Signature.");
21 }
22 catch (...) {
23 ...
24 }
25 }
26 return true;
27 }

[Top]

Valid XHTML 1.0 Strict