add key serialization

This commit is contained in:
Sen 2025-05-30 01:43:37 +02:00
parent be0ab15153
commit b14e539464
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
5 changed files with 117 additions and 13 deletions

View file

@ -2,8 +2,6 @@ package client.gui;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Base64;
import client.gui.GuiConnect.ServerInfo; import client.gui.GuiConnect.ServerInfo;
import client.gui.element.ActButton; import client.gui.element.ActButton;
import client.gui.element.ButtonCallback; import client.gui.element.ButtonCallback;
@ -97,7 +95,7 @@ public class GuiServer extends Gui implements FieldCallback {
this.keyButton = this.add(new ActButton(0, 120, 391, 24, new ButtonCallback() { this.keyButton = this.add(new ActButton(0, 120, 391, 24, new ButtonCallback() {
public void use(ActButton elem, PressType action) { public void use(ActButton elem, PressType action) {
if(GuiServer.this.keypair == null) { if(GuiServer.this.keypair == null) {
GuiServer.this.keypair = EncryptUtil.generateKeyPair(); GuiServer.this.keypair = EncryptUtil.createKeypair();
GuiServer.this.keyDigest = EncryptUtil.getXorSha512Hash(GuiServer.this.keypair.getPublic().getEncoded()); GuiServer.this.keyDigest = EncryptUtil.getXorSha512Hash(GuiServer.this.keypair.getPublic().getEncoded());
GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-2048 " + GuiServer.this.keyDigest); GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-2048 " + GuiServer.this.keyDigest);
GuiServer.this.keyButton.setText("Schlüsselpaar entfernen"); GuiServer.this.keyButton.setText("Schlüsselpaar entfernen");
@ -152,7 +150,7 @@ public class GuiServer extends Gui implements FieldCallback {
this.copyKeyButton = this.add(new ActButton(395, 120, 85, 24, new ButtonCallback() { this.copyKeyButton = this.add(new ActButton(395, 120, 85, 24, new ButtonCallback() {
public void use(ActButton elem, PressType action) { public void use(ActButton elem, PressType action) {
if(GuiServer.this.keypair != null) if(GuiServer.this.keypair != null)
Window.setClipboard(Base64.getEncoder().encodeToString(GuiServer.this.keypair.getPublic().getEncoded())); Window.setClipboard(EncryptUtil.getArmoredPubkey(GuiServer.this.keypair.getPublic(), GuiServer.this.userBox.getText()));
} }
}, "Kopieren")); }, "Kopieren"));
this.copyKeyButton.enabled = this.keypair != null; this.copyKeyButton.enabled = this.keypair != null;
@ -176,7 +174,7 @@ public class GuiServer extends Gui implements FieldCallback {
this.copyIdButton = this.add(new ActButton(395, 300, 85, 24, new ButtonCallback() { this.copyIdButton = this.add(new ActButton(395, 300, 85, 24, new ButtonCallback() {
public void use(ActButton elem, PressType action) { public void use(ActButton elem, PressType action) {
if(GuiServer.this.serverKey != null) if(GuiServer.this.serverKey != null)
Window.setClipboard(Base64.getEncoder().encodeToString(GuiServer.this.serverKey.getEncoded())); Window.setClipboard(EncryptUtil.getArmoredPubkey(GuiServer.this.serverKey, GuiServer.this.nameBox.getText()));
} }
}, "Kopieren")); }, "Kopieren"));
this.copyIdButton.enabled = this.serverKey != null; this.copyIdButton.enabled = this.serverKey != null;

View file

@ -67,7 +67,7 @@ public class ClientLoginHandler extends NetHandler implements IClientLoginHandle
return; return;
} }
this.connection.setConnectionState(PacketRegistry.LOGIN); this.connection.setConnectionState(PacketRegistry.LOGIN);
final SecretKey secret = EncryptUtil.createNewSharedKey(); final SecretKey secret = EncryptUtil.createSharedKey();
final PublicKey pubkey = packet.getKey(); final PublicKey pubkey = packet.getKey();
final byte[] token = packet.getToken(); final byte[] token = packet.getToken();
if(this.server.getServerKey() == null) { if(this.server.getServerKey() == null) {

View file

@ -15,6 +15,9 @@ import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
@ -27,7 +30,7 @@ import javax.crypto.spec.SecretKeySpec;
import common.log.Log; import common.log.Log;
public class EncryptUtil { public class EncryptUtil {
public static SecretKey createNewSharedKey() { public static SecretKey createSharedKey() {
try { try {
KeyGenerator keygen = KeyGenerator.getInstance("AES"); KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128); keygen.init(128);
@ -38,7 +41,7 @@ public class EncryptUtil {
} }
} }
public static KeyPair generateKeyPair() { public static KeyPair createKeypair() {
try { try {
KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA");
pairgen.initialize(2048); pairgen.initialize(2048);
@ -79,14 +82,14 @@ public class EncryptUtil {
} }
public static byte[] encryptData(Key key, byte[] data) { public static byte[] encryptData(Key key, byte[] data) {
return cipherOperation(Cipher.ENCRYPT_MODE, key, data); return cipher(Cipher.ENCRYPT_MODE, key, data);
} }
public static byte[] decryptData(Key key, byte[] data) { public static byte[] decryptData(Key key, byte[] data) {
return cipherOperation(Cipher.DECRYPT_MODE, key, data); return cipher(Cipher.DECRYPT_MODE, key, data);
} }
private static byte[] cipherOperation(int mode, Key key, byte[] data) { private static byte[] cipher(int mode, Key key, byte[] data) {
try { try {
return createCipher(mode, key.getAlgorithm(), key).doFinal(data); return createCipher(mode, key.getAlgorithm(), key).doFinal(data);
} }
@ -137,4 +140,67 @@ public class EncryptUtil {
} }
return Util.getHexString(xor); return Util.getHexString(xor);
} }
public static String getArmoredPubkey(PublicKey pubkey, String cn) {
StringBuilder sb = new StringBuilder("tcr-rsa-2048 ");
sb.append(Base64.getEncoder().encodeToString(pubkey.getEncoded()));
sb.append(' ').append(Base64.getEncoder().encodeToString(crc24(pubkey.getEncoded())));
return cn == null || cn.isEmpty() ? sb.toString() : sb.append(' ').append(Util.sanitizeCommonName(cn)).toString();
}
public static Tuple<PublicKey, String> parseArmoredPubkey(String armor) throws IllegalArgumentException {
String[] tok = armor.trim().split(" ");
if(tok.length != 3 && tok.length != 4)
throw new IllegalArgumentException("Key muss aus 3 oder 4 Segmenten bestehen");
if(!tok[0].equals("tcr-rsa-2048"))
throw new IllegalArgumentException("Algorithmus '" + tok[0] + "' ist nicht unterstützt, es wird derzeit nur tcr-rsa-2048 verwendet");
byte[] key;
try {
key = Base64.getDecoder().decode(tok[1]);
}
catch(IllegalArgumentException e) {
throw new IllegalArgumentException("Schlüssel ist nicht im Base64-Format", e);
}
byte[] hash;
try {
hash = Base64.getDecoder().decode(tok[2]);
}
catch(IllegalArgumentException e) {
throw new IllegalArgumentException("Prüfwert ist nicht im Base64-Format", e);
}
if(hash.length != 3)
throw new IllegalArgumentException("Prüfwert hat die falsche Länge, erwarte 3 Bytes, habe " + hash.length + " Bytes");
byte[] keyHash = crc24(key);
if(!Arrays.equals(hash, keyHash))
throw new IllegalArgumentException("Prüfwert ist falsch, erwarte " + Util.getHexString(hash) + ", habe " + Util.getHexString(keyHash));
PublicKey pubkey;
try {
EncodedKeySpec spec = new X509EncodedKeySpec(key);
KeyFactory factory = KeyFactory.getInstance("RSA");
pubkey = factory.generatePublic(spec);
}
catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalArgumentException("Öffentlicher Schlüssel konnte nicht dekodiert werden", e);
}
if(tok.length == 4 && !Util.isValidCommonName(tok[3]))
throw new IllegalArgumentException("Name muss aus 'a-z' '0-9' und '-' bestehen, kann keine aufeinander folgenden Bindestriche haben und darf nicht damit beginnen oder darin enden");
return new Tuple<PublicKey, String>(pubkey, tok.length == 4 ? tok[3] : null);
}
private static final long CRC24_INIT = 0xB704CEL;
private static final long CRC24_POLY = 0x1864CFBL;
private static byte[] crc24(byte[] data) {
long crc = CRC24_INIT;
for(byte bt : data) {
crc ^= (long)bt << 16;
for(int z = 0; z < 8; z++) {
crc <<= 1;
if((crc & 0x1000000) != 0)
crc ^= CRC24_POLY;
}
}
int value = (int)(crc & 0xFFFFFFL);
return new byte[] {(byte)((value >> 16) & 0xff), (byte)((value >> 8) & 0xff), (byte)(value & 0xff)};
}
} }

View file

@ -431,4 +431,44 @@ int utf_len(const char *str) {
} }
return bytes; return bytes;
} }
public static String breakString(String str, int width) {
StringBuilder sb = new StringBuilder();
for(int z = 0; z < str.length() / width; z++) {
sb.append(str.substring(z * width, (z + 1) * width)).append('\n');
}
return sb.append(str.substring(str.length() - (str.length() % width), str.length())).toString();
}
public static String sanitizeCommonName(String str) {
str = str.trim();
StringBuilder sb = new StringBuilder();
boolean hyphen = true;
for(int z = 0; z < str.length(); z++) {
char ch = Character.toLowerCase(str.charAt(z));
if((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
sb.append(ch);
hyphen = false;
}
else if(ch == '-' || ch == ' ') {
if(!hyphen)
sb.append('-');
hyphen = true;
}
}
return sb.length() > 0 && sb.charAt(sb.length() - 1) == '-' ? sb.substring(0, sb.length() - 1) : sb.toString();
}
public static boolean isValidCommonName(String str) {
boolean hyphen = true;
for(int z = 0; z < str.length(); z++) {
char ch = Character.toLowerCase(str.charAt(z));
if((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '-')
return false;
if(ch == '-' && hyphen)
return false;
hyphen = ch == '-';
}
return !hyphen;
}
} }

View file

@ -464,7 +464,7 @@ public final class Server implements IThreadListener {
long wtime = this.loadServerConfig(); long wtime = this.loadServerConfig();
if(this.keyPair == null) { if(this.keyPair == null) {
Log.SYSTEM.info("Generiere neues Schlüsselpaar"); Log.SYSTEM.info("Generiere neues Schlüsselpaar");
this.keyPair = EncryptUtil.generateKeyPair(); this.keyPair = EncryptUtil.createKeypair();
} }
// if(dtime == -1L) // { // if(dtime == -1L) // {
// dtime = World.START_TIME; // dtime = World.START_TIME;
@ -499,7 +499,7 @@ public final class Server implements IThreadListener {
} }
else { else {
Log.SYSTEM.info("Generiere temporäres Schlüsselpaar"); Log.SYSTEM.info("Generiere temporäres Schlüsselpaar");
this.keyPair = EncryptUtil.generateKeyPair(); this.keyPair = EncryptUtil.createKeypair();
Config.clear(); Config.clear();
UniverseRegistry.clear(); UniverseRegistry.clear();
Config.set("daylightCycle", "false", false); Config.set("daylightCycle", "false", false);