SSLHandshakeException: Certificate Unknown (Java Spring Boot & Android) - java

I have a Spring Boot API that runs locally, with a self-signed certificate, using the HTTPS protocol.
Obviously, when I send GET Requests from the browser, I receive the io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown error on the server side, which is normal, because the self-signed is not trusted by the browser. Postman works just fine for GET and POST.
However, I want to send GET requests from an Android client to the Spring API but, even I've used a function to allow all SSL traffic (yes, I know it's not recommended), I still can't send requests to the API, receiving the following output:
I/STATUS: 405
I/MSG: Method Not Allowed
I thought my allowAllSSL() function (HttpsTrustManager class) would solve the issue, because if I remove the function call, I receive the following error, which seems to match the one on the server side:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:239)
Now, you may think that the GET request is not implemented correctly in Spring, but it's not true, since the same GET request works just fine from Postman. I believe that the problem is still linked to the certificate, but I can't figure out what do I need to change. Here is my code:
Spring BOOT Rest Controller
#RestController
#RequestMapping("/post")
public class PostRequest {
#GetMapping("")
public String string(#RequestBody ImageRequest newEmployee){
....
The ImageRequest class contains just three private String members.
HttpsTrustManager class (to allow all SSL)
package com.example.androidclient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(
X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(
X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context != null ? context.getSocketFactory() : null);
}
}
Android Request
HttpsTrustManager.allowAllSSL();
URL url = new URL("https://192.168.1.106:8443/post");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
conn.setDoInput(true);
JSONObject jsonParam = new JSONObject();
jsonParam.put("location", "Somewhere");
jsonParam.put("date", "22.05.2020");
jsonParam.put("imageBytes", strings[0]);
Log.i("JSON", jsonParam.toString());
DataOutputStream os = new DataOutputStream(conn.getOutputStream());
//os.writeBytes(URLEncoder.encode(jsonParam.toString(), "UTF-8"));
os.writeBytes(jsonParam.toString());
os.flush();
os.close();
Log.i("STATUS", String.valueOf(conn.getResponseCode()));
Log.i("MSG", conn.getResponseMessage());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}

Use this function in your android application.
Please note this will allow all ssl certificates without verification. I would encourage you to follow the recommended approach when dealing with self-signed certificates outlined here: https://developer.android.com/training/articles/security-ssl#java
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
#Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

I have found the solution on my own.
Apparently, the Connection.setDoOutput(true) method is working just for POST and PUT requests, but not for GET.
Thus, I have changed my RequestMapping to work on POST, like, this:
#RequestMapping(
value = "/post",
produces = "application/json",
method = RequestMethod.POST)
Now I receive 200 OK.

Related

Impossible to start multiple HTTPS connections from Spring application

