Search code examples
javasslwsdlcxfclient-certificates

Apache CXF wsdl download via SSL/TLS


I have a cxf service running at

https://localhost:8443/services/MyService?wsdl

with client certificate required. The WSDL is not important here.

I am able to invoke the service when I remove the client certificate or https requirement.

The service and client classes were generated with cxf wsdl2java utility.

Here is MyService.class:

package com.mycompany;

import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceFeature;

/**
 * This class was generated by Apache CXF 2.7.3 2013-03-29T13:59:37.423-03:00 Generated source version: 2.7.3
 */
@WebServiceClient(name = "MyService", wsdlLocation = "myservice.wsdl", targetNamespace = "http://server/schemas/services")
public class MyService extends Service {

    public final static URL WSDL_LOCATION;

    public final static QName SERVICE = new QName("http://server/schemas/services", "MyService");
    public final static QName MyServicePort = new QName("http://server/schemas/services", "MyServicePort");
    static {
        URL url = MyService.class.getResource("myservice.wsdl");
        if (url == null) {
            Logger.getLogger(MyService.class.getName()).log(Level.INFO, "Can not initialize the default wsdl from {0}", "myservice.wsdl");
        }
        WSDL_LOCATION = url;
    }

    public MyService(URL wsdlLocation) {
        super(wsdlLocation, SERVICE);
    }

    public MyService(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    public MyService() {
        super(WSDL_LOCATION, SERVICE);
    }

    /**
     * 
     * @return returns EncaminharMensagemPortType
     */
    @WebEndpoint(name = "MyServicePort")
    public MyServicePortType getMyServicePort() {
        return super.getPort(MyServicePort, MyServicePortType.class);
    }

    /**
     * 
     * @param features
     *            A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy. Supported features not in the
     *            <code>features</code> parameter will have their default values.
     * @return returns EncaminharMensagemPortType
     */
    @WebEndpoint(name = "MyServicePort")
    public MyServicePortType getMyServicePort(WebServiceFeature... features) {
        return super.getPort(MyServicePort, MyServicePortType.class, features);
    }

}

Here is my client without client certificate requirement: (that work fine)

package com.mycompany;

import java.net.URL;

import javax.xml.namespace.QName;

import com.mycompany.IdHolder;
import com.mycompany.MyDataObject;

public class CxfClientSslTest {

