From 7dd32acc54810412eaeff35ae0e699d09a1a4f81 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Thu, 22 Sep 2016 12:34:26 +0530 Subject: [PATCH 1/6] Making CloseableHttpClient pluggable on default HttpClientImpl --- .../com/indix/httpClient/impl/HttpClientImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java b/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java index 13a6dce..a99a3bd 100644 --- a/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java +++ b/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java @@ -35,7 +35,16 @@ class HttpClientImpl implements HttpClient { * configuration. */ public HttpClientImpl() { - closeableHttpClient = HttpClients.createDefault(); + this(HttpClients.createDefault()); + } + + /** + * Creates with a custom {@link CloseableHttpClient} instance. + * + * @param closableHttpClient + */ + public HttpClientImpl(CloseableHttpClient closableHttpClient) { + closeableHttpClient = closableHttpClient; objectMapper = new ObjectMapper(); } From bfeab5cce2c3cefaf8cbda0c82478a4438709448 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Thu, 22 Sep 2016 12:37:58 +0530 Subject: [PATCH 2/6] Adding constructor to HttpClientFactory for ClosableHttpClient --- .../java/com/indix/httpClient/impl/HttpClientFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/indix/httpClient/impl/HttpClientFactory.java b/src/main/java/com/indix/httpClient/impl/HttpClientFactory.java index f00ac94..9b9d534 100644 --- a/src/main/java/com/indix/httpClient/impl/HttpClientFactory.java +++ b/src/main/java/com/indix/httpClient/impl/HttpClientFactory.java @@ -1,6 +1,7 @@ package com.indix.httpClient.impl; import com.indix.httpClient.HttpClient; +import org.apache.http.impl.client.CloseableHttpClient; /** * Instantiates http client instances @@ -10,4 +11,8 @@ public class HttpClientFactory { public static HttpClient newHttpClient() { return new HttpClientImpl(); } + + public static HttpClient newHttpClient(CloseableHttpClient httpClient) { + return new HttpClientImpl(httpClient); + } } From 81a94e31728eab0f0f6505719194b7e739c57873 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Thu, 22 Sep 2016 14:35:07 +0530 Subject: [PATCH 3/6] Fixes SSL handshake issue while accessing api.indix.com We trust the LetsEncrypt's root certificate as part of the http client's init --- .../indix/httpClient/impl/HttpClientImpl.java | 3 +- src/main/java/com/indix/tools/SSLTrustCA.java | 85 ++++++++++++++++++ src/main/resources/ca/DSTRootCAX3.der | Bin 0 -> 846 bytes 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/indix/tools/SSLTrustCA.java create mode 100644 src/main/resources/ca/DSTRootCAX3.der diff --git a/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java b/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java index a99a3bd..8f69702 100644 --- a/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java +++ b/src/main/java/com/indix/httpClient/impl/HttpClientImpl.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.indix.exception.*; import com.indix.httpClient.HttpClient; +import com.indix.tools.SSLTrustCA; import org.apache.http.*; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; @@ -35,7 +36,7 @@ class HttpClientImpl implements HttpClient { * configuration. */ public HttpClientImpl() { - this(HttpClients.createDefault()); + this(HttpClients.custom().setSSLContext(SSLTrustCA.trustLetsEncryptRootCA()).build()); } /** diff --git a/src/main/java/com/indix/tools/SSLTrustCA.java b/src/main/java/com/indix/tools/SSLTrustCA.java new file mode 100644 index 0000000..dbf8076 --- /dev/null +++ b/src/main/java/com/indix/tools/SSLTrustCA.java @@ -0,0 +1,85 @@ +package com.indix.tools; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +/** + * Forked off from https://github.com/micw/ArduinoProjekte/blob/b7e308533d20c9d23fda5e08899c22afd1dc1303/java/ArduinoHomeServer/src/main/java/tools/SSLTrustCa.java + *

+ * Helps to add LetsEncrypt to current JVM instance's keystore so we can access api.indix.com. This change has no effect + * if the host JVM is >= JDK8u101, since this is already part of them. + *

+ * References + * - http://stackoverflow.com/questions/3508050/how-can-i-get-a-list-of-trusted-root-certificates-in-java/3508175#3508175 + * - http://stackoverflow.com/questions/34110426/does-java-support-lets-encrypt-certificates + * - https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/37 + */ +public final class SSLTrustCA { + + public static SSLContext trustLetsEncryptRootCA() { + return trustCa(SSLTrustCA.class.getResource("/ca/DSTRootCAX3.der")); + } + + private static KeyStore keyStore; + private final static Logger LOG = LoggerFactory.getLogger(SSLTrustCA.class); + + private synchronized static KeyStore initialize(boolean loadJavaDefaultKeytore) + throws GeneralSecurityException, IOException { + + if (SSLTrustCA.keyStore == null) { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + + if (loadJavaDefaultKeytore) { + // Load the java default keystore + Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts"); + try (InputStream in = Files.newInputStream(ksPath)) { + keyStore.load(in, "changeit".toCharArray()); + } + } + + SSLTrustCA.keyStore = keyStore; + } + + return SSLTrustCA.keyStore; + } + + private synchronized static SSLContext trustCa(URL caFile) { + try { + LOG.debug("Trusting CAFile: " + caFile.toExternalForm()); + Certificate crt; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + try (InputStream caInput = new BufferedInputStream(caFile.openStream())) { + crt = cf.generateCertificate(caInput); + } + + String certName = ((X509Certificate) crt).getSubjectDN().getName(); + KeyStore keyStore = initialize(true); + keyStore.setCertificateEntry(certName, crt); + + // Set this as the default keystore + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keyStore); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + SSLContext.setDefault(sslContext); + return sslContext; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/src/main/resources/ca/DSTRootCAX3.der b/src/main/resources/ca/DSTRootCAX3.der new file mode 100644 index 0000000000000000000000000000000000000000..95500f6bd1373c2dce681e3779ac7647a84ef251 GIT binary patch literal 846 zcmXqLV)imbp7(EZg*cIAj~}vT2q`KpE>UpK*E1A15Cy4X7Upva4p9io z&j$)SDnu9?$cghB7#LU@8yFcH8W~zfiSrs6g1Ckz28M>FQIuQI#HfVqXhv2B<|amd z27@L>E~X|%Muz+AUrrC;Xb#_UCMosXgr+ygFY9C*oQi&P^Kk8Phl3I#-49+Emw9SE zwpa9=yvC1jy`9JD)OUX_EAppa(zNRcy4j_*@^4P z-Bq|BeN3xswcg%>wMV(7R40@a9j(q&NgLp&$0Xa zPdXO75i7c?_QoRA()q%lzY;MHAE&>*I%{^X?5ss6 zTx-sJA~0K0Ynx{H=YL>|WnyMzU|j5E;9$THj3HTJM#ldvtOm?L%770fzz-5& z0j35v16dHCk420{Az=xANbwf&S15*z;pelr>~hlUS#T)JUzuK&f|!LT!du298=T>o{t;X zuDlc3)}8lq;@7rQzjme>uZoO5_h`9TvHNN5Gcz}sdz^n9=Kb|NKTnZS`>_u$yMv2z zoxiGi%nC~19kT+H`dpb@E zJ-bvpvHPd3QE%#&mo*#1%U&JbreV2XG*#xrxAJLOT)pMoHzsnk^s(R8+{X28Zj-60 F004`~O?LnQ literal 0 HcmV?d00001 From 42d8e90fa6770f0c0031f6c3f30b6530c2dbbe65 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Thu, 22 Sep 2016 19:47:56 +0530 Subject: [PATCH 4/6] Removing java.nio related classes Apparently they are not supported on Android. Ref - http://stackoverflow.com/a/24869904 --- src/main/java/com/indix/tools/SSLTrustCA.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/indix/tools/SSLTrustCA.java b/src/main/java/com/indix/tools/SSLTrustCA.java index dbf8076..db4521c 100644 --- a/src/main/java/com/indix/tools/SSLTrustCA.java +++ b/src/main/java/com/indix/tools/SSLTrustCA.java @@ -5,13 +5,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.Certificate; @@ -46,8 +41,9 @@ private synchronized static KeyStore initialize(boolean loadJavaDefaultKeytore) if (loadJavaDefaultKeytore) { // Load the java default keystore - Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts"); - try (InputStream in = Files.newInputStream(ksPath)) { + String SEP = File.pathSeparator; + File ksPath = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security" + SEP + "cacerts"); + try (InputStream in = new FileInputStream(ksPath)) { keyStore.load(in, "changeit".toCharArray()); } } From eeb9347b5bb4c6b820214c3166edb39025c85dc0 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Thu, 22 Sep 2016 19:49:29 +0530 Subject: [PATCH 5/6] Fixing the separator for files --- src/main/java/com/indix/tools/SSLTrustCA.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/indix/tools/SSLTrustCA.java b/src/main/java/com/indix/tools/SSLTrustCA.java index db4521c..31de287 100644 --- a/src/main/java/com/indix/tools/SSLTrustCA.java +++ b/src/main/java/com/indix/tools/SSLTrustCA.java @@ -41,7 +41,7 @@ private synchronized static KeyStore initialize(boolean loadJavaDefaultKeytore) if (loadJavaDefaultKeytore) { // Load the java default keystore - String SEP = File.pathSeparator; + String SEP = File.separator; File ksPath = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security" + SEP + "cacerts"); try (InputStream in = new FileInputStream(ksPath)) { keyStore.load(in, "changeit".toCharArray()); From 242c71f30914a2d277b77d364854fbc1ac85ec75 Mon Sep 17 00:00:00 2001 From: Ashwanth Kumar Date: Tue, 27 Sep 2016 12:21:29 +0530 Subject: [PATCH 6/6] Not loading default JVM's truststore, since it's not available on android platforms --- README.md | 22 +++++++++++++++++++ src/main/java/com/indix/tools/SSLTrustCA.java | 15 +++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 065a8ae..e594336 100644 --- a/README.md +++ b/README.md @@ -189,3 +189,25 @@ The following example shows how to obtain the output of a bulk job, as requested indixApiClient.close(); } ``` + +## Known issue(s) +If you're using the client on Android you might see the following error +``` +java.lang.NoSuchMethodError: No virtual method setSSLContext(Ljavax/net/ssl/SSLContext;)Lorg/apache/http/impl/client/HttpClientBuilder; +``` + +That's because the HttpClient that comes with this client is little newer than the one that's generally used in Android. The fix is to do the following + +``` +import com.indix.httpClient.HttpClient; +import com.indix.httpClient.impl.HttpClientFactory; +import com.indix.tools.SSLTrustCA; + +import org.apache.http.impl.client.HttpClients; + +HttpClient client = HttpClientFactory.newHttpClient(HttpClients.custom() + .setSslcontext(SSLTrustCA.trustLetsEncryptRootCA()) + .build()); +IndixApiClient indixApiClient = IndixApiClientFactory + .newIndixApiClient(appId, appKey, client); +``` diff --git a/src/main/java/com/indix/tools/SSLTrustCA.java b/src/main/java/com/indix/tools/SSLTrustCA.java index 31de287..180f3e0 100644 --- a/src/main/java/com/indix/tools/SSLTrustCA.java +++ b/src/main/java/com/indix/tools/SSLTrustCA.java @@ -26,6 +26,8 @@ */ public final class SSLTrustCA { + private static final char[] KEYSTORE_DEFAULT_PASSWORD = "changeit".toCharArray(); + public static SSLContext trustLetsEncryptRootCA() { return trustCa(SSLTrustCA.class.getResource("/ca/DSTRootCAX3.der")); } @@ -33,20 +35,13 @@ public static SSLContext trustLetsEncryptRootCA() { private static KeyStore keyStore; private final static Logger LOG = LoggerFactory.getLogger(SSLTrustCA.class); - private synchronized static KeyStore initialize(boolean loadJavaDefaultKeytore) + private synchronized static KeyStore initialize() throws GeneralSecurityException, IOException { if (SSLTrustCA.keyStore == null) { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - if (loadJavaDefaultKeytore) { - // Load the java default keystore - String SEP = File.separator; - File ksPath = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security" + SEP + "cacerts"); - try (InputStream in = new FileInputStream(ksPath)) { - keyStore.load(in, "changeit".toCharArray()); - } - } + keyStore.load(null, KEYSTORE_DEFAULT_PASSWORD); SSLTrustCA.keyStore = keyStore; } @@ -64,7 +59,7 @@ private synchronized static SSLContext trustCa(URL caFile) { } String certName = ((X509Certificate) crt).getSubjectDN().getName(); - KeyStore keyStore = initialize(true); + KeyStore keyStore = initialize(); keyStore.setCertificateEntry(certName, crt); // Set this as the default keystore