diff --git a/client/src/main/java/client/gui/ingame/GuiForm.java b/client/src/main/java/client/gui/ingame/GuiForm.java index 19c9636..94c2671 100644 --- a/client/src/main/java/client/gui/ingame/GuiForm.java +++ b/client/src/main/java/client/gui/ingame/GuiForm.java @@ -67,8 +67,8 @@ public class GuiForm extends Gui implements ButtonCallback { GuiForm.this.labels[index].setText(name); } }; - this.inputs[z] = this.add((param & 0x80000000) != 0 ? new PasswordField(0, 50 * z, 300, 24, Math.min(param & 0xffff, 256), callback, (String)obj) : - new Field(0, 50 * z, 300, 24, Math.min(param & 0xffff, 256), callback, (String)obj)); + this.inputs[z] = this.add((param & 0x80000000) != 0 ? new PasswordField(0, 50 * z, 300, 24, Math.min(param & 0xffff, 1024), callback, (String)obj) : + new Field(0, 50 * z, 300, 24, Math.min(param & 0xffff, 1024), callback, (String)obj)); } } this.add(new NavButton(0, 50 * (this.inputs.length + 1), 148, 24, null, "Abbrechen")); diff --git a/common/src/main/java/common/packet/CPacketForm.java b/common/src/main/java/common/packet/CPacketForm.java index 127ddfb..5c9e5bf 100644 --- a/common/src/main/java/common/packet/CPacketForm.java +++ b/common/src/main/java/common/packet/CPacketForm.java @@ -39,7 +39,7 @@ public class CPacketForm implements Packet obj = buf.readVarInt(); break; default: - obj = buf.readString(256); + obj = buf.readString(1024); break; } this.data[z] = obj; diff --git a/common/src/main/java/common/packet/RPacketServerConfig.java b/common/src/main/java/common/packet/RPacketServerConfig.java index 7aa5992..5c5a2e9 100644 --- a/common/src/main/java/common/packet/RPacketServerConfig.java +++ b/common/src/main/java/common/packet/RPacketServerConfig.java @@ -47,7 +47,7 @@ public class RPacketServerConfig implements Packet { } public boolean isAuthenticating() { - return this.passwordAuth; + return this.requiresAuth; } public boolean canUsePassword() { diff --git a/common/src/main/java/common/util/EncryptUtil.java b/common/src/main/java/common/util/EncryptUtil.java index 50bd1d7..277a69e 100644 --- a/common/src/main/java/common/util/EncryptUtil.java +++ b/common/src/main/java/common/util/EncryptUtil.java @@ -10,9 +10,11 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; @@ -24,7 +26,9 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import common.log.Log; @@ -189,6 +193,7 @@ public class EncryptUtil { private static final long CRC24_INIT = 0xB704CEL; private static final long CRC24_POLY = 0x1864CFBL; + private static final byte[] CLIENT_SALT = "ItIsSaltySalt2765666 tftffs ##89n!.le98udMeowHAsh44m;+*3m9DI9Sqn".getBytes(); private static byte[] crc24(byte[] data) { long crc = CRC24_INIT; @@ -203,4 +208,23 @@ public class EncryptUtil { int value = (int)(crc & 0xFFFFFFL); return new byte[] {(byte)((value >> 16) & 0xff), (byte)((value >> 8) & 0xff), (byte)(value & 0xff)}; } + + public static byte[] hashPassword(String password, byte[] salt) { + try { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return factory.generateSecret(spec).getEncoded(); + } + catch(NoSuchAlgorithmException | InvalidKeySpecException e) { + Log.SYSTEM.error(e, "Konnte Passwort-Prüfwert nicht errechnen"); + return null; + } + } + + public static Pair hashPassword(String password) { + SecureRandom rand = new SecureRandom(); + byte[] salt = new byte[64]; + rand.nextBytes(salt); + return new Pair(hashPassword(password, salt), salt); + } } diff --git a/server/src/main/java/server/Server.java b/server/src/main/java/server/Server.java index 8dd097c..5c9e910 100755 --- a/server/src/main/java/server/Server.java +++ b/server/src/main/java/server/Server.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; import java.security.KeyPair; +import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; import java.text.SimpleDateFormat; @@ -854,7 +855,7 @@ public final class Server implements IThreadListener { if(tag != null) conn.readTags(tag); if(Config.authenticate) { - if(conn.getPassword() == null && conn.getPubkey() == null) { + if(conn.getPasswordHash() == null && conn.getPubkey() == null) { if(tag != null) return loginKey != null ? "Falscher Pubkey" : "Falsches Passwort"; if(!Config.register) @@ -870,11 +871,12 @@ public final class Server implements IThreadListener { return "Ein neues Passwort ist erforderlich um diesen Server zu betreten (mindestens " + Config.minPassLength + " Zeichen)"; if(loginPass.length() < Config.minPassLength) return "Passwort ist zu kurz, mindestens " + Config.minPassLength + " Zeichen"; - conn.setPassword(loginPass); + conn.setPasswordHash(EncryptUtil.hashPassword(loginPass)); Log.NETWORK.info(loginUser + " registrierte sich mit Passwort"); } } - else if(conn.getPubkey() != null ? !conn.getPubkey().equals(loginKey) : !conn.getPassword().equals(loginPass)) { + else if(conn.getPubkey() != null ? !conn.getPubkey().equals(loginKey) : + (loginPass == null || !MessageDigest.isEqual(EncryptUtil.hashPassword(loginPass, conn.getPasswordHash().second()), conn.getPasswordHash().first()))) { return loginKey != null ? "Falscher Pubkey" : "Falsches Passwort"; } else { diff --git a/server/src/main/java/server/command/CommandEnvironment.java b/server/src/main/java/server/command/CommandEnvironment.java index dcb7faa..7bf8f24 100644 --- a/server/src/main/java/server/command/CommandEnvironment.java +++ b/server/src/main/java/server/command/CommandEnvironment.java @@ -25,6 +25,7 @@ import server.command.commands.CommandOfflinetp; import server.command.commands.CommandPasswd; import server.command.commands.CommandPlayers; import server.command.commands.CommandPotion; +import server.command.commands.CommandPubkey; import server.command.commands.CommandRegister; import server.command.commands.CommandRemove; import server.command.commands.CommandRevoke; @@ -280,6 +281,7 @@ public class CommandEnvironment { this.registerExecutable(new CommandMessage()); this.registerExecutable(new CommandShutdown()); this.registerExecutable(new CommandPasswd()); + this.registerExecutable(new CommandPubkey()); this.registerExecutable(new CommandPlayers()); this.registerExecutable(new CommandSave()); this.registerExecutable(new CommandRegister()); diff --git a/server/src/main/java/server/command/commands/CommandPasswd.java b/server/src/main/java/server/command/commands/CommandPasswd.java index 7cf15ad..93aff3f 100644 --- a/server/src/main/java/server/command/commands/CommandPasswd.java +++ b/server/src/main/java/server/command/commands/CommandPasswd.java @@ -1,8 +1,11 @@ package server.command.commands; +import java.security.MessageDigest; + import common.color.TextColor; import common.init.Config; import common.network.IPlayer; +import common.util.EncryptUtil; import server.command.Command; import server.command.CommandEnvironment; import server.command.Executor; @@ -25,6 +28,10 @@ public class CommandPasswd extends Command { throw new RunException("Bei Verwendung als Spieler darf kein Passwort angegeben werden"); if(player.getAdmin() && player != exec) throw new RunException("%s ist ein Admin", player.getUser()); + if(player.getPubkey() != null && player == exec) + throw new RunException("Es darf kein Pubkey vorhanden sein, um diesen Befehl an sich selbst anzuwenden"); + if(player.getPasswordHash() == null && player == exec) + throw new RunException("Es muss ein Passwort vorhanden sein, um diesen Befehl an sich selbst anzuwenden"); ((Player)exec).displayForm(new Form() { private Field checkField; private Field passwordField; @@ -42,11 +49,11 @@ public class CommandPasswd extends Command { protected void accept() { Player plr = env.getServer().getPlayer(player.getUser()); - if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec)) { + if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec) || (plr.getPasswordHash() == null && plr == exec) || (plr.getPubkey() != null && plr == exec)) { exec.logConsole(TextColor.DRED + "Ein Fehler ist aufgetreten"); return; } - if(this.checkField != null && !this.checkField.get().equals(plr.getPassword())) { + if(this.checkField != null && !MessageDigest.isEqual(EncryptUtil.hashPassword(this.checkField.get(), plr.getPasswordHash().second()), plr.getPasswordHash().first())) { exec.logConsole(TextColor.RED + "Falsches Passwort eingegeben"); return; } @@ -54,7 +61,8 @@ public class CommandPasswd extends Command { exec.logConsole(TextColor.RED + "Passwörter stimmen nicht überein"); return; } - plr.setPassword(this.passwordField.get()); + plr.setPasswordHash(EncryptUtil.hashPassword(this.passwordField.get())); + plr.setPubkey(null); exec.logConsole(TextColor.GREEN + "Passwort" + (plr != exec ? " für %s" : "") + " gesetzt", plr.getUser()); } }); @@ -62,7 +70,10 @@ public class CommandPasswd extends Command { else if(exec.isConsole()) { if(password == null) throw new RunException("Bei Verwendung in der Konsole muss ein Passwort angegeben werden"); - player.setPassword(password); + if(password.length() < 8) + throw new RunException("Das Passwort ist zu kurz, mindestens 8 Zeichen sind erforderlich"); + player.setPasswordHash(EncryptUtil.hashPassword(password)); + player.setPubkey(null); exec.logConsole(TextColor.GREEN + "Passwort für %s gesetzt", player.getUser()); } } diff --git a/server/src/main/java/server/command/commands/CommandPubkey.java b/server/src/main/java/server/command/commands/CommandPubkey.java new file mode 100644 index 0000000..80c1cc0 --- /dev/null +++ b/server/src/main/java/server/command/commands/CommandPubkey.java @@ -0,0 +1,87 @@ +package server.command.commands; + +import java.security.MessageDigest; +import java.security.PublicKey; + +import common.color.TextColor; +import common.network.IPlayer; +import common.util.EncryptUtil; +import common.util.Pair; +import server.command.Command; +import server.command.CommandEnvironment; +import server.command.Executor; +import server.command.RunException; +import server.network.Player; +import server.util.Form; + +public class CommandPubkey extends Command { + public CommandPubkey() { + super("pubkey"); + + this.addPlayer("player", true); + this.setParamsOptional(); + this.addString("keySpec", false); + this.addString("keyData", false); + this.addString("keyHash", false); + this.addString("keyName", false); + } + + public void exec(CommandEnvironment env, Executor exec, Player player, String keySpec, String keyData, String keyHash, String keyName) { + if(exec.isPlayer()) { + if(keySpec != null || keyData != null || keyHash != null || keyName != null) + throw new RunException("Bei Verwendung als Spieler darf kein Schlüssel angegeben werden"); + if(player.getAdmin() && player != exec) + throw new RunException("%s ist ein Admin", player.getUser()); + ((Player)exec).displayForm(new Form() { + private Field checkField; + private Field keyField; + + protected void init() { + this.checkField = player.getPasswordHash() == null || player != exec ? null : this.addPassword("Aktuelles Passwort", 0, IPlayer.MAX_PASS_LENGTH, ""); + this.keyField = this.addField("Neuer Pubkey", 1, 960, ""); + } + + public String getTitle() { + return "Schlüssel für " + player.getUser() + " ändern"; + } + + protected void accept() { + Player plr = env.getServer().getPlayer(player.getUser()); + if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec)) { + exec.logConsole(TextColor.DRED + "Ein Fehler ist aufgetreten"); + return; + } + if(this.checkField != null && plr.getPasswordHash() != null && !MessageDigest.isEqual(EncryptUtil.hashPassword(this.checkField.get(), plr.getPasswordHash().second()), plr.getPasswordHash().first())) { + exec.logConsole(TextColor.RED + "Falsches Passwort eingegeben"); + return; + } + Pair key; + try { + key = EncryptUtil.parseArmoredPubkey(this.keyField.get()); + } + catch(IllegalArgumentException e) { + exec.logConsole(TextColor.RED + "Ungültiger Schlüssel"); + return; + } + plr.setPasswordHash(null); + plr.setPubkey(key.first()); + exec.logConsole(TextColor.GREEN + "Schlüssel" + (plr != exec ? " für %s" : "") + " gesetzt", plr.getUser()); + } + }); + } + else if(exec.isConsole()) { + if(keySpec == null || keyData == null || keyHash == null) + throw new RunException("Bei Verwendung in der Konsole muss ein Schlüssel angegeben werden"); + Pair key; + try { + key = EncryptUtil.parseArmoredPubkey(keySpec + " " + keyData + " " + keyHash + (keyName == null ? null : " " + keyName)); + } + catch(IllegalArgumentException e) { + throw new RunException(e, "Ungültiger Schlüssel"); + } + player.setPasswordHash(null); + player.setPubkey(key.first()); + exec.logConsole(TextColor.GREEN + "Schlüssel für %s gesetzt", player.getUser()); + } + } +} diff --git a/server/src/main/java/server/network/Player.java b/server/src/main/java/server/network/Player.java index 338ecd6..3021b61 100755 --- a/server/src/main/java/server/network/Player.java +++ b/server/src/main/java/server/network/Player.java @@ -115,6 +115,7 @@ import common.util.EncryptUtil; import common.util.ExtMath; import common.util.Facing; import common.util.IntHashMap; +import common.util.Pair; import common.util.PortalType; import common.util.Position; import common.util.Vec3i; @@ -172,7 +173,7 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer private boolean admin; private int ping; private boolean deleted; - private String password; + private Pair password; private PublicKey pubkey; private boolean profiling; @@ -343,11 +344,11 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer return this.isAdmin(); } - public void setPassword(String pass) { - this.password = pass; + public void setPasswordHash(Pair hash) { + this.password = hash; } - public String getPassword() { + public Pair getPasswordHash() { return this.password; } @@ -628,8 +629,8 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer public void readTags(TagObject tag) { this.admin = tag.getBool("admin"); - if(tag.hasString("password")) - this.password = tag.getString("password"); + if(tag.hasByteArray("passwordHash") && tag.hasByteArray("passwordSalt")) + this.password = new Pair(tag.getByteArray("passwordHash"), tag.getByteArray("passwordSalt")); if(tag.hasByteArray("pubkey")) this.pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("pubkey")); this.selected = tag.getInt("selected"); @@ -644,8 +645,10 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer public void writeTags(TagObject tag) { if(this.admin) tag.setBool("admin", this.admin); - if(this.password != null) - tag.setString("password", this.password); + if(this.password != null) { + tag.setByteArray("passwordHash", this.password.first()); + tag.setByteArray("passwordSalt", this.password.second()); + } if(this.pubkey != null) tag.setByteArray("pubkey", this.pubkey.getEncoded()); if(!this.characters.isEmpty()) {