IVOA

Securing Java web services
Version 0.1

IVOA WG Note 2005 May 16

Working Group:
http://www.ivoa.net/twiki/bin/view/IVOA/IvoaGridAndWebServices
Author(s):
Matthew Graham

Abstract

Status of this Document

This is a Note. The first release of this document was 2005 May 16.

A list of current IVOA Recommendations and other technical documents can be found at http://www.ivoa.net/Documents/.

Acknowledgments

Contents


1. Introduction

This note describes how to secure a Java web service using WSS4J. In attempting this myself, I found that one of the biggest challenges was to collate all the pieces of information in FAQs, sample code, newsgroups and blogs into a coherent self-consistent whole that worked. This note is the result of that experience.

2. Definitions

2.1 Private/public keys

Each person has a pair of keys - the public key which is published and the private key which is kept secret. The private key is linked mathematically to the public key normally by a large random number. The keys are based on a passphrase/word. Anyone can send an encrypted message using a public key but it can only be decrypted by the corresponding private key.

2.2 Encryption

Alice sends a message to Bob by encrypting the message using Bob's public key. Bob (and only Bob) can then decrypt the message using his private key.

2.3 Digital signatures

To sign a message, Alice performs a computation involving both her private key and the message itself. The output is a digital signature and is attached to the message. To verify the signature, Bob performs a computation involving the message, the purported signature and Alice's public key. If the result is mathematically correct then the signature is verified to be genuine.

2.4 Certificates

Certificates are digital documents vouchsafing the association of a public key and an individual or other entity, i.e. they verify that this public key belongs to this specific individual/entity. In their simplest form, certificates contain a public key and a name; commonly, they also contain an expiration date, the name of the certifying authority that issued the certificate, a serial number and other information. Most importantly, it contains the digital signature of the certificate issuer (the certifying authority).

2.5 X.509 certificates

The most widely accepted format for certificates is the ITU-T X.509 international standard.

2.6 PKCS#12 (also known as PFX)

A portable format standard developed by RSA for storing or transporting a user's private keys, certificates, etc. It stores in a binary format. It is the default format for a lot of browsers (certainly IE and Firefox).

2.7 PEM

The default format for OpenSSL for storing or transporting private keys, X.509 certificates, etc. It stores data in a Base64-encoded ASN1 DER format surrounded by ASCII headers so is suitable for text mode transfers between systems.

2.8 Certificate usage

Bob receives a signed message with an associated certificate, allegedly from Alice. He verifies the certificate using the certifying authority's public key (which he already knows) - he is now confident of the public key of the sender and can then proceed with verifying the message's signature.

2. 9 Certificate chains

A hierarchical set of certificates wherein one certificate testifies to the authenticity of the previous certificate. At the end of the hierarachy is a top-level certifying authority, which is trusted without a certificate from any other certifying authority.

2.10 SAML

The Security Assertion Markup Language is used to wrap authentication statements in an XML format: for example, the entity named Bob is the owner of a public key named "BobKey", the asserting authority has authenticated Bob using XML digital signatures and this assertion is valid from noon today until midnight tomorrow. SAML assertions are associated with messages like certificates.

3. Certificates

3.1 Obtaining a certificate

To obtain a certificate, you need to generate a private key for yourself and create a certificate request (CSR) based on this. The CSR then needs to be signed by a certifying authority (CA) to convert it to a certificate. Fortunately the key and the CSR can be created in one go using openssl:

# openssl req -new -newkey rsa:512 -nodes -out client.csr -keyout client_private.key
Using configuration from /usr/share/ssl/openssl.cnf
Generating a 512 bit RSA private key
..++++++++++++
.++++++++++++
writing new private key to 'client_private.key'
-----
You are about to enter information that will be incorporated into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quire a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:Pasadena
Organization Name (eg, company) []:Caltech
Organizational Unit Name (eg, section) []:CACR
Common Name (eg, your name or your server's hostname) []:Matthew Graham
Email Address []:mjg@cacr.caltech.edu

Please entry the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

The CSR produced looks like:

-----BEGIN CERTIFICATE REQUEST-----
MIIBWTCCAQMCAQAwgZ0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MREwDwYDVQQHEwhQYXNhZGVuYTEQMA4GA1UEChMHQ2FsdGVjaDEVMBMGA1UECxMM
QXN0cm9waHlzaWNzMRcwFQYDVQQDEw5NYXR0aGV3IEdyYWhhbTEkMCIGCSqGSIb3
DQEJARYVbWpnQGFzdHJvLmNhbHRlY2guZWR1MFwwDQYJKoZIhvcNAQEBBQADSwAw
SAJBANWUbVnZ+kbWycOcWiICvOZajKyhGFQhzOk5mbc9UcCYha9KkdzxZqtvYslt
8+/m6xC2qvQ+nNSLo8TKc0aJvAECAwEAAaAAMA0GCSqGSIb3DQEBBAUAA0EArAHt
lt0rLhSe0IPuft5h3dNrdASOqLCT49Lhdq+4In62NZFum8Ks3dEykMjhon92NjuQ
zQB6F3ipro+yCTpUOA==
-----END CERTIFICATE REQUEST-----

The signed X.509 certificate returned by the CA is in PEM format and looks like:

-----BEGIN CERTIFICATE-----
MIICFDCCAb4CAQcwDQYJKoZIhvcNAQEEBQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhQYXNhZGVuYTEQMA4GA1UEChMHQ2Fs
dGVjaDENMAsGA1UECxMEQ0FDUjEOMAwGA1UEAxMFQ2lyY2UxIzAhBgkqhkiG9w0B
CQEWFG1qZ0BjYWNyLmNhbHRlY2guZWR1MB4XDTA1MDQyMTIxNTkyNVoXDTA1MDUy
MTIxNTkyNVowgZ0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREw
DwYDVQQHEwhQYXNhZGVuYTEQMA4GA1UEChMHQ2FsdGVjaDEVMBMGA1UECxMMQXN0
cm9waHlzaWNzMRcwFQYDVQQDEw5NYXR0aGV3IEdyYWhhbTEkMCIGCSqGSIb3DQEJ
ARYVbWpnQGFzdHJvLmNhbHRlY2guZWR1MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
ANWUbVnZ+kbWycOcWiICvOZajKyhGFQhzOk5mbc9UcCYha9KkdzxZqtvYslt8+/m
6xC2qvQ+nNSLo8TKc0aJvAECAwEAATANBgkqhkiG9w0BAQQFAANBACwiM3r+07/i
ZfiIrF7YPEC1Eml+k+5esbbzObl/OyaSHrUSP0xYM12fuFiBSVMmwU9NlyLCNDHn
M8dWnFTIDyI=
-----END CERTIFICATE-----

However, you can examine the content with:

# openssl -printcert -in client.pem
Owner: EMAILADDRESS=mjg@cacr.caltech.edu, CN=Matthew Graham, OU=Astrophysics, O=Caltech, L=Pasadena, 
ST=California, C=US
Issuer: EMAILADDRESS=mjg@cacr.caltech.edu, CN=Circe, OU=CACR, O=Caltech, L=Pasadena, ST=California, C=US
Serial number: 7
Valid from: Thu Apr 21 14:59:25 PDT 2005 until: Sat May 21 14:59:25 PDT 2005
Certificate fingerprints:
         MD5:  C0:00:75:FC:D2:7A:BE:B1:35:2D:31:53:3B:27:9D:01
         SHA1: 50:9C:96:4B:14:D3:0B:72:3F:49:CC:99:E2:3A:B7:45:FE:D5:F2:24

For use in Java client code and most browsers, the certificate needs to be converted to PKCS#12 format:

# openssl pkcs12 -export -clcerts -in client.pem -inkey client.key -out client.p12 
-name "My Client Certificate"
Enter Export Password:clientpw
Verifying password - Enter Export Password:clientpw

Whatever export password you actually end up using needs to be remembered as the client code will require it.

3.2 Signing a certificate

TBW

3.3 Storing a CA certificate

TBW

4. Client code

WSS4J on the client side makes use of an Axis handler class - org.apache.ws.axis.security.WSDoAllSender - which needs to be configured (with a deployment descriptor file) to specify exactly what security actions are being used. This can be done either entirely in the configuration file or in code with a minimal configuration file. The configurations given here are for digitally signed SOAP messages.

Property Java constant Description
action WSHandlerConstants.ACTION This sets the security action, e.g.
  • Signature (WSHandlerConstants.SIGNATURE) for digitally signed messages
  • Encrypt (WSHandlerConstants.ENCRYPT) for encrypted messages
  • UsernameToken (WSHandlerConstants.USERNAMETOKEN) for username/password
Security actions can be combined or concatenated, e.g. to encrypt some parts of a message and sign others [WSS4J Deploy]. Each security action has additional properties associated with it that need to be set.
user WSHandlerConstants.USER The name of the user - for Signature, this needs to match the "name" or alias of your certificate, e.g. My Client Certificate
passwordCallbackClass WSHandlerConstants.PW_CALLBACK_CLASS The name of the class that implements a method to get the user's password - for Signature, this is the export password, e.g. clientpw.
signaturePropfile WSHandlerConstants.SIG_PROP_FILE The name of the signature crypto property file to use.
signatureKeyIdentifier WSHandlerConstants.SIG_KEY_ID The key identifier type to use.

4.1 Configuring using a configuration file

The configuration file - client_deploy.wsdd - should look like:

<handler type="java:org.apache.ws.axis.security.WSDoAllSender">
	<parameter name="action" value="Signature"/>
	<parameter name="user" value="My Client Certificate"/>
	<parameter name="passwordCallbackClass" value="some.service.client.PWCallback"/>
	<parameter name="signaturePropFile" value="client_crypto.properties"/>
	<parameter name="signatureKeyIdentifier" value="DirectReference"/>
</handler>

The client code then just needs to be told to use the configuration file:

package some.service.client;

import org.apache.axis.configuration.FileProvider;

public class SomeServiceSecureClient {
    
    public SomeServiceSecureClient() {
        try {
	SomeServiceLocator loc = new SomeServiceLocator(new FileProvider("client_deploy.wsdd"));
	SomeServiceStub service = (SomeServiceStub) loc.getPort();
        } catch (Exception e) {
            Exception handling code...
        }
    }
}

4.2 Configuring in code

The configuration file - client_deploy.wsdd - now just specifies the handler class:

<handler type="java:org.apache.ws.axis.security.WSDoAllSender"/>

and the configuration of the handler is done in the client constructor:

package some.service.client;

import org.apache.axis.EngineConfiguration;
import org.apache.axis.client.Stub;
import org.apache.axis.configuration.FileProvider;
import org.apache.ws.axis.security.util.AxisUtil;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.message.WSSignEnvelope;

public class SomeServiceSecureClient {
    
    public SomeServiceSecureClient() {
        try {
	EngineConfiguration config = new FileProvider("client_deploy.wsdd");
	SomeServiceLocator loc = new SomeServiceLocator(config);
	Stub axisPort = (Stub)	loc.getPort(SomeServiceSoap.class);
	axisPort._setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.SIGNATURE);
	axisPort._setProperty(WSHandlerConstants.SIG_PROP_FILE, "client_crypto.properties");
	axisPort._setProperty(WSHandlerConstants.USER, "My Client Certificate");
	axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, "some.service.client.PWCallBack");
	axisPort._setProperty(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
	SomeServiceStub service = (SomeServiceStub) axisPort;
        } catch (Exception e) {
            Exception handling code...
        }
    }
}

