summary refs log tree commit diff stats
path: root/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ganarchy/friendcode/sam/I2PSamControl.java')
-rw-r--r--src/main/java/ganarchy/friendcode/sam/I2PSamControl.java102
1 files changed, 81 insertions, 21 deletions
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");