javajax-ws

Simple Config for POJO https Client using JAX WS


I'm trying to get a simple POJO https Client/Server implementation running with JAX-WS.

The Server side seems to be running fine & was fairly simple, but I haven't found how to configure the Client in a simple fashion.

I've got it to work with a couple of Properties to include the Servers Keystore.
But, of course, that's only good for testing.

Any ideas?

I'm using JDK 17 with Eclipse 2023-09.

See comments in Source for details of Keystore & Certificate.

For convenience, I've posted them to paste.c-net.org:
Keystore keystore.password.p12
Certificate keystore.password.cer

Here's the source:

package uk.co.sslws6001.server;

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.KeyStore;
import java.util.HexFormat;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.namespace.QName;

import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;

import jakarta.jws.WebParam;
import jakarta.jws.WebService;
import jakarta.xml.ws.Endpoint;
import jakarta.xml.ws.Service;

public class WS {
    @WebService
    public interface MyWebService {
        public String             toHexDigits(@WebParam(name="intValue") final int intValue);
    }
    @WebService(endpointInterface=                       "uk.co.sslws6001.server.WS$MyWebService")
    public static final class MyWebServiceImpl implements uk.co.sslws6001.server.WS.MyWebService {
        @Override
        public String             toHexDigits(final int intValue) {
            return HexFormat.of().toHexDigits(          intValue);
        }
    }

    /*
     * Keystore & Certificate created thus:
     * keytool.exe  -genkeypair  -alias self_signed  -keystore keystore.password.p12  -storepass password  -keypass password  -keyalg RSA  -validity 99999  -storetype PKCS12
     * keytool.exe  -export      -alias self_signed  -keystore keystore.password.p12  -storepass password  -file keystore.password.cer
     */
    private static HttpsServer createHttpsServer() throws Exception {

        final     var pw  =                      "password".toCharArray();
        final     var ks  = KeyStore.getInstance("pkcs12");

        try(final var fis = new FileInputStream ("keystore.password.p12")) {
            ;         ks.load(fis, pw);
        }
        final     var kmf = KeyManagerFactory  .getInstance("SunX509");
        ;             kmf.init(ks, pw);

        final     var tmf = TrustManagerFactory.getInstance("SunX509");
        ;             tmf.init(ks);

        final     var sslContext = SSLContext.getInstance("TLS");
        ;             sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        final     var httpsServer =  HttpsServer.create(new InetSocketAddress("localhost", 6001), 0);
        ;             httpsServer.setHttpsConfigurator (new HttpsConfigurator(sslContext));

        return        httpsServer;
    }

    public  static void main(final String[] args) throws Exception {

        final var httpsAddress  = "/uk/co/sslws6001/server/MyWebService";
        final var implementor   = new MyWebServiceImpl();

        System.out.println("publishing.........: " + httpsAddress + "\t-> " + implementor);

        final var httpsServer   = createHttpsServer();

        final var httpsContext  = httpsServer.createContext(httpsAddress);
        final var httpsEndpoint = Endpoint.create(implementor);
        ;         httpsEndpoint.publish(httpsContext);
        try {
            httpsServer.start();

            System.out.println("https Server up....: " + httpsServer);

            testClient();
        }
        finally {
            httpsServer.stop(3);
            System.out.println("https Server DOWN...");
        }
    }

    private static void testClient() throws Exception {
        System.out.println("Test Client.........");

        /*
         * TODO get https Client to work WITHOUT following 2 lines (Servers private!! Keystore)...
         * ...: i.e. use the Servers exported Certificate ("keystore.password.cer") instead.
         */
        System.getProperties().put("javax.net.ssl.trustStore",         "keystore.password.p12");  
        System.getProperties().put("javax.net.ssl.trustStorePassword", "password");

        final var serviceQName   = new QName("http://server.sslws6001.co.uk/", "MyWebServiceImplService");

        final var serviceWsdlURL = new URL  ("https://localhost:6001/uk/co/sslws6001/server/MyWebService?wsdl");

        final var service        = Service.create(serviceWsdlURL, serviceQName);
        final var serviceProxy   = service.getPort(uk.co.sslws6001.server.WS.MyWebService.class);

        System.out.println("Service............: " + service);
        System.out.println("Service Proxy......: " + serviceProxy);

        System.out.println("Result Hex Digits..: " + serviceProxy.toHexDigits((int) System.currentTimeMillis()));
    }
}

