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>
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();
}
}
}