4.3 Password callback class

Instead of using cleartext passwords, the WSS4J Axis handler uses a password callback technique to get the password, i.e. the handler instantiates the defined callback class and calls its handle method when it requires a password. The callback class must implement the javax.security.auth.callback.CallbackHandler interface. A simple example of a password callback class is:

package some.service.client;

import org.apache.ws.security.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class PWCallback implements CallbackHandler {

    /*
     * (non-Javadoc)
     * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
     */

    /**
     * Method handle
     *
     * @param callbacks
     * @throws IOException
     * @throws UnsupportedCallbackException
     */
    public void handle(Callback[] callbacks)
            throws IOException, UnsupportedCallbackException {

        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof WSPasswordCallback) {
                WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

                /*
                 * here call a function/method to lookup the password for
                 * the given identifier (e.g. a user name or keystore alias)
                 * e.g.: pc.setPassword(passStore.getPassword(pc.getIdentfifier))
                 * for festing we supply a fixed name here.
                 */
                pc.setPassword("clientpw");
            } else {
                throw new UnsupportedCallbackException(callbacks[i],
                        "Unrecognized Callback");
            }
        }
    }
}

5. Server code

TBW

6. Using HTTPS with certificates

A standard HTTPS connection is secured with username/password. It is relatively straightforward, however, to use a certificate instead - this is how a lot of browsers work - so that you can use the same security token to sign your SOAP messages and secure your HTTP traffic, e.g to a WebDAV server.

