Discussion:
SNI not sent when using CXF WebClient v3.1.7 in Java 8?
Chris Lott
2016-09-14 21:22:26 UTC
Permalink
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
2016-09-15 09:43:27 UTC
Permalink
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
Chris Lott
2016-09-15 10:59:50 UTC
Permalink
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
Post by Sergey Beryozkin
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
Sergey Beryozkin
2016-09-15 15:43:29 UTC
Permalink
Hi
Post by Chris Lott
Thanks Sergey for looking at this. I am using fully qualified domain names in all cases, should've mentioned that.
https://bugs.openjdk.java.net/browse/JDK-8072464
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8144566
Perhaps you can use Java 9 to run your client code ?
Post by Chris Lott
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?
It does not seem to be a solution that can guarantee anything. Colm do
you think it is worth it ?
Post by Chris Lott
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.
It does but I can see one issue being closed as can not reproduce.
If you'd like you can experiment with the following:
- check out CXF 3.1.x source
- add NoOpHostnameVerifier which will throw UOE in this package
https://github.com/apache/cxf/tree/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https
- update this code,
https://github.com/apache/cxf/blob/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java#L167

to check if the verifier an instance of NoOpHostnameVerifier, if yes -
do not set it at all.
- rebuild rt/transports/http

- configure HTTPConduit TSL properties with an instance of
NoOpHostnameVerifier (you can get to it with
WebClient.getConfig(webClient).getHttpConduit())

and see if avoiding setting the verifier at all can fix it...
If yes then we can decide on the final fix

Cheers, Sergey
Post by Chris Lott
HTH
Post by Sergey Beryozkin
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/
Sergey Beryozkin
2016-09-15 16:22:04 UTC
Permalink
I've just given it a quick try myself - temporarily updated
HttpsURLConnectionFactory ignore the verifier but I still see no
server_name extension in the debug - I've tried jax_rs/basic_http demo
with Java 8.

Sergey
Post by Sergey Beryozkin
Hi
Post by Chris Lott
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
https://bugs.openjdk.java.net/browse/JDK-8072464
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8144566
Perhaps you can use Java 9 to run your client code ?
Post by Chris Lott
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?
It does not seem to be a solution that can guarantee anything. Colm do
you think it is worth it ?
Post by Chris Lott
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.
It does but I can see one issue being closed as can not reproduce.
- check out CXF 3.1.x source
- add NoOpHostnameVerifier which will throw UOE in this package
https://github.com/apache/cxf/tree/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https
- update this code,
https://github.com/apache/cxf/blob/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java#L167
to check if the verifier an instance of NoOpHostnameVerifier, if yes -
do not set it at all.
- rebuild rt/transports/http
- configure HTTPConduit TSL properties with an instance of
NoOpHostnameVerifier (you can get to it with
WebClient.getConfig(webClient).getHttpConduit())
and see if avoiding setting the verifier at all can fix it...
If yes then we can decide on the final fix
Cheers, Sergey
Post by Chris Lott
HTH
Post by Sergey Beryozkin
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/
Sergey Beryozkin
2016-09-15 16:27:20 UTC
Permalink
But you can indeed set a custom SSLFactory wrapper easily on HttpConduit
TLS properties, get HTTPConduit as shown below and see if that solution
works...

Sergey
Post by Sergey Beryozkin
I've just given it a quick try myself - temporarily updated
HttpsURLConnectionFactory ignore the verifier but I still see no
server_name extension in the debug - I've tried jax_rs/basic_http demo
with Java 8.
Sergey
Post by Sergey Beryozkin
Hi
Post by Chris Lott
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
https://bugs.openjdk.java.net/browse/JDK-8072464
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8144566
Perhaps you can use Java 9 to run your client code ?
Post by Chris Lott
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?
It does not seem to be a solution that can guarantee anything. Colm do
you think it is worth it ?
Post by Chris Lott
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.
It does but I can see one issue being closed as can not reproduce.
- check out CXF 3.1.x source
- add NoOpHostnameVerifier which will throw UOE in this package
https://github.com/apache/cxf/tree/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https
- update this code,
https://github.com/apache/cxf/blob/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java#L167
to check if the verifier an instance of NoOpHostnameVerifier, if yes -
do not set it at all.
- rebuild rt/transports/http
- configure HTTPConduit TSL properties with an instance of
NoOpHostnameVerifier (you can get to it with
WebClient.getConfig(webClient).getHttpConduit())
and see if avoiding setting the verifier at all can fix it...
If yes then we can decide on the final fix
Cheers, Sergey
Post by Chris Lott
HTH
Post by Sergey Beryozkin
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
--
Sergey Beryozkin

