Skip to content

Commit

Permalink
1.10.0
Browse files Browse the repository at this point in the history
Allow the user to set:
the supported TLS versions,
the minimum DH key size,
and the supported SSL cipher suites.
  • Loading branch information
e3ndr committed Nov 20, 2020
1 parent 8bc2c2d commit b395948
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 65 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>co.casterlabs</groupId>
<artifactId>Katana</artifactId>
<version>1.9.1</version>
<version>1.10.0</version>

<repositories>
<repository>
Expand Down Expand Up @@ -86,5 +86,11 @@
<version>1.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.e3ndr</groupId>
<artifactId>ReflectionLib</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
5 changes: 3 additions & 2 deletions src/main/java/co/casterlabs/katana/Katana.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Scanner;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;

Expand All @@ -23,8 +24,8 @@
@Getter
public class Katana {
public static final String ERROR_HTML = "<!DOCTYPE html><html><head><title>$RESPONSECODE</title></head><body><h1>$RESPONSECODE</h1><p>$DESCRIPTION</p><br/><p><i>Running Casterlabs Katana, $ADDRESS</i></p></body></html>";
public static final String VERSION = "1.9.1";
public static final Gson GSON = new Gson();
public static final String VERSION = "1.10.0";
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();

private Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
private CommandRegistry<Void> commandRegistry = new CommandRegistry<>();
Expand Down
32 changes: 14 additions & 18 deletions src/main/java/co/casterlabs/katana/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.collections4.MultiValuedMap;
Expand All @@ -16,12 +15,25 @@
import com.google.gson.JsonElement;

import co.casterlabs.katana.http.HttpSession;
import co.casterlabs.katana.http.TLSVersion;
import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.NanoHTTPD.Response;
import fi.iki.elonen.NanoHTTPD.Response.Status;

public class Util {

public static String[] convertTLS(TLSVersion[] tls) {
List<String> versions = new ArrayList<>();

for (TLSVersion version : tls) {
if (version.existsInRuntime()) {
versions.add(version.getRuntimeName());
}
}

return versions.toArray(new String[0]);
}

public static <T extends Collection<String>> T fillFromJson(JsonArray array, T collection) {
if (array.isJsonArray()) {
for (JsonElement element : array.getAsJsonArray()) {
Expand Down Expand Up @@ -87,22 +99,6 @@ public static String getFileOrNull(String file) {
}
}

public static String getMimeForUriString(String uri) {
return NanoHTTPD.getMimeTypeForFile(uri);
}

public static String getMimeForUri(URI uri) {
return NanoHTTPD.getMimeTypeForFile(uri.toString());
}

public static String getMimeForPath(Path path) {
return getMimeForUri(path.toUri());
}

public static String getMimeForFile(File file) {
return getMimeForUri(file.toURI());
}

public static <T> T readFileAsJson(File file, Class<T> clazz) throws Exception {
byte[] bytes = Files.readAllBytes(file.toPath());

Expand Down
11 changes: 8 additions & 3 deletions src/main/java/co/casterlabs/katana/config/SSLConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package co.casterlabs.katana.config;

import co.casterlabs.katana.http.TLSVersion;
import lombok.ToString;

@ToString
public class SSLConfiguration {
public boolean enabled = false;

public TLSVersion[] tls = TLSVersion.values();
public String[] enabled_cipher_suites = null; // Null = All Available
public boolean allow_insecure = true;
public boolean force = false;
public int dh_size = 2048;

public String keystore_password = "";
public String key_password = "";
public String keystore = "";

public String keystore;
public String keystore_password;
public String key_password;
}
13 changes: 2 additions & 11 deletions src/main/java/co/casterlabs/katana/config/ServerConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
public class ServerConfiguration {
private SSLConfiguration SSL = new SSLConfiguration();
private List<Servlet> servlets = new ArrayList<>();
private boolean panel;
private String name;
private int port;
private String name = "www";
private int port = 80;

public ServerConfiguration(JsonObject json, Katana katana) throws IllegalArgumentException {
this.SSL = Katana.GSON.fromJson(json.get("ssl"), SSLConfiguration.class);
Expand Down Expand Up @@ -69,12 +68,4 @@ public ServerConfiguration(JsonObject json, Katana katana) throws IllegalArgumen
});
}

public void addServlet(Servlet servlet) {
this.servlets.add(servlet);

Collections.sort(this.servlets, (Servlet s1, Servlet s2) -> {
return s1.getPriority() > s2.getPriority() ? -1 : 1;
});
}

}
27 changes: 26 additions & 1 deletion src/main/java/co/casterlabs/katana/http/HttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ public HttpServer(ServerConfiguration config, Katana katana) throws IOException
if (!certificate.exists()) {
this.logger.severe("Unable to find SSL certificate file.");
} else {
// https://www.java.com/en/configure_crypto.html
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#customizing_dh_keys
System.setProperty("jdk.tls.ephemeralDHKeySize", String.valueOf(ssl.dh_size));
String disabledAlgorithmsProperty = System.getProperty("jdk.tls.disabledAlgorithms", "DH keySize");
String[] disabledAlgorithms = disabledAlgorithmsProperty.split(",");
boolean replacedParameter = false;

for (int i = 0; i != disabledAlgorithms.length; i++) {
if (disabledAlgorithms[i].startsWith("DH keySize")) {
replacedParameter = true;

disabledAlgorithms[i] = "DH keySize < " + ssl.dh_size;

break;
}
}

if (replacedParameter) {
System.setProperty("jdk.tls.disabledAlgorithms", String.join(", ", disabledAlgorithms));
} else {
System.setProperty("jdk.tls.disabledAlgorithms", disabledAlgorithmsProperty + ", DH keySize < " + ssl.dh_size);
}

this.logger.debug("Ephemeral DH Key Size: %s", ssl.dh_size);

KeyStore keystore = KeyStore.getInstance("jks");
keystore.load(new FileInputStream(certificate), ssl.keystore_password.toCharArray());

Expand All @@ -94,7 +119,7 @@ public HttpServer(ServerConfiguration config, Katana katana) throws IOException
SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(keystore, managerFactory);

this.nanoSecure = new NanoWrapper(443, true);
this.nanoSecure.makeSecure(factory, null);
this.nanoSecure.makeSecure(new WrappedSSLSocketFactory(factory, ssl), Util.convertTLS(ssl.tls));
this.nanoSecure.setAsyncRunner(new NanoRunner());

this.forceHttps = ssl.force;
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/co/casterlabs/katana/http/TLSVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package co.casterlabs.katana.http;

import java.lang.reflect.InvocationTargetException;

import xyz.e3ndr.reflectionlib.ReflectionLib;

public enum TLSVersion {
TLSv1,
TLSv1_1,
TLSv1_2,
TLSv1_3;

public String getRuntimeName() {
return this.name().replace('_', '.');
}

@SuppressWarnings("restriction")
public boolean existsInRuntime() {
try {
ReflectionLib.invokeStaticMethod(sun.security.ssl.ProtocolVersion.class, "valueOf", this.getRuntimeName());

return true;
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) {
if (e.getCause() instanceof IllegalArgumentException) {
return false;
} else {
e.printStackTrace();
return true;
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package co.casterlabs.katana.http;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;

import co.casterlabs.katana.config.SSLConfiguration;
import xyz.e3ndr.fastloggingframework.logging.FastLogger;
import xyz.e3ndr.fastloggingframework.logging.LogLevel;

public class WrappedSSLSocketFactory extends SSLServerSocketFactory {
private SSLServerSocketFactory wrappedFactory;
private String[] cipherSuites;

public WrappedSSLSocketFactory(SSLServerSocketFactory factory, SSLConfiguration ssl) {
this.wrappedFactory = factory;

if ((ssl.enabled_cipher_suites == null) || (ssl.enabled_cipher_suites.length == 0)) {
this.cipherSuites = this.wrappedFactory.getSupportedCipherSuites();
} else {
List<String> enabled = Arrays.asList(ssl.enabled_cipher_suites);
List<String> supported = new ArrayList<>();

for (String def : this.wrappedFactory.getSupportedCipherSuites()) {
if (enabled.contains(def)) {
supported.add(def);
} else {
FastLogger.logStatic(LogLevel.DEBUG, "Disabled Cipher Suite: %s.", def);
}
}

FastLogger.logStatic(LogLevel.DEBUG, "Using the following Cipher Suites: %s.", supported);

this.cipherSuites = supported.toArray(new String[0]);
}
}

@Override
public String[] getDefaultCipherSuites() {
return this.cipherSuites;
}

@Override
public String[] getSupportedCipherSuites() {
return this.cipherSuites;
}

@Override
public ServerSocket createServerSocket() throws IOException {
SSLServerSocket socket = (SSLServerSocket) this.wrappedFactory.createServerSocket();

socket.setEnabledCipherSuites(this.cipherSuites);

return socket;
}

@Override
public ServerSocket createServerSocket(int port) throws IOException {
SSLServerSocket socket = (SSLServerSocket) this.wrappedFactory.createServerSocket(port);

socket.setEnabledCipherSuites(this.cipherSuites);

return socket;
}

@Override
public ServerSocket createServerSocket(int port, int backlog) throws IOException {
SSLServerSocket socket = (SSLServerSocket) this.wrappedFactory.createServerSocket(port, backlog);

socket.setEnabledCipherSuites(this.cipherSuites);

return socket;
}

@Override
public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException {
SSLServerSocket socket = (SSLServerSocket) this.wrappedFactory.createServerSocket(port, backlog, ifAddress);

socket.setEnabledCipherSuites(this.cipherSuites);

return socket;
}

}
38 changes: 9 additions & 29 deletions src/main/resources/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,24 @@
"port": 80,
"ssl": {
"enabled": false,
"tls": [
"TLSv1",
"TLSv1_1",
"TLSv1_2",
"TLSv1_3"
],
"force": true,
"allow_insecure": true,
"certificate": "ssl/certificate.pem",
"private_key": "ssl/private.key",
"chain": "ssl/chain.pem"
"keystore": "ssl.jks",
"keystore_password": "",
"key_password": ""
},
"hosts": [
{
"type": "static",
"hostname": "localhost",
"directory": "www",
"require_file_extensions": false
},
{
"type": "redirect",
"hostnames": [
"fuf.me",
"www.fuf.me"
],
"redirect_url": "https://example.com/",
"include_path": true
},
{
"type": "proxy",
"hostname": "127.0.0.1",
"proxy_url": "https://example.com",
"proxy_path": "",
"include_path": false
},
{
"type": "websocketproxy",
"hostnames": [
"fuf.me",
"www.fuf.me"
],
"proxy_url": "wss://echo.websocket.org",
"proxy_path": "/echo",
"include_path": false
}
]
}
Expand Down

0 comments on commit b395948

Please sign in to comment.