Use SSL when GeoTrust is not listed as CA

When using xServer-internet with Java, you may run into SSL issues if you migrate your local code to a hosted Java platform. When invoking an xserver-internet request you may get the error:

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

The reason is that some of these hosted platforms don't have Geotrust in their list of Certificate Autorities (CA). Geotrust is the Certificate Authority for xServer-internet. One example is the SAP Hana Cloud Platform, which maintains a custom list of trusted Certificate Autorities.

This section explains how to add the Geotrust CA to your application without changing the Java system certificates, but still maintaining the Java certificate trust chain. You can download the sample application "CertTest" here.

1. Create a Keystore file with the Geotrust Certificate

We first create our own Java keystore (.jks) file with the Java keytool program (located at %JAVA_HOME%/bin). We assign "p7v_x53rv3r_1n73rn37"as password for our new keystore. Next we copy the Geotrust certificate from the Java system certificates file (cacerts) to our custom keystore. When asked for the password for cacerts on export, the default password is "changeit". Then we add this file to our class path.

"%JAVA_HOME%/bin/keytool" -genkey -alias xserver-internet -keystore ptv-xserver-internet.jks

"%JAVA_HOME%/bin/keytool" -exportcert -alias geotrustglobalca -keystore "%JAVA_HOME%/jre/lib/security/cacerts" > geotrust.crt

"%JAVA_HOME%/bin/keytool" -import -trustcacerts -alias geotrustglobalca -file geotrust.crt -keystore ptv-xserver-internet.jks

2. Change the CXF Configuration to use the Default SSL Socket Factory

To allow the modification of the SSL factory for the PTV Java clients, we have to modify the CXF configuration. This requires the Spring framework to be referenced in your project, we are using Spring 3.2 here. Then we add a file CXF.xml to our class path.

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:http-conf="http://cxf.apache.org/transports/http/configuration" xsi:schemaLocation="http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
   <http-conf:conduit name="*.http-conduit">
      <http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true" />
   </http-conf:conduit>
</beans>

3. Create a EnrichedSSLContext Class and set it as Default SSL Socket Factory

We provide a class EnrichedSSLContext in our sample. It initializes an SSLContext with a superordinate key and a trust manager that incorporates the environment's default and a custom key store. value. After setting the defaultSSLSocketFactory to the SocketFactory of our EnrichedSSLContext class, we can use the PTV client classes as usual.

// get a SSLContext that incorporates PTV xServer internet related certificates.
// The specified key store, ptv-xserver-internet.jks, is expected on the classpath.
SSLContext context = EnrichedSSLContext.incorporate("ptv-xserver-internet.jks", "p7v_x53rv3r_1n73rn37".toCharArray());
		
// wire the context 
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());

4. Test it in an Application

To test this, we create an application and set the default system property "javax.net.ssl.trustStore" to a non-existing file ("nada.jks"). Now we should get a error "unable to find valid certification path to requested target" when we access xserver-internet. If we now change the default SSL Factory before accessing xServer-internet, everything should work as expected. This is a test application that does an xLocate request with our enriched Geotrust context:

public class CertTest {

	public static void main(String[] args) throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
		
		BasicConfigurator.configure();
		LogManager.getRootLogger().setLevel(Level.OFF);
		
		// get a SSLContext that incorporates PTV xServer internet related certificates.
		// The specified key store, ptv-xserver-internet.jks, is expected on the classpath.
		SSLContext context = EnrichedSSLContext.incorporate("ptv-xserver-internet.jks", "p7v_x53rv3r_1n73rn37".toCharArray());
		
		// wire the context 
		HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
			
		//Create the client, setting username = "xtok", the required token and the url 
	  
        XLocateRemoteInterface client = (XLocateRemoteInterface)ClientFactory.createClient(
            XLocateRemoteInterface.class, RemoteType.DOCSTYLE_CXF,
            "xtok", "<insert your xserver-internet token here>",
            "https://xlocate-eu-n-test.cloud.ptvgroup.com/xlocate/ws/XLocate"
        );
        
        //Set a caller context switching coordinate format to EPSG:4326 
  
        CallerContextProperty setCoordFormat = new CallerContextProperty();
  
        setCoordFormat.setKey("CoordFormat");
        setCoordFormat.setValue("OG_GEODECIMAL");
  
        CallerContext cc = new CallerContext();
  
        cc.setProperties(new CallerContextProperty[] { setCoordFormat });
  
        client.setCallerContext(cc);
  
        //Setup the address you want to geocode 
  
        Address addr = new Address();
  
        addr.setCountry("D");
        addr.setPostCode("76131");
        addr.setCity("Karlsruhe");
        addr.setStreet("Haid-und-Neu-Straße 13");
  
        try {
        	//Call the service
  
            AddressResponse resp = client.findAddress(addr, null, null, null);
  
            // Evaluate response; print results on the console 
  
            ResultAddress[] resultAddresses = resp.getErrorCode() != 0 ?
                    new ResultAddress[0] : resp.getResultList();
  
                    System.out.println("found " + resultAddresses.length + " candidate(s)");
  
                    int n=0;
                    for (ResultAddress resultAddress : resultAddresses) {
                        PlainPoint p = resultAddress.getCoordinates().getPoint();
                        System.out.println("location #" + (++n) + ": x=" + p.getX() + ", y=" + p.getY());
                    }
        } catch (Throwable t) {
        	//handle errors
            System.out.println("failed to geocode address: " + t.getMessage());
        }
    }
	
	static {
		// set trustStore to an invalid store for testing purposes. Doing this, 
		// the certificates required by xServer internet will initially not be 
		// available.
		
		System.setProperty("javax.net.ssl.trustStore", "nada.jks");
	}
}