aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCow <cow@volloeko.de>2022-10-22 14:06:49 +0200
committerCow <cow@volloeko.de>2022-10-22 14:06:49 +0200
commitfeea22f251cd188d91b843ff9348e057e0f9e91d (patch)
tree10f09a15e028d4301ae093e3d6827dbdbb39e904
parent0e2e8859a2652a781a381267d9d327d29df92c68 (diff)
downloadCowlection-feea22f251cd188d91b843ff9348e057e0f9e91d.tar.gz
Cowlection-feea22f251cd188d91b843ff9348e057e0f9e91d.tar.bz2
Cowlection-feea22f251cd188d91b843ff9348e057e0f9e91d.zip
Added Let's Encrypt support for ancient Java versions
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/main/java/de/cowtipper/cowlection/Cowlection.java2
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java96
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java66
-rw-r--r--src/main/resources/https-for-ancient-java.jksbin0 -> 2040 bytes
5 files changed, 139 insertions, 26 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1612212..0790647 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Minecraft username lookup by uuid changed since username history API got removed
- Fixed GUI related issues caused by updates since the last release of Cowlection:
- Fixed detection for `/bestiary` overview
+- (technical fix: add support for [public Hypixel API SSL certificate changes](https://hypixel.net/threads/public-api-ssl-certificate-changes.5116193/))
## [1.8.9-0.14.0] - 14.08.2021
### Added
diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java
index 332d08e..2ee7e34 100644
--- a/src/main/java/de/cowtipper/cowlection/Cowlection.java
+++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java
@@ -65,8 +65,8 @@ public class Cowlection {
configDir.mkdirs();
}
- friendsHandler = new FriendsHandler(this, new File(configDir, "friends.json"));
moo = new CredentialStorage(new Configuration(new File(configDir, "do-not-share-me-with-other-players.cfg")));
+ friendsHandler = new FriendsHandler(this, new File(configDir, "friends.json"));
partyFinderRules = new Rules(this, new File(configDir, "partyfinder-rules.json"));
config = new MooConfig(this, new Configuration(new File(configDir, MODID + ".cfg"), "2"));
}
diff --git a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
index f4e64f6..86deda9 100644
--- a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
+++ b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
@@ -4,8 +4,23 @@ import de.cowtipper.cowlection.Cowlection;
import de.cowtipper.cowlection.util.ApiUtils;
import de.cowtipper.cowlection.util.Utils;
import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.MathHelper;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
+import org.apache.http.conn.ssl.SSLContexts;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Enumeration;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Key and secret holder in its own file to avoid people leaking their keys accidentally.
@@ -13,6 +28,7 @@ import net.minecraftforge.common.config.Property;
public class CredentialStorage {
public static String moo;
public static boolean isMooValid;
+ public static SSLContext sslContext;
private Property propMoo;
private Property propIsMooValid;
private final Configuration cfg;
@@ -20,17 +36,18 @@ public class CredentialStorage {
public CredentialStorage(Configuration configuration) {
cfg = configuration;
initConfig();
+ verifyNewerHttpsSupport();
}
private void initConfig() {
cfg.load();
propMoo = cfg.get(Configuration.CATEGORY_CLIENT,
- "moo", "", "Don't share this with anybody! Do not edit this entry manually either!", Utils.VALID_UUID_PATTERN)
+ "moo", "", "Don't share this with anybody! Do not edit this entry manually either!", Utils.VALID_UUID_PATTERN)
.setShowInGui(false);
propMoo.setLanguageKey(Cowlection.MODID + ".config." + propMoo.getName());
propIsMooValid = cfg.get(Configuration.CATEGORY_CLIENT,
- "isMooValid", false, "Is the value valid?")
+ "isMooValid", false, "Is the value valid?")
.setShowInGui(false);
moo = propMoo.getString();
isMooValid = propIsMooValid.getBoolean();
@@ -39,6 +56,81 @@ public class CredentialStorage {
}
}
+ private void verifyNewerHttpsSupport() {
+ String javaVersion = System.getProperty("java.version", "unknown java version");
+ Pattern javaVersionPattern = Pattern.compile("1\\.8\\.0_(\\d+)"); // e.g. 1.8.0_51
+ Matcher javaVersionMatcher = javaVersionPattern.matcher(javaVersion);
+ if (!javaVersionMatcher.matches()
+ || MathHelper.parseIntWithDefault(javaVersionMatcher.group(1), 1337) >= 101) {
+ // newer Java version (>=8u101): *should* already have support by default
+ return;
+ }
+
+ // running Java <8u101
+ if (testNewHttps()) {
+ // uhm... looks like someone added the certs to the default JKS already
+ return;
+ }
+ System.out.println("Injecting Let's Encrypt support due to ancient Java version...");
+ try {
+ KeyStore originalKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ originalKeyStore.load(Files.newInputStream(Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts")), "changeit".toCharArray());
+
+ KeyStore mooKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ mooKeyStore.load(getClass().getResourceAsStream("/https-for-ancient-java.jks"), "mooveit".toCharArray());
+
+ KeyStore tempKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ tempKeyStore.load(null, "moovedit".toCharArray());
+
+ for (Enumeration<String> aliases = originalKeyStore.aliases(); aliases.hasMoreElements(); ) {
+ String alias = aliases.nextElement();
+ tempKeyStore.setCertificateEntry(alias, originalKeyStore.getCertificate(alias));
+ }
+ for (Enumeration<String> aliases = mooKeyStore.aliases(); aliases.hasMoreElements(); ) {
+ String alias = aliases.nextElement();
+ tempKeyStore.setCertificateEntry(alias, mooKeyStore.getCertificate(alias));
+ }
+ sslContext = SSLContexts.custom()
+ .loadKeyMaterial(tempKeyStore, "moovedit".toCharArray())
+ .loadTrustMaterial(tempKeyStore, null)
+ .build();
+
+ if (!testNewHttps()) {
+ System.err.println("Error while trying to add Let's Encrypt support: Could not contact site after running setup");
+ }
+ } catch (GeneralSecurityException | IOException e) {
+ System.err.println("Error while trying to add Let's Encrypt support:");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Tests accessing a site that uses Let's Encrypt
+ *
+ * @return true, if connection was successful
+ */
+ private boolean testNewHttps() {
+ try {
+ URLConnection connection = new URL("https://helloworld.letsencrypt.org/").openConnection();
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(8000);
+ connection.setDoInput(false);
+ connection.setDoOutput(false);
+ connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")");
+ if (CredentialStorage.sslContext != null && connection instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) connection).setSSLSocketFactory(CredentialStorage.sslContext.getSocketFactory());
+ }
+
+ connection.connect();
+
+ // seems to be working since there was no IOException!
+ return true;
+ } catch (IOException ignored) {
+ // most likely a newer https related issue, so setup might fix things
+ }
+ return false;
+ }
+
public void setMooIfValid(String moo, boolean commandTriggered) {
ApiUtils.fetchApiKeyInfo(moo, hyApiKey -> {
if (hyApiKey != null && hyApiKey.isSuccess()) {
diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
index 663936f..1397a36 100644
--- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
+++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
@@ -14,9 +14,14 @@ import de.cowtipper.cowlection.data.*;
import de.cowtipper.cowlection.error.ApiAskPolitelyErrorEvent;
import de.cowtipper.cowlection.error.ApiHttpErrorEvent;
import de.cowtipper.cowlection.error.ApiHttpErrorException;
+import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.common.MinecraftForge;
import org.apache.http.HttpStatus;
+import sun.security.provider.certpath.SunCertPathBuilderException;
+import sun.security.validator.ValidatorException;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLHandshakeException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -194,31 +199,46 @@ public class ApiUtils {
}
private static BufferedReader makeApiCall(String url) throws IOException {
- HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
- connection.setConnectTimeout(5000);
- connection.setReadTimeout(8000);
- connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")");
-
- connection.getResponseCode();
- if (connection.getResponseCode() == HttpStatus.SC_NO_CONTENT) { // http status 204
- return null;
- } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://api.hypixel.net/")) { // http status 502 (cloudflare)
- throw new ApiHttpErrorException("Couldn't contact Hypixel API (502 Bad Gateway). API might be down, check https://status.hypixel.net for info.", "https://status.hypixel.net");
- } else if (connection.getResponseCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) { // http status 503 Service Unavailable
- int queryParamStart = url.indexOf('?', 10);
- String baseUrl = queryParamStart > 0 ? url.substring(0, queryParamStart) : url;
- throw new ApiHttpErrorException("Couldn't contact the API (503 Service unavailable). API might be down, or you might be blocked by Cloudflare, check if you can reach: " + baseUrl, url);
- } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://moulberry.codes/")) { // http status 502 (cloudflare)
- throw new ApiHttpErrorException("Couldn't contact Moulberry's API (502 Bad Gateway). API might be down, check if " + LOWEST_BINS + " is reachable.", LOWEST_BINS);
- } else {
- BufferedReader reader;
- InputStream errorStream = connection.getErrorStream();
- if (errorStream != null) {
- reader = new BufferedReader(new InputStreamReader(errorStream));
+ try {
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ if (CredentialStorage.sslContext != null && connection instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) connection).setSSLSocketFactory(CredentialStorage.sslContext.getSocketFactory());
+ }
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(8000);
+ connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")");
+
+ connection.getResponseCode();
+ if (connection.getResponseCode() == HttpStatus.SC_NO_CONTENT) { // http status 204
+ return null;
+ } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://api.hypixel.net/")) { // http status 502 (cloudflare)
+ throw new ApiHttpErrorException("Couldn't contact Hypixel API (502 Bad Gateway). API might be down, check https://status.hypixel.net for info.", "https://status.hypixel.net");
+ } else if (connection.getResponseCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) { // http status 503 Service Unavailable
+ int queryParamStart = url.indexOf('?', 10);
+ String baseUrl = queryParamStart > 0 ? url.substring(0, queryParamStart) : url;
+ throw new ApiHttpErrorException("Couldn't contact the API (503 Service unavailable). API might be down, or you might be blocked by Cloudflare, check if you can reach: " + baseUrl, url);
+ } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://moulberry.codes/")) { // http status 502 (cloudflare)
+ throw new ApiHttpErrorException("Couldn't contact Moulberry's API (502 Bad Gateway). API might be down, check if " + LOWEST_BINS + " is reachable.", LOWEST_BINS);
} else {
- reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ BufferedReader reader;
+ InputStream errorStream = connection.getErrorStream();
+ if (errorStream != null) {
+ reader = new BufferedReader(new InputStreamReader(errorStream));
+ } else {
+ reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ }
+ return reader;
+ }
+ } catch (SSLHandshakeException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof ValidatorException && cause.getCause() instanceof SunCertPathBuilderException) {
+ throw new ApiHttpErrorException("" + EnumChatFormatting.DARK_RED + EnumChatFormatting.BOLD + " ! "
+ + EnumChatFormatting.RED + "Java is outdated and doesn't support Let's Encrypt certificates (out of the box). A game restart might fix this issue. If the problem persists, open a ticket on the Cowshed discord server.", Cowlection.INVITE_URL);
+ } else {
+ // not a newer https related issue, thus rethrow exception:
+ throw e;
}
- return reader;
}
}
+
}
diff --git a/src/main/resources/https-for-ancient-java.jks b/src/main/resources/https-for-ancient-java.jks
new file mode 100644
index 0000000..2b3d337
--- /dev/null
+++ b/src/main/resources/https-for-ancient-java.jks
Binary files differ