    public static void main(String[] args) {
        try {

            QName SERVICE_NAME = new QName("http://server/schemas/services", "MyService");
            URL wsdlURL = new URL("https://localhost:8443/services/MyService?wsdl");
            MyService ss = new MyService(wsdlURL, SERVICE_NAME);
            MyServicePortType port = ss.getMyServicePort();

            IdHolder mensagem = new IdHolder();
            mensagem.setId(1L);
            MyDataObject dataObject = port.getById(mensagem);

            System.out.println("Id: " + dataObject.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And here is my client send his certificate:

package com.mycompany;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.namespace.QName;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;

public class CxfClientSslTest {

    public static void main(String[] args) {
        try {

            QName SERVICE_NAME = new QName("http://server/schemas/services", "MyService");
            URL wsdlURL = new URL("https://localhost:8443/services/MyService?wsdl");
            MyService ss = new MyService(wsdlURL, SERVICE_NAME);
            MyServicePortType port = ss.getMyServicePort();

            tslIt(port);

            IdHolder mensagem = new IdHolder();
            mensagem.setId(1L);
            MyDataObject dataObject = port.getById(mensagem);

            System.out.println("Id: " + dataObject.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void tslIt(MyServicePortType port) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException,
            UnrecoverableKeyException {
        Client client = ClientProxy.getClient(port);
        HTTPConduit http = (HTTPConduit) client.getConduit();

        TLSClientParameters tlsClientParameters = http.getTlsClientParameters();

        KeyStore keyStore = getKeyStore();
        KeyStore trustStore = getTrustStore();

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

        keyManagerFactory.init(keyStore, "123456".toCharArray());
        KeyManager[] keyMgrs = keyManagerFactory.getKeyManagers();
        tlsClientParameters.setKeyManagers(keyMgrs);

        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        tlsClientParameters.setTrustManagers(trustManagers);

        tlsClientParameters.setDisableCNCheck(true);
    }

    public static KeyStore getKeyStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        URL keyStoreUrl = CxfClientSslTest.class.getResource("/certs/client.jks");
        File keystoreFile = new File(keyStoreUrl.getPath());
        if (!keystoreFile.exists()) {
            throw new RuntimeException("keystore doesn't exists: " + keystoreFile.getAbsolutePath());
        }

        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream keystoreInput = new FileInputStream(keystoreFile.getAbsolutePath());
        keystore.load(keystoreInput, "changeit".toCharArray());
        keystoreInput.close();
        return keystore;
    }

    public static KeyStore getTrustStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        URL trustStoreUrl = CxfClientSslTest.class.getResource("/certs/client-trust.jks");
        File trustStoreFile = new File(trustStoreUrl.getPath());
        if (!trustStoreFile.exists()) {
            throw new RuntimeException("truststore doesn't exists: " + trustStoreFile.getAbsolutePath());
        }

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream trustStoreInput = new FileInputStream(trustStoreFile.getAbsolutePath());
        trustStore.load(trustStoreInput, "changeit".toCharArray());
        trustStoreInput.close();
        return trustStore;
    }

}

The TLS configuration both from client and server were checked and are ok. But when I run the program I get this:

Information: Can not initialize the default wsdl from myservice.wsdl
javax.xml.ws.WebServiceException: org.apache.cxf.service.factory.ServiceConstructionException: Failed to create service.
    at org.apache.cxf.jaxws.ServiceImpl.<init>(ServiceImpl.java:149)
    at org.apache.cxf.jaxws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:98)
    at javax.xml.ws.Service.<init>(Service.java:77)
    at com.mycompany.MyService.<init>(MyService.java:36)
    at com.mycompany.CxfClientSslTest.main(CxfClientSslTest.java:32)
Caused by: org.apache.cxf.service.factory.ServiceConstructionException: Failed to create service.
    at org.apache.cxf.wsdl11.WSDLServiceFactory.<init>(WSDLServiceFactory.java:100)
    at org.apache.cxf.jaxws.ServiceImpl.initializePorts(ServiceImpl.java:199)
    at org.apache.cxf.jaxws.ServiceImpl.<init>(ServiceImpl.java:147)
    ... 4 more
Caused by: javax.wsdl.WSDLException: WSDLException: faultCode=PARSER_ERROR: Problem parsing 'https://localhost:8443/services/MyService?wsdl'.: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching localhost found
    at com.ibm.wsdl.xml.WSDLReaderImpl.getDocument(Unknown Source)
    at com.ibm.wsdl.xml.WSDLReaderImpl.readWSDL(Unknown Source)
    at com.ibm.wsdl.xml.WSDLReaderImpl.readWSDL(Unknown Source)
    at org.apache.cxf.wsdl11.WSDLManagerImpl.loadDefinition(WSDLManagerImpl.java:262)
    at org.apache.cxf.wsdl11.WSDLManagerImpl.getDefinition(WSDLManagerImpl.java:205)
    at org.apache.cxf.wsdl11.WSDLServiceFactory.<init>(WSDLServiceFactory.java:98)
    ... 6 more
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching localhost found
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1868)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1337)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:154)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:998)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1294)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1321)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1305)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:523)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1296)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:653)
    at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:189)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:799)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123)
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:240)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:300)
    ... 12 more
Caused by: java.security.cert.CertificateException: No name matching localhost found
    at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:208)
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:93)
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:347)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:203)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1319)
    ... 30 more

I can see that the problem is happening before my https configuration get done, when cxf tries to download the wsdl.

I made a research on how to make cxf use that https configuration to download the wsdl. It take me a lot of time, but I can't find the answer out there.

So my question is: how to make cxf use the https configuration to download the wsdl?

Please, I have the answer already and I intend to put it here. So, if you don't have a good answer, or better the answer, please don't post one.