Talend Community Coders
http://coders.talend.com/
Chris Lott
2016-09-16 01:09:49 UTC
Permalink
Sergey thanks for the many constructive suggestions. In the mean time we punted - bought a multi-name (SAN) certificate. CXF works *perfectly* with that certificate; my trivial Java client pukes :( a neat reversal of the previous situation!
But you can indeed set a custom SSLFactory wrapper easily on HttpConduit TLS properties, get HTTPConduit as shown below and see if that solution works...
Sergey
Post by Sergey Beryozkin
I've just given it a quick try myself - temporarily updated
HttpsURLConnectionFactory ignore the verifier but I still see no
server_name extension in the debug - I've tried jax_rs/basic_http demo
with Java 8.
Sergey
Post by Sergey Beryozkin
Hi
Post by Chris Lott
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
https://bugs.openjdk.java.net/browse/JDK-8072464
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8144566
Perhaps you can use Java 9 to run your client code ?
Post by Chris Lott
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?
It does not seem to be a solution that can guarantee anything. Colm do
you think it is worth it ?
Post by Chris Lott
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.
It does but I can see one issue being closed as can not reproduce.
- check out CXF 3.1.x source
- add NoOpHostnameVerifier which will throw UOE in this package
https://github.com/apache/cxf/tree/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https
- update this code,
https://github.com/apache/cxf/blob/master/rt/transports/http/src/main/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java#L167
to check if the verifier an instance of NoOpHostnameVerifier, if yes -
do not set it at all.
- rebuild rt/transports/http
- configure HTTPConduit TSL properties with an instance of
NoOpHostnameVerifier (you can get to it with
WebClient.getConfig(webClient).getHttpConduit())
and see if avoiding setting the verifier at all can fix it...
If yes then we can decide on the final fix
Cheers, Sergey
Post by Chris Lott
HTH
Post by Sergey Beryozkin
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
Post by Chris Lott
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
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.
*
* Set as header parameter "username"
* Set as header parameter "password";
* 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.
*
*/
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/
--
Sergey Beryozkin
Talend Community Coders
http://coders.talend.com/
p***@gmail.com
2018-09-11 08:01:18 UTC
Permalink
Hi Chris,

I have to set SNI hostname while making rest call using CXF client 3.1.2.
I'm not able to find any configuration for this. Can you please help me how
to set SNI name using CXF client. I'm able to do the same using HTTP client.



--
Sent from: http://cxf.547215.n5.nabble.com/cxf-user-f547216.html
Colm O hEigeartaigh
2018-09-11 13:21:29 UTC
Permalink
Could you try with a more up-to-date version of CXF?

Colm.
Post by p***@gmail.com
Hi Chris,
I have to set SNI hostname while making rest call using CXF client 3.1.2.
I'm not able to find any configuration for this. Can you please help me how
to set SNI name using CXF client. I'm able to do the same using HTTP client.
--
Sent from: http://cxf.547215.n5.nabble.com/cxf-user-f547216.html
--
Colm O hEigeartaigh

Talend Community Coder
http://coders.talend.com
Matthew Broadhead
2018-09-11 14:09:26 UTC
Permalink
i created a wrapper around WebClient to solve this problem.  I have not
checked recently whether it is still necessary in TomEE 7.0.5

// TODO this should not be necessary in java 9
    //
http://cxf.547215.n5.nabble.com/Custom-SSLSocketFactory-td5741482.html
    //
http://stackoverflow.com/questions/30817934/extended-server-name-sni-extension-not-sent-with-jdk1-8-0-but-send-with-jdk1-7
    private void webClientSslSocket() {
        HTTPConduit httpConduit =
WebClient.getConfig(webClient).getHttpConduit();
        if (httpConduit.getTlsClientParameters() != null) {
httpConduit.getTlsClientParameters().setSSLSocketFactory(new
SSLSocketFactoryFacade());
        } else {
            TLSClientParameters tlsCP = new TLSClientParameters();
            tlsCP.setSSLSocketFactory(new SSLSocketFactoryFacade());
            httpConduit.setTlsClientParameters(tlsCP);
        }
    }
Post by p***@gmail.com
Hi Chris,
I have to set SNI hostname while making rest call using CXF client 3.1.2.
I'm not able to find any configuration for this. Can you please help me how
to set SNI name using CXF client. I'm able to do the same using HTTP client.
--
Sent from: http://cxf.547215.n5.nabble.com/cxf-user-f547216.html
Matthew Broadhead
2018-09-11 14:36:37 UTC
Permalink
here is a gist containing the wrapper
https://gist.github.com/chongma/608152be50bfe99a294bf420137f3b71 you
will have to ignore the bits that relate to my project.

i checked and TomEE 7.0.5 uses cxf 3.1.15.  not sure if that version
contains the fix?
Post by Matthew Broadhead
i created a wrapper around WebClient to solve this problem.  I have
not checked recently whether it is still necessary in TomEE 7.0.5
// TODO this should not be necessary in java 9
    //
http://cxf.547215.n5.nabble.com/Custom-SSLSocketFactory-td5741482.html
    //
http://stackoverflow.com/questions/30817934/extended-server-name-sni-extension-not-sent-with-jdk1-8-0-but-send-with-jdk1-7
    private void webClientSslSocket() {
        HTTPConduit httpConduit =
WebClient.getConfig(webClient).getHttpConduit();
        if (httpConduit.getTlsClientParameters() != null) {
httpConduit.getTlsClientParameters().setSSLSocketFactory(new
SSLSocketFactoryFacade());
        } else {
            TLSClientParameters tlsCP = new TLSClientParameters();
            tlsCP.setSSLSocketFactory(new SSLSocketFactoryFacade());
            httpConduit.setTlsClientParameters(tlsCP);
        }
    }
Post by p***@gmail.com
Hi Chris,
I have to set SNI hostname while making rest call using CXF client 3.1.2.
I'm not able to find any configuration for this. Can you please help me how
to set SNI name using CXF client. I'm able to do the same using HTTP client.
--
Sent from: http://cxf.547215.n5.nabble.com/cxf-user-f547216.html
Loading...