summary refs log tree commit diff stats
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/ganarchy/friendcode/util/ConfigUtil.java5
-rw-r--r--src/main/java/ganarchy/friendcode/util/KeyUtil.java109
2 files changed, 107 insertions, 7 deletions
diff --git a/src/main/java/ganarchy/friendcode/util/ConfigUtil.java b/src/main/java/ganarchy/friendcode/util/ConfigUtil.java
index b2d6af2..66d497f 100644
--- a/src/main/java/ganarchy/friendcode/util/ConfigUtil.java
+++ b/src/main/java/ganarchy/friendcode/util/ConfigUtil.java
@@ -22,8 +22,9 @@ public class ConfigUtil {
      * The message stored in the config. Could probably use some improvement.
      */
     private static final String CONFIG_MESSAGE =
-        "This is the friendcode config file."
-            + " It's created when you open your world to a friend code.";
+        "THIS FILE CONTAINS SECRETS! This is the friendcode config file."
+            + " It's created when you open your world to a friend code."
+            + " Keep it private. And make a backup.";
     /**
      * The (cached) global friendcode config dir.
      */
diff --git a/src/main/java/ganarchy/friendcode/util/KeyUtil.java b/src/main/java/ganarchy/friendcode/util/KeyUtil.java
index ab5d614..79a6a45 100644
--- a/src/main/java/ganarchy/friendcode/util/KeyUtil.java
+++ b/src/main/java/ganarchy/friendcode/util/KeyUtil.java
@@ -1,11 +1,27 @@
 package ganarchy.friendcode.util;
 
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.*;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.security.*;
+import java.util.Properties;
 
 /**
  * Helper to deal with private keys.
  */
 public class KeyUtil {
+    // it is important to use encryption, as you don't want random users
+    // hosting the world code. by encrypting the world code to an user, it
+    // prevents that.
+    // then you can freely share the world files and nothing bad happens.
+
     /**
      * Reads and decrypts a private key file.
      *
@@ -13,18 +29,101 @@ public class KeyUtil {
      * @return The decrypted private key, or null if it doesn't exist.
      */
     public static String readKeyFile(Path keyFile) {
-        return null;
+        byte[] file;
+        byte[] out;
+        try {
+            file = Files.readAllBytes(keyFile);
+        } catch (IOException e) {
+            return null;
+        }
+        if (file.length < 12 + 16 + 884) { // 12 (IV) + 16 (tag) + 884 (key)
+            return null;
+        }
+        try {
+            var c = Cipher.getInstance("AES/GCM/NoPadding");
+            Key key = getGlobalKey();
+            byte[] iv = new byte[12];
+            System.arraycopy(file, 0, iv, 0, 12);
+            c.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
+            out = c.doFinal(file, 12, file.length - 12);
+        } catch (AEADBadTagException e) {
+            return null;
+        } catch (
+            NoSuchAlgorithmException | NoSuchPaddingException
+            | InvalidAlgorithmParameterException | InvalidKeyException
+            | IllegalBlockSizeException | BadPaddingException e
+        ) {
+            throw new RuntimeException(e);
+        }
+        return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(out)).toString();
     }
 
 
     /**
      * Encrypts and writes a private key file.
      *
-     * @param keyFile The path to the key file.
-     * @param key     The private key.
+     * @param keyFile    The path to the key file.
+     * @param privateKey The private key.
      * @return Whether writing the key succeeded.
      */
-    public static boolean writeKeyFile(Path keyFile, String key) {
-        return false;
+    public static boolean writeKeyFile(Path keyFile, String privateKey) {
+        byte[] in = privateKey.getBytes(StandardCharsets.UTF_8);
+        byte[] iv = new byte[12];
+        byte[] out;
+        try {
+            var c = Cipher.getInstance("AES/GCM/NoPadding");
+            Key key = getGlobalKey();
+            var rand = new SecureRandom();
+            rand.nextBytes(iv);
+            c.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
+            out = c.doFinal(in, 0, in.length);
+        } catch (
+            NoSuchAlgorithmException | NoSuchPaddingException
+            | InvalidAlgorithmParameterException | InvalidKeyException
+            | IllegalBlockSizeException | BadPaddingException e
+        ) {
+            throw new RuntimeException(e);
+        }
+        try (var file = Files.newOutputStream(keyFile)) {
+            file.write(iv);
+            file.write(out);
+            file.flush();
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the global key used for the encryption/decryption of world keys.
+     */
+    private static Key getGlobalKey() {
+        Properties prop = new Properties();
+        if (ConfigUtil.getSettings(prop)) {
+            var key = prop.getProperty("key.aes");
+            if (key != null) {
+                byte[] bytes = null;
+                try {
+                    bytes = Base64.decodeBase64(key);
+                } catch (IllegalArgumentException ignored) {
+                }
+                if (bytes != null && bytes.length == 16) {
+                    return new SecretKeySpec(bytes, "AES");
+                }
+            }
+        }
+        prop.clear();
+        try {
+            var gen = KeyGenerator.getInstance("AES");
+            gen.init(128);
+            Key key = gen.generateKey();
+            var encoded = Base64.encodeBase64String(key.getEncoded());
+            prop.setProperty("key.aes", encoded);
+            // if the key didn't save we'll just regen world codes.
+            ConfigUtil.updateSettings(prop);
+            return key;
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
     }
 }