HttpsUrlConnection (called via Jersey client) not calling createSocket of set SSLSocketFactory - java

Look at the bottom of this question for more up to date information
I am trying to intercept all SSL handshakes (so that I can get information on them as well as present visual information to users, much like the green lock in browsers) that happen through my Jersey client. Unfortunately it does not seem like Jersey is using my SSLSocketFactory implementation, because none of the createSocket methods are called. No errors occur, it is just that nothing gets logged. The code should be clear:
Invocation + Instantiation:
this.httpClient = getHttpsClient(new DefaultSSLContextProvider());
Invocation.Builder invBuilder = httpClient.target(API_URL_PRIVATE + API_VERSION_2 + "markets").request(MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML);
invBuilder.header("Content-Type", "application/x-www-form-urlencoded");
invBuilder.header("User-Agent", USER_AGENT);
Response response = invBuilder.get();
logger.debug("response: " + response);
httpClient:
public Client getHttpsClient(SSLContextProvider sslContextProvider) throws KeyStoreException
{
ClientConfig config = new ClientConfig().connectorProvider(new HttpUrlConnectorProvider().connectionFactory(
url ->
{
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContextProvider.getSSLSocketFactory());
return connection;
}));
return ClientBuilder.newBuilder()
.sslContext(sslContextProvider.getSSLContext())
.withConfig(config)
.build();
}
DefaultSSLContextProvider:
public class DefaultSSLContextProvider implements SSLContextProvider
{
private SSLContext sslContext;
private ObservableSSLSocketFactory observableSSLSocketFactory;
private static final Logger logger = LoggerFactory.getLogger(DefaultSSLContextProvider.class);
public DefaultSSLContextProvider()
{
try
{
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
sslContext = SSLContext.getInstance("SSL");
KeyStore keyStore = getKeyStore();
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
observableSSLSocketFactory = new ObservableSSLSocketFactory(sslContext);
HttpsURLConnection.setDefaultSSLSocketFactory(observableSSLSocketFactory);
SSLContext.setDefault(sslContext);
}
catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
catch (KeyManagementException | KeyStoreException e)
{
logger.error("could not create DefaultSSLContextProvider", e);
throw new IllegalStateException(e);
}
}
#Override
public SSLContext getSSLContext()
{
return sslContext;
}
#Override
public SSLSocketFactory getSSLSocketFactory()
{
return observableSSLSocketFactory;
}
#Override
public KeyStore getKeyStore()
{
// snip
}
}
ObservableSSLSocketFactory:
/**
* Based heavily on:
* http://stackoverflow.com/a/23365536/3634630
*/
public class ObservableSSLSocketFactory extends SSLSocketFactory
{
private final SSLContext sslContext;
private final String[] preferredCipherSuites;
private final String[] preferredProtocols;
private static final Logger logger = LoggerFactory.getLogger(ObservableSSLSocketFactory.class);
protected ObservableSSLSocketFactory(SSLContext sslContext)
{
logger.debug("CREATING OBSERVABLE SOCKET FACTORY!");
this.sslContext = sslContext;
preferredCipherSuites = getCiphers();
preferredProtocols = getProtocols();
logger.debug("Observable socket factory created");
logger.debug("preferredCipherSuites: " + preferredCipherSuites);
logger.debug("preferredProcotols: " + preferredProtocols);
}
#Override
public String[] getDefaultCipherSuites()
{
return preferredCipherSuites;
}
#Override
public String[] getSupportedCipherSuites()
{
return preferredCipherSuites;
}
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(address, port, localAddress, localPort);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port, localHost, localPort);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(InetAddress host, int port) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(String host, int port) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
private String[] getProtocols()
{
// snip
}
private String[] getCiphers()
{
// snip
}
class HandshakeListener implements HandshakeCompletedListener
{
public HandshakeListener()
{
logger.debug("Created new HandshakeListener");
}
public void handshakeCompleted(HandshakeCompletedEvent e)
{
logger.debug("Handshake successful!");
logger.debug("using cipher suite: " + e.getCipherSuite());
}
}
}
As I said, no exceptions or errors occur (and indeed the original request goes through with no problem (HTTP 200), however the only things that are logged are:
00:01:37.867 DEBUG [ObservableSSLSocketFactory] CREATING OBSERVABLE SOCKET FACTORY!
00:01:38.072 DEBUG [ObservableSSLSocketFactory] Observable socket factory created
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredCipherSuites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredProcotols: [TLSv1, TLSv1.1, TLSv1.2]
00:01:39.435 DEBUG [Exchange] response: InboundJaxrsResponse{context=ClientResponse{method=GET, uri=https://www.bitstamp.net/api/order_book/, status=200, reason=OK}}
Nothing from createSocket()'s or the HandshakeCompletedListener.
Any help would be greatly appreciated.
Update 1:
I added some additional log statements, and the situation is indeed strange. The Jersey client is in fact calling the HttpUrlConnectorProvider implementation, and in fact an instance of ObservableSSLSocketFactory is set on the connection, it appears that when the connect method is called on the HttpsURLConnection, it does not use the socket factory.
Update 2:
I found an old bug which is titled: "HttpsURLConnection not using the set SSLSocketFactory for creating all its Sockets". This seems to be my exact problem. This bug was stated to be fixed sometime in JDK7. I am using (as stated) 8u60, which is quite beyond the time this bug was fixed. I am quite puzzled. I found the java argument -Djavax.net.debug=all and set it - I did not see any errors or anything out of place, it seems to be that the HttpsUrlConnection is not using the SSLSocketFactory set on it.
Update 3:
Taking a suggestion from wyvern on #java # irc.freenode.net, I decided to use the apache-connector instead of the JDK HttpsUrlConnection connector, and finally after some smudging and coaxing, I am presented with:
[HandshakeCompletedNotify-Thread][ObservableSSLConnectionSocketFactory] Handshake successful!
So, I guess I am all set then =).

Before making the SSL handshake make sure the client and service have the correct certificates are installed on both(client and service) the server. Refer to url:http://www.programcreek.com/java-api-examples/index.php?api=javax.net.ssl.SSLSocketFactory for SSLFactory Implementation

Related

How to use a self signed certificate to connect to a Mqtt server in Android (paho client)?

Im having problem connecting to a mqtt server using self signed certificate. im using Paho client and want to connect to a server using a TLSv1.2. actually i was successful to connect in Android APIs 20+ but no success for below this version.
what ive done till now :
1- created a PKCS#12 keystore and put the .crt file and assing a password to it and save it (it will be a .pfx file)
2- added the .pfx file to raw folder of recourse in android project
3- used below code to load self signed certificate:
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
and the getSSLSocketFactory method which is the most important part is :
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
sslSockFactory = ctx.getSocketFactory();
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
this works perfectly but no success in Android APIs below 20.
finally found a solution to this.
based on this documentation, TLS 1.1 and TLS 1.2 is supported from android API level 16 (Android 4.1, Jelly Bean). But it is'nt enabled by default until API level 20+ (Android 4.4 for watch, Kitkat Watch and Android 5.0 for phone, Lollipop).
so all we need is to enable them parogrammatically in code. how we gonna do this ? there is a solution for this problem here BUT it just resolves the problem for the case that you want to accept any certificate instead.
what we need is do the same thing but with our own self signed certificate. so we do it like below. the first part is just like what i did before : (keyStoreInputStream is input stream of a .pfx file)
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
the getSSLSocketFactory method changes to:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
sslSockFactory = new TLSSocketFactory(tm);
} else {
sslSockFactory = ctx.getSocketFactory();
}
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
and the TLSSocketFactory class is like below:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSSocketFactory(TrustManager[] trustManagers) throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, null);
internalSSLSocketFactory = context.getSocketFactory();
}
#Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}
#Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
#Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
#Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.1"});
}
return socket;
}
}