6.1 Server configuration

An HTTPS port needs to be defined in Tomcat to request a client certificate from any service attempting to establish a connection and validate it by checking that the CA of the certificate is in the list of trusted CAs that Tomcat uses. An entry is required in the Tomcat server configuration file $TOMCAT_HOME/conf/server.xml:

<Connector port="8443"
	maxThreads="150" minSpareThreads"25" maxSpareThreads"75"
	enableLookups="true" disableUploadTimeout="false"
	acceptCount="100" debug="0" scheme="https" secure="true"
	clientAuth="true" sslProtocol="TLS"
	URIEncoding="UTF-8"/>

This will use the system keystore as the trusted CA certificate repository.

6.2 Client configuration

The client needs to register a new protocol handler for https so that a certificate is used to establish a connection instead of a username/password:

package some.service.client;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.protocol.*;

public class SomeServiceSecureClient {

    public SomeServiceSecureClient(String server, int servicePort) {
        try {
            // Establish secure http connection with certificate
            Protocol.registerProtocol("https", new Protocol("https", new SSLCertSocketFactory("ca.pem", "client.p12"), 443));
            HttpURL hrl = new HttpsURL(server, servicePort, "/webdav");
        ...

The protocol handler SSLCertSocketFactory needs to implement org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory:

package some.service.client;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.*;

import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * A class to represent a SecureProtocolSocketFactory that works with SSL
 *  and X.509 certificates stored locally (not in a keystore).
 *
 * @author  Matthew Graham  Caltech
 * @version  prototype  3 March 2005
 */
public class SSLCertSocketFactory implements SecureProtocolSocketFactory {

    /** Log object for this class */
    private static final Log LOG = LogFactory.getLog(SSLCertSocketFactory.class);
    private static final String PASSWORD = "clientpw";
    private SSLSocketFactory sf;

    /** 
     * Construct a basic SSLCertSocketFactory 
     *
     * @param caCert the location of the x509 certificate for the CA
     * @param clientCert the location of the PKCS12 certificate of the client
     */
    public SSLCertSocketFactory(String caCert, String clientCert) {
        super();

        try {
            // Add Bouncy Castle security provider
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvi
der());

            // Create SSL Context
            SSLContext sc = SSLContext.getInstance("TLS");

            // Load CA Chain file
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(new File
InputStream(caCert));
        
            // Load client's public and private keys from PKCS12 certificate
            KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
            ks.load(new FileInputStream(clientCert), PASSWORD.toCharArray());
            ks.setCertificateEntry("storeca", cert);

            // Initialise manager factories
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ks);
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, PASSWORD.toCharArray()); 
            // Initialise context
            sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            // Get socket factory
            sf = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    /** 
     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.I
netAddress,int)
     */
    public Socket createSocket(String host, int port, InetAddress clientHost, int cl
ientPort) throws IOException, UnknownHostException {
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port, clientHost, cl
ientPort);
        return sslSocket;
    }

    /**
     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
     */
    public Socket createSocket(String host, int port) throws IOException, UnknownHos
tException {
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port);
        return sslSocket;
    }   

    /**
     * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.Strin
g,int,boolean)
     */
    public Socket createSocket(Socket socket, String host, int port, boolean autoClo
se) throws IOException, UnknownHostException {
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host, port, autoCl
ose);
        return sslSocket;
    }

}

Note that the client export password clientpw needs to be specified in this class. This class uses the Bouncy Castle Crypto API [BCP] as its security provider since there were problems with the default Sun crypto provider and PKCS12-format certificates just stored as files.

References

[WSS4J Deploy] WSS4J Deployment Examples, http://ws.apache.org/ws-fx/wss4j/package.html

[BCP] Bouncy Castle Crypto Provider, http://www.bouncycasle.org