|
This document describes how to setup Apache XML-RPC to work using SSL (https) with server authentication.
xmlrpc-2.0.jar and commons-codec-1.3.jar are in your classpath.
keytoolType the following command:
keytool -genkey -keystore keyStoreServer -keyalg RSA -alias myKey -storepass blabla1 -keypass blabla2 -validity 180
Feel free to use your own keystore, storepass, keypass and validity parameters. In my case a file called keyStoreServer is created containing public and private key.
keytool -export -keystore keyStoreServer -alias myKey -storepass blabla1 -file server.cer
In my case, a file called
server.ceris created containing public key.
keytool -import -keystore trustStoreClient -alias myKey -file server.cer -storepass blabla1
In my case, a file called
trustStoreClientis created.
Apache XML-RPC comes with a SecureWebServer class. The best way to write a xml-rpc server is to extend SecureWebServer and override the createServerSocket(int port, int backlog, java.net.InetAddress add)method. See Listing 1:
Listing 1: (download file)
import org.apache.xmlrpc.secure.*;
import java.net.*;
import java.io.*;
import java.security.*;
import javax.net.ServerSocketFactory;
import javax.net.ssl.*;
public class MySecureWebServer extends SecureWebServer {
private static final String SECURE_ALGORITHM = "SunX509";
private static final String SECURE_PROTOCOL = "TLS";
private static final String KEYSTORE_TYPE = "JKS";
private static final boolean requireClientAuth = false;
private int serverPort;
private String keyStoreFilename;
private String keyStorePassword;
private String keyPassword;
public MySecureWebServer(int serverPort, String keyStoreFilename, String keyStorePassword,
String keyPassword) {
super(serverPort);
this.serverPort = serverPort;
this.keyStoreFilename = keyStoreFilename;
this.keyStorePassword = keyStorePassword;
this.keyPassword = keyPassword;
}
protected java.net.ServerSocket createServerSocket(int port, int backlog, java.net.InetAddress add) throws java.lang.Exception {
return getServerSocket(port, keyStoreFilename, keyStorePassword, keyPassword, requireClientAuth);
}
ServerSocket getServerSocket(int serverPort, String keyStoreFilename, String keyStorePassword,
String keyPassword, boolean requireClientAuth) throws Exception {
// Make sure that JSSE is available
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
// A keystore is where keys and certificates are kept
// Both the keystore and individual private keys should be password protected
KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE);
keystore.load(new FileInputStream(keyStoreFilename), keyStorePassword.toCharArray());
// A KeyManagerFactory is used to create key managers
KeyManagerFactory kmf = KeyManagerFactory.getInstance(SECURE_ALGORITHM);
// Initialize the KeyManagerFactory to work with our keystore
kmf.init(keystore, keyPassword.toCharArray());
// An SSLContext is an environment for implementing JSSE
// It is used to create a ServerSocketFactory
SSLContext sslc = SSLContext.getInstance(SECURE_PROTOCOL);
// Initialize the SSLContext to work with our key managers
sslc.init(kmf.getKeyManagers(), null, null);
// Create a ServerSocketFactory from the SSLContext
ServerSocketFactory ssf = sslc.getServerSocketFactory();
// Socket to me
SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(serverPort);
// Authenticate the client?
serverSocket.setNeedClientAuth(requireClientAuth);
// Return a ServerSocket on the desired port
return serverSocket;
}
}
Listing 2: (download file)
import java.util.Hashtable;
public class SecureXmlRpcServerDemo {
private static final int PORT_NUM = 443;
private static final String KEY_STORE = "KeyStoreServer";
private static final String KEY_STORE_PASS = "blabla1";
private static final String KEY_PASS = "blabla2";
public SecureXmlRpcServerDemo() {
}
public Hashtable sumAndDifference (int x, int y) {
System.out.println("method sumAndDifference() invoked!");
Hashtable result = new Hashtable();
result.put("sum", new Integer(x + y));
result.put("difference", new Integer(x - y));
return result;
}
public static void main(String[] args) {
try {
MySecureWebServer server = new MySecureWebServer(PORT_NUM, KEY_STORE, KEY_STORE_PASS, KEY_PASS);
server.addHandler("sample", new SecureXmlRpcServerDemo());
server.setParanoid(false);
server.start();
System.out.println("Server started!");
System.out.println("Listening on port: "+PORT_NUM);
}
catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
Notes about Listing 2: You can use server.setParanoid(true) to allow only certain IPs to connect to server. You add the IPs you trust with server.acceptClient() method
Listing 3: (download file)
import java.io.*;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.Vector;
import javax.net.ssl.*;
import org.apache.xmlrpc.*;
import org.apache.xmlrpc.secure.*;
public class SecureXmlRpcClientDemo {
private final static int PORT_NUM = 443;
private final static String serverURL = "https://localhost:"+String.valueOf(PORT_NUM);
private final static String trustStoreFilename = "trustStoreClient";
public static void main(String[] args) {
try {
System.setProperty("javax.net.ssl.trustStore", trustStoreFilename);
setHostNameVerifier();
SecureXmlRpcClient client = new SecureXmlRpcClient(serverURL);
// Fill in the appropriate method call that's available on your server
String method = "sample.sumAndDifference";
// Build our parameter list.
Vector params = new Vector();
params.addElement(new Integer(10));
params.addElement(new Integer(7));
System.out.println("Invoking remote method:");
// Call the server, and get our result.
Hashtable result = (Hashtable) client.execute(method, params);
int sum = ((Integer) result.get("sum")).intValue();
int difference = ((Integer) result.get("difference")).intValue();
// Print out our result.
System.out.println("Sum: " + Integer.toString(sum) + ", Difference: " + Integer.toString(difference));
}
catch (XmlRpcException e) {
e.printStackTrace();
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
}
private static void setHostNameVerifier() {
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
System.out.println("Warning: URL Host: "+urlHostName+" vs. "+session.getPeerHost());
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
}
Notes about Listing 3:
Alternative to setting
System.setProperty("javax.net.ssl.trustStore", trustStoreFilename);
is to put your trust store file in one of the following files under JAVA_HOME directory where it will be automaticaly found:
lib/security/jssecacertslib/security/cacerts
Do not forget to set the host name verifier! If you do not set it, you will probably get the following exception:
java.io.IOException: HTTPS hostname wrong: should be <localhost>
Here is the alternative to client code presented in Listing 3.
Author: Andrej Furlan
Last change: August 11, 2005