Spring Rest Template : Host name 'localhost' does not match the certificate subject provided by the peer

I use RestTemplate config like this :
private RestTemplate createRestTemplate() throws Exception {
final String username = "admin";
final String password = "admin";
final String proxyUrl = "localhost";
final int port = 443;
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyUrl, port),
new UsernamePasswordCredentials(username, password));
HttpHost host = new HttpHost(proxyUrl, port, "https");
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
clientBuilder.setProxy(host).setDefaultCredentialsProvider(credsProvider).disableCookieManagement();
HttpClient httpClient = clientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
And the this is how my method work:
public String receiveMessage(String message) {
try {
restTemplate = createRestTemplate();
ObjectMapper mapper = new ObjectMapper();
Class1 class1 = null;
String json2 = "";
class1= mapper.readValue(message, Class1.class);
Class1 class2 = restTemplate.getForObject(URL_SERVICE_1 + "/class1/findByName?name=" + class1.getName(),
Class1.class);
System.out.println("Server 1 : " + message);
json2 = mapper.writeValueAsString(class2);
return "Error - " + json2;
} catch (Exception e) {
// TODO Auto-generated catch block
return e.getMessage();
}
}
URL_SERVICE_1 contains https://localhost
When I tried to call function GET, I always get return like this :
I/O error on GET request for "https://localhost/class1/findByName?name=20-1P": Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated); nested exception is javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated)
I don't know the correct setting for restTemplate with https. I already tried 23 references about SSL Settings and got same error.
1. Reference
2. Reference
3. Reference
As accepted answer has deprecated code, this is what I found helpful:
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
httpclient = HttpAsyncClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
The correct solution for this problem is to correct the ssl certificate by adding localhost to the list of subjects. However, if your intent is to bypass ssl for development purpose, you would need to define a connection factory which always returns the result of hostname verification as true.
SSLClientHttpRequestFactory
public class SSLClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see
* http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566
* -2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose)
throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1" });
return socket;
}
}
}
And use the above mentioned connection factory as the constructor argument for RestTemplate. The part of the code which overrides the host name verification to always return true is as follows:
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
Happy coding!
Here is how I made it to work:
1. This bean ignores SSL check
2. It also ignores certificate mismatch
#Bean
public RestTemplate restTemplate()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(
NoopHostnameVerifier.INSTANCE)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
This option worked for me after trying lot of different options from online... Thanks a lot ...
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
HttpClient httpClient = HttpAsyncClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();