I have a Spring Boot application that tries to open a javax.net.ssl.HttpsURLConnection to a server but the response received is: java.io.IOException: Server returned HTTP response code: 403 for URL: https://serverIP:8443/path
When the keyStore, trustStore and their passwords are set as system properties the request works correctly and the expected JSON response is received:
System.setProperty("javax.net.ssl.keyStore", "src/main/resources/myKeyStore.p12");
System.setProperty("javax.net.ssl.trustStore", "src/main/resources/myTrustStore.truststore");
System.setProperty("javax.net.ssl.keyStorePassword", "myPassword");
System.setProperty("javax.net.ssl.trustStorePassword", "myPassword");
But the 403 response code is received when trying to set the information in SSLContext, instead of setting the system properties, by using this method that returns an SSLContext object:
public static SSLContext getSslContext(String trustStoreFile, String keystoreFile, String password)
throws GeneralSecurityException, IOException {
final KeyStore keystore = KeyStore.getInstance("pkcs12"); // also tried with JKS
try (final InputStream inKeystore = new FileInputStream(keystoreFile)) {
keystore.load(inKeystore, password.toCharArray());
}
try (final InputStream inTruststore = new FileInputStream(trustStoreFile)) {
keystore.load(inTruststore, password.toCharArray());
}
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); // also tried with .getDefaultAlgorithm()
keyManagerFactory.init(keystore, password.toCharArray());
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
X509TrustManager x509Tm = null;
for (final TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
x509Tm = (X509TrustManager) trustManager;
break;
}
}
final X509TrustManager finalTm = x509Tm;
final X509ExtendedTrustManager customTm = new X509ExtendedTrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return finalTm.getAcceptedIssuers();
}
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
};
final SSLContext sslContext = SSLContext.getInstance("TLS"); // also tried with SSL
sslContext.init(
keyManagerFactory.getKeyManagers(),
new TrustManager[]{customTm},
new SecureRandom());
final HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
return sslContext;
}
OBS: The trustStore and keyStore have the same password, that's why the method has only one password parameter and used for both key and trust manager factories.
The way the getSslContext method is called and used is:
final SSLContext sslContext = SSLContextHelper.getSslContext("src/main/resources/myTrustStore.truststore",
"src/main/resources/myKeyStore.p12",
"myPassword");
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
final URL url = new URL("https://serverIP:8443/path");
final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(sslSocketFactory);
// tried adding some headers to the request
urlConnection.addRequestProperty("Content-Type", "application/json");
urlConnection.addRequestProperty("Accept", "application/json");
urlConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0");
urlConnection.connect();
final InputStream inputstream = urlConnection.getInputStream();
The error is thrown at the last line when trying to get the inputStream of the URL connection.
Also, I tried using the following classes from org.apache.http: SSLConnectionSocketFactory, HttpClient, HttpGet, HttpResponse but response code is still 403.
I can only think that there is something missing from the SSL configuration because the system properties work. Any suggestions on what I miss setting in the SSLContext/SSLSocketFactory or how can I solve/better debug the problem are welcome! Thanks!
I managed to open the HTTPS connections only by using Spring's RestTemplate (org.springframework.web.client.RestTemplate) that uses the org.apache.http.client.HttpClient.
The method for getting the RestTemplate that has in its SSLContext the keyStore, trustStore and their passwords is the following:
public RestTemplate getRestTemplate(final String keyStoreFile, final String trustStoreFile,
final String password) throws Exception {
final SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(ResourceUtils.getFile(keyStoreFile), password.toCharArray(), password.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(trustStoreFile), password.toCharArray())
.build();
final HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
final HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpComponentsClientHttpRequestFactory.setHttpClient(client);
return new RestTemplate(httpComponentsClientHttpRequestFactory);
}
The way that the RestTemplate is used for the HTTPS call is:
final String keyStoreFile = "src/main/resources/myKeyStore.p12";
final String trustStoreFile = "src/main/resources/myTrustStore.truststore";
final String password = "myPassword"; // same password for keyStore and trustStore
final String response = getRestTemplate(keyStoreFile, trustStoreFile, password).getForObject("https://serverIP:8443/path", String.class);
LOGGER.info("Response received: " + response);
Hope this helps anyone, had a lot of struggle with the HTTPS connections :)

Ignore trusted certificate

I found some "standard code" from forums allowing me to skip the validation of trusted certificates in Java.
My code implements some old style WSDL client implementation. I'm using JDK 1.8.
public class myClass exteds Service{
public SessionProxy(String baseurl, SSLSocketFactory sslFactory, boolean compression) {
// Get the port
port = super.getPort(new QName("http://host.com/d3s/jel/dk/service/something/v2u1.wsdl", "Port"), Port.class);
// Set the endpoint address
Map<String, Object> context = ((BindingProvider) port).getRequestContext();
if (baseurl != null) {
context.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, baseurl);
}
SSLTool.disableCertificateValidation();
if (sslFactory != null) {
context.put(JAXWSProperties.SSL_SOCKET_FACTORY, sslFactory);
}
}
public String recordSessions(CasinoGameSessions sessions) throws
SystemFaultException, UserFaultException {
String result= "";
result += port.recordSessions(sessions) + "\n";
disposeSSLSocketFactory();
return result;
}
This is the SSLTool class I found (many other code samples are really similar to this one):
import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
public class SSLTool {
public static void disableCertificateValidation() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}};
// Ignore differences between given hostname and certificate hostname
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) { return true; }
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv);
} catch (Exception e) {}
}
}
When I run my code, despite the fact I call SSLTool.disableCertificationValidation() I get the exception:
aused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) ~[?:1.8.0_162]
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154) ~[?:1.8.0_162]
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2038) ~[?:1.8.0_162]
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1135) ~[?:1.8.0_162]
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385) ~[?:1.8.0_162]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413) ~[?:1.8.0_162]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397) ~[?:1.8.0_162]
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559) ~[?:1.8.0_162]
at sun.net.www.protocol.https.AbstractDelegateHttpsUR
This should mean my client still tries to validate the server certificate and to make the handshake with it.

