diff --git a/README.MD b/README.MD index 2a4f7b5..fd1fa3f 100644 --- a/README.MD +++ b/README.MD @@ -9,4 +9,61 @@ Open Autonomous Public License (OAPL)
A special exception applies exclusively to the project Open Autonomous Connection (OAC).
Within OAC, the UnlegitLibrary is also licensed under the OAPL.
In this context, OAPL terms take precedence.
-→ https://github.com/Open-Autonomous-Connection/OAPL \ No newline at end of file +→ https://github.com/Open-Autonomous-Connection/OAPL + +## Include in own projects +```` + + + github + https://maven.pkg.github.com/unlegitdqrk/unlegitlibrary + + true + + + + + + me.finn.unlegitlibrary + unlegitlibrary + 1.5.15 + + +```` + +## Certificate generation for NetworkSystem +### Creating Root-CA: +```` +openssl genrsa -out myCA.key 4096 +openssl req -x509 -new -nodes -key myCA.key -sha256 -days 3650 -out myCA.pem + +myCA.key = private Key for CA (keep secret) +myCA.pem = public Root-Certificate for signing server and client certificates +```` +### Creating Server Certificate based on Root-CA: +```` +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -out server.csr +openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 825 -sha256 + +server.key = private Key for Server +server.crt = Server-Certificate signed by Root-CA +```` +### Optional: Creating Client Certificate based on Root-CA: +```` +openssl genrsa -out client.key 2048 +openssl req -new -key client.key -out client.csr +openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 825 -sha256 + +client.key = private Key for Client +client.crt = Client-Certificate signed by Root-CA +```` + +1. Generate a Root-CA. Every client and server NEED this Root-CA *.pem-File. Keep the *.key file private
+2. Generate a Server-Certificate +3. Optional: Generate a Client-Certificate +4. Put the Root-CA on your server and client in "certificates/ca"-Folder +5. Put the Server-Certificate-Key in "certificates/key"-Folder +6. Put the Server-Certificate in "certificates/server"-Folder +7. Optional: Put the Client-Certificate-Key in "certificates/key"-Folder +8. Optional: Put the Client-Certificate in "certificates/client"-Folder \ No newline at end of file diff --git a/pom.xml b/pom.xml index d3918e2..a3969a8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.finn.unlegitlibrary unlegitlibrary - 1.5.15 + 1.6.0 https://unlegitdqrk.dev/UnlegitLibrary/ Just a big library @@ -66,8 +66,8 @@ maven-compiler-plugin 3.8.1 - 1.8 - 1.8 + 16 + 16 diff --git a/src/main/java/me/finn/unlegitlibrary/network/system/client/NetworkClient.java b/src/main/java/me/finn/unlegitlibrary/network/system/client/NetworkClient.java index a0f96e2..a9b2399 100644 --- a/src/main/java/me/finn/unlegitlibrary/network/system/client/NetworkClient.java +++ b/src/main/java/me/finn/unlegitlibrary/network/system/client/NetworkClient.java @@ -1,11 +1,3 @@ -/* - * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved - * - * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk - * See LICENSE-File if exists - */ - package me.finn.unlegitlibrary.network.system.client; import me.finn.unlegitlibrary.event.EventManager; @@ -13,15 +5,16 @@ import me.finn.unlegitlibrary.network.system.client.events.*; import me.finn.unlegitlibrary.network.system.packets.Packet; import me.finn.unlegitlibrary.network.system.packets.PacketHandler; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; +import me.finn.unlegitlibrary.network.utils.PemUtils; import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider; import me.finn.unlegitlibrary.utils.Logger; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import javax.net.ssl.*; +import java.io.*; import java.net.ConnectException; -import java.net.Socket; -import java.net.SocketException; +import java.net.Proxy; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; public final class NetworkClient { private final String host; @@ -29,50 +22,33 @@ public final class NetworkClient { private final PacketHandler packetHandler; private final EventManager eventManager; private final Logger logger; - private final int maxReconnectAttempts; - private final int reconnectDelay; - private Socket socket; private final int timeout; + private SSLSocket socket; private ObjectOutputStream outputStream; private ObjectInputStream inputStream; - private int clientID; - private int currentAttempts; - private NetworkClient(String host, int port, PacketHandler packetHandler, EventManager eventManager, Logger logger, int reconnectAttempts, int reconnectDelay, int timeout) { - this.host = host; - this.port = port; - this.clientID = -1; - this.timeout = timeout; + private final SSLSocketFactory sslSocketFactory; + private final SSLParameters sslParameters; - this.packetHandler = packetHandler; - this.eventManager = eventManager; - this.logger = logger; + private final Thread receiveThread = new Thread(this::receive); - this.maxReconnectAttempts = reconnectAttempts; - this.reconnectDelay = reconnectDelay; - this.currentAttempts = 0; - - this.packetHandler.setClientInstance(this); - this.packetHandler.registerPacket(new ClientIDPacket()); - } - - public int getClientID() { - return clientID; - } private final Thread receiveThread = new Thread(this::receive); - - public void setClientID(int clientID) { - if (this.clientID == -1) this.clientID = clientID; - } - - public Socket getSocket() { - return socket; + public PacketHandler getPacketHandler() { + return packetHandler; } public EventManager getEventManager() { return eventManager; } - public PacketHandler getPacketHandler() { - return packetHandler; + public ObjectInputStream getInputStream() { + return inputStream; + } + + public SSLSocket getSocket() { + return socket; + } + + public ObjectOutputStream getOutputStream() { + return outputStream; } public Logger getLogger() { @@ -87,200 +63,188 @@ public final class NetworkClient { return host; } - public boolean isConnected() { - return socket != null && socket.isConnected() && !socket.isClosed() && socket.isBound() - && receiveThread.isAlive() && !receiveThread.isInterrupted(); + private NetworkClient(String host, int port, PacketHandler packetHandler, + EventManager eventManager, Logger logger, + int timeout, SSLSocketFactory sslSocketFactory, + SSLParameters sslParameters) { + this.host = host; + this.port = port; + this.packetHandler = packetHandler; + this.eventManager = eventManager; + this.logger = logger; + this.timeout = timeout; + this.sslSocketFactory = sslSocketFactory; + this.sslParameters = sslParameters; + + this.packetHandler.setClientInstance(this); + this.packetHandler.registerPacket(new ClientIDPacket()); } - public boolean isAutoReconnectEnabled() { - return maxReconnectAttempts != 0; + public boolean isConnected() { + return socket != null && socket.isConnected() && !socket.isClosed() + && receiveThread.isAlive() && !receiveThread.isInterrupted(); } public synchronized boolean connect() throws ConnectException { if (isConnected()) return false; - if (logger == null) System.out.println("Trying to connect to " + host + ":" + port + "..."); - else logger.info("Trying to connect to " + host + ":" + port + "..."); + if (logger != null) logger.info("Trying to connect to " + host + ":" + port + "..."); + else System.out.println("Trying to connect to " + host + ":" + port + "..."); try { - socket = new Socket(host, port); + if (sslSocketFactory == null) throw new ConnectException("SSL socket factory not set. Client certificate required!"); + + socket = (SSLSocket) sslSocketFactory.createSocket(host, port); + + if (sslParameters != null) socket.setSSLParameters(sslParameters); + else { + SSLParameters defaultParams = socket.getSSLParameters(); + defaultParams.setProtocols(new String[]{"TLSv1.3"}); + socket.setSSLParameters(defaultParams); + } + socket.setTcpNoDelay(true); socket.setSoTimeout(timeout); + try { + socket.startHandshake(); + } catch (Exception handshakeEx) { + throw new ConnectException("Handshake failed: " + handshakeEx.getMessage()); + } outputStream = new ObjectOutputStream(socket.getOutputStream()); inputStream = new ObjectInputStream(socket.getInputStream()); receiveThread.start(); - - if (currentAttempts == 0) currentAttempts++; - if (logger == null) - System.out.println("Connected to " + host + ":" + port + " (Attempts: " + currentAttempts + ")"); - else logger.info("Connected to " + host + ":" + port + " (Attempts: " + currentAttempts + ")"); - eventManager.executeEvent(new ClientConnectedEvent(this)); - - currentAttempts = 0; + if (logger != null) logger.info("Connected to " + host + ":" + port); + else System.out.println("Connected to " + host + ":" + port); return true; - } catch (IOException exception) { - if (isAutoReconnectEnabled()) { - try { - Thread.sleep(reconnectDelay); - } catch (InterruptedException sleepThreadException) { - if (logger == null) System.err.println("Reconnect exception: " + sleepThreadException.getMessage()); - else logger.exception("Reconnect exception", sleepThreadException); - } - - currentAttempts++; - if (currentAttempts < maxReconnectAttempts || maxReconnectAttempts < 0) return connect(); - } + } catch (Exception e) { + throw new ConnectException("Failed to connect: " + e.getMessage()); } - - throw new ConnectException("Failed to connect to " + host + ":" + port); } private void receive() { - if (!isConnected()) return; - - while (isConnected()) { - try { + try { + while (isConnected()) { Object received = inputStream.readObject(); - - if (received instanceof Integer) { - int packetID = (Integer) received; - if (packetHandler.isPacketIDRegistered(packetID)) { - Packet packet = packetHandler.getPacketByID(packetID); - if (packetHandler.handlePacket(packetID, packet, inputStream)) - eventManager.executeEvent(new C_PacketReceivedEvent(this, packet)); - else - eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet)); - } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); - } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); - } catch (SocketException ignored) { - try { - disconnect(); - } catch (ConnectException exception) { - exception.printStackTrace(); - } - } catch (Exception exception) { - exception.printStackTrace(); - return; + handleReceived(received); } + } catch (Exception e) { disconnect(); } + } + + private void handleReceived(Object received) throws IOException, ClassNotFoundException { + if (received instanceof Integer id) { + if (packetHandler.isPacketIDRegistered(id)) { + Packet packet = packetHandler.getPacketByID(id); + boolean handled = packetHandler.handlePacket(id, packet, inputStream); + if (handled) eventManager.executeEvent(new C_PacketReceivedEvent(this, packet)); + else eventManager.executeEvent(new C_PacketReceivedFailedEvent(this, packet)); + } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); + } else eventManager.executeEvent(new C_UnknownObjectReceivedEvent(this, received)); + } + + public synchronized boolean disconnect() { + if (!isConnected()) return false; + try { + receiveThread.interrupt(); + if (outputStream != null) outputStream.close(); + if (inputStream != null) inputStream.close(); + if (socket != null) socket.close(); + } catch (IOException e) { + if (logger != null) logger.exception("Error closing connection", e); + else System.err.println("Error closing connection: " + e.getMessage()); + } finally { + socket = null; + outputStream = null; + inputStream = null; + eventManager.executeEvent(new ClientDisconnectedEvent(this)); } + return true; } public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { if (!isConnected()) return false; - - try { - if (packetHandler.sendPacket(packet, outputStream)) { - eventManager.executeEvent(new C_PacketSendEvent(this, packet)); - return true; - } else { - eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet)); - return false; - } - } catch (IOException | ClassNotFoundException exception) { - throw exception; - } - } - - public synchronized boolean disconnect() throws ConnectException { - boolean wasConnected = isConnected(); - - if (wasConnected) { - if (logger == null) System.out.println("Disconnecting from server..."); - else logger.info("Disconnecting from server..."); - } - - if (receiveThread.isAlive() && !receiveThread.isInterrupted()) receiveThread.interrupt(); - - if (wasConnected) { - try { - outputStream.close(); - inputStream.close(); - socket.close(); - } catch (IOException exception) { - if (logger == null) System.err.println("Failed to close socket: " + exception.getMessage()); - else logger.exception("Failed to close socket", exception); - } - } - - outputStream = null; - inputStream = null; - socket = null; - - currentAttempts = 0; - - if (wasConnected) { - if (logger == null) System.out.println("Disconnected from server"); - else logger.info("Disconnected from server"); - } - - eventManager.executeEvent(new ClientDisconnectedEvent(this)); - - clientID = -1; - if (isAutoReconnectEnabled() && (currentAttempts < maxReconnectAttempts || maxReconnectAttempts < 0)) - return connect(); - - return true; + boolean sent = packetHandler.sendPacket(packet, outputStream); + if (sent) eventManager.executeEvent(new C_PacketSendEvent(this, packet)); + else eventManager.executeEvent(new C_PacketSendFailedEvent(this, packet)); + return sent; } + // --- Builder --- public static class ClientBuilder extends DefaultMethodsOverrider { private String host; private int port; - private PacketHandler packetHandler; private EventManager eventManager; private Logger logger; + private int timeout = 5000; + private SSLSocketFactory sslSocketFactory; + private SSLParameters sslParameters; + private File caFolder; + private File clientFolder; + private File keyFolder; - private int maxReconnectAttempts = 0; - private int reconnectDelay = 3000; - private int timeout = 0; + public ClientBuilder setHost(String host) { this.host = host; return this; } + public ClientBuilder setPort(int port) { this.port = port; return this; } + public ClientBuilder setPacketHandler(PacketHandler handler) { this.packetHandler = handler; return this; } + public ClientBuilder setEventManager(EventManager manager) { this.eventManager = manager; return this; } + public ClientBuilder setLogger(Logger logger) { this.logger = logger; return this; } + public ClientBuilder setTimeout(int timeout) { this.timeout = timeout; return this; } + public ClientBuilder setSSLSocketFactory(SSLSocketFactory factory) { this.sslSocketFactory = factory; return this; } + public ClientBuilder setSSLParameters(SSLParameters params) { this.sslParameters = params; return this; } + public ClientBuilder setRootCAFolder(File folder) { this.caFolder = folder; return this; } + public ClientBuilder setClientCertificatesFolder(File clientFolder, File keyFolder) { this.clientFolder = clientFolder; this.keyFolder = keyFolder; return this; } - public final NetworkClient build() { - return new NetworkClient(host, port, packetHandler, eventManager, logger, maxReconnectAttempts, reconnectDelay, timeout); + public NetworkClient build() { + if (sslSocketFactory == null && caFolder != null) { + try { sslSocketFactory = createSSLSocketFactory(caFolder, clientFolder, keyFolder); } + catch (Exception e) { throw new RuntimeException("Failed to create SSLFactory", e); } + } + + return new NetworkClient(host, port, packetHandler, eventManager, logger, + timeout, sslSocketFactory, sslParameters); } - public final ClientBuilder setEventManager(EventManager eventManager) { - this.eventManager = eventManager; - return this; - } + public static SSLSocketFactory createSSLSocketFactory(File caFolder, File clientCertFolder, File clientKeyFolder) throws Exception { + // TrustStore (Root-CAs) + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); - public final ClientBuilder setHost(String host) { - this.host = host; - return this; - } + int caIndex = 1; + for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { + try (FileInputStream fis = new FileInputStream(caFile)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + java.security.cert.Certificate caCert = cf.generateCertificate(fis); + trustStore.setCertificateEntry("ca" + (caIndex++), caCert); + } + } - public final ClientBuilder setLogger(Logger logger) { - this.logger = logger; - return this; - } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); - public final ClientBuilder setMaxReconnectAttempts(int maxReconnectAttempts) { - this.maxReconnectAttempts = maxReconnectAttempts; - return this; - } + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(null, null); // Kein Passwort nötig - public final ClientBuilder setPacketHandler(PacketHandler packetHandler) { - this.packetHandler = packetHandler; - return this; - } + int clientIndex = 1; + for (File certFile : clientCertFolder.listFiles((f) -> f.getName().endsWith(".crt"))) { + String baseName = certFile.getName().replace(".crt", ""); + File keyFile = new File(clientKeyFolder, baseName + ".key"); + if (!keyFile.exists()) continue; - public final ClientBuilder setPort(int port) { - this.port = port; - return this; - } + java.security.PrivateKey key = PemUtils.loadPrivateKey(keyFile); + java.security.cert.Certificate cert = PemUtils.loadCertificate(certFile); - public final ClientBuilder setReconnectDelay(int reconnectDelay) { - this.reconnectDelay = reconnectDelay; - return this; - } + keyStore.setKeyEntry("client" + (clientIndex++), key, null, new java.security.cert.Certificate[]{cert}); + } - public final ClientBuilder setTimeout(int timeout) { - this.timeout = timeout; - return this; + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); + + // SSLContext + SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext.getSocketFactory(); } } - - } diff --git a/src/main/java/me/finn/unlegitlibrary/network/system/server/ConnectionHandler.java b/src/main/java/me/finn/unlegitlibrary/network/system/server/ConnectionHandler.java index 12bafdb..4387cc8 100644 --- a/src/main/java/me/finn/unlegitlibrary/network/system/server/ConnectionHandler.java +++ b/src/main/java/me/finn/unlegitlibrary/network/system/server/ConnectionHandler.java @@ -1,35 +1,42 @@ -/* - * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved - * - * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk - * See LICENSE-File if exists - */ - package me.finn.unlegitlibrary.network.system.server; import me.finn.unlegitlibrary.network.system.packets.Packet; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; import me.finn.unlegitlibrary.network.system.server.events.*; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.net.Socket; import java.net.SocketException; +import java.security.cert.X509Certificate; public class ConnectionHandler { - - public final NetworkServer networkServer; - private Socket socket; + private SSLSocket socket; private int clientID; - private ObjectOutputStream outputStream; private ObjectInputStream inputStream; - public ConnectionHandler(NetworkServer server, Socket socket, int clientID) throws IOException, ClassNotFoundException { - this.networkServer = server; - this.socket = socket; - this.clientID = clientID; + private final NetworkServer server; + + public SSLSocket getSocket() { + return socket; + } + + public ObjectOutputStream getOutputStream() { + return outputStream; + } + + public ObjectInputStream getInputStream() { + return inputStream; + } + + public NetworkServer getServer() { + return server; + } + + public ConnectionHandler(NetworkServer server, SSLSocket socket, int clientID) throws IOException, ClassNotFoundException { + this.server = server; this.socket = socket; this.clientID = clientID; outputStream = new ObjectOutputStream(socket.getOutputStream()); inputStream = new ObjectInputStream(socket.getInputStream()); @@ -37,97 +44,57 @@ public class ConnectionHandler { receiveThread.start(); sendPacket(new ClientIDPacket()); - networkServer.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this)); - } public final Thread receiveThread = new Thread(this::receive); - - public int getClientID() { - return clientID; + server.getEventManager().executeEvent(new ConnectionHandlerConnectedEvent(this)); } - public final boolean isConnected() { - return networkServer.isRunning() && socket != null && socket.isConnected() && !socket.isClosed() && socket.isBound() - && receiveThread.isAlive() && !receiveThread.isInterrupted(); - } + public final Thread receiveThread = new Thread(this::receive); + + public int getClientID() { return clientID; } + public boolean isConnected() { return socket != null && socket.isConnected() && !socket.isClosed() && receiveThread.isAlive(); } public synchronized boolean disconnect() { - boolean wasConnected = isConnected(); + if (!isConnected()) return false; + if (receiveThread.isAlive()) receiveThread.interrupt(); - if (wasConnected) { - if (networkServer.getLogger() == null) - System.out.println("Client ID '" + clientID + "' is disconnecting from server..."); - else networkServer.getLogger().info("Client ID '" + clientID + "' is disconnecting from server..."); - } + try { outputStream.close(); inputStream.close(); socket.close(); } catch (IOException ignored) {} + socket = null; outputStream = null; inputStream = null; clientID = -1; - if (receiveThread.isAlive() && !receiveThread.isInterrupted()) receiveThread.interrupt(); + server.getConnectionHandlers().remove(this); + server.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this)); - if (wasConnected) { - try { - outputStream.close(); - inputStream.close(); - socket.close(); - } catch (IOException exception) { - if (networkServer.getLogger() == null) - System.err.println("Client ID '" + clientID + "' failed to close socket: " + exception.getMessage()); - else - networkServer.getLogger().exception("Client ID '" + clientID + "' failed to close socket", exception); - } - } - - outputStream = null; - inputStream = null; - socket = null; - - networkServer.getConnectionHandlers().remove(this); - - if (wasConnected) { - if (networkServer.getLogger() == null) - System.out.println("Client ID '" + clientID + "' disconnected from server"); - else networkServer.getLogger().info("Client ID '" + clientID + "' disconnected from server"); - } - - networkServer.getEventManager().executeEvent(new ConnectionHandlerDisconnectedEvent(this)); - clientID = -1; return true; } public boolean sendPacket(Packet packet) throws IOException, ClassNotFoundException { if (!isConnected()) return false; + boolean sent = server.getPacketHandler().sendPacket(packet, outputStream); - if (networkServer.getPacketHandler().sendPacket(packet, outputStream)) { - networkServer.getEventManager().executeEvent(new S_PacketSendEvent(packet, this)); - return true; - } else { - networkServer.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this)); - return false; - } + if (sent) server.getEventManager().executeEvent(new S_PacketSendEvent(packet, this)); + else server.getEventManager().executeEvent(new S_PacketSendFailedEvent(packet, this)); + + return sent; } private void receive() { - if (!isConnected()) return; - while (isConnected()) { try { Object received = inputStream.readObject(); - if (received instanceof Integer) { - int packetID = (Integer) received; - if (networkServer.getPacketHandler().isPacketIDRegistered(packetID)) { - Packet packet = networkServer.getPacketHandler().getPacketByID(packetID); - if (networkServer.getPacketHandler().handlePacket(packetID, packet, inputStream)) - networkServer.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet)); - else - networkServer.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet)); - } else - networkServer.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); - } else networkServer.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); - } catch (SocketException ignored) { + int id = (Integer) received; + + if (server.getPacketHandler().isPacketIDRegistered(id)) { + Packet packet = server.getPacketHandler().getPacketByID(id); + + if (server.getPacketHandler().handlePacket(id, packet, inputStream)) server.getEventManager().executeEvent(new S_PacketReceivedEvent(this, packet)); + else server.getEventManager().executeEvent(new S_PacketReceivedFailedEvent(this, packet)); + } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); + } else server.getEventManager().executeEvent(new S_UnknownObjectReceivedEvent(received, this)); + } catch (SocketException se) { disconnect(); } + catch (Exception ex) { + if (server.getLogger() != null) server.getLogger().exception("Receive thread exception for client " + clientID, ex); + else System.err.println("Receive thread exception for client " + clientID + ": " + ex.getMessage()); disconnect(); - } catch (Exception exception) { - exception.printStackTrace(); - return; } } } - - -} +} \ No newline at end of file diff --git a/src/main/java/me/finn/unlegitlibrary/network/system/server/NetworkServer.java b/src/main/java/me/finn/unlegitlibrary/network/system/server/NetworkServer.java index 86f7348..39cfbd6 100644 --- a/src/main/java/me/finn/unlegitlibrary/network/system/server/NetworkServer.java +++ b/src/main/java/me/finn/unlegitlibrary/network/system/server/NetworkServer.java @@ -1,27 +1,21 @@ -/* - * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved - * - * You are unauthorized to remove this copyright. - * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk - * See LICENSE-File if exists - */ - package me.finn.unlegitlibrary.network.system.server; import me.finn.unlegitlibrary.event.EventManager; -import me.finn.unlegitlibrary.network.system.packets.Packet; import me.finn.unlegitlibrary.network.system.packets.PacketHandler; import me.finn.unlegitlibrary.network.system.packets.impl.ClientIDPacket; import me.finn.unlegitlibrary.network.system.server.events.IncomingConnectionEvent; +import me.finn.unlegitlibrary.network.utils.PemUtils; import me.finn.unlegitlibrary.utils.DefaultMethodsOverrider; import me.finn.unlegitlibrary.utils.Logger; -import java.io.IOException; -import java.net.ServerSocket; +import javax.net.ssl.*; +import java.io.File; +import java.io.FileInputStream; import java.net.Socket; +import java.security.KeyStore; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; public final class NetworkServer { private final int port; @@ -29,33 +23,14 @@ public final class NetworkServer { private final EventManager eventManager; private final Logger logger; private final int timeout; - private final int maxRestartAttempts; - private final int restartDelay; + private SSLServerSocket serverSocket; + private final SSLServerSocketFactory sslServerSocketFactory; + private final List connectionHandlers = new ArrayList<>(); - private int currentAttempts; - private ServerSocket serverSocket; - private NetworkServer(int port, PacketHandler packetHandler, EventManager eventManager, Logger logger, int maxRestartAttempts, int restartDelay, int timeout) { - this.port = port; - this.timeout = timeout; + private final Thread incomingThread = new Thread(this::incomingConnections); - this.packetHandler = packetHandler; - this.eventManager = eventManager; - this.logger = logger; - - this.maxRestartAttempts = maxRestartAttempts; - this.restartDelay = restartDelay; - this.currentAttempts = 0; - - this.packetHandler.setServerInstance(this); - this.packetHandler.registerPacket(new ClientIDPacket()); - } public final Thread incomingConnectionThread = new Thread(this::incomingConnection); - - public Logger getLogger() { - return logger; - } - - public EventManager getEventManager() { - return eventManager; + public List getConnectionHandlers() { + return connectionHandlers; } public int getPort() { @@ -66,164 +41,133 @@ public final class NetworkServer { return packetHandler; } - public List getConnectionHandlers() { - return connectionHandlers; + public Logger getLogger() { + return logger; } - public boolean isAutoRestartEnabled() { - return maxRestartAttempts != 0; + public SSLServerSocket getServerSocket() { + return serverSocket; } - public boolean isRunning() { - return serverSocket != null && !serverSocket.isClosed() && serverSocket.isBound() && - incomingConnectionThread.isAlive() && !incomingConnectionThread.isInterrupted(); + public EventManager getEventManager() { + return eventManager; } - public ConnectionHandler getConnectionHandlerByID(int clientID) { - for (ConnectionHandler connectionHandler : connectionHandlers) - if (connectionHandler.getClientID() == clientID) return connectionHandler; - return null; + private boolean requireClientCert; + + private NetworkServer(int port, PacketHandler packetHandler, EventManager eventManager, + Logger logger, int timeout, SSLServerSocketFactory factory, boolean requireClientCert) { + this.port = port; this.packetHandler = packetHandler; this.eventManager = eventManager; + this.logger = logger; this.timeout = timeout; this.sslServerSocketFactory = factory; + + this.packetHandler.setServerInstance(this); + this.packetHandler.registerPacket(new ClientIDPacket()); + this.requireClientCert = requireClientCert; } - public synchronized boolean start() { - if (isRunning()) return false; - - if (logger == null) System.out.println("Trying to start on port " + port + "..."); - else logger.info("Trying to start on port " + port + "..."); - + public boolean start() { try { - serverSocket = new ServerSocket(port); + serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); + serverSocket.setNeedClientAuth(requireClientCert); serverSocket.setSoTimeout(timeout); - - incomingConnectionThread.start(); - - if (currentAttempts == 0) currentAttempts++; - if (logger == null) System.out.println("Started at port " + port + " (Attempts: " + currentAttempts + ")"); - else logger.info("Started at port " + port + " (Attempts: " + currentAttempts + ")"); - - currentAttempts = 0; + serverSocket.setEnabledProtocols(new String[]{"TLSv1.3"}); + incomingThread.start(); + if (logger != null) logger.log("Server started on port " + port); + else System.out.println("Server started on port " + port); return true; - } catch (IOException exception) { - if (maxRestartAttempts != 0) { - try { - Thread.sleep(restartDelay); - } catch (InterruptedException sleepThreadException) { - if (logger == null) System.err.println("Restart exception: " + sleepThreadException.getMessage()); - else logger.exception("Restart exception", sleepThreadException); - } - - currentAttempts++; - if (currentAttempts <= maxRestartAttempts || maxRestartAttempts < 0) return start(); - } - - if (logger == null) System.err.println("Failed to start on port " + port + ": " + exception.getMessage()); - else logger.exception("Failed to start on port " + port, exception); - } - - return false; + } catch (Exception e) {if (logger != null) logger.exception("Failed to start", e); + else System.err.println("Failed to start: " + e.getMessage()); return false; } } - public boolean broadcastPacket(Packet packet) { - AtomicBoolean toReturn = new AtomicBoolean(false); - connectionHandlers.forEach(connectionHandler -> { - try { - if (!toReturn.get()) return; - toReturn.set(connectionHandler.sendPacket(packet)); - } catch (IOException | ClassNotFoundException e) { - toReturn.set(false); - } - }); - - return toReturn.get(); - } - - private void incomingConnection() { - if (!isRunning()) return; - + private void incomingConnections() { try { - while (isRunning()) { + while (!serverSocket.isClosed()) { Socket socket = serverSocket.accept(); - socket.setTcpNoDelay(true); - socket.setSoTimeout(timeout); - - if (logger == null) System.out.println("Accepted connection from " + socket.getRemoteSocketAddress()); - else logger.info("Accepted connection from " + socket.getRemoteSocketAddress()); - - IncomingConnectionEvent incomingConnectionEvent = new IncomingConnectionEvent(this, socket); - - eventManager.executeEvent(incomingConnectionEvent); - - if (incomingConnectionEvent.isCancelled()) { - socket.close(); - return; + if (!(socket instanceof SSLSocket ssl)) { socket.close(); continue; } + ssl.setTcpNoDelay(true); + ssl.setSoTimeout(timeout); + try { ssl.startHandshake(); } + catch (Exception handshakeEx) { + if (logger != null) logger.exception("Handshake failed", handshakeEx); + else System.err.println("Handshake failed: " + handshakeEx.getMessage()); + ssl.close(); + continue; } - ConnectionHandler connectionHandler = new ConnectionHandler(this, socket, connectionHandlers.size() + 1); - connectionHandlers.add(connectionHandler); + IncomingConnectionEvent event = new IncomingConnectionEvent(this, ssl); + eventManager.executeEvent(event); + if (event.isCancelled()) { ssl.close(); continue; } + + try { + ConnectionHandler connectionHandler = new ConnectionHandler(this, ssl, connectionHandlers.size() + 1); + connectionHandlers.add(connectionHandler); + } catch (Exception exception) { + ssl.close(); + continue; + } } - } catch (IOException | ClassNotFoundException exception) { - exception.printStackTrace(); - } - } - - public boolean sendPacket(int clientID, Packet packet) throws IOException, ClassNotFoundException { - return getConnectionHandlerByID(clientID).sendPacket(packet); - } - - public boolean sendPacket(Packet packet, int clientID) throws IOException, ClassNotFoundException { - return sendPacket(clientID, packet); + } catch (Exception e) { e.printStackTrace(); } } + // --- Builder --- public static class ServerBuilder extends DefaultMethodsOverrider { private int port; - private PacketHandler packetHandler; private EventManager eventManager; private Logger logger; + private int timeout = 5000; + private SSLServerSocketFactory factory; + private boolean requireClientCert; + private File caFolder; + private File serverCertFile; + private File serverKeyFile; - private int maxRestartAttempts = 0; - private int restartDelay = 3000; - private int timeout = 0; + public ServerBuilder setPort(int port) { this.port = port; return this; } + public ServerBuilder setPacketHandler(PacketHandler handler) { this.packetHandler = handler; return this; } + public ServerBuilder setEventManager(EventManager manager) { this.eventManager = manager; return this; } + public ServerBuilder setLogger(Logger logger) { this.logger = logger; return this; } + public ServerBuilder setTimeout(int timeout) { this.timeout = timeout; return this; } + public ServerBuilder setSSLServerSocketFactory(SSLServerSocketFactory factory) { this.factory = factory; return this; } + public ServerBuilder setRequireClientCertificate(boolean requireClientCertificate) { this.requireClientCert = requireClientCertificate; return this; } + public ServerBuilder setRootCAFolder(File folder) { this.caFolder = folder; return this; } + public ServerBuilder setServerCertificate(File certFile, File keyFile) { this.serverCertFile = certFile; this.serverKeyFile = keyFile; return this; } - public final NetworkServer build() { - return new NetworkServer(port, packetHandler, eventManager, logger, maxRestartAttempts, restartDelay, timeout); + public NetworkServer build() { + if (factory == null && caFolder != null && serverCertFile != null && serverKeyFile != null) { + try { factory = createSSLServerSocketFactory(caFolder, serverCertFile, serverKeyFile); } + catch (Exception e) { throw new RuntimeException("Failed to create SSLServerSocketFactory", e); } + } + return new NetworkServer(port, packetHandler, eventManager, logger, timeout, factory, requireClientCert); } - public final ServerBuilder setEventManager(EventManager eventManager) { - this.eventManager = eventManager; - return this; - } + public static SSLServerSocketFactory createSSLServerSocketFactory(File caFolder, File serverCert, File serverKey) throws Exception { + // TrustStore (Root-CAs) + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); - public final ServerBuilder setLogger(Logger logger) { - this.logger = logger; - return this; - } + int caIndex = 1; + for (File caFile : caFolder.listFiles((f) -> f.getName().endsWith(".pem"))) { + try (FileInputStream fis = new FileInputStream(caFile)) { + java.security.cert.Certificate cert = PemUtils.loadCertificate(caFile); + trustStore.setCertificateEntry("ca" + (caIndex++), cert); + } + } - public final ServerBuilder setMaxReconnectAttempts(int maxRestartAttempts) { - this.maxRestartAttempts = maxRestartAttempts; - return this; - } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); - public final ServerBuilder setPacketHandler(PacketHandler packetHandler) { - this.packetHandler = packetHandler; - return this; - } + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(null, null); + java.security.PrivateKey key = PemUtils.loadPrivateKey(serverKey); + java.security.cert.Certificate cert = PemUtils.loadCertificate(serverCert); + keyStore.setKeyEntry("server", key, null, new java.security.cert.Certificate[]{cert}); - public final ServerBuilder setPort(int port) { - this.port = port; - return this; - } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); - public final ServerBuilder setReconnectDelay(int reconnectDelay) { - this.restartDelay = reconnectDelay; - return this; - } - - public final ServerBuilder setTimeout(int timeout) { - this.timeout = timeout; - return this; + SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext.getServerSocketFactory(); } } - - } diff --git a/src/main/java/me/finn/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java b/src/main/java/me/finn/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java index 42fa801..685c669 100644 --- a/src/main/java/me/finn/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java +++ b/src/main/java/me/finn/unlegitlibrary/network/system/server/events/IncomingConnectionEvent.java @@ -11,13 +11,16 @@ package me.finn.unlegitlibrary.network.system.server.events; import me.finn.unlegitlibrary.event.impl.CancellableEvent; import me.finn.unlegitlibrary.network.system.server.NetworkServer; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import java.net.Socket; +import java.security.cert.X509Certificate; public class IncomingConnectionEvent extends CancellableEvent { public final NetworkServer server; - public final Socket socket; + public final SSLSocket socket; - public IncomingConnectionEvent(NetworkServer server, Socket socket) { + public IncomingConnectionEvent(NetworkServer server, SSLSocket socket) { this.server = server; this.socket = socket; } diff --git a/src/main/java/me/finn/unlegitlibrary/network/utils/PemUtils.java b/src/main/java/me/finn/unlegitlibrary/network/utils/PemUtils.java new file mode 100644 index 0000000..ba6342c --- /dev/null +++ b/src/main/java/me/finn/unlegitlibrary/network/utils/PemUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 UnlegitDqrk - All Rights Reserved + * + * You are unauthorized to remove this copyright. + * You have to give Credits to the Author in your project and link this GitHub site: https://github.com/UnlegitDqrk + * See LICENSE-File if exists + */ + +package me.finn.unlegitlibrary.network.utils; + +import java.io.*; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; + +public class PemUtils { + public static PrivateKey loadPrivateKey(File keyFile) throws Exception { + String keyPem = new String(Files.readAllBytes(keyFile.toPath())) + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + byte[] keyBytes = Base64.getDecoder().decode(keyPem); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); // oder "EC" je nach Key + return kf.generatePrivate(spec); + } + + public static X509Certificate loadCertificate(File certFile) throws Exception { + try (FileInputStream fis = new FileInputStream(certFile)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(fis); + } + } +}