diff --git a/.gitignore b/.gitignore index d6754cd..da956eb 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ fabric.properties .idea/caches/build_file_checksums.ser certificates/ +config.json diff --git a/build.gradle.kts b/build.gradle.kts index 1f9c591..5427258 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,4 +10,6 @@ repositories { } dependencies { + implementation("org.nanohttpd:nanohttpd:2.3.1") + implementation("com.google.code.gson:gson:2.10.1") } diff --git a/config.json b/config.json new file mode 100644 index 0000000..19fd068 --- /dev/null +++ b/config.json @@ -0,0 +1,6 @@ +{ + "certDomain": "localhost", + "sfsPort": 7392, + "sftpPort": 2222, + "httpPort": 8000 +} \ No newline at end of file diff --git a/openssl.cnf b/openssl.cnf new file mode 100644 index 0000000..225715a --- /dev/null +++ b/openssl.cnf @@ -0,0 +1,18 @@ +[req] +distinguished_name = req_distinguished_name +x509_extensions = v3_req +prompt = no + +[req_distinguished_name] +C = US +ST = State +L = Local +O = Org +CN = localhost + +[v3_req] +subjectAltName = @alt_names + +[alt_names] +IP.1 = 127.0.0.1 +DNS.1 = localhost \ No newline at end of file diff --git a/src/main/java/tech/nevets/sfss/Config.java b/src/main/java/tech/nevets/sfss/Config.java new file mode 100644 index 0000000..7c06447 --- /dev/null +++ b/src/main/java/tech/nevets/sfss/Config.java @@ -0,0 +1,42 @@ +package tech.nevets.sfss; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import java.io.*; + +public class Config { + private final static Gson GSON = new Gson(); + private JsonObject config; + + public Config() { + try { + File configFile = new File("./config.json"); + if (!configFile.exists()) { + configFile.createNewFile(); + new FileWriter(configFile).write("{}"); + } + config = GSON.fromJson(new FileReader(configFile), JsonObject.class); + } catch (IOException e) { + config = new JsonObject(); + e.printStackTrace(); + } + } + + public String getString(String key) { + return config.get(key).getAsString(); + } + + public int getInt(String key) { + return config.get(key).getAsInt(); + } + + public short getShort(String key) { + return config.get(key).getAsShort(); + } + + public boolean getBoolean(String key) { + return config.get(key).getAsBoolean(); + } +} diff --git a/src/main/java/tech/nevets/sfss/Main.java b/src/main/java/tech/nevets/sfss/Main.java index 8a7f588..efd3e21 100644 --- a/src/main/java/tech/nevets/sfss/Main.java +++ b/src/main/java/tech/nevets/sfss/Main.java @@ -1,7 +1,47 @@ package tech.nevets.sfss; +import tech.nevets.sfss.net.HTTPServer; +import tech.nevets.sfss.net.SFSServer; +import tech.nevets.sfss.net.SFTPServer; + +import java.io.IOException; + public class Main { + public static final Config CONFIG = new Config(); + public static void main(String[] args) { StoreManager.generateCertificates(); + StoreManager.loadCertificates(); + + Thread sfsServer = new Thread(() -> { + try { + System.out.println("Starting sfs server..."); + new SFSServer(CONFIG.getInt("sfsPort")); + } catch (IOException e) { + e.printStackTrace(); + } + }); + sfsServer.start(); + + Thread sftpServer = new Thread(() -> { + try { + System.out.println("Starting sftp server..."); + new SFTPServer(CONFIG.getInt("sftpPort")); + } catch (IOException e) { + e.printStackTrace(); + } + }); + sftpServer.start(); + + Thread httpServer = new Thread(() -> { + try { + System.out.println("Starting http server..."); + new HTTPServer(CONFIG.getInt("httpPort")); + } catch (IOException e) { + e.printStackTrace(); + } + }); + httpServer.start(); + } } diff --git a/src/main/java/tech/nevets/sfss/Server.java b/src/main/java/tech/nevets/sfss/Server.java deleted file mode 100644 index c4c8708..0000000 --- a/src/main/java/tech/nevets/sfss/Server.java +++ /dev/null @@ -1,24 +0,0 @@ -package tech.nevets.sfss; - -import javax.net.ServerSocketFactory; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class Server { - private final SSLServerSocket serverSocket; - - public Server(String host, int port) { - InetAddress addr; - try { - addr = InetAddress.getByName(host); - } catch (UnknownHostException e) { - addr = null; - System.out.println("Unknown Host:"); - e.printStackTrace(); - } - SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); - serverSocket = (SSLServerSocket) factory.createServerSocket(port, 0, addr); - } -} diff --git a/src/main/java/tech/nevets/sfss/StoreManager.java b/src/main/java/tech/nevets/sfss/StoreManager.java index 00b5e03..2d2b0a3 100644 --- a/src/main/java/tech/nevets/sfss/StoreManager.java +++ b/src/main/java/tech/nevets/sfss/StoreManager.java @@ -1,64 +1,85 @@ package tech.nevets.sfss; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.*; import java.io.*; -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; +import java.security.*; +import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; public class StoreManager { - public static final File PUBLIC_KEY = new File("./certificates/public.cer"); public static final File PRIVATE_KEY = new File("./certificates/private.key"); + public static final File PRIVATE_PKCS8 = new File("./certificates/private_key_pkcs8.der"); + public static final File PUBLIC_CERTIFICATE = new File("./certificates/public.cer"); + private static SSLContext sslCtx = null; + public static volatile boolean certificatesLoaded = false; + public static void generateCertificates() { + + if (PRIVATE_KEY.exists() && PUBLIC_CERTIFICATE.exists() && PRIVATE_PKCS8.exists()) { + return; + } + String[][] cmds = { - {"openssl", "genrsa", "-out", "./certificates/private.key", "2048"}, - {"openssl", "req", "-new", "-x509", "-subj", "/C=US/ST=Nope/L=Haha/O=IncInc/CN=www.example.com", "-key", "./certificates/private.key", "-out", "./certificates/public.cer", "-days", "365"} + {"openssl", "genrsa", "-out", PRIVATE_KEY.getAbsolutePath(), "2048"}, + {"openssl", "req", "-new", "-x509", "-config", "./openssl.cnf", "-key", PRIVATE_KEY.getAbsolutePath(), "-out", PUBLIC_CERTIFICATE.getAbsolutePath(), "-days", "365"}, + {"openssl", "pkcs8", "-topk8", "-inform", "PEM", "-outform", "DER", "-in", PRIVATE_KEY.getAbsolutePath(), "-out", PRIVATE_PKCS8.getAbsolutePath(), "-nocrypt"} }; new File("./certificates").mkdirs(); for (String[] cmd : cmds) { Process ps; try { ps = Runtime.getRuntime().exec(cmd); - int exitCode = ps.waitFor(); // Wait for the process to finish + int exitCode = ps.waitFor(); if (exitCode != 0) { System.err.println("Error: Process exited with code " + exitCode); return; } - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); return; - } catch (InterruptedException e) { - e.printStackTrace(); - Thread.currentThread().interrupt(); - return; } } } public static void loadCertificates() { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); - InputStream publicKeyStream = new FileInputStream(PUBLIC_KEY); - X509Certificate publicKey = (X509Certificate) certFactory.generateCertificate(publicKeyStream); + // Load the certificate + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + try (InputStream certStream = new FileInputStream(PUBLIC_CERTIFICATE)) { + X509Certificate cert = (X509Certificate) certFactory.generateCertificate(certStream); + keyStore.setCertificateEntry("certificate", cert); + } - InputStream privateKeyStream = new FileInputStream(PRIVATE_KEY); - X509Certificate privateKey = (X509Certificate) certFactory.generateCertificate(privateKeyStream); + // Load the private key + try (FileInputStream fis = new FileInputStream(PRIVATE_PKCS8)) { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] keyBytes = fis.readAllBytes(); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + PrivateKey privateKey = keyFactory.generatePrivate(spec); + keyStore.setKeyEntry("privateKey", privateKey, new char[0], new Certificate[]{keyStore.getCertificate("certificate")}); + } - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null); - keyStore.setCertificateEntry("privateKey", privateKey); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, new char[0]); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(keyStore); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keyStore); - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); + sslCtx = SSLContext.getInstance("TLS"); + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + certificatesLoaded = true; + } catch (final Exception e) { + e.printStackTrace(); + } + } - - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(publicKey, "".toCharArray()); + public static SSLContext getSSLContext() { + return certificatesLoaded ? sslCtx : null; } } diff --git a/src/main/java/tech/nevets/sfss/net/HTTPServer.java b/src/main/java/tech/nevets/sfss/net/HTTPServer.java new file mode 100644 index 0000000..e19286d --- /dev/null +++ b/src/main/java/tech/nevets/sfss/net/HTTPServer.java @@ -0,0 +1,35 @@ +package tech.nevets.sfss.net; + +import fi.iki.elonen.NanoHTTPD; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class HTTPServer extends NanoHTTPD { + public HTTPServer(int port) throws IOException { + super(port); + + start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); + } + + @Override + public Response serve(IHTTPSession session) { + String uri = session.getUri(); + switch (uri) { + case "/public.cer" -> { + Response res; + try { + res = newChunkedResponse(Response.Status.OK, "application/x-x509-ca-cert", new FileInputStream("./certificates/public.cer")); + } catch (FileNotFoundException e) { + res = newFixedLengthResponse("Error"); + e.printStackTrace(); + } + return res; + } + default -> { + return newFixedLengthResponse(Response.Status.NOT_FOUND, "", "Endpoint not found"); + } + } + } +} diff --git a/src/main/java/tech/nevets/sfss/net/SFSData.java b/src/main/java/tech/nevets/sfss/net/SFSData.java new file mode 100644 index 0000000..24018d9 --- /dev/null +++ b/src/main/java/tech/nevets/sfss/net/SFSData.java @@ -0,0 +1,27 @@ +package tech.nevets.sfss.net; + +public class SFSData { + private final byte length; + + public SFSData(byte[] data) { + this.length = data[0]; + } + + public SFSData(int length) { + if (length > 255 || length < 0) { + throw new IllegalArgumentException("Length outside range 0-255"); + } + this.length = (byte) length; + } + + public byte getLength() { + return length; + } + + public byte[] toBytes() { + return new byte[]{ + length + }; + } + +} diff --git a/src/main/java/tech/nevets/sfss/net/SFSServer.java b/src/main/java/tech/nevets/sfss/net/SFSServer.java new file mode 100644 index 0000000..98b4707 --- /dev/null +++ b/src/main/java/tech/nevets/sfss/net/SFSServer.java @@ -0,0 +1,25 @@ +package tech.nevets.sfss.net; + +import tech.nevets.sfss.StoreManager; + +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import java.io.IOException; + +public class SFSServer { + private final SSLServerSocket serverSocket; + + public SFSServer(int port) throws IOException { + SSLServerSocketFactory factory = StoreManager.getSSLContext().getServerSocketFactory(); + serverSocket = (SSLServerSocket) factory.createServerSocket(port); + serverSocket.setEnabledCipherSuites(new String[]{ + "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256" + }); + serverSocket.setEnabledProtocols(new String[]{"TLSv1.2","TLSv1.3"}); + serverSocket.setNeedClientAuth(false); + serverSocket.setWantClientAuth(false); + + SSLSocket s = (SSLSocket) serverSocket.accept(); + } +} diff --git a/src/main/java/tech/nevets/sfss/net/SFTPServer.java b/src/main/java/tech/nevets/sfss/net/SFTPServer.java new file mode 100644 index 0000000..20c50e1 --- /dev/null +++ b/src/main/java/tech/nevets/sfss/net/SFTPServer.java @@ -0,0 +1,10 @@ +package tech.nevets.sfss.net; + +import java.io.IOException; + +public class SFTPServer { + + public SFTPServer(int port) throws IOException { + + } +} diff --git a/src/main/java/tech/nevets/sfss/util/UByte.java b/src/main/java/tech/nevets/sfss/util/UByte.java new file mode 100644 index 0000000..63053f1 --- /dev/null +++ b/src/main/java/tech/nevets/sfss/util/UByte.java @@ -0,0 +1,217 @@ +package tech.nevets.sfss.util; + +import java.util.HashMap; +import java.util.Map; + +public class UByte extends Number { + private final int VALUE; + private final Map BINARY_VALUE_MAP = new HashMap<>(){{ + put(0, 128); + put(1, 64); + put(2, 32); + put(3, 16); + put(4, 8); + put(5, 4); + put(6, 2); + put(7, 1); + }}; + private final Map DEC_HEX_MAP = new HashMap<>(){{ + put(0, '0'); + put(1, '1'); + put(2, '2'); + put(3, '3'); + put(4, '4'); + put(5, '5'); + put(6, '6'); + put(7, '7'); + put(8, '8'); + put(9, '9'); + put(10, 'A'); + put(11, 'B'); + put(12, 'C'); + put(13, 'D'); + put(14, 'E'); + put(15, 'F'); + }}; + + /** + * + * @param value int value from 0-255, or 8 digit binary value. Make sure the binary values do not have a leading 0 + * @param intBinFlag Set to true if passing an integer value, set to false if passing a binary value + */ + public UByte(int value, boolean intBinFlag) { + if (intBinFlag) { // Integer + if (value <= 255 && value >= 0) { + VALUE = value; + } else { + VALUE = 0; + throw new IllegalArgumentException("Number outside of range 0-255"); + } + } else { // Binary + char[] binArray = String.valueOf(value).toCharArray(); + if (binArray.length <= 8 && binArray.length > 0) { + int intValue = 0; + int digit = 8 - binArray.length; + for (char c : binArray) { + if (c == '1') { + intValue += BINARY_VALUE_MAP.get(digit); + } + digit++; + } + VALUE = intValue; + } else { + VALUE = 0; + throw new NumberFormatException("Invalid Binary Value"); + } + } + } + + public UByte(String hex) { + if (hex.length() <= 2 && hex.length() > 0) { + char[] hexChars = hex.toCharArray(); + int intValue = 0; + for (int i = hexChars.length - 1; i >= 0; i--) { + switch (hexChars[i]) { + case '0' -> intValue += 0; + case '1' -> intValue += Math.abs(16*(i-1)); + case '2' -> intValue += 2 * Math.abs(16*(i-1)); + case '3' -> intValue += 3 * Math.abs(16*(i-1)); + case '4' -> intValue += 4 * Math.abs(16*(i-1)); + case '5' -> intValue += 5 * Math.abs(16*(i-1)); + case '6' -> intValue += 6 * Math.abs(16*(i-1)); + case '7' -> intValue += 7 * Math.abs(16*(i-1)); + case '8' -> intValue += 8 * Math.abs(16*(i-1)); + case '9' -> intValue += 9 * Math.abs(16*(i-1)); + case 'A', 'a' -> intValue += 10 * Math.abs(16*(i-1)); + case 'B', 'b' -> intValue += 11 * Math.abs(16*(i-1)); + case 'C', 'c' -> intValue += 12 * Math.abs(16*(i-1)); + case 'D', 'd' -> intValue += 13 * Math.abs(16*(i-1)); + case 'E', 'e' -> intValue += 14 * Math.abs(16*(i-1)); + case 'F', 'f' -> intValue += 15 * Math.abs(16*(i-1)); + default -> throw new NumberFormatException("Invalid Hex Character at index " + i); + } + } + VALUE = intValue; + } else { + VALUE = 0; + throw new NumberFormatException("Invalid Hex Byte"); + } + } + + public UByte(byte byteValue) { + if ((int) byteValue < 0) { + VALUE = 256 + (int) byteValue; + } else { + VALUE = byteValue; + } + } + + public int toBinary() { + int remainingValue = VALUE; + StringBuilder sb = new StringBuilder(); + for (int i = 128; i > 0; i = i / 2) { + if (remainingValue >= i) { + sb.append("1"); + remainingValue -= i; + } else { + sb.append("0"); + } + } + return Integer.parseInt(sb.toString()); + } + + public String toHex() { + int value = VALUE; + int remainder = value; + StringBuilder sb = new StringBuilder(); + while (remainder >= 0) { + if (remainder < 16) { + sb.append(DEC_HEX_MAP.get(remainder)); + break; + } else { + int temp_remainder = remainder % 16; + int quotient = (remainder - temp_remainder) / 16; + + sb.append(DEC_HEX_MAP.get(temp_remainder)); + remainder = quotient; + } + } + if (sb.length() < 2) { + sb.append("0"); + } + return sb.reverse().toString(); + + } + + public int toInt() { + return VALUE; + } + + public byte toByte() { + return (byte) VALUE; + } + + public static UByte fromBinary(int binary) { + return new UByte(binary, false); + } + + public static UByte fromHex(String hex) { + return new UByte(hex); + } + + public static UByte fromInt(int integer) { + return new UByte(integer, true); + } + + public static UByte fromByte(byte byteValue) { + return new UByte(byteValue); + } + + public static UByte fromFlags(boolean flagOne, boolean flagTwo, boolean flagThree, boolean flagFour, + boolean flagFive, boolean flagSix, boolean flagSeven, boolean flagEight) { + StringBuilder sb = new StringBuilder(); + sb.append(flagEight ? "1" : "0"); + sb.append(flagSeven ? "1" : "0"); + sb.append(flagSix ? "1" : "0"); + sb.append(flagFive ? "1" : "0"); + sb.append(flagFour ? "1" : "0"); + sb.append(flagThree ? "1" : "0"); + sb.append(flagTwo ? "1" : "0"); + sb.append(flagOne ? "1" : "0"); + + return new UByte(Integer.parseInt(sb.toString()), false); + } + + public String toString(byte b) { + return Integer.toString(b); + } + + public String toString() { + return String.valueOf(VALUE); + } + + @Override + public byte byteValue(){ + return (byte) VALUE; + } + + @Override + public int intValue() { + return VALUE; + } + + @Override + public long longValue() { + return VALUE; + } + + @Override + public float floatValue() { + return VALUE; + } + + @Override + public double doubleValue() { + return VALUE; + } +}