JAX-WS, SOAP web service with Certificate Authentication

There are some examples on internet showing how to configure the certificate authentication for a web service publised with JAX-WS, I’ve seen some of them are using a self-signed certificate or signed by a third party. In this entry I’m going to show how to configure both to publish and consume a simple web service example.

All the code is hosted on my Github repository but I’m going to comment the most relevant here.

First we need to create the certificates, as simple overview, to esatblish the trust contract beetwen client and the server we have to use a valid certificate signed by an Certificate Authority (public or third party) or use a Self Signed Certificate. The first one is the most common use case used by enterprises, the second is a way to test the autentication for development pourposes.

Please, choose one of the next methods:

Method 1: Creating the certificates signed by a private CA

I prefer this method because is similar to production environments. And you can create a dummy CA to simulate this interaction for your development environments.

  1. The service consumer send a Certificate Sign Request to the Certificate Authority (CA)
  2. The CA valid the info and deliver a signed cert
  3. The service provider import the public certificate from CA as a trusted entity
  4. The service consumer send their certificate (signed by the CA) to the service provider to validate their idendity, wich is taken as a trusted (because is signed by the CA), therefore it could trust and allow the access to the client

Use this commands:

# Create a Certificate Authority (CA)

openssl genrsa -out rootCA.key 2048
openssl req -new -x509 -days 9999 -key rootCA.key -out rootCA.pem

# Generate the server key pair and import CA certificate

keytool -genkeypair -alias dummyserver -keyalg RSA -keysize 2048 -validity 3650 -dname "CN=Dummy Server,OU=IT dummy,O=Dummy Server,L=MX,ST=CDMX,C=MX" -keypass changeit -keystore server.jks -storepass changeit -deststoretype pkcs12
keytool -importcert -alias DummyCA -file rootCA.pem -keystore server.jks -storepass changeit -noprompt

# Generate client key pair and Certificate Signing Request (CSR)

keytool -genkeypair -alias client-keystore -keyalg RSA -keysize 2048 -validity 3650 -dname "CN=localhost,O=dummyC1.com" -ext SAN=DNS:localhost -keypass changeit -keystore client-keystore.jks -storepass changeit -deststoretype pkcs12
keytool -certreq -alias client-keystore -keystore client-keystore.jks -file client.csr -storepass changeit

# Sign the certificate and validate

openssl x509 -req -days 365 -in client.csr -CA rootCA.pem -CAkey rootCA.key -set_serial 01 -out client.crt
openssl verify -CAfile rootCA.pem client.crt

Then import the client.crt into the client-keystore.jks using the Keystore Explorer, select the key pair with the context menu > Import CA Reply > From File, after that the certificate will be replaced by the certificate issued by the CA.

Method 2: Creating a Self Signed Certificate

Use this method only for development pourposes, not in production environment.

  1. The service consumer and the service provider use the same keypair
  2. The consumer send their Self Signed Certificate, wich is a valid because the server trust on all all certificates from the key pair, so the service provider alllow the access.

As the Baeldung blog indicates:

«we should not use a self-signed certificate but a certificate which has been certified by a Certificate Authority (CA) which clients can trust by default«

# Creating a Self Signed Certtificate

keytool -genkeypair -alias dummy_scc -keyalg RSA -keysize 2048 -validity 3650 \
-dname "CN=Dummy SSC,OU=IT dummy,O=Dummy SSC,L=MX,ST=CDMX,C=MX" \
-ext SAN=DNS:localhost -keypass changeit -keystore server.jks \
-storepass changeit -deststoretype pkcs12

The Subject and the Issuer are the same.

Setting the keystore into the project

By default the project is builded with a Self-signed Certificate, when it is compiled by maven the keytool-maven-plugin creates a new keystore with a new key pair. This is included into pom.xml file. If you want to create your own keystore replace the server.jks file.

<build>
	<plugins>
		<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>keytool-maven-plugin</artifactId>
			<version>1.6</version>
			<executions>
				<execution>
					<id>keypair-generator</id>
					<goals>
						<goal>generateKeyPair</goal>
					</goals>
					<phase>generate-resources</phase>
				</execution>
			</executions>
			<configuration>
				<keystore>src/main/resources/server.jks</keystore>
				<storepass>changeit</storepass>
				<keypass>changeit</keypass>
				<alias>dummyServer</alias>
				<dname>cn=www.orbitalzero.com, ou=IT, L=Mexico, ST=CDMX, o=OrbitalZero, c=MX</dname>
				<validity>100</validity>
				<keyalg>RSA</keyalg>
				<keysize>2048</keysize>
				<storetype>PKCS12</storetype>
				<exts>SAN=DNS:localhost</exts>
				<skipIfExist>true</skipIfExist>
			</configuration>

		</plugin>
	</plugins>