SSLHandshakeException on WebSocket connection with OkHttp3

I'm trying to connect to WSS websocket using OkHttp3, but I got in onFailure() method of my WebSocketListener in particular the Exception said:
javax.net.ssl.SSLHandshakeException: Handshake failed
Now, I'm using this code to connect to WSS
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("wss://mywssdomain").build();
WebSocketListenerCP listener = new WebSocketListenerCP(); //My listener, do nothing only override methods
WebSocket ws = client.newWebSocket(request, listener);
client.dispatcher().executorService().shutdown();
I've read a lot about issue with Android < Lollipop but I'm on Nougat anyway I've tried to nuke all SSL certificate by doing (before connection)
SSLCertificateHandler.nuke();
SSLCertificateHandler class
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class SSLCertificateHandler {
/**
* Enables https connections
*/
public static void nuke() {
try {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] myTrustedAnchors = new X509Certificate[0];
return myTrustedAnchors;
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (Exception e) {
}
}
}
And I also tried to add connectionSpecs to my OkHttp3 client
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA)
.build();
OkHttpClient client = new OkHttpClient.Builder().connectionSpecs(Collections.singletonList(spec)).build();
Request request = new Request.Builder().url("wss://mywssdomain").build();
WebSocketListenerCP listener = new WebSocketListenerCP();
WebSocket ws = client.newWebSocket(request, listener);
client.dispatcher().executorService().shutdown();
I'm not understand
1) Why I get SSLHandshakeException?
2) Is right connection to WSS?

How do I initialize a TrustManagerFactory with multiple sources of trust?

