Thanks Sergey for looking at this. I am using fully qualified domain names in
all cases, should've mentioned that.
These bug reports at OpenJDK/Sun/Oracle are relevant; the second says they know
the problem but there's no resolution; any fix will probably be in v9, not 1.8.:
https://bugs.openjdk.java.net/browse/JDK-8072464
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8144566
Stack overflow also says that CXF+SNI worked in 1.7, not in 1.8 and offers a
CXF workaround for 1.8 users; I wonder if this solution could be incorporated
into CXF?
http://stackoverflow.com/questions/30817934/extended-server-name-sni-extension-not-sent-with-jdk1-8-0-but-send-with-jdk1-7
So, does CXF configure a custom hostname verifier? The mere presence of that
seems to trigger the bug in v1.8.
HTH
> On Sep 15, 2016, at 5:43 AM, Sergey Beryozkin <[email protected]> wrote:
>
> Hi
>
> According to
> http://stackoverflow.com/questions/35366763/in-java-8-can-httpsurlconnection-be-made-to-send-server-name-indication-sni
>
> SNI is sent only if it is a fully qualified name.
>
> HTH, Sergey
>
> On 14/09/16 22:22, Chris Lott wrote:
>>
>> I'd like to ask about a behavior I see in CXF v 3.0.10 and v 3.1.7. I'm
>> using JDK 1.8, and I have a tiny test program (see below). Our REST
>> service is provided by Apache HTTPD (fronting Tomcat). The HTTPD is
>> configured with 2 virtual hosts that differ only in name. A conforming
>> client that sends SNI information works perfectly - receives the
>> appropriate certificate. The CXF client does not work - throws an
>> exception like this:
>>
>> Caused by: java.io.IOException: HTTPS hostname wrong: should be
>> <my-host-qa.my.company.com>
>> at
>> sun.net.www.protocol.https.HttpsClient.checkURLSpoofing(HttpsClient.java:649)
>>
>> at
>> sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:573)
>> at
>> sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
>>
>> at
>> sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1513)
>>
>> at
>> sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
>>
>> at
>> java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
>> at
>> sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
>>
>> at
>> org.apache.cxf.transport.http.URLConnectionHTTPConduit$URLConnectionWrappedOutputStream.getResponseCode(URLConnectionHTTPConduit.java:332)
>>
>> at
>> org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.doProcessResponseCode(HTTPConduit.java:1581)
>>
>> at
>> org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1610)
>>
>> at
>> org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1551)
>>
>> at
>> org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1348)
>>
>> ... 12 more
>>
>>
>> In a nutshell, the CXF library does not seem to provide Server Name
>> Indication (SNI) to the remote HTTPS server. Note that fetching
>> application/json content using a very plain Java method works just fine
>> - it provides SNI and gets the appropriate certificate - so I do not
>> believe it's a limitation of the platform that I'm using.
>>
>> Is there some additional configuration I need to do in this sample client?
>>
>> Thanks for listening.
>>
>> ---
>>
>> package com.mycompany.ecomp;
>>
>> import java.io.BufferedReader;
>> import java.io.InputStreamReader;
>> import java.net.URI;
>> import java.net.URL;
>> import java.security.cert.Certificate;
>> import java.security.cert.X509Certificate;
>>
>> import javax.net.ssl.HttpsURLConnection;
>> import javax.ws.rs.core.MediaType;
>> import javax.ws.rs.core.Response;
>> import org.apache.cxf.jaxrs.client.WebClient;
>>
>> /**
>> * Trivial Java REST client to test whether SNI is provided when HTTPS
>> content
>> * is fetched. Tested with Apache CXF ver 3.0.10 and 3.1.7.
>> */
>> public class FetchRestContent {
>>
>> class HttpResponse {
>> int code;
>> String body;
>> }
>>
>> /**
>> * Fetches REST content using only features provided by standard Java
>> * libraries.
>> *
>> * @param url
>> * @param user
>> * Set as header parameter "username"
>> * @param pass
>> * Set as header parameter "password";
>> * @return Code and body fetched from remote server
>> * @throws Exception
>> * on any failure
>> */
>> private HttpResponse getRestContentJdk(String uriString, String
>> path, String user, String pass) throws Exception {
>>
>> URL url = new URL(uriString + '/' + path);
>> HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
>> if (con == null)
>> throw new Exception("Failed to open HTTPS connection");
>>
>> con.setRequestMethod("GET");
>> con.setConnectTimeout(2000);
>> con.setReadTimeout(2000);
>> con.setRequestProperty("username", user);
>> con.setRequestProperty("password", pass);
>>
>> // Throws an exception on an HTTPS connection if the
>> // local trust store is not configured appropriately to
>> // accept the self-signed certificate at the remote end.
>> HttpResponse resp = new HttpResponse();
>> resp.code = con.getResponseCode();
>> BufferedReader in = new BufferedReader(new
>> InputStreamReader(con.getInputStream(), "UTF-8"));
>>
>> Certificate[] certs = con.getServerCertificates();
>> for (Certificate cert : certs) {
>> if (cert instanceof X509Certificate) {
>> X509Certificate xcert = (X509Certificate) cert;
>> System.out.println("X509 Principal name " +
>> xcert.getSubjectX500Principal().getName());
>> }
>> }
>>
>> StringBuffer sb = new StringBuffer();
>> String inputLine;
>> while ((inputLine = in.readLine()) != null)
>> sb.append(inputLine);
>> in.close();
>> con.getInputStream().close();
>> con.disconnect();
>> resp.body = sb.toString();
>> return resp;
>> }
>>
>> /**
>> * Fetches REST content using Jax-RS, as implemented by Apache CXF.
>> *
>> * @param uri
>> * @param path
>> * @param user
>> * @param pass
>> * @return
>> * @throws Exception
>> */
>> private HttpResponse getRestContentJaxrs(String uriString, String
>> path, String user, String pass) throws Exception {
>> URI uri = new URI(uriString);
>> WebClient client = WebClient.create(uri);
>>
>> client.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
>> client.path(path);
>> Response response = client.get();
>> HttpResponse httpResponse = new HttpResponse();
>> httpResponse.code = response.getStatus();
>> httpResponse.body = response.readEntity(String.class);
>> return httpResponse;
>> }
>>
>> public static void main(String[] args) throws Exception {
>> if (args.length != 4)
>> throw new IllegalArgumentException("Expect 4 arguments: URL
>> path username password");
>> if (!args[0].toLowerCase().startsWith("https"))
>> throw new IllegalArgumentException("Expect https prefix");
>> FetchRestContent fetcher = new FetchRestContent();
>>
>> System.out.println("GET-ing content via Java from " + args[0] +
>> ", path " + args[1]);
>> HttpResponse r1 = fetcher.getRestContentJdk(args[0], args[1],
>> args[2], args[3]);
>> System.out.println("HTTP response code is " + r1.code);
>> System.out.println("Response body follows:");
>> System.out.println(r1.body);
>>
>> System.out.println("GET-ing content via Jax-RS from " + args[0]
>> + ", path " + args[1]);
>> HttpResponse r2 = fetcher.getRestContentJaxrs(args[0], args[1],
>> args[2], args[3]);
>> System.out.println("HTTP response code is " + r2.code);
>> System.out.println("Response body follows:");
>> System.out.println(r2.body);
>> }
>> }
>
>
> --
> Sergey Beryozkin
>
> Talend Community Coders
> http://coders.talend.com/
>