</build>

Configuring and running the web service provider

1. Into jaxws-server module, the class CustomHttpServerFactory has the methods getSslContext() and getHttpsConfigurator(), the first one is used to load the keystore and the truststore, the second one is used to configure the https server with the SSL parameter needClientAuth.

public SSLContext getSslContext() throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {

	// setup the key manager factory
	KeyManagerFactory keyManager = KeyManagerFactory.getInstance(
			KeyManagerFactory.getDefaultAlgorithm()
	);
	keyManager.init(keystore, keystorePassword);

	// setup the trust manager factory
	TrustManagerFactory trustManager = TrustManagerFactory.getInstance(
			KeyManagerFactory.getDefaultAlgorithm()
	);
	trustManager.init(truststore);

	SSLContext sslContext = SSLContext.getInstance("TLS");

	// setup the HTTPS context and parameters
	sslContext.init(keyManager.getKeyManagers()
			, trustManager.getTrustManagers()
			, null);

	return sslContext;
}

public HttpsConfigurator getHttpsConfigurator() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

	SSLContext sslContext = getSslContext();

	return new HttpsConfigurator(sslContext){
		public void configure(HttpsParameters params) {
			try {

				// get the remote address if needed
				InetSocketAddress remote = params.getClientAddress();
				log.info("client addr:{}",remote.getAddress() );

				// initialise the SSL context
				SSLEngine sslEngine = sslContext.createSSLEngine();
				SSLParameters sslParameters = sslContext.getDefaultSSLParameters();

				sslParameters.setNeedClientAuth( true );
				sslParameters.setCipherSuites( sslEngine.getEnabledCipherSuites() );
				sslParameters.setProtocols( sslEngine.getEnabledProtocols() );

				params.setSSLParameters(sslParameters);

				log.info("The HTTPS server is connected");

			} catch (Exception ex) {
				log.info("Failed to create the HTTPS port");
			}
		}
	};
}

2. Then the HttpsConfigurator must be passed when the server is created, after that, the Enpoint is published.

// Create a dummy https web server
HttpsConfigurator httpsConfigurator  = getHttpsConfigurator();

HttpsServer httpsServer=HttpsServer.create(new InetSocketAddress(port), 0);
httpsServer.setHttpsConfigurator(httpsConfigurator);

//Launch server 
httpsServer.createContext("/test", new DefaultHandler());
httpsServer.start();

Endpoint ep = Endpoint.create(new HelloServiceImpl());
ep.publish(httpsServer.createContext("/HelloServer"));

3. To execute the server run the main method from DummyServer class.

4. In order to validate the access open the web browser and open the menu to select Settings Privacy and Security > Security > Manage device certificates, and import the server.jks file into Personal storage, then open the url: https://localhost:8443/HelloService?wsdl, the web service descriptor must be showed.

Configuring and running the web service client

1. Into jaxws-client module, in order to use the certificate to authenticate with the web service provider a SSL context object must be provided. Into the HelloServiceClientclass there is attached to the request context:

if(sslContext != null){
	SOAPBinding binding = (SOAPBinding) bindingProvider.getBinding();
	binding.setMTOMEnabled(true);
	bindingProvider.getRequestContext().put(
			"com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory"
			, sslContext.getSocketFactory());
}

2. To build the sslContext is similar as the provider service, loading the keystore and the truststore:

private SSLContext getSslContext() throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException {

	URL keystore = HelloServiceClientTest.class.getClassLoader().getResource("server.jks");
	URL truststore = HelloServiceClientTest.class.getClassLoader().getResource("server.jks");

	String pass= "changeit";

	// Cargar el archivo de clave privada
	KeyStore keyStore = KeyStore.getInstance("PKCS12");
	keyStore.load(new FileInputStream(keystore.getFile()), pass.toCharArray());

	// Configurar el KeyManagerFactory para el acceso a la clave privada
	KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
	kmf.init(keyStore, pass.toCharArray());

	// Cargar el archivo de certificado público
	KeyStore trustStore = KeyStore.getInstance("PKCS12");
	trustStore.load(new FileInputStream(truststore.getFile()), pass.toCharArray());

	// Configurar el TrustManagerFactory para la validación del certificado del servidor
	TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
	tmf.init(trustStore);

	// Configurar SSLContext para la autenticación con certificado
	SSLContext sslContext = SSLContext.getInstance("TLS");
	sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

	return sslContext;
}

3. Execute the client with the method greet() from the HelloServiceClientTest class.

And that’s all, see ya! =)

Referencias

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.