středa 6. dubna 2011

JAX-WS enterprise application client důvěřující všem serverovým certifikátům

Nedávno jsem vytvořil webovou službu využívající SSL a k ní enterprise aplikačního klienta pro server glassfish za použití JAX-WS. Bylo třeba aby tato dvojice byla spustitelná i na jiném počítači. Tedy klient musel akceptovat i jiné, v době vývoje neznámé, serverové certifikáty. Jedním z možných řešení je vytvořit klienta tak, aby automaticky důvěřoval všem serverovým certifikátům ať obsahují cokoli. Upozorňuji že toto řešení v žádném případě není vhodné pro produkční prostředí.
Aby klient důvěřoval všem certifikátům je třeba vytvořit následující třídu:
1:  import javax.net.ssl.X509TrustManager;  
2:  public class TrustEverythingTrustManager implements X509TrustManager {  
3:      public java.security.cert.X509Certificate[] getAcceptedIssuers() {  
4:        return null;  
5:      }  
6:      public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {  }  
7:      public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {  }  
8:    }  

Navíc WSDL, jehož kopie je i lokálně na straně klienta, obsahuje následující adresu: <soap:address location="https://localhost:8181/services/testPort"/>. Pokud server vrátí certifikát jehož CN bude jiné než localhost, což s největší pravděpodobností bude, nastane následující vyjímka:
 java.lang.reflect.InvocationTargetException  
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
     at java.lang.reflect.Method.invoke(Method.java:597)  
     at org.glassfish.appclient.client.acc.AppClientContainer.launch(AppClientContainer.java:424)  
     at org.glassfish.appclient.client.AppClientFacade.main(AppClientFacade.java:134)  
 Caused by: com.sun.xml.ws.client.ClientTransportException: HTTP transport error: java.io.IOException: HTTPS hostname wrong: should be <localhost>  
     at com.sun.xml.ws.transport.http.client.HttpClientTransport.getOutput(HttpClientTransport.java:135)  
     at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:163)  
     at com.sun.xml.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:95)  
     at com.sun.xml.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java:105)  
     at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:629)  
     at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:588)  
     at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:573)  
     at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:470)  
     at com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl.process(AbstractTubeImpl.java:112)  
     at com.sun.enterprise.security.webservices.ClientSecurityPipe.processSecureRequest(ClientSecurityPipe.java:192)  
     at com.sun.enterprise.security.webservices.ClientSecurityPipe.process(ClientSecurityPipe.java:180)  
     at com.sun.xml.ws.api.pipe.helper.PipeAdapter.processRequest(PipeAdapter.java:115)  
     at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:629)  
     at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:588)  
     at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:573)  
     at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:470)  
     at com.sun.xml.ws.client.Stub.process(Stub.java:319)  
     at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:157)  
     at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:109)  
     at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:89)  
     at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:140)  
     at $Proxy46.testOperation(Unknown Source)  
     at testwsappclient.Main.main(Main.java:62)  
     ... 6 more  
 Caused by: java.io.IOException: HTTPS hostname wrong: should be <localhost>  
     at sun.net.www.protocol.https.HttpsClient.checkURLSpoofing(HttpsClient.java:524)  
     at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:448)  
     at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)  
     at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1014)  
     at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:230)  
     at com.sun.xml.ws.transport.http.client.HttpClientTransport.getOutput(HttpClientTransport.java:123)  
     ... 28 more  

Pro tento případ potřebujeme vytvořit další třídu:
1:  import javax.net.ssl.HostnameVerifier;  
2:  import javax.net.ssl.SSLSession;  
3:  public class VerifyEverythingHostnameVerifier implements HostnameVerifier {  
4:    public boolean verify(String string, SSLSession sslSession) {  
5:      return true;  
6:    }  
7:  }  

A nyní obě třídy využijeme v kódu klienta:
1:  public class Main {  
2:    @WebServiceRef(wsdlLocation = "META-INF/test.wsdl")  
3:    private static TestService service;  
4:    public static void main(String[] args) {  
5:      Main main = new Main();  
6:      TestPortType port = service.getTestPort();  
7:      ((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "user");  
8:      ((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");  
9:      HostnameVerifier hostNameVerifier = new VerifyEverythingHostnameVerifier();  
10:      ((BindingProvider) port).getRequestContext().put("com.sun.xml.ws.transport.https.client.hostname.verifier", hostNameVerifier);  
11:      SSLContext sslContext;  
12:      try {  
13:        TrustManager[] trustManager = new TrustManager[]{new TrustEverythingTrustManager()};  
14:        sslContext = SSLContext.getInstance("SSL");  
15:        sslContext.init(null, trustManager, new java.security.SecureRandom());  
16:        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());  
17:      } catch (NoSuchAlgorithmException ex) {  
18:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
19:      } catch (KeyManagementException ex) {  
20:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
21:      }  
22:      ObjectFactory of = new ObjectFactory();  
23:      TestOperationRequest req = of.createTestOperationRequest();  
24:      req.setFirstName("Fist");  
25:      req.setLastName("Last");  
26:      try {  
27:        String back = port.testOperation(req);  
28:        System.out.println(back);  
29:      } catch (TestOperationFault ex) {  
30:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
31:      }  
32:    }  
33:  }  

Obdobný problém u REST služby je řešen zde.