My application has a personal keystore containing trusted self-signed certificates for use in the local network - say mykeystore.jks. I wish to be able to connect to public sites(say google.com) as well as ones in my local network using self-signed certificates which have been provisioned locally.
The problem here is that, when I connect to https://google.com, path building fails, because setting my own keystore overrides the default keystore containing root CAs bundled with the JRE, reporting the exception
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
However, if I import a CA certificate into my own keystore(mykeystore.jks) it works fine. Is there a way to support both?
I have my own TrustManger for this purpose,
public class CustomX509TrustManager implements X509TrustManager {
X509TrustManager defaultTrustManager;
public MyX509TrustManager(KeyStore keystore) {
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(keystore);
TrustManager trustManagers[] = trustMgrFactory.getTrustManagers();
for (int i = 0; i < trustManagers.length; i++) {
if (trustManagers[i] instanceof X509TrustManager) {
defaultTrustManager = (X509TrustManager) trustManagers[i];
return;
}
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
/* Handle untrusted certificates */
}
}
}
I then initialize the SSLContext,
TrustManager[] trustManagers =
new TrustManager[] { new CustomX509TrustManager(keystore) };
SSLContext customSSLContext =
SSLContext.getInstance("TLS");
customSSLContext.init(null, trustManagers, null);
and set the socket factory,
HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory());
The main program,
URL targetServer = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) targetServer.openConnection();
If I don't set my own trust managers, it connects to https://google.com just fine. How do I get a "default trust manager" which points to the default key store?
In trustMgrFactory.init(keystore); you're configuring defaultTrustManager with your own personal keystore, not the system default keystore.
Based on reading the source code for sun.security.ssl.TrustManagerFactoryImpl, it looks like trustMgrFactory.init((KeyStore) null); would do exactly what you need (load the system default keystore), and based on quick testing, it seems to work for me.
The answer here is how I came to understand how to do this. If you just want to accept the system CA certs plus a custom keystore of certs I simplified it into a single class with some convenience methods. Full code available here:
https://gist.github.com/HughJeffner/6eac419b18c6001aeadb
KeyStore keystore; // Get your own keystore here
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager[] tm = CompositeX509TrustManager.getTrustManagers(keystore);
sslContext.init(null, tm, null);
I've run into the same issue with Commons HttpClient. Working solution for my case was to create delegation chain for PKIX TrustManagers in following way:
public class TrustManagerDelegate implements X509TrustManager {
private final X509TrustManager mainTrustManager;
private final X509TrustManager trustManager;
private final TrustStrategy trustStrategy;
public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager, TrustStrategy trustStrategy) {
this.mainTrustManager = mainTrustManager;
this.trustManager = trustManager;
this.trustStrategy = trustStrategy;
}
#Override
public void checkClientTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
this.trustManager.checkClientTrusted(chain, authType);
}
#Override
public void checkServerTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
if (!this.trustStrategy.isTrusted(chain, authType)) {
try {
mainTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
this.trustManager.checkServerTrusted(chain, authType);
}
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return this.trustManager.getAcceptedIssuers();
}
}
And initialize HttpClient in following way (yes it's ugly):
final SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
final TrustManagerFactory javaDefaultTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
javaDefaultTrustManager.init((KeyStore)null);
final TrustManagerFactory customCaTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
customCaTrustManager.init(getKeyStore());
sslContext.init(
null,
new TrustManager[]{
new TrustManagerDelegate(
(X509TrustManager)customCaTrustManager.getTrustManagers()[0],
(X509TrustManager)javaDefaultTrustManager.getTrustManagers()[0],
new TrustSelfSignedStrategy()
)
},
secureRandom
);
} catch (final NoSuchAlgorithmException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
} catch (final KeyManagementException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
}
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build()
);
//maximum parallel requests is 500
cm.setMaxTotal(500);
cm.setDefaultMaxPerRoute(500);
CredentialsProvider cp = new BasicCredentialsProvider();
cp.setCredentials(
new AuthScope(apiSettings.getIdcApiUrl(), 443),
new UsernamePasswordCredentials(apiSettings.getAgencyId(), apiSettings.getAgencyPassword())
);
client = HttpClients.custom()
.setConnectionManager(cm)
.build();
In your case with simple HttpsURLConnection you may get by with simplified version of delegating class:
public class TrustManagerDelegate implements X509TrustManager {
private final X509TrustManager mainTrustManager;
private final X509TrustManager trustManager;
public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager) {
this.mainTrustManager = mainTrustManager;
this.trustManager = trustManager;
}
#Override
public void checkClientTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
this.trustManager.checkClientTrusted(chain, authType);
}
#Override
public void checkServerTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
try {
mainTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
this.trustManager.checkServerTrusted(chain, authType);
}
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return this.trustManager.getAcceptedIssuers();
}
}
A detailed description of the solution is here: https://blog.novoj.net/posts/2016-02-29-how-to-make-apache-httpclient-trust-lets-encrypt-certificate-authority/
For Android developers, this can be much easier. In summary, you can add a xml res file to config your custom certs.
Step 1: open your manifest xml add an attribute.
<manifest ... >
<application android:networkSecurityConfig="#xml/network_security_config"
... >
...
</application>
</manifest>
Step 2: Add network_security_config.xml to res/xml, config certs as you want.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="#raw/extracas"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
Note: this xml can support many other usage, and this solution only works on api24+.
Official reference: here
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Represents an ordered list of {#link X509TrustManager}s with additive trust. If any one of the composed managers
* trusts a certificate chain, then it is trusted by the composite manager.
*
* This is necessary because of the fine-print on {#link SSLContext#init}: Only the first instance of a particular key
* and/or trust manager implementation type in the array is used. (For example, only the first
* javax.net.ssl.X509KeyManager in the array will be used.)
*
* #author codyaray
* #since 4/22/2013
* #see <a href="http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm">
* http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
* </a>
*/
#SuppressWarnings("unused")
public class CompositeX509TrustManager implements X509TrustManager {
private final List<X509TrustManager> trustManagers;
public CompositeX509TrustManager(List<X509TrustManager> trustManagers) {
this.trustManagers = ImmutableList.copyOf(trustManagers);
}
public CompositeX509TrustManager(KeyStore keystore) {
this.trustManagers = ImmutableList.of(getDefaultTrustManager(), getTrustManager(keystore));
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager trustManager : trustManagers) {
try {
trustManager.checkClientTrusted(chain, authType);
return; // someone trusts them. success!
} catch (CertificateException e) {
// maybe someone else will trust them
}
}
throw new CertificateException("None of the TrustManagers trust this certificate chain");
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager trustManager : trustManagers) {
try {
trustManager.checkServerTrusted(chain, authType);
return; // someone trusts them. success!
} catch (CertificateException e) {
// maybe someone else will trust them
}
}
throw new CertificateException("None of the TrustManagers trust this certificate chain");
}
#Override
public X509Certificate[] getAcceptedIssuers() {
ImmutableList.Builder<X509Certificate> certificates = ImmutableList.builder();
for (X509TrustManager trustManager : trustManagers) {
for (X509Certificate cert : trustManager.getAcceptedIssuers()) {
certificates.add(cert);
}
}
return Iterables.toArray(certificates.build(), X509Certificate.class);
}
public static TrustManager[] getTrustManagers(KeyStore keyStore) {
return new TrustManager[] { new CompositeX509TrustManager(keyStore) };
}
public static X509TrustManager getDefaultTrustManager() {
return getTrustManager(null);
}
public static X509TrustManager getTrustManager(KeyStore keystore) {
return getTrustManager(TrustManagerFactory.getDefaultAlgorithm(), keystore);
}
public static X509TrustManager getTrustManager(String algorithm, KeyStore keystore) {
TrustManagerFactory factory;
try {
factory = TrustManagerFactory.getInstance(algorithm);
factory.init(keystore);
return Iterables.getFirst(Iterables.filter(
Arrays.asList(factory.getTrustManagers()), X509TrustManager.class), null);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
e.printStackTrace();
}
return null;
}
}
Although this question is 6 years old, I want to share my solution for this challenge. It uses the same code snippet under the covers from Cody A. Ray which Hugh Jeffner also shared.
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial() // --> uses the JDK trusted certificates
.withTrustMaterial("/path/to/mykeystore.jks", "password".toCharArray())
.build();
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory.getSslSocketFactory());
During the ssl handshake process it will first check if the server certificate is present in the jdk trusted certificates, if not it will continue by also checking your custom keystore and if it doesn't find a match it will fail. You can even further chain it with more custom keystores, or pem files, or list of certificates etc. See here for other configurations: other possible configurations
This library is maintained by me and you can find it here: https://github.com/Hakky54/sslcontext-kickstart