Android 5.0 SSL Connectivity Issue

I am trying to interact with a webservice which is a HTTPS call that works totally fine on different variants of 4.0(I havent checked it below 4.0 so I cant say about them) and its perfectly working. The issue I am facing is on Android 5.0 and the device I was able to grab was Nexus 5 and below is the exception i get when doing connectivity
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
at org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:146)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
After tonnes of searching and analyzing our production server SSL certificate i figured out that the server accept TLSv1 and the only cipher suite it supports is TLS_RSA_WITH_3DES_EDE_CBC_SHA. Though i understand that its not safe and it should be upgraded but right now i have to find out some way to get my Android app connected with the server.
I tried through the way suggested on this page
https://code.google.com/p/android-developer-preview/issues/attachmentText?id=1200&aid=12000009000&name=CompatSSLSocketFactory.java&token=ABZ6GAcWKpRZhuG6Skof32VtvF0Lzv3Z-A%3A1435550700632
And replaced my required algorithm i.e TLS_RSA_WITH_3DES_EDE_CBC_SHA but now the problem is that i am seeing this exception
Caused by: java.lang.IllegalArgumentException: cipherSuite
TLS_RSA_WITH_3DES_EDE_CBC_SHA is not supported.
at com.android.org.conscrypt.NativeCrypto.checkEnabledCipherSuites(NativeCrypto.java:1091)
at com.android.org.conscrypt.SSLParametersImpl.setEnabledCipherSuites(SSLParametersImpl.java:244)
at com.android.org.conscrypt.OpenSSLSocketImpl.setEnabledCipherSuites(OpenSSLSocketImpl.java:822)
So according to this exception the cipher suite i required is not supported by Android 5.0. But i got puzzled after seeing it in Android 5.0's supported list on this page
http://developer.android.com/reference/javax/net/ssl/SSLEngine.html
Anybody any idea whats this mystery?
I got the answer finally after working out on the issue for three days. Posting out the correct solution for people who gets stuck in a similar issue in future
First implement CustomTrustManager
public class CustomX509TrustManager implements X509TrustManager {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] certs,
String authType) throws CertificateException {
// Here you can verify the servers certificate. (e.g. against one which is stored on mobile device)
// InputStream inStream = null;
// try {
// inStream = MeaApplication.loadCertAsInputStream();
// CertificateFactory cf = CertificateFactory.getInstance("X.509");
// X509Certificate ca = (X509Certificate)
// cf.generateCertificate(inStream);
// inStream.close();
//
// for (X509Certificate cert : certs) {
// // Verifing by public key
// cert.verify(ca.getPublicKey());
// }
// } catch (Exception e) {
// throw new IllegalArgumentException("Untrusted Certificate!");
// } finally {
// try {
// inStream.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
Than implement your own Socket Factory
public class CustomSSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public CustomSSLSocketFactory(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new CustomX509TrustManager();
sslContext.init(null, new TrustManager[] { tm }, null);
}
public CustomSSLSocketFactory(SSLContext context)
throws KeyManagementException, NoSuchAlgorithmException,
KeyStoreException, UnrecoverableKeyException {
super(null);
sslContext = context;
}
#Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
Socket newSocket = sslContext.getSocketFactory().createSocket(socket, host, port,
autoClose);
((SSLSocket) newSocket).setEnabledCipherSuites(((SSLSocket) newSocket).getSupportedCipherSuites());
AdjustSocket(newSocket);
return newSocket;
}
#Override
public Socket createSocket() throws IOException {
Socket socket = sslContext.getSocketFactory().createSocket();
((SSLSocket) socket).setEnabledCipherSuites(((SSLSocket) socket).getSupportedCipherSuites());
adjustSocket(socket);
return socket;
}
private void adjustSocket(Socket socket)
{
String[] cipherSuites = ((SSLSocket) socket).getSSLParameters().getCipherSuites();
ArrayList<String> cipherSuiteList = new ArrayList<String>(Arrays.asList(cipherSuites));
cipherSuiteList.add("TLS_RSA_WITH_3DES_EDE_CBC_SHA");
cipherSuites = cipherSuiteList.toArray(new String[cipherSuiteList.size()]);
((SSLSocket) socket).getSSLParameters().setCipherSuites(cipherSuites);
String[] protocols = ((SSLSocket) socket).getSSLParameters().getProtocols();
ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(protocols));
for (int ii = protocolList.size() - 1; ii >= 0; --ii )
{
if ((protocolList.get(ii).contains("SSLv3")) || (protocolList.get(ii).contains("TLSv1.1")) || (protocolList.get(ii).contains("TLSv1.2")))
protocolList.remove(ii);
}
protocols = protocolList.toArray(new String[protocolList.size()]);
((SSLSocket)socket).setEnabledProtocols(protocols);
}
}
Now add a function in the class to create a HttpClient
public HttpClient createHttpClient(){
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
CustomSSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
sf.setHostnameVerifier(CustomSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 15000);
HttpConnectionParams.setSoTimeout(params, 5000);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
And now write below lines to call the server/webservice
HttpClient httpClient = createHttpClient();
HttpPost httpost = new HttpPost(url);
HttpResponse response = null;
try {
response = httpClient.execute(httpost);
StatusLine statusLine = response.getStatusLine();
} catch (IOException e) {
e.printStackTrace();
}

Java: Loading SSL Keystore via a resource

If I have:
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/fdms/WS1001237590._.1.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'DV8u4xRVDq');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
I'm able to open a secure connection without a problem.
However, I'd like to have the certificates stored directly in the war, so I use: (The file input stream will eventually become a resource stream, but I'm doing this to get it to work.)
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/etc/certificates/fdms/WS1001237590._.1.ks"), "DV8u4xRVDq".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "DV8u4xRVDq".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
Now, if I open the same connection, I get: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
I had to do something similar a while back. I had a certificate file and I had to figure out a way to load it in and use it for an SSL connection. Hopefully what I did will help you out.
First I had to create a trust manager:
public class MyX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
MyX509TrustManager() throws Exception {
String certFile = "/certificates/MyCertFile.cer";
Certificate myCert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("myCert", myCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
After that I had to create a socket factory that used my trust manager:
public class MySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext sslContext = null;
public MySSLProtocolSocketFactory() {
super();
}
private static SSLContext createMySSLContext() {
try {
MyX509TrustManager myX509TrustManager = new MyX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new MyX509TrustManager[] { myX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createMySSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(MySSLProtocolSocketFactory.class));
}
public int hashCode() {
return MySSLProtocolSocketFactory.class.hashCode();
}
}
Then I used that socket factory to send my POST:
Protocol.registerProtocol("myhttps", new Protocol("myhttps", new MySSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod("myhttps://some.url.here");
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
The only thing I couldn't figure out was how to simply add the certificate file to the regular keystore. All the example source code I found during my research pointed to creating a socket factor and then registering a protocol with that socket factory. Perhaps there is a way to simply use the socket factory to make a connection without registering a protocol; I haven't investigated that thoroughly. In my particular situation, creating a specific protocol was necessary. Hopefully this will get your further along the way. I admit it seems a bit roundabout; I felt the same way when I did it initially. But this was the only way I got it to work. Maybe other people have a better solution.
For posterity's sake, all of this was far too complicated, and we pretty much just had a check in the static block:
if( environment == 'production') {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/prod/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
} else {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/test/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
}
With Axis, I think you need to configure its SSLSocketFactory via:
AxisProperties.setProperty("axis.socketSecureFactory",
"com.example.MySSLSocketFactory");
where com.example.MySSLSocketFactory is your class that implements org.apache.axis.components.net.SecureSocketFactory (you could extend org.apache.axis.components.net.JSSESocketFactory perhaps).
In the create method, create a socket using the socket factory obtained from the SSLContext you've configured.
If you want, here's an API to create SSLSocket and SSLServerSocket easily:
https://github.com/gpotter2/SSLKeystoreFactories
It does not require any other jars.... just get the files and use them like:
SSLSocket s = SSLSocketKeystoreFactory.getSocketWithCert(ip, port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
Or:
SSLServerSocket s = SSLServerSocketKeystoreFactory.getSocketWithCert(port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
That's much easier to use :)
I had similar problem, I solved creating a function that returns an SSL context using a keystore coming from and input stream.
protected SSLContext getSslCtx(InputStream is, String password) {
try {
// Load keystore
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(is, password.toCharArray());
// Load trust manager
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(keystore);
// Load key manager
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyMgrFactory.init(keystore, password.toCharArray());
// Create SSL context
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(keyMgrFactory.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);
return ctx;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Hope this helps.

PKIX path building failed while making SSL connection

I'm integrating with a Merchant Account called CommWeb and I'm sending an SSL post to their URL (https://migs.mastercard.com.au/vpcdps). When I try to send the post, I get the following exception:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The code (which I didn't write, and that already exists in our codebase) that performs the post is:
public static HttpResponse sendHttpPostSSL(String url, Map<String, String> params) throws IOException {
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
}
The documentation for the Merchant Account integration says nothing about certificates. They did provide some sample JSP code that seems to blindly accept certificates:
<%! // Define Static Constants
// ***********************
public static X509TrustManager s_x509TrustManager = null;
public static SSLSocketFactory s_sslSocketFactory = null;
static {
s_x509TrustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; }
public boolean isClientTrusted(X509Certificate[] chain) { return true; }
public boolean isServerTrusted(X509Certificate[] chain) { return true; }
};
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[] { s_x509TrustManager }, null);
s_sslSocketFactory = context.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
...
...
// write output to VPC
SSLSocket ssl = (SSLSocket)s_sslSocketFactory.createSocket(s, vpc_Host, vpc_Port, true);
ssl.startHandshake();
os = ssl.getOutputStream();
// get response data from VPC
is = ssl.getInputStream();
...
...
%>
Our webapp has a keystore, and I tried adding the certificate (which I exported from firefox) using the keytool command, but that didn't work and I got the same error. I've tried solutions on the web (importing the key and using System.setProperty) but that seems kind of clunky and it didn't work (gave me a NoSuchAlgorithmError). Any help is appreciated!
Evidently the valicert class 3 CA certificate is not in your default truststore (which is probably the cacerts file in your JRE lib/security directory, but see the JSSE documentation for the full story).
You could add this certificate to the cacerts file, but I don't recommend this. Instead, I think you should create your own truststore file (which can be a copy of the cacerts file) and add the valicert root ca to this. Then point to this file with the javax.net.ssl.trustStore system property.
I figure I should update this answer with what I actually did. Using the documentation that GregS provided, I created a trust manager for valicert. In the trust manager, I load the certificate files:
public class ValicertX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
ValicertX509TrustManager() throws Exception {
String valicertFile = "/certificates/ValicertRSAPublicRootCAv1.cer";
String commwebDRFile = "/certificates/DR_10570.migs.mastercard.com.au.crt";
String commwebPRODFile = "/certificates/PROD_10549.migs.mastercard.com.au.new.crt";
Certificate valicert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
Certificate commwebDR = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebDRFile));
Certificate commwebPROD = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebPRODFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("valicert", valicert);
keyStore.setCertificateEntry("commwebDR", commwebDR);
keyStore.setCertificateEntry("commwebPROD", commwebPROD);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
Now, using this trust manager, I had to create a socket factory:
public class ValicertSSLProtocolSocketFactory implements ProtocolSocketFactory {
private SSLContext sslContext = null;
public ValicertSSLProtocolSocketFactory() {
super();
}
private static SSLContext createValicertSSLContext() {
try {
ValicertX509TrustManager valicertX509TrustManager = new ValicertX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new ValicertX509TrustManager[] { valicertX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createValicertSSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(ValicertSSLProtocolSocketFactory.class));
}
public int hashCode() {
return ValicertSSLProtocolSocketFactory.class.hashCode();
}
}
Now I just register a new protocol:
Protocol.registerProtocol("vhttps", new Protocol("vhttps", new ValicertSSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
The only disadvantage is that I had to create a specific protocol (vhttps) for this particular certificate.

Categories