I have a SOAP-client application that calls a third-party SOAP endpoint and we use SSL to have access to it. Now the certificate is expiring in a few months and we received a new certificate of our third-party. Now we use Apache CXF client for connecting to the endpoint. We use a private key and certificate to create a key entry in own Keystore in Java code.
Note that the third-party on the server side has not yet made use of the new certificate and expects me the client to support both at the same time so they can do the migration seamlessly.
Now I adapted our code to add the new certificate to that one existing key entry. Because in the code in a Keystore you can have one alias and multiple certificates (certificate chain) defined for a key entry. The only thing is that the client always uses the first certificate when sending the SOAP request.
From my understanding of this link The default keymanager always chooses the first one in the chain of certificates
Our Keystore implementation:
@Bean
public Merlin merlin(KeyStore keyStore) throws WSSecurityException, IOException {
return new KeyStoreMerlin(keyStore);
}
@Bean
public KeyStore keyStore(SSLConnectionProperties sslConnectionProperties,
RandomPasswordCallbackHandler randomPasswordCallbackHandler) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate[] x509Certificates = sslConnectionProperties.getCertifcates().stream()
.map(crt -> createX509Certificate(certificateFactory, crt.getCertificate()))
.toArray(X509Certificate[]::new);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PemReader pemReader = new PemReader(new StringReader(sslConnectionProperties.getKey()));
PemObject pemObject = pemReader.readPemObject();
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pemObject.getContent()));
keyStore.load(null);
keyStore.setKeyEntry(CERTIFICATE_ALIAS, privateKey, randomPasswordCallbackHandler.getPassword().toCharArray(), x509Certificates);
return keyStore;
}
Create authentication header for CXF client:
private WSS4JOutInterceptor createAuthenticationInterceptor(Merlin merlin, RandomPasswordCallbackHandler randomPasswordCallbackHandler) {
return new WSS4JOutInterceptor(new HashMap<String, Object>() {
{
put(WSHandlerConstants.ACTION, "Signature");
put(WSHandlerConstants.USER, CERTIFICATE_ALIAS);
put(WSHandlerConstants.PW_CALLBACK_REF, randomPasswordCallbackHandler);
put(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
put(ConfigurationConstants.SIG_PROP_REF_ID, SIGNATURE_PROPERTIES);
put(SIGNATURE_PROPERTIES, merlin);
}
});
}
@Bean
public TLSClientParameters tlsClientParameters(KeyStore keyStore, RandomPasswordCallbackHandler randomPasswordCallbackHandler) throws Exception {
String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(defaultAlgorithm);
keyManagerFactory.init(keyStore, randomPasswordCallbackHandler.getPassword().toCharArray());
final TLSClientParameters tlsClientParameters = new TLSClientParameters();
tlsClientParameters.setKeyManagers(keyManagerFactory.getKeyManagers());
return tlsClientParameters;
}
Enabling HTTPS on client:
private void enableHttpsOnClient(Client client) {
HTTPConduit conduit = (HTTPConduit) client.getConduit();
conduit.setTlsClientParameters(tlsClientParameters);
}
I read that you can choose dynamically the certificate based on your custom requirements. Or is there an other way to migrate safely to the new certificate at runtime without losing uptime?