From 902c795eccad739ebc5c0cd435a7d35c854b260f Mon Sep 17 00:00:00 2001 From: Sen Date: Thu, 29 May 2025 12:51:39 +0200 Subject: [PATCH] extend and fix pubkeys --- client/src/main/java/client/Client.java | 72 ++++++++++------ .../src/main/java/client/gui/GuiConnect.java | 33 ++++++- .../src/main/java/client/gui/GuiServer.java | 85 +++++++++++++------ .../client/network/ClientLoginHandler.java | 38 +++++++-- .../main/java/common/util/EncryptUtil.java | 20 +++++ 5 files changed, 184 insertions(+), 64 deletions(-) diff --git a/client/src/main/java/client/Client.java b/client/src/main/java/client/Client.java index fb7390c..b0dd8d9 100755 --- a/client/src/main/java/client/Client.java +++ b/client/src/main/java/client/Client.java @@ -513,34 +513,54 @@ public class Client implements IThreadListener { })).channel(NioSocketChannel.class)).connect(address, serverPort).syncUninterruptibly(); return networkmanager; } + + public void displayConnecting(ServerInfo server) { + this.displayGuiScreen(GuiLoading.makeWaitTask("Verbinde zu " + (server.getAddress() == null ? "localhost" : server.getAddress()) + ":" + server.getPort() + " ...")); + } - public void connect(ServerInfo server) { - this.displayGuiScreen(GuiLoading.makeWaitTask("Verbinde zu " + (server.getAddress() == null ? "localhost" : server.getAddress()) + ":" + server.getPort() + " ...")); + public void connect(final ServerInfo server) { + this.displayConnecting(server); Log.NETWORK.info("Verbinde zu " + (server.getAddress() == null ? "localhost" : server.getAddress()) + ":" + server.getPort()); - final NetConnection connection; - try - { - connection = createNetworkManagerAndConnect(server.getAddress() == null ? InetAddress.getLoopbackAddress() : InetAddress.getByName(IDN.toASCII(server.getAddress())), server.getPort()); - connection.setNetHandler(new ClientLoginHandler(connection, this, server)); - connection.sendPacket(new HPacketHandshake(Util.PROTOCOL), new GenericFutureListener>() { - public void operationComplete(Future u) throws Exception { - connection.setConnectionState(PacketRegistry.LOGIN); - } - }); - } - catch (UnknownHostException u) - { - Log.NETWORK.error(u, "Konnte nicht zu Server verbinden"); - this.disconnected("Unbekannter Hostname: " + (server.getAddress() == null ? "localhost" : server.getAddress())); - return; - } - catch (Exception e) - { - Log.NETWORK.error(e, "Konnte nicht zu Server verbinden"); - this.disconnected(e.toString()); - return; - } - this.connection = connection; + new Thread(new Runnable() { + public void run() { + final NetConnection connection; + try + { + connection = createNetworkManagerAndConnect(server.getAddress() == null ? InetAddress.getLoopbackAddress() : InetAddress.getByName(IDN.toASCII(server.getAddress())), server.getPort()); + connection.setNetHandler(new ClientLoginHandler(connection, Client.this, server)); + connection.sendPacket(new HPacketHandshake(Util.PROTOCOL), new GenericFutureListener>() { + public void operationComplete(Future u) throws Exception { + connection.setConnectionState(PacketRegistry.LOGIN); + } + }); + } + catch (UnknownHostException u) + { + Log.NETWORK.error(u, "Konnte nicht zu Server verbinden"); + Client.this.schedule(new Runnable() { + public void run() { + Client.this.disconnected("Unbekannter Hostname: " + (server.getAddress() == null ? "localhost" : server.getAddress())); + } + }); + return; + } + catch (Exception e) + { + Log.NETWORK.error(e, "Konnte nicht zu Server verbinden"); + Client.this.schedule(new Runnable() { + public void run() { + Client.this.disconnected(e.toString()); + } + }); + return; + } + Client.this.schedule(new Runnable() { + public void run() { + Client.this.connection = connection; + } + }); + } + }, "Server connector").start(); } public void unloadWorld() { diff --git a/client/src/main/java/client/gui/GuiConnect.java b/client/src/main/java/client/gui/GuiConnect.java index 07ad72f..da6a589 100644 --- a/client/src/main/java/client/gui/GuiConnect.java +++ b/client/src/main/java/client/gui/GuiConnect.java @@ -34,13 +34,15 @@ public class GuiConnect extends GuiList implements Button private String password; private String access; private KeyPair keypair; + private String keyDigest; private PublicKey serverKey; + private String serverDigest; private boolean enforceEncryption; private long lastConnected; public ServerInfo(String address, int port, String user, String password, String access) { this.direct = true; - this.name = ""; + this.name = DIRECT_NAME; this.address = address; this.port = port; this.user = user; @@ -61,6 +63,10 @@ public class GuiConnect extends GuiList implements Button this.lastConnected = lastConnected; this.serverKey = serverKey; this.enforceEncryption = enforceEnc; + if(this.keypair != null) + this.keyDigest = EncryptUtil.getXorSha512Hash(this.keypair.getPublic().getEncoded()); + if(this.serverKey != null) + this.serverDigest = EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()); } public boolean isDirect() { @@ -117,6 +123,8 @@ public class GuiConnect extends GuiList implements Button this.keypair = keypair; this.serverKey = serverKey; this.enforceEncryption = encryptReq; + this.keyDigest = this.keypair != null ? EncryptUtil.getXorSha512Hash(this.keypair.getPublic().getEncoded()) : null; + this.serverDigest = this.serverKey != null ? EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()) : null; } public void setLastConnected() { @@ -125,6 +133,7 @@ public class GuiConnect extends GuiList implements Button public void setServerKey(PublicKey key) { this.serverKey = key; + this.serverDigest = this.serverKey != null ? EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()) : null; } public int compareTo(ServerInfo comp) { @@ -144,12 +153,22 @@ public class GuiConnect extends GuiList implements Button public void draw(int x, int y, int mouseXIn, int mouseYIn, boolean hover) { Drawing.drawText(this.name + TextColor.GRAY + " - " + TextColor.RESET + this.user, x + 2, y, 0xffffffff); - Drawing.drawText(this.address + TextColor.GRAY + " Port " + TextColor.RESET + this.port, x + 2, y + 18, 0xff808080); + if(this.keypair != null || !this.password.isEmpty()) + Drawing.drawTextRight( + (this.keypair != null ? "Pubkey " + this.keyDigest : "") + (this.keypair != null && !this.password.isEmpty() ? " + " : "") + (!this.password.isEmpty() ? "Passwort" : ""), + x + 660 - 4 - 2, y, 0xffffffff); + Drawing.drawText(this.address + TextColor.GRAY + " Port " + TextColor.RESET + this.port + (this.enforceEncryption ? TextColor.GRAY + ", nur verschlüsselt" : ""), + x + 2, y + 18, 0xff808080); + if(!this.access.isEmpty()) + Drawing.drawTextRight((this.keypair != null || !this.password.isEmpty() ? "+ " : "") + "Server-Passwort", x + 660 - 4 - 2, y + 18, 0xff808080); Drawing.drawText("Zuletzt verbunden: " + (this.lastConnected == -1L ? "nie" : DATE_FORMAT.format(new Date(this.lastConnected))), x + 2, y + 18 + 16, 0xff808080); + if(this.serverDigest != null) + Drawing.drawTextRight("Server-ID: " + this.serverDigest, x + 660 - 4 - 2, y + 18 + 16, 0xff808080); } } public static final GuiConnect INSTANCE = new GuiConnect(); + private static final String DIRECT_NAME = ""; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); private static final File SERVERS_FILE = new File("servers.cfg"); @@ -185,7 +204,7 @@ public class GuiConnect extends GuiList implements Button if(line == null || (line.startsWith("[") && line.endsWith("]"))) { if(!name.isEmpty() && !address.isEmpty() && !user.isEmpty() && user.length() < IPlayer.MAX_USER_LENGTH && IPlayer.isValidUser(user) && password.length() < IPlayer.MAX_PASS_LENGTH && access.length() < IPlayer.MAX_PASS_LENGTH && address.length() < 128 && name.length() < 128 && - port >= 1024 && port <= 32767 && (password.length() >= 8 || password.isEmpty()) && (access.length() >= 8 || access.isEmpty())) { + port >= 1024 && port <= 32767 && (password.length() >= 8 || password.isEmpty()) && (access.length() >= 8 || access.isEmpty()) && !this.isNameTaken(name)) { PrivateKey priv = key == null ? null : EncryptUtil.decodePrivateKey(key); PublicKey pub = pubkey == null ? null : EncryptUtil.decodePublicKey(pubkey); PublicKey serv = serverKey == null ? null : EncryptUtil.decodePublicKey(serverKey); @@ -269,6 +288,14 @@ public class GuiConnect extends GuiList implements Button this.gm.displayGuiScreen(this); } + public boolean isNameTaken(String name) { + for(int z = 0; z < this.elements.size(); z++) { + if(this.selectedElement != z && this.elements.get(z).getName().equals(name)) + return true; + } + return DIRECT_NAME.equals(name); + } + public void editServer(ServerInfo server) { if(!server.isDirect()) this.save(); diff --git a/client/src/main/java/client/gui/GuiServer.java b/client/src/main/java/client/gui/GuiServer.java index 030c7ab..db29cd9 100644 --- a/client/src/main/java/client/gui/GuiServer.java +++ b/client/src/main/java/client/gui/GuiServer.java @@ -37,11 +37,15 @@ public class GuiServer extends Gui implements FieldCallback { private Label userLabel; private Label passLabel; private Label accLabel; + private Label keyLabel; + private Label idLabel; private Toggle encToggle; private ActButton keyButton; private ActButton resetButton; private KeyPair keypair; + private String keyDigest; private PublicKey serverKey; + private String serverDigest; public GuiServer(ServerInfo server) { this.server = server; @@ -59,34 +63,40 @@ public class GuiServer extends Gui implements FieldCallback { private String lastAcc = ""; public void init(int width, int height) { - if(this.server != null) + if(this.server != null) { this.nameBox = this.add(new Field(0, -50, 400, 24, 128, this, this.server.getName())); + this.keypair = this.server.getKeypair(); + this.keyDigest = this.keypair == null ? null : EncryptUtil.getXorSha512Hash(this.keypair.getPublic().getEncoded()); + } this.addrBox = this.add(new Field(0, 20, 400, 24, 128, this, this.server == null ? this.lastAddr : this.server.getAddress())); int port = this.server == null ? this.lastPort : this.server.getPort(); this.portBox = this.add(new Field(404, 20, 76, 24, 5, this, port < 0 ? "" : "" + port)); this.userBox = this.add(new Field(0, 70, 220, 24, IPlayer.MAX_USER_LENGTH, this, IPlayer.VALID_USER, this.server == null ? this.lastUser : this.server.getUser())); - this.passBox = this.add(new Field(0, 120, 480, 24, IPlayer.MAX_PASS_LENGTH, this, this.server == null ? this.lastPass : this.server.getPassword())); - this.accBox = this.add(new Field(0, 170, 480, 24, IPlayer.MAX_PASS_LENGTH, this, this.server == null ? this.lastAcc : this.server.getAccess())); - this.add(new ActButton(0, this.server == null ? 220 : 370, 480, 24, new ButtonCallback() { + this.passBox = this.add(new Field(0, this.server == null ? 120 : 170, 480, 24, IPlayer.MAX_PASS_LENGTH, this, this.server == null ? this.lastPass : this.server.getPassword())); + this.accBox = this.add(new Field(0, this.server == null ? 170 : 220, 480, 24, IPlayer.MAX_PASS_LENGTH, this, this.server == null ? this.lastAcc : this.server.getAccess())); + this.add(new ActButton(0, this.server == null ? 220 : 350, 480, 24, new ButtonCallback() { public void use(ActButton elem, PressType action) { GuiServer.this.connect(); } }, this.server == null ? "Verbinden" : (this.server.getName().isEmpty() ? "Hinzufügen" : "Übernehmen"))); - this.add(new NavButton(0, this.server == null ? 250 : 400, 480, 24, this.server != null ? GuiConnect.INSTANCE : GuiMenu.INSTANCE, "Zurück")); + this.add(new NavButton(0, this.server == null ? 250 : 380, 480, 24, this.server != null ? GuiConnect.INSTANCE : GuiMenu.INSTANCE, "Zurück")); if(this.server != null) - this.nameLabel = this.add(new Label(0, -70, 410, 20, "Name", true)); - this.addrLabel = this.add(new Label(0, 0, 410, 20, "Adresse", true)); + this.nameLabel = this.add(new Label(0, -70, 410, 20, "Name in der Liste", true)); + this.addrLabel = this.add(new Label(0, 0, 410, 20, "Adresse / Hostname", true)); this.portLabel = this.add(new Label(404, 0, 76, 20, "Port", true)); this.rangeLabel = this.add(new Label(370, 44, 110, 20, "[1024..32767]", true)); - this.userLabel = this.add(new Label(0, 50, 220, 20, "Nutzer", true)); - this.passLabel = this.add(new Label(0, 100, 480, 20, "Passwort (mind. 8 Zeichen)", true)); - this.accLabel = this.add(new Label(0, 150, 480, 20, "Zugang (mind. 8 Zeichen)", true)); + this.userLabel = this.add(new Label(0, 50, 220, 20, "Benutzername", true)); + this.passLabel = this.add(new Label(0, this.server == null ? 100 : 150, 480, 20, (this.keypair == null ? "Anmelde" : "Ersatz") + "-Passwort (mind. 8 Zeichen)", true)); + this.accLabel = this.add(new Label(0, this.server == null ? 150 : 200, 480, 20, "Server-Passwort (mind. 8 Zeichen)", true)); if(this.server != null) { - this.keyButton = this.add(new ActButton(0, 220, 480, 24, new ButtonCallback() { + this.keyButton = this.add(new ActButton(0, 120, 480, 24, new ButtonCallback() { public void use(ActButton elem, PressType action) { if(GuiServer.this.keypair == null) { GuiServer.this.keypair = EncryptUtil.generateKeyPair(); + GuiServer.this.keyDigest = EncryptUtil.getXorSha512Hash(GuiServer.this.keypair.getPublic().getEncoded()); + GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-2048 " + GuiServer.this.keyDigest); GuiServer.this.keyButton.setText("Schlüsselpaar entfernen"); + GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)"); } else { final String name = GuiServer.this.nameBox.getText(); @@ -97,6 +107,8 @@ public class GuiServer extends Gui implements FieldCallback { final boolean reqEnc = GuiServer.this.encToggle.getValue(); final KeyPair keys = GuiServer.this.keypair; final PublicKey key = GuiServer.this.serverKey; + final String digest = GuiServer.this.keyDigest; + final String sdigest = GuiServer.this.serverDigest; GuiServer.this.gm.displayGuiScreen(new GuiConfirm(new GuiConfirm.Callback() { public void confirm(boolean confirmed) { GuiServer.this.gm.displayGuiScreen(GuiServer.this); @@ -107,30 +119,43 @@ public class GuiServer extends Gui implements FieldCallback { GuiServer.this.accBox.setText(acc); GuiServer.this.encToggle.setValue(reqEnc); GuiServer.this.serverKey = key; + GuiServer.this.serverDigest = sdigest; + GuiServer.this.idLabel.setText("Server-Pubkey: " + (key != null ? "RSA-2048 " + GuiServer.this.serverDigest : "nicht vorhanden")); GuiServer.this.resetButton.enabled = key != null; if(confirmed) { GuiServer.this.keypair = null; + GuiServer.this.keyDigest = null; + GuiServer.this.keyLabel.setText("Anmelde-Pubkey: nicht vorhanden"); GuiServer.this.keyButton.setText("Neues Schlüsselpaar generieren"); + GuiServer.this.passLabel.setText("Anmelde-Passwort (mind. 8 Zeichen)"); } else { GuiServer.this.keypair = keys; + GuiServer.this.keyDigest = digest; + GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-2048 " + GuiServer.this.keyDigest); GuiServer.this.keyButton.setText("Schlüsselpaar entfernen"); + GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)"); } } }, "Schlüsselpaar wirklich entfernen?", "Wenn das Schlüsselpaar gelöscht wird, ist es nicht mehr möglich, sich damit auf dem Server zu identifizieren und sich anzumelden.\nDamit könnte der Zugriff auf den Server unmöglich werden.", "Schlüsselpaar löschen", "Abbrechen")); } } - }, (this.keypair = this.server.getKeypair()) == null ? "Neues Schlüsselpaar generieren" : "Schlüsselpaar entfernen")); - this.encToggle = this.add(new Toggle(0, 270, 480, 24, false, this.server.requiresEncryption(), null, "Nur Verschlüsselt verbinden")); - GuiServer.this.serverKey = this.server.getServerKey(); - this.resetButton = this.add(new ActButton(0, 320, 480, 24, new ButtonCallback() { + }, this.keypair == null ? "Neues Schlüsselpaar generieren" : "Schlüsselpaar entfernen")); + this.keyLabel = this.add(new Label(0, 100, 480, 20, "Anmelde-Pubkey: " + (this.keypair != null ? "RSA-2048 " + this.keyDigest : "nicht vorhanden"), true)); + this.encToggle = this.add(new Toggle(0, 250, 480, 24, false, this.server.requiresEncryption(), null, "Nur Verschlüsselte Verbindung akzeptieren")); + this.serverKey = this.server.getServerKey(); + this.serverDigest = this.serverKey == null ? null : EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()); + this.idLabel = this.add(new Label(0, 280, 480, 20, "Server-Pubkey: " + (this.serverKey != null ? "RSA-2048 " + this.serverDigest : "nicht vorhanden"), true)); + this.resetButton = this.add(new ActButton(0, 300, 480, 24, new ButtonCallback() { public void use(ActButton elem, PressType action) { if(GuiServer.this.serverKey != null) { GuiServer.this.serverKey = null; + GuiServer.this.serverDigest = null; + GuiServer.this.idLabel.setText("Server-Pubkey: nicht vorhanden"); GuiServer.this.resetButton.enabled = false; } } - }, "Server-Key zurücksetzen")); + }, "Server-Identifizierung / Pubkey zurücksetzen")); this.resetButton.enabled = this.serverKey != null; } this.shift(); @@ -147,13 +172,17 @@ public class GuiServer extends Gui implements FieldCallback { if(this.server != null) { name = this.nameBox.getText(); if(name.isEmpty()) { - this.nameLabel.setText(TextColor.RED + "Name"); + this.nameLabel.setText(TextColor.RED + "Name in der Liste"); + return; + } + if(GuiConnect.INSTANCE.isNameTaken(name)) { + this.nameLabel.setText("Name in der Liste - " + TextColor.RED + "Bereits vorhanden"); return; } } String addr = this.addrBox.getText(); if(addr.isEmpty()) { - this.addrLabel.setText(TextColor.RED + "Adresse"); + this.addrLabel.setText(TextColor.RED + "Adresse / Hostname"); return; } int port = -1; @@ -176,17 +205,17 @@ public class GuiServer extends Gui implements FieldCallback { } String user = this.userBox.getText(); if(user.isEmpty()) { - this.userLabel.setText(TextColor.RED + "Nutzer"); + this.userLabel.setText(TextColor.RED + "Benutzername"); return; } String pass = this.passBox.getText(); - if(pass.length() < 8) { - this.passLabel.setText(pass.isEmpty() ? TextColor.RED + "Passwort (mind. 8 Zeichen)" : "Passwort (" + TextColor.RED + "mind. 8 Zeichen" + TextColor.RESET + ")"); + if(!pass.isEmpty() && pass.length() < 8) { + this.passLabel.setText((this.keypair == null ? "Anmelde" : "Ersatz") + "-Passwort (" + TextColor.RED + "mind. 8 Zeichen" + TextColor.RESET + ")"); return; } String acc = this.accBox.getText(); - if(acc.length() < 8) { - this.accLabel.setText(acc.isEmpty() ? TextColor.RED + "Zugang (mind. 8 Zeichen)" : "Zugang (" + TextColor.RED + "mind. 8 Zeichen" + TextColor.RESET + ")"); + if(!acc.isEmpty() && acc.length() < 8) { + this.accLabel.setText("Zugang (" + TextColor.RED + "mind. 8 Zeichen" + TextColor.RESET + ")"); return; } if(this.server == null) { @@ -211,19 +240,19 @@ public class GuiServer extends Gui implements FieldCallback { } else if(value == FieldAction.FOCUS) { if(elem == this.addrBox) - this.addrLabel.setText("Adresse"); + this.addrLabel.setText("Adresse / Hostname"); else if(elem == this.portBox) { this.portLabel.setText("Port"); this.rangeLabel.setText("[1024..32767]"); } else if(elem == this.userBox) - this.userLabel.setText("Nutzer"); + this.userLabel.setText("Benutzername"); else if(elem == this.passBox) - this.passLabel.setText("Passwort (mind. 8 Zeichen)"); + this.passLabel.setText((this.keypair == null ? "Anmelde" : "Ersatz") + "-Passwort (mind. 8 Zeichen)"); else if(elem == this.accBox) - this.accLabel.setText("Zugang (mind. 8 Zeichen)"); + this.accLabel.setText("Server-Passwort (mind. 8 Zeichen)"); else if(this.server != null && elem == this.nameBox) - this.nameLabel.setText("Name"); + this.nameLabel.setText("Name in der Liste"); } } } diff --git a/client/src/main/java/client/network/ClientLoginHandler.java b/client/src/main/java/client/network/ClientLoginHandler.java index aa7719b..ae2923a 100755 --- a/client/src/main/java/client/network/ClientLoginHandler.java +++ b/client/src/main/java/client/network/ClientLoginHandler.java @@ -11,7 +11,6 @@ import client.Client; import client.gui.GuiConfirm; import client.gui.GuiConnect; import client.gui.GuiConnect.ServerInfo; -import client.gui.GuiLoading; import common.net.util.concurrent.Future; import common.net.util.concurrent.GenericFutureListener; import common.network.IClientLoginHandler; @@ -80,22 +79,21 @@ public class ClientLoginHandler extends NetHandler implements IClientLoginHandle if(confirmed) { ClientLoginHandler.this.server.setServerKey(pubkey); GuiConnect.INSTANCE.editServer(ClientLoginHandler.this.server); - ClientLoginHandler.this.gm.displayGuiScreen( - GuiLoading.makeWaitTask("Verbinde zu " + (ClientLoginHandler.this.server.getAddress() == null ? "localhost" : - ClientLoginHandler.this.server.getAddress()) + ":" + ClientLoginHandler.this.server.getPort() + " ...")); + ClientLoginHandler.this.gm.displayConnecting(ClientLoginHandler.this.server); ClientLoginHandler.this.startEncryption(secret, pubkey, token); } else { ClientLoginHandler.this.connection.closeChannel("Verbindung wurde abgebrochen"); } } - }, "Die Identität des Servers ist unbekannt", "Es wurde noch nie mit diesem Server verbunden.\nSoll die Verbindung wirklich fortgesetzt werden?\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Base64.getEncoder().encodeToString(pubkey.getEncoded()), "Verbindung herstellen", "Abbrechen und trennen")); + }, "Die Identität des Servers ist unbekannt", "Es wurde noch nie mit diesem Server verbunden.\nSoll die Verbindung wirklich fortgesetzt werden?\n\nDie Pubkey-ID des Servers lautet:\nRSA-2048 " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Base64.getEncoder().encodeToString(pubkey.getEncoded()), "Verbindung herstellen", "Abbrechen und trennen")); } }); return; } else if(!this.server.getServerKey().equals(pubkey)) { - this.connection.closeChannel("Die Identität des Servers hat sich geändert\n\nDer Server hat einen anderen öffentlichen Schlüssel als vorher bekannt war, dies kann bedeuten dass der Inhaber jetzt einen anderen Schlüssel verwendet oder sich ein anderer Server als dieser ausgibt (man-in-the-middle attack).\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Base64.getEncoder().encodeToString(pubkey.getEncoded()) + "\n\nDer vertrauenswürdige Schlüssel lautet:\n" + Base64.getEncoder().encodeToString(this.server.getServerKey().getEncoded())); + this.connection.closeChannel("Die Identität des Servers hat sich geändert\n\nDer Server hat einen anderen öffentlichen Schlüssel als vorher bekannt war, dies kann bedeuten dass der Inhaber jetzt einen anderen Schlüssel verwendet oder sich ein anderer Server als dieser ausgibt (\"Man-in-the-middle-attack\").\n\nDie Pubkey-ID des Servers lautet:\nRSA-2048 " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Base64.getEncoder().encodeToString(pubkey.getEncoded()) + "\n\nDie vertrauenswürdige Pubkey-ID lautet:\nRSA-2048 " + EncryptUtil.getXorSha512Hash(this.server.getServerKey().getEncoded()) + "\n\nDer vertrauenswürdige Schlüssel lautet:\n" + Base64.getEncoder().encodeToString(this.server.getServerKey().getEncoded()) + + "\n\nFalls der Server trotzdem vertrauenswürdig wirkt, kann die Server-Identifizierung im den Einstellungen von '" + this.server.getName() + "' zurückgesetzt werden."); return; } this.startEncryption(secret, pubkey, token); @@ -125,7 +123,7 @@ public class ClientLoginHandler extends NetHandler implements IClientLoginHandle this.state = LoginState.ENCRYPTED; } - public void handleConfig(RPacketServerConfig packet) { + public void handleConfig(final RPacketServerConfig packet) { if(this.state == LoginState.HANDSHAKE) { this.connection.setConnectionState(PacketRegistry.LOGIN); if(this.server.requiresEncryption()) { @@ -133,11 +131,37 @@ public class ClientLoginHandler extends NetHandler implements IClientLoginHandle + this.server.getName() + "' als erforderlich eingestellt, stelle keine ungesicherte Verbindung her."); return; } + if(this.server.getServerKey() != null) { + this.state = LoginState.CONFIRMING; + this.gm.schedule(new Runnable() { + public void run() { + ClientLoginHandler.this.gm.displayGuiScreen(new GuiConfirm(new GuiConfirm.Callback() { + public void confirm(boolean confirmed) { + if(confirmed) { + ClientLoginHandler.this.server.setServerKey(null); + GuiConnect.INSTANCE.editServer(ClientLoginHandler.this.server); + ClientLoginHandler.this.gm.displayConnecting(ClientLoginHandler.this.server); + ClientLoginHandler.this.state = LoginState.HANDSHAKE; + ClientLoginHandler.this.handleConfig(packet); + } + else { + ClientLoginHandler.this.connection.closeChannel("Verbindung wurde abgebrochen"); + } + } + }, "Der Server hat die Verschlüsselung deaktiviert", "Es wurde bereits verschlüsselt mit diesem Server kommuniziert.\n\nSoll die Verbindung wirklich fortgesetzt werden?\n\nDie Pubkey-Authentifizierung wird nicht verfügbar sein und die Server-ID wird ignoriert und zurückgesetzt.", "Unverschlüsselt verbinden", "Abbrechen und trennen")); + } + }); + return; + } } else if(this.state != LoginState.ENCRYPTED) { this.connection.closeChannel("Unerwartetes Konfigurations-Paket"); return; } + if(packet.hasAccessPassword() && this.server.getAccess().isEmpty()) { + this.connection.closeChannel("Der Server verlangt ein Zugangs-Passwort zur Authentifizierung"); + return; + } boolean auth = packet.isAuthenticating(); boolean passwordAuth = packet.canUsePassword(); boolean pubkeyAuth = packet.canUsePubkey() && this.state == LoginState.ENCRYPTED; diff --git a/common/src/main/java/common/util/EncryptUtil.java b/common/src/main/java/common/util/EncryptUtil.java index dba9541..be8b831 100644 --- a/common/src/main/java/common/util/EncryptUtil.java +++ b/common/src/main/java/common/util/EncryptUtil.java @@ -6,6 +6,7 @@ import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -117,4 +118,23 @@ public class EncryptUtil { throw new RuntimeException(e); } } + + public static String getXorSha512Hash(byte[] data) { + byte[] hash; + try { + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + hash = digest.digest(data); + } + catch(NoSuchAlgorithmException e) { + Log.SYSTEM.error(e, "Konnte Prüfwert nicht berechnen"); + return ""; + } + byte[] xor = new byte[8]; + for(int z = 0; z < hash.length / xor.length; z++) { + for(int n = 0; n < xor.length; n++) { + xor[n] ^= hash[z * xor.length + n]; + } + } + return Util.getHexString(xor); + } }