4

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?

KENG_1080
  • 55
  • 5
  • You may be confusing "multiple entries in a KeyStore" with a Certificate Chain. Each entry in a keystore is associated to a certificate chain (which usually contains only one certificate, but it may vary). A certificate chain being a list of certificates, let's call them A, B, C, where A signed B and B signed C. The post you link to explains how entries are chosen inside a keystore, which works wether or not the entry is associated with a chain of one or multiple certificates. What you need here is multiple *entries* in you keyStore, not (necessarily) an entry with multiple certs in its chain. – GPI Jun 25 '19 at 07:57
  • I tried using you last suggestion. So I have two key entries with two aliases each contain one certificate. How to choose it dynamically? He's still fetching the first alias he gets after using some logic when creating the auth header. Can this be done at runtime before sending the request? – KENG_1080 Jun 25 '19 at 08:21
  • Does the old and new certificate share a common root/issuing certificate? If so, you could just add this to your truststore, then they can change their certificate without you ever noticing. – Tobb Jun 25 '19 at 08:35
  • 1
    As explained in the link you provide, the entry is chosen by the SSL/TLS implementation (not yourself). It is the first that matches the server's constraints (typically : who issued the certificate, is it still valid, what algorithm is used, ...). If **both** your entries match **all** the constraints, then indeed you are in trouble, and you need to build keystore with only one of the entry, before hand, based on your own logic to chose (e.g. on or before july 1st, build a keystore with cert A, after, build a keystore with cert B). – GPI Jun 25 '19 at 08:44
  • @GPI Yeah, that's the way to go I think. – KENG_1080 Jun 25 '19 at 09:08
  • So the Apache CXF client does not support by default 2-way SSL handshake as described here: https://stackoverflow.com/a/55916380/11692464. This kind of solution should also help me so the client chooses the certificate provided by the server. – KENG_1080 Jun 25 '19 at 09:27
  • You're not exactly right in your interpretation : the question you link to states that there is no way to force standard SSL/TLS java sockets on the client side to force the use of 2way SSL (e.g. refuse a server connection that would not ask for a client certificate). This is not CXF specific, and has **nothing to do** with a standard support of 2way SSL/TLS . I guarantee you that CXF **does** support 2Way SSL with multiple different entries for different hosts. – GPI Jun 25 '19 at 10:23

0 Answers0