for rest easy https calls, how to accept all certs

i am trying to call the REST service using jboss rest easy in the following way
public ETTestCasePackage getPackageById(String packageId) throws PackageNotFound {
ClientRequest req = new ClientRequest("https://facebook/api");
req.header("Authorization", "Basic " + EztrackerConstants.base64AuthenticationValue);
req.pathParameter("id", packageId);
ETTestCasePackage etPackage = null;
try {
logger.info("invoking "+req.getUri());
//ProxyFactory.create
ClientResponse<ETTestCasePackage> res = req.get(ETTestCasePackage.class);
etPackage = res.getEntity();
} catch (Exception e) {
logger.debug("Not able to retrieve details for testcase package having id = " + packageId, e);
throw new PackageNotFound("Package with id " + packageId + " not found", e);
}
return etPackage;
}
but the above code obviously throw "peer not authenticated";
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(Unknown Source)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:126)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:437)
at
I can add the respective cert to my local java security jks to solve this.
but i may run this so many machines, so cannot do that to all machines. so i want to make my http client accept all request by overridding the http checks.
but for rest easy httprequest, i am not able to find a way to do this. would some one help me in doing for this rest easy.
Thanks in Advance,
syam.
I have tried this piece of code calling the actual code for ignoring but still didn't override the default settings. any idea for to make it work for this rest easy client.
private void test(){
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
}
}
static {
//for localhost testing only
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
return true;
}
});
}
}
Use signed certs as a plan A. As a plan B, when targeting a staging version of another system that you do not control for example, you can use the following solution.
For Resteasy 3, you need to provide your own all-trusting Httpclient to the client instance.
Of course you should never use that in production, so make sure not to hardoce it.
Normally (using jax-rs 2.0) you'd initialize a client like this:
javax.ws.rs.client.Client client = javax.ws.rs.client.ClientBuilder.newClient();
For all trusting client, replace it as follows:
Client client = null;
if (config.trustAllCertificates) {
log.warn("Trusting all certificates. Do not use in production mode!");
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(createAllTrustingClient());
client = new ResteasyClientBuilder().httpEngine(engine).build();
}
else {
client = ClientBuilder.newClient();
}
The createAllTrustingClient() would look like this:
private DefaultHttpClient createAllTrustingClient() throws GeneralSecurityException {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
};
SSLSocketFactory factory = new SSLSocketFactory(trustStrategy, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER );
registry.register(new Scheme("https", 443, factory));
ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
mgr.setMaxTotal(1000);
mgr.setDefaultMaxPerRoute(1000);
DefaultHttpClient client = new DefaultHttpClient(mgr, new DefaultHttpClient().getParams());
return client;
}
Just in case you have trouble figuring out the package names of the classes, here are the relevant imports:
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
For reference:
https://docs.jboss.org/resteasy/docs/3.0-beta-3/userguide/html/RESTEasy_Client_Framework.html#transport_layer
The easiest method is to get a proper certificate, with a correct DN and signed by a public CA, on each machine on which you are deploying the service. It's bureaucratic and annoying and probably costs real money, but it is definitely easiest overall.
Otherwise, you have to configure the clients to have a verifier that doesn't actually verify. That's dangerous, since anyone at all (including random hackers, organised criminals and dodgy government agencies) can make a self-signed certificate and there's no practical way to detect that they have done so. Except by going through and distributing to every client the entire list of server certificates that will ever be used (allowing the verifier to do its check using the club doorman technique: “if you're not on the list, you're not coming in”).
The verifier is technically going to be some kind of instance of X509TrustManager.
To add up on Arnelism's answer: if you are using httpclient-4.2.6.jar (which is a dependency for resteasy-jaxrs-3.0.10.Final.jar), you will find that ThreadSafeClientConnManager is #Deprecated. You can modify it to BasicClientConnectionManager or PoolingClientConnectionManager instead:
private static DefaultHttpClient createAllTrustingClient()
throws GeneralSecurityException {
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory())
);
TrustStrategy trustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] arg0,
String arg1) throws java.security.cert.CertificateException {
return true;
}
};
SSLSocketFactory factory = new SSLSocketFactory(
trustStrategy,
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
);
registry.register(new Scheme("https", 443, factory));
BasicClientConnectionManager mgr = new BasicClientConnectionManager(registry);
DefaultHttpClient client =
new DefaultHttpClient(mgr, new DefaultHttpClient().getParams());
return client;
}
It's necessary to hack the ApacheHttpClient4Executor, the code below is work with HTTPS and will provide a ClientRequest:
UriBuilder uri = UriBuilder.fromUri(request.endpoint() + request.path());
System.out.println(request.endpoint() + request.path());
class ApacheHttpClient4Executor2 extends ApacheHttpClient4Executor {
}
ApacheHttpClient4Executor2 executor = new ApacheHttpClient4Executor2();
Scheme http = new Scheme("http", 80, PlainSocketFactory.getSocketFactory());
TrustStrategy trustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
};
SSLSocketFactory factory = null;
try {
factory = new SSLSocketFactory(trustStrategy, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e1) {
e1.printStackTrace();
}
Scheme https = new Scheme("https", 443, factory);
executor.getHttpClient().getConnectionManager().getSchemeRegistry().register(http);
executor.getHttpClient().getConnectionManager().getSchemeRegistry().register(https);
ClientRequest client = new ClientRequest(uri, executor, providerFactory);

Categories