Solution

  • After a lot of research on the web without success, I decide it is time to debug the cxf API. That is one of the points of open source, right?

    So I found out that cxf don't download directly the wsdl. It delegates that to wsdl4j through the call of

    javax.wsdl.xml.WSDLReader.readWSDL(javax.wsdl.xml.WSDLLocator)
    

    which calls

    javax.wsdl.xml.WSDLLocator.getBaseInputSource()
    

    which calls

    org.apache.cxf.wsdl11.ResourceManagerWSDLLocator.getInputSource(String, String)
    

    because ResourceManagerWSDLLocator were the WSDLLocator of the first method call.

    ResourceManagerWSDLLocator.getInputSource first line is:

    InputStream ins = bus.getExtension(ResourceManager.class).getResourceAsStream(importLocation);
    

    Now as ResourceManager is a extension of xcf's Bus, and you can add more ResourceResolver to it and the DefaultResourceManager (implements ResourceManager) will loop through all registered resolvers and will use the first one resolving a non-null value, you just need add a ResourceResolver to the ResourceManager.

    My final and working client application is:

    package com.mycompany;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateException;
    
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.xml.namespace.QName;
    
    import org.apache.cxf.Bus;
    import org.apache.cxf.BusFactory;
    import org.apache.cxf.configuration.jsse.TLSClientParameters;
    import org.apache.cxf.endpoint.Client;
    import org.apache.cxf.frontend.ClientProxy;
    import org.apache.cxf.resource.ResourceManager;
    import org.apache.cxf.resource.ResourceResolver;
    import org.apache.cxf.transport.http.HTTPConduit;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.BasicClientConnectionManager;
    import org.apache.http.params.BasicHttpParams;
    import org.apache.http.params.HttpParams;
    
    public class CxfClientSslTest {
    
        public static void main(String[] args) {
            try {
    
                Bus bus = BusFactory.getThreadDefaultBus();
                ResourceManager extension = bus.getExtension(ResourceManager.class);
    
                extension.addResourceResolver(new ResourceResolver() {
                    @Override
                    public <T> T resolve(String resourceName, Class<T> resourceType) {
                        System.out.println("resourceName: " + resourceName + " - resourceType: " + resourceType);
                        return null;
                    }
    
                    @Override
                    public InputStream getAsStream(String name) {
                        try {
                            if (!name.startsWith("https")) {
                                return null;
                            }
                            SSLSocketFactory sslSocketFactory = SslUtil.getSslSocketFactory();
                            SchemeRegistry schemeRegistry = new SchemeRegistry();
                            schemeRegistry.register(new Scheme("https", 8443, sslSocketFactory));
    
                            final HttpParams httpParams = new BasicHttpParams();
                            DefaultHttpClient httpClient = new DefaultHttpClient(new BasicClientConnectionManager(schemeRegistry), httpParams);
    
                            HttpGet get = new HttpGet(name);
                            HttpResponse response = httpClient.execute(get);
                            return response.getEntity().getContent();
                        } catch (Exception e) {
                            return null;
                        }
                    }
                });
    
                QName SERVICE_NAME = new QName("http://server/schemas/services", "MyService");
                URL wsdlURL = new URL("https://localhost:8443/services/MyService?wsdl");
                MyService ss = new MyService(wsdlURL, SERVICE_NAME);
                MyServicePortType port = ss.getMyServicePort();
    
                tslIt(port);
    
                IdHolder mensagem = new IdHolder();
                mensagem.setId(1L);
                MyDataObject dataObject = port.getById(mensagem);
    
                System.out.println("Id: " + dataObject.getId());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void tslIt(MyServicePortType port) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException,
                UnrecoverableKeyException {
            Client client = ClientProxy.getClient(port);
            HTTPConduit http = (HTTPConduit) client.getConduit();
    
            TLSClientParameters tlsClientParameters = http.getTlsClientParameters();
    
            KeyStore keyStore = getKeyStore();
            KeyStore trustStore = getTrustStore();
    
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    
            keyManagerFactory.init(keyStore, "123456".toCharArray());
            KeyManager[] keyMgrs = keyManagerFactory.getKeyManagers();
            tlsClientParameters.setKeyManagers(keyMgrs);
    
            trustManagerFactory.init(trustStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            tlsClientParameters.setTrustManagers(trustManagers);
    
            tlsClientParameters.setDisableCNCheck(true);
        }
    
        public static KeyStore getKeyStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
            URL keyStoreUrl = CxfClientSslTest.class.getResource("/certs/client.jks");
            File keystoreFile = new File(keyStoreUrl.getPath());
            if (!keystoreFile.exists()) {
                throw new RuntimeException("keystore doesn't exists: " + keystoreFile.getAbsolutePath());
            }
    
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream keystoreInput = new FileInputStream(keystoreFile.getAbsolutePath());
            keystore.load(keystoreInput, "changeit".toCharArray());
            keystoreInput.close();
            return keystore;
        }
    
        public static KeyStore getTrustStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
            URL trustStoreUrl = CxfClientSslTest.class.getResource("/certs/client-trust.jks");
            File trustStoreFile = new File(trustStoreUrl.getPath());
            if (!trustStoreFile.exists()) {
                throw new RuntimeException("truststore doesn't exists: " + trustStoreFile.getAbsolutePath());
            }
    
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream trustStoreInput = new FileInputStream(trustStoreFile.getAbsolutePath());
            trustStore.load(trustStoreInput, "changeit".toCharArray());
            trustStoreInput.close();
            return trustStore;
        }
    }