And here's the POM:

<project
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>uk.co.sslws6001</groupId>
    <artifactId>client.server</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-ri</artifactId>
            <version>4.0.1</version>
            <type>pom</type>
        </dependency>
    </dependencies>
</project>

Solution

  • ok, the solution was...

    HttpsURLConnection.setDefaultSSLSocketFactory(getCustomSSLSocketFactory());
    

    getCustomSSLSocketFactory() wasn't too long-winded either.

    Here's a working example:

    package uk.co.sslws6001.server;
    
    import java.io.FileInputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.net.URL;
    import java.security.KeyStore;
    import java.security.SecureRandom;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.util.Date;
    import java.util.HexFormat;
    import java.util.LinkedList;
    import java.util.Optional;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.net.ssl.X509ExtendedTrustManager;
    import javax.xml.namespace.QName;
    
    import com.sun.net.httpserver.HttpsConfigurator;
    import com.sun.net.httpserver.HttpsServer;
    
    import jakarta.jws.WebParam;
    import jakarta.jws.WebService;
    import jakarta.xml.ws.Endpoint;
    import jakarta.xml.ws.Service;
    
    public class WS {
        @WebService
        public interface MyWebService {
            public String             toHexDigits(@WebParam(name="intValue") final int intValue);
        }
        @WebService(endpointInterface=                       "uk.co.sslws6001.server.WS$MyWebService")
        public static final class MyWebServiceImpl implements uk.co.sslws6001.server.WS.MyWebService {
            @Override
            public String             toHexDigits(final int intValue) {
                return HexFormat.of().toHexDigits(          intValue);
            }
        }
    
        /*
         * Keystore & Certificate created thus:
         * keytool.exe  -genkeypair  -alias self_signed  -keystore keystore.password.p12  -storepass password  -keypass password  -keyalg RSA  -validity 99999  -storetype PKCS12
         * keytool.exe  -export      -alias self_signed  -keystore keystore.password.p12  -storepass password  -file keystore.password.cer
         */
        private static HttpsServer createHttpsServer() throws Exception {
    
            final     var pw  =                      "password".toCharArray();
            final     var ks  = KeyStore.getInstance("pkcs12");
    
            try(final var fis = new FileInputStream ("keystore.password.p12")) {
                ;         ks.load(fis, pw);
            }
            final     var kmf = KeyManagerFactory  .getInstance("SunX509");
            ;             kmf.init(ks, pw);
    
            final     var tmf = TrustManagerFactory.getInstance("SunX509");
            ;             tmf.init(ks);
    
            final     var sslContext = SSLContext.getInstance("TLS");
            ;             sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
            final     var httpsServer =  HttpsServer.create(new InetSocketAddress("localhost", 6001), 0);
            ;             httpsServer.setHttpsConfigurator (new HttpsConfigurator(sslContext));
    
            return        httpsServer;
        }
    
        public  static void main(final String[] args) throws Exception {
    
            final var httpsAddress  = "/uk/co/sslws6001/server/MyWebService";
            final var implementor   = new MyWebServiceImpl();
    
            System.out.println("publishing.........: " + httpsAddress + "\t-> " + implementor);
    
            final var httpsServer   = createHttpsServer();
    
            final var httpsContext  = httpsServer.createContext(httpsAddress);
            final var httpsEndpoint = Endpoint.create(implementor);
            ;         httpsEndpoint.publish(httpsContext);
            try {
                httpsServer.start();
    
                System.out.println("https Server up....: " + httpsServer);
    
                testClient();
            }
            finally {
                httpsServer.stop(3);
                System.out.println("https Server DOWN...");
            }
        }
    
        private static void testClient() throws Exception {
            System.out.println("Test Client.........");
    
            HttpsURLConnection.setDefaultSSLSocketFactory(getCustomSSLSocketFactory());
    
            final var serviceQName   = new QName("http://server.sslws6001.co.uk/", "MyWebServiceImplService");
    
            final var serviceWsdlURL = new URL  ("https://localhost:6001/uk/co/sslws6001/server/MyWebService?wsdl");
    
            final var service        = Service.create(serviceWsdlURL, serviceQName);
            final var serviceProxy   = service.getPort(uk.co.sslws6001.server.WS.MyWebService.class);
    
            System.out.println("Service............: " + service);
            System.out.println("Service Proxy......: " + serviceProxy);
    
            System.out.println("Result Hex Digits..: " + serviceProxy.toHexDigits((int) System.currentTimeMillis()));
        }
    
        private static SSLSocketFactory getCustomSSLSocketFactory() throws Exception {
    
            final var sslContext    = SSLContext.getInstance("TLS");
            ;         sslContext.init(null, new TrustManager[] {getCustomTrustManager()}, new SecureRandom());
    
            final var socketFactory = sslContext.getSocketFactory();
    
            System.out.println("Custom SockFactory.: " + socketFactory);
    
            return    socketFactory;
        }
    
        private static X509ExtendedTrustManager getCustomTrustManager() {
    
            final var okCerts = new LinkedList<X509Certificate>();
    
            getServerPublicCertificate().ifPresent(okCerts :: add);
    
            return new X509ExtendedTrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[0];
                }
                @Override
                public void checkClientTrusted(final X509Certificate[] chain, final String authType)                         throws CertificateException {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void checkServerTrusted(final X509Certificate[] chain, final String authType)                         throws CertificateException {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void checkClientTrusted(final X509Certificate[] chain, final String authType, final Socket socket)    throws CertificateException {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void checkServerTrusted(final X509Certificate[] chain, final String authType, final Socket socket)    throws CertificateException {
                    System.out.println("checkServerTrusted.: chain=" + chain + " authType=" + authType + " socket=" + socket);
    
                    for (    final var suppliedCert : chain) {
                        for (final var okCert       : okCerts) {
    
                            if (suppliedCert.equals(okCert)) {
                                System.out.println("Certificate ok.....: id=" + suppliedCert.getSerialNumber());
                                return;
                            } else {
                                System.out.println("Certificate BAD....: id=" + suppliedCert.getSerialNumber());
                            }
                        }
                    }
                    throw new CertificateException("Bad Karma!");
                }
                @Override
                public void checkClientTrusted(final X509Certificate[] chain, final String authType, final SSLEngine engine) throws CertificateException {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void checkServerTrusted(final X509Certificate[] chain, final String authType, final SSLEngine engine) throws CertificateException {
                    throw new UnsupportedOperationException();
                }
            };
        }
    
        private static Optional<X509Certificate> getServerPublicCertificate() {
    
            try(final var is   = new FileInputStream("keystore.password.cer")) {
    
                final var fac  = CertificateFactory.getInstance("X509");
                final var cert = (X509Certificate) fac.generateCertificate(is);
    
                System.out.print  ("Server Public Cert.: sn=" + cert.getSerialNumber());
                System.out.println(      " -> Validity.: " + cert.getNotBefore() + " to " + cert.getNotAfter());
    
                final var now = new Date();
    
                if (now.before(cert.getNotBefore())
                ||  now.after (cert.getNotAfter())) {
                    System.out.println("Certificate INVALID: sn=" + cert.getSerialNumber());
                    return Optional.empty();
                }
                return     Optional.of(cert);
            }
            catch (final Exception e) {
                return     Optional.empty();
            }
        }
    }