summary refs log tree commit diff stats
path: root/src/main/java/ganarchy/friendcode/sam
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2022-07-24 21:42:55 -0300
committerSoniEx2 <endermoneymod@gmail.com>2022-07-24 21:42:55 -0300
commit354df6d333ffb7b69e92117406c8ce8d61ea09e0 (patch)
tree976a3a4a153903baf96572033a0f46ab33b48f66 /src/main/java/ganarchy/friendcode/sam
parent5200e97d90e2adbeebdf06bd756f66df4e8f1b01 (diff)
Prepare for improved SAM auth and World Codes
Diffstat (limited to 'src/main/java/ganarchy/friendcode/sam')
-rw-r--r--src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java32
-rw-r--r--src/main/java/ganarchy/friendcode/sam/I2PSamCommand.java3
-rw-r--r--src/main/java/ganarchy/friendcode/sam/I2PSamControl.java102
-rw-r--r--src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java21
4 files changed, 123 insertions, 35 deletions
diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java b/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java
new file mode 100644
index 0000000..fad222c
--- /dev/null
+++ b/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java
@@ -0,0 +1,32 @@
+package ganarchy.friendcode.sam;
+
+/**
+* Helper for I2P SAM authentication.
+*/
+public class I2PSamAuthUtil {
+    /**
+    * Returns the currently active SAM auth pair.
+    */
+    public static AuthenticationPair getAuthPair() {
+        return new AuthenticationPair("minecraft_friendcode", "friendcode");
+    }
+
+    /**
+    * Generates and stores a modern auth pair.
+    *
+    * @return The generated auth pair.
+    */
+    public static AuthenticationPair upgradeAuthPair() {
+        return new AuthenticationPair("minecraft_friendcode", "friendcode");
+    }
+
+    /**
+    * Returns whether strong auth is enabled.
+    */
+    public static boolean isStrongAuth() {
+        return false;
+    }
+
+    public record AuthenticationPair(String user, String password) {
+    }
+}
diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamCommand.java b/src/main/java/ganarchy/friendcode/sam/I2PSamCommand.java
index b1cece8..c453bf0 100644
--- a/src/main/java/ganarchy/friendcode/sam/I2PSamCommand.java
+++ b/src/main/java/ganarchy/friendcode/sam/I2PSamCommand.java
@@ -3,7 +3,6 @@ package ganarchy.friendcode.sam;
 import com.google.common.collect.ImmutableMap;
 
 import java.util.Map;
-import java.util.regex.Pattern;
 
 /**
  * An I2P SAM command.
@@ -56,7 +55,7 @@ record I2PSamCommand(String name, String opcode, Map<String, String> parameters)
                 throw new IllegalArgumentException("PING/PONG does not accept parameters");
             }
             // skip any other validation
-            return;
+            //return;
         }
 
         // everything else is fine
diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java b/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java
index f470bb0..693cba3 100644
--- a/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java
+++ b/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java
@@ -10,11 +10,27 @@ import java.net.Socket;
 
 public class I2PSamControl extends I2PSamStateMachine {
     private final boolean zeroHop;
-    private String pubkey;
+    /**
+     * The session's private key.
+     *
+     * The threat model assumes the local machine (including RAM) to be trusted.
+     */
+    private String privateKey;
+    /**
+     * The session's public key.
+     */
+    private String publicKey;
     private String id;
 
-    public I2PSamControl(boolean zeroHop) {
+    /**
+     * Creates a new SAM control socket.
+     *
+     * @param zeroHop Whether to use zero-hop tunnels.
+     * @param privateKey The (optional) identity private key, for persistent friend codes.
+     */
+    public I2PSamControl(boolean zeroHop, String privateKey) {
         this.zeroHop = zeroHop;
+        this.privateKey = privateKey;
     }
 
     @Override
@@ -44,15 +60,16 @@ public class I2PSamControl extends I2PSamStateMachine {
         try {
             // try to enable auth
             this.enableAuth();
-            // FIXME "Friend Code Type: World" vs "Friend Code Type: Session"
-            // generate our keys
-            this.sendCommand(new I2PSamCommand(
-                "DEST", "GENERATE",
-                ImmutableMap.of("SIGNATURE_TYPE", "EdDSA_SHA512_Ed25519")
-            ));
-            var dest = this.getCommand("DEST", "REPLY");
-            this.pubkey = dest.parameters().get("PUB");
-            var privkey = dest.parameters().get("PRIV");
+            if (this.privateKey == null) {
+                // generate our keys
+                this.sendCommand(new I2PSamCommand(
+                        "DEST", "GENERATE",
+                        ImmutableMap.of("SIGNATURE_TYPE", "EdDSA_SHA512_Ed25519")
+                ));
+                var dest = this.getCommand("DEST", "REPLY");
+                this.publicKey = dest.parameters().get("PUB");
+                this.privateKey = dest.parameters().get("PRIV");
+            }
             // setup our session
             int i = 0;
             String status;
@@ -70,7 +87,7 @@ public class I2PSamControl extends I2PSamStateMachine {
                     ).put(
                         "ID", this.id
                     ).put(
-                        "DESTINATION", privkey
+                        "DESTINATION", this.privateKey
                     ).put(
                         "inbound.length", this.zeroHop ? "0" : "1"
                     ).put(
@@ -88,7 +105,19 @@ public class I2PSamControl extends I2PSamStateMachine {
                     ).build()
                 ));
             } while ("DUPLICATED_ID".equals(status = this.getCommand("SESSION", "STATUS").parameters().get("RESULT")));
-            return "OK".equals(status);
+            if ("OK".equals(status)) {
+                // find our public key (if we're using persistent keys)
+                if (this.publicKey == null) {
+                    this.sendCommand(new I2PSamCommand("NAMING", "LOOKUP", ImmutableMap.of("NAME", "ME")));
+                    final var lookup = this.getCommand("NAMING", "REPLY");
+                    if (!"OK".equals(lookup.parameters().get("RESULT"))) {
+                        return false;
+                    }
+                    this.publicKey = lookup.parameters().get("VALUE");
+                }
+                return true;
+            }
+            return false;
         } catch (IOException e) {
             return false;
         }
@@ -111,10 +140,17 @@ public class I2PSamControl extends I2PSamStateMachine {
     }
 
     /**
-     * Returns the session pubkey.
+     * Returns the session public key.
      */
-    public String pubkey() {
-        return this.pubkey;
+    public String publicKey() {
+        return this.publicKey;
+    }
+
+    /**
+     * Returs the session private key.
+     */
+    public String privateKey() {
+        return this.privateKey;
     }
 
     /**
@@ -123,15 +159,39 @@ public class I2PSamControl extends I2PSamStateMachine {
      * @throws IOException If an I/O error occurs.
      */
     private void enableAuth() throws IOException {
-        // enable auth (if it hasn't been explicitly disabled by the user), for the user's sake
+        // enable auth (if it hasn't been explicitly disabled by the user), for
+        // the user's sake
         // (ugh i2p you really fucked up by not making this the default)
-        // btw tor does this with a filesystem path to an auth cookie (which is much more secure) but we digress
+        // btw tor does this with a filesystem path to an auth cookie (which is
+        // much more secure) but we digress.
+        // check if we're already using strong auth.
+        if (I2PSamAuthUtil.isStrongAuth()) {
+            return;
+        }
+        // attempt to delete old user - versions 1.0.0 and 1.0.1 of the mod are
+        // deprecated, unsupported, and should never be used.
+        this.sendCommand(new I2PSamCommand(
+            "AUTH", "REMOVE",
+            ImmutableMap.of("USER", "minecraft_friendcode")
+        ));
+        // check if removed.
+        var removed = "OK".equals(
+            this.getCommand("AUTH", "STATUS").parameters().get("RESULT")
+        );
+        // generate secure auth pair
+        var auth = I2PSamAuthUtil.upgradeAuthPair();
+        // add new user
         this.sendCommand(new I2PSamCommand(
             "AUTH", "ADD",
-            ImmutableMap.of("USER", "minecraft_friendcode", "PASSWORD", "friendcode")
+            ImmutableMap.of("USER", auth.user(), "PASSWORD", auth.password())
         ));
-        // if the user already exists, don't enable auth
-        if ("OK".equals(this.getCommand("AUTH", "STATUS").parameters().get("RESULT"))) {
+        if ("OK".equals(
+            this.getCommand("AUTH", "STATUS").parameters().get("RESULT")
+        )) {
+            // old user existed - don't mess with auth enable.
+            if (removed) {
+                return;
+            }
             this.sendCommand(new I2PSamCommand("AUTH", "ENABLE"));
             // ignore the response on this one, just get it off the queue
             this.getCommand("AUTH", "STATUS");
diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java b/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java
index 1c55bc6..57424dd 100644
--- a/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java
+++ b/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java
@@ -13,22 +13,12 @@ import java.util.Iterator;
 import java.util.Objects;
 
 public abstract class I2PSamStateMachine implements Closeable {
-    // FIXME user/password from config
-    private static final I2PSamCommand HELLO_MESSAGE = new I2PSamCommand(
-        "HELLO", "VERSION",
-        ImmutableMap.of(
-            "MIN", "3.2",
-            "USER", "minecraft_friendcode",
-            "PASSWORD", "friendcode"
-        )
-    );
     private Socket samSocket;
     private PushbackReader reader;
     private OutputStreamWriter writer;
     private final ArrayDeque<I2PSamCommand> queue = new ArrayDeque<>();
     private boolean started;
     private boolean connected;
-    private String id;
 
     protected I2PSamStateMachine() {
     }
@@ -69,8 +59,15 @@ public abstract class I2PSamStateMachine implements Closeable {
 
 
             // send HELLO
-            // FIXME let the user change the credentials
-            this.sendCommand(HELLO_MESSAGE);
+            var auth = I2PSamAuthUtil.getAuthPair();
+            this.sendCommand(new I2PSamCommand(
+                "HELLO", "VERSION",
+                ImmutableMap.of(
+                    "MIN", "3.2",
+                    "USER", auth.user(),
+                    "PASSWORD", auth.password()
+                )
+            ));
             return "OK".equals(this.getCommand("HELLO", "REPLY").parameters().get("RESULT"));
         } catch (IOException e) {
             return false;