diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java index 05dc87a48..d755e9b43 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/configuration/WebserverLogMessages.java @@ -110,4 +110,26 @@ public class WebserverLogMessages { logger.warn(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_PASSED)); } } + + public void invalidCertificateMissingAlias(String alias, String keystorePath) { + logger.error(locale.getString(PluginLang.WEB_SERVER_NOTIFY_CERT_NO_SUCH_ALIAS, alias, keystorePath)); + } + + public void unableToLoadKeystore(Exception e, String keystorePath) { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_STORE_LOAD)); + errorLogger.error(e, ErrorContext.builder() + .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") + .related(keystorePath).build()); + } + + public void wrongCertFileFormat() { + logger.error(locale.getString(PluginLang.WEB_SERVER_FAIL_EMPTY_FILE)); + } + + public void keystoreLoadingError(Exception e) { + errorLogger.error(e, ErrorContext.builder() + .logErrorMessage() + .whatToDo("Make sure the Certificate settings are correct / You can try remaking the keystore without -passin or -passout parameters.") + .build()); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java index 150333aa3..c8673be83 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/http/JettyWebserver.java @@ -32,10 +32,15 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import javax.inject.Inject; import javax.inject.Singleton; +import java.io.EOFException; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; 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.X509Certificate; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -197,6 +202,10 @@ public class JettyWebserver implements WebServer { return legacyJettySSLContextLoader.load(keyStorePath, storepass, keypass, alias); } + if (!verifyAliasIsInKeystore(keyStorePath, storepass, alias)) { + return Optional.empty(); + } + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setSniRequired(false); @@ -207,6 +216,29 @@ public class JettyWebserver implements WebServer { return Optional.of(sslContextFactory); } + private boolean verifyAliasIsInKeystore(String keyStorePath, String storepass, String alias) { + String keyStoreKind = keyStorePath.endsWith(".p12") ? "PKCS12" : "JKS"; + try (FileInputStream fIn = new FileInputStream(keyStorePath)) { + KeyStore keystore = KeyStore.getInstance(keyStoreKind); + + keystore.load(fIn, storepass.toCharArray()); + Certificate cert = keystore.getCertificate(alias); + + if (cert == null) { + webserverLogMessages.invalidCertificateMissingAlias(alias, keyStorePath); + return false; + } + return true; + } catch (KeyStoreException | CertificateException e) { + webserverLogMessages.unableToLoadKeystore(e, keyStorePath); + } catch (EOFException e) { + webserverLogMessages.wrongCertFileFormat(); + } catch (NoSuchAlgorithmException | IOException e) { + webserverLogMessages.keystoreLoadingError(e); + } + return false; + } + @Override public boolean isEnabled() { return webserver != null && (webserver.isStarting() || webserver.isStarted()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java index b65fe8b0a..a71d1d34f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/PluginLang.java @@ -54,6 +54,7 @@ public enum PluginLang implements Lang { WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE("plugin.webserver.notify.certificateExpiresOn", "Webserver notify - Cert expiry", "Webserver: Loaded certificate is valid until ${0}."), WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_SOON("plugin.webserver.notify.certificateExpiresSoon", "Webserver notify - Cert expiry soon", "Webserver: Certificate expires in ${0}, consider renewing the certificate."), WEB_SERVER_NOTIFY_CERT_EXPIRE_DATE_PASSED("plugin.webserver.notify.certificateExpiresPassed", "Webserver notify - Cert expiry passed", "Webserver: Certificate has expired, consider renewing the certificate."), + WEB_SERVER_NOTIFY_CERT_NO_SUCH_ALIAS("plugin.webserver.notify.certificateNoSuchAlias", "Webserver notify - Cert no alias", "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'."), DISABLED("plugin.disable.disabled", "Disable", "Player Analytics Disabled."), DISABLED_WEB_SERVER("plugin.disable.webserver", "Disable - WebServer", "Webserver has been disabled."), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java index 6e9d4746b..9746f9d6a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/ErrorContext.java @@ -28,6 +28,7 @@ public class ErrorContext implements Serializable { private final transient List<Object> related; private String whatToDo; + private boolean logErrorMessage = false; private ErrorContext() { related = new ArrayList<>(); @@ -41,6 +42,10 @@ public class ErrorContext implements Serializable { return Optional.ofNullable(whatToDo); } + public boolean shouldLogErrorMessage() { + return logErrorMessage; + } + public Collection<String> toLines() { List<String> lines = new ArrayList<>(); getWhatToDo().ifPresent(lines::add); @@ -71,6 +76,11 @@ public class ErrorContext implements Serializable { return this; } + public Builder logErrorMessage() { + context.logErrorMessage = true; + return this; + } + public Builder related(Object related) { context.related.add(related); return this; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java index 4e39215ce..35ff2f3c6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/logging/PluginErrorLogger.java @@ -205,7 +205,7 @@ public class PluginErrorLogger implements ErrorLogger { String errorMsg = throwable.getMessage(); String errorLocation = errorLog.toString(); return new String[]{ - "Ran into " + errorName + " - logged to " + errorLocation, + "Ran into " + errorName + (context.shouldLogErrorMessage() ? ": " + throwable.getMessage() : "") + " - logged to " + errorLocation, "(INCLUDE CONTENTS OF THE FILE IN ANY REPORTS)", context.getWhatToDo().map(td -> "What to do: " + td).orElse("Error msg: \"" + errorMsg + "\"") };