From 057edccf26350a0c4b39faa309708a8a1799af14 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Thu, 19 Jan 2017 17:06:29 +0100 Subject: [PATCH] Manually inject root cert for cross-signed LetsEncrypt cert in requests JREs prior to 8u101 do not ship with the root certificate that cross-signed the LetsEncrypt X3 CA used to sign server certificates. As the standard native Minecraft launcher for Windows and OSX ship with an older Java version, this commit imports the required root cert and uses it wherever communication with the replaymod.com server is established. Effectively reverts 682fb4b --- .../java/com/replaymod/core/utils/Utils.java | 48 +++++++++++++++++++ .../com/replaymod/extras/OpenEyeExtra.java | 9 +++- .../com/replaymod/online/api/ApiClient.java | 19 ++++++-- .../replaymod/online/api/SimpleApiClient.java | 9 ++-- .../online/api/replay/FileUploader.java | 7 ++- .../api/replay/ReplayModApiMethods.java | 2 +- src/main/resources/dst_root_ca_x3.pem | 20 ++++++++ 7 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/dst_root_ca_x3.pem diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 2457ae15..8b27fff4 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -27,8 +27,20 @@ import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import javax.imageio.ImageIO; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -51,6 +63,42 @@ public class Utils { DEFAULT_THUMBNAIL = thumbnail; } + + /** + * Neither the root certificate of LetsEncrypt nor the root that cross-signed it is included in the default + * Java keystore prior to 8u101. + * Therefore whenever a connection to the replaymod.com site is made, this SSLContext has to be used instead. + * It has been constructed to include the necessary root certificates. + * @see #SSL_SOCKET_FACTORY + */ + public static final SSLContext SSL_CONTEXT; + + /** + * @see #SSL_CONTEXT + */ + public static final SSLSocketFactory SSL_SOCKET_FACTORY; + + static { + // Largely from https://community.letsencrypt.org/t/134/37 + try (InputStream in = Utils.class.getResourceAsStream("/dst_root_ca_x3.pem")){ + Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(in); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("1", certificate); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, trustManagerFactory.getTrustManagers(), null); + SSL_CONTEXT = ctx; + SSL_SOCKET_FACTORY = ctx.getSocketFactory(); + } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + public static String convertSecondsToShortString(int seconds) { int hours = seconds/(60*60); int min = seconds/60 - hours*60; diff --git a/src/main/java/com/replaymod/extras/OpenEyeExtra.java b/src/main/java/com/replaymod/extras/OpenEyeExtra.java index e198a826..ad5c1004 100644 --- a/src/main/java/com/replaymod/extras/OpenEyeExtra.java +++ b/src/main/java/com/replaymod/extras/OpenEyeExtra.java @@ -17,6 +17,7 @@ import de.johni0702.minecraft.gui.utils.Colors; import net.minecraftforge.fml.common.Loader; import org.apache.commons.io.FileUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.File; import java.io.FileOutputStream; import java.net.URL; @@ -24,8 +25,10 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class OpenEyeExtra implements Extra { - private static final String DOWNLOAD_URL = "http://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION; + private static final String DOWNLOAD_URL = "https://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION; private static final Setting ASK_FOR_OPEN_EYE = new Setting<>("advanced", "askForOpenEye", null, true); private ReplayMod mod; @@ -69,7 +72,9 @@ public class OpenEyeExtra implements Extra { File targetFile = new File("mods/" + Loader.MC_VERSION, "OpenEye.jar"); FileUtils.forceMkdir(targetFile.getParentFile()); - ReadableByteChannel in = Channels.newChannel(new URL(DOWNLOAD_URL).openStream()); + HttpsURLConnection connection = (HttpsURLConnection) new URL(DOWNLOAD_URL).openConnection(); + connection.setSSLSocketFactory(SSL_SOCKET_FACTORY); + ReadableByteChannel in = Channels.newChannel(connection.getInputStream()); FileChannel out = new FileOutputStream(targetFile).getChannel(); out.transferFrom(in, 0, Long.MAX_VALUE); } catch (Throwable e) { diff --git a/src/main/java/com/replaymod/online/api/ApiClient.java b/src/main/java/com/replaymod/online/api/ApiClient.java index f8e8a1b1..a12cd822 100755 --- a/src/main/java/com/replaymod/online/api/ApiClient.java +++ b/src/main/java/com/replaymod/online/api/ApiClient.java @@ -15,13 +15,19 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import javax.imageio.ImageIO; +import javax.net.ssl.HttpsURLConnection; import java.awt.image.BufferedImage; -import java.io.*; -import java.net.HttpURLConnection; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.List; import java.util.function.Consumer; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class ApiClient { private static final Minecraft mc = Minecraft.getMinecraft(); @@ -130,7 +136,11 @@ public class ApiClient { QueryBuilder builder = new QueryBuilder(ReplayModApiMethods.get_thumbnail); builder.put("id", file); URL url = new URL(builder.toString()); - return ImageIO.read(url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setSSLSocketFactory(SSL_SOCKET_FACTORY); + try (InputStream in = connection.getInputStream()) { + return ImageIO.read(in); + } } private boolean cancelDownload = false; @@ -143,7 +153,8 @@ public class ApiClient { builder.put("id", file); String url = builder.toString(); URL website = new URL(url); - HttpURLConnection con = (HttpURLConnection) website.openConnection(); + HttpsURLConnection con = (HttpsURLConnection) website.openConnection(); + con.setSSLSocketFactory(SSL_SOCKET_FACTORY); int fileSize = con.getContentLength(); diff --git a/src/main/java/com/replaymod/online/api/SimpleApiClient.java b/src/main/java/com/replaymod/online/api/SimpleApiClient.java index b2871c6c..c3add7a3 100755 --- a/src/main/java/com/replaymod/online/api/SimpleApiClient.java +++ b/src/main/java/com/replaymod/online/api/SimpleApiClient.java @@ -7,12 +7,14 @@ import com.google.gson.JsonParser; import com.replaymod.online.api.replay.holders.ApiError; import org.apache.commons.io.IOUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + public class SimpleApiClient { private static final JsonParser jsonParser = new JsonParser(); @@ -72,10 +74,11 @@ public class SimpleApiClient { // read response String responseContent = null; InputStream is = null; - HttpURLConnection httpUrlConnection = null; + HttpsURLConnection httpUrlConnection = null; try { URL url = new URL(urlString); - httpUrlConnection = (HttpURLConnection) url.openConnection(); + httpUrlConnection = (HttpsURLConnection) url.openConnection(); + httpUrlConnection.setSSLSocketFactory(SSL_SOCKET_FACTORY); httpUrlConnection.setRequestMethod("GET"); diff --git a/src/main/java/com/replaymod/online/api/replay/FileUploader.java b/src/main/java/com/replaymod/online/api/replay/FileUploader.java index f369b310..8dce3faa 100755 --- a/src/main/java/com/replaymod/online/api/replay/FileUploader.java +++ b/src/main/java/com/replaymod/online/api/replay/FileUploader.java @@ -9,16 +9,18 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import javax.net.ssl.HttpsURLConnection; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Set; import java.util.function.Consumer; +import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; + @RequiredArgsConstructor public class FileUploader { private static final Gson gson = new Gson(); @@ -48,7 +50,8 @@ public class FileUploader { postData += "&name=" + URLEncoder.encode(filename, "UTF-8"); String url = ReplayModApiMethods.upload_file + postData; - HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); + HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection(); + con.setSSLSocketFactory(SSL_SOCKET_FACTORY); con.setUseCaches(false); con.setDoOutput(true); con.setRequestMethod("POST"); diff --git a/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java b/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java index 6277c1cd..524fbba1 100755 --- a/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java +++ b/src/main/java/com/replaymod/online/api/replay/ReplayModApiMethods.java @@ -2,7 +2,7 @@ package com.replaymod.online.api.replay; public class ReplayModApiMethods { - public static final String REPLAYMOD_BASE_URL = "http://ReplayMod.com/api/"; + public static final String REPLAYMOD_BASE_URL = "https://ReplayMod.com/api/"; public static final String register = REPLAYMOD_BASE_URL+"register"; public static final String check_authkey = REPLAYMOD_BASE_URL+"check_authkey"; diff --git a/src/main/resources/dst_root_ca_x3.pem b/src/main/resources/dst_root_ca_x3.pem new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/src/main/resources/dst_root_ca_x3.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE-----