]> jfr.im git - irc/rizon/acid.git/commitdiff
Cloak support
authorOrillion <redacted>
Sat, 29 Aug 2015 15:37:02 +0000 (11:37 -0400)
committerAdam <redacted>
Sat, 29 Aug 2015 15:37:02 +0000 (11:37 -0400)
.gitmodules
acid/acidictive.example.yml
acid/src/main/java/net/rizon/acid/conf/ServerInfo.java
acid/src/main/java/net/rizon/acid/core/Acidictive.java
acid/src/main/java/net/rizon/acid/core/User.java
acid/src/main/java/net/rizon/acid/util/CloakGenerator.java [new file with mode: 0644]

index 004735fedee692fc026e9a0432c11ee7f6d5c2dc..27e66900e010634a751a7dacc10c08d706d5f875 100644 (file)
@@ -1,3 +1,4 @@
 [submodule "pyva/pyva-native"]
        path = pyva/pyva-native
        url = git@gitlab.com:rizon/pyva
 [submodule "pyva/pyva-native"]
        path = pyva/pyva-native
        url = git@gitlab.com:rizon/pyva
+       ignore = dirty
\ No newline at end of file
index f935915538a2973d977e9da9d524ae0bc19778f6..0fa154b0d414e1ce10d01440aa40a3313a9ff705 100644 (file)
@@ -7,6 +7,11 @@ serverinfo:
   name: acid.rizon.net
   description: (H) Rizon Services
   id: 97S
   name: acid.rizon.net
   description: (H) Rizon Services
   id: 97S
+  network: Rizon
+  cloakkeys:
+   - "SuperFancyKey1"
+   - "SuperFancyKey2"
+   - "SuperFancyKey3"
 
 uplink:
   host: 192.168.1.2
 
 uplink:
   host: 192.168.1.2
index 4594e2799e48056f1a14cea06dc610b7b1416461..42ee9ba82329050784ed13feac01132bc6b5143b 100644 (file)
@@ -1,8 +1,12 @@
 package net.rizon.acid.conf;
 
 package net.rizon.acid.conf;
 
+import net.rizon.acid.util.CloakGenerator;
+
 public class ServerInfo implements Validatable
 {
        public String name, description, id;
 public class ServerInfo implements Validatable
 {
        public String name, description, id;
+       public String network;
+       public String[] cloakkeys;
 
        @Override
        public void validate() throws ConfigException
 
        @Override
        public void validate() throws ConfigException
@@ -10,5 +14,18 @@ public class ServerInfo implements Validatable
                Validator.validateNotEmpty("name", name);
                Validator.validateNotEmpty("description", description);
                Validator.validateNotEmpty("id", id);
                Validator.validateNotEmpty("name", name);
                Validator.validateNotEmpty("description", description);
                Validator.validateNotEmpty("id", id);
+               Validator.validateNotEmpty("network", network);
+               Validator.validateNotNull("cloakkeys", cloakkeys);
+               if (cloakkeys.length != 3)
+               {
+                       throw new ConfigException("Cloak system must have 3 keys.");
+               }
+               for (int i = 0; i < 3; i++)
+               {
+                       if (!CloakGenerator.validateCloakKey(cloakkeys[i]))
+                       {
+                               throw new ConfigException("Cloak key " + (i + 1) + " is not random enough.");
+                       }
+               }
        }
 }
\ No newline at end of file
        }
 }
\ No newline at end of file
index 0562bb12dcc4c900d5a03b12c936133f600a293c..0d842f4bd96eb05659c0fc06c7c67232dc111232 100644 (file)
@@ -5,6 +5,7 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.net.UnknownHostException;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.security.NoSuchAlgorithmException;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -20,11 +21,13 @@ import net.rizon.acid.conf.Config;
 import net.rizon.acid.sql.SQL;
 import net.rizon.acid.util.Blowfish;
 import net.rizon.acid.util.ClassLoader;
 import net.rizon.acid.sql.SQL;
 import net.rizon.acid.util.Blowfish;
 import net.rizon.acid.util.ClassLoader;
+import net.rizon.acid.util.CloakGenerator;
 import net.rizon.acid.util.Util;
 
 public class Acidictive extends AcidCore
 {
        private static final Logger log = Logger.getLogger(Acidictive.class.getName());
 import net.rizon.acid.util.Util;
 
 public class Acidictive extends AcidCore
 {
        private static final Logger log = Logger.getLogger(Acidictive.class.getName());
+       public static CloakGenerator cloakGenerator;
        public static Config conf;
        public static long startTime;
        public static int syncTime;
        public static Config conf;
        public static long startTime;
        public static int syncTime;
@@ -54,6 +57,16 @@ public class Acidictive extends AcidCore
                        log.log(Level.CONFIG, "Unable to start due to configuration problems", ex);
                        System.exit(1);
                }
                        log.log(Level.CONFIG, "Unable to start due to configuration problems", ex);
                        System.exit(1);
                }
+               
+               try
+               {
+                       cloakGenerator = new CloakGenerator(conf.serverinfo.cloakkeys, conf.serverinfo.network + "-");
+               }
+               catch (NoSuchAlgorithmException ex)
+               {
+                       log.log(Level.SEVERE, "Unable to load MD5 digest");
+                       System.exit(1);
+               }
 
                AcidCore.start(conf.uplink.host, conf.uplink.port, conf.serverinfo.name, conf.serverinfo.description, conf.uplink.pass, conf.serverinfo.id, conf.uplink.ssl);
 
 
                AcidCore.start(conf.uplink.host, conf.uplink.port, conf.serverinfo.name, conf.serverinfo.description, conf.uplink.pass, conf.serverinfo.id, conf.uplink.ssl);
 
index 077fc6f73b7d600bf3c0157e10b9cc13927bbfb6..cbc15f0593cbfdf4a4bbd92bd3016b78a0763616 100644 (file)
@@ -13,12 +13,14 @@ import java.util.Set;
 import java.util.logging.Level;
 
 import net.rizon.acid.conf.AccessPreset;
 import java.util.logging.Level;
 
 import net.rizon.acid.conf.AccessPreset;
+import net.rizon.acid.util.CloakGenerator;
 
 public class User implements Comparable<User>
 {
        private static final Logger log = Logger.getLogger(User.class.getName());
        private String nick, user, host, vhost, name, identnick, modes, UID, ip,
                        certfp, authflags, su;
 
 public class User implements Comparable<User>
 {
        private static final Logger log = Logger.getLogger(User.class.getName());
        private String nick, user, host, vhost, name, identnick, modes, UID, ip,
                        certfp, authflags, su;
+       private String cloakedIp, cloakedHost;
        private int nickTS, conTS;
        private Server server;
        private String flags; // Access flags
        private int nickTS, conTS;
        private Server server;
        private String flags; // Access flags
@@ -42,6 +44,25 @@ public class User implements Comparable<User>
                this.modes = modes;
                this.UID = UID;
                this.ip = ip;
                this.modes = modes;
                this.UID = UID;
                this.ip = ip;
+               // Check if this IP isn't spoofed.
+               if (!this.ip.equals("255.255.255.255") && !this.ip.equals("0"))
+               {
+                       // Cloak IP if IP is visible host, else cloak hostname.
+                       this.cloakedIp = Acidictive.cloakGenerator.cloak(this.ip);
+                       if (this.ip.equals(this.host))
+                       {
+                               this.cloakedHost = this.cloakedIp;
+                       }
+                       else
+                       {
+                               this.cloakedHost = Acidictive.cloakGenerator.cloak(this.host);
+                       }
+               }
+               else
+               {
+                       this.cloakedIp = this.ip;
+                       this.cloakedHost = this.host;
+               }
                this.flags = "";
                this.certfp = "";
                this.authflags = "";
                this.flags = "";
                this.certfp = "";
                this.authflags = "";
@@ -302,6 +323,16 @@ public class User implements Comparable<User>
 
                return true;
        }
 
                return true;
        }
+       
+       public String getCloakedIp()
+       {
+               return this.cloakedIp;
+       }
+       
+       public String getCloakedHost()
+       {
+               return this.cloakedHost;
+       }
 
        public String getFlags()
        {
 
        public String getFlags()
        {
@@ -352,12 +383,12 @@ public class User implements Comparable<User>
        @Override
        public String toString()
        {
        @Override
        public String toString()
        {
-               return nick + "!" + user + "@" + host + "/" + ip + "/" + "(" + name + "\ f)[" + server + "] / " + oldNicksList();
+               return nick + "!" + user + "@" + host + "/" + ip + "/" + this.cloakedIp + "/" + this.cloakedHost + "/" + "(" + name + ")[" + server + "] / " + oldNicksList();
        }
 
        public String getNickString()
        {
        }
 
        public String getNickString()
        {
-               return nick + "!" + user + "@" + host + "/" + ip + "/" + vhost + "(" + name + "\ f)";
+               return nick + "!" + user + "@" + host + "/" + ip + "/" + this.cloakedIp + "/" + this.cloakedHost + "/" + vhost + "(" + name + ")";
        }
 
        public String getSNString()
        }
 
        public String getSNString()
@@ -367,12 +398,12 @@ public class User implements Comparable<User>
 
        public String getNString()
        {
 
        public String getNString()
        {
-               return nick + "!" + user + "@" + host + "/" + ip + "/" + vhost + "(" + name + "\ f)(" + modes + ")[" + server + "]";
+               return nick + "!" + user + "@" + host + "/" + ip + "/" + this.cloakedIp + "/" + this.cloakedHost + "/" + vhost + "(" + name + ")(" + modes + ")[" + server + "]";
        }
 
        public String getNStringON()
        {
        }
 
        public String getNStringON()
        {
-               return nick + "!" + user + "@" + host + "/" + ip + "/" + vhost + "(" + name + "\ f)(" + modes + ")[" + server + "] / " + oldNicksList();
+               return nick + "!" + user + "@" + host + "/" + ip + "/" + this.cloakedIp + "/" + this.cloakedHost + "/" + vhost + "(" + name + ")(" + modes + ")[" + server + "] / " + oldNicksList();
        }
 
        public String getNickLine()
        }
 
        public String getNickLine()
diff --git a/acid/src/main/java/net/rizon/acid/util/CloakGenerator.java b/acid/src/main/java/net/rizon/acid/util/CloakGenerator.java
new file mode 100644 (file)
index 0000000..fe27d35
--- /dev/null
@@ -0,0 +1,263 @@
+package net.rizon.acid.util;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * CloakGenerator that generates a cloaked host from an input IP or hostname.
+ *
+ * @author Shiz
+ */
+public class CloakGenerator
+{
+
+       protected String[] keys;
+       protected String hostPrefix;
+       protected MessageDigest digest;
+
+       public static boolean validateCloakKey(final String key)
+       {
+               boolean hasLowerCase = false;
+               boolean hasUpperCase = false;
+               boolean hasDigit = false;
+
+               for (int i = 0; i < key.length(); i++)
+               {
+                       char c = key.charAt(i);
+                       if (!hasLowerCase && Character.isLowerCase(c))
+                       {
+                               hasLowerCase = true;
+                       }
+                       if (!hasUpperCase && Character.isUpperCase(c))
+                       {
+                               hasUpperCase = true;
+                       }
+                       if (!hasDigit && Character.isDigit(c))
+                       {
+                               hasDigit = true;
+                       }
+                       if (hasLowerCase && hasUpperCase && hasDigit)
+                       {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       public CloakGenerator(String[] keys, String hostPrefix) throws NoSuchAlgorithmException
+       {
+               this.keys = keys;
+               this.hostPrefix = hostPrefix;
+               this.digest = MessageDigest.getInstance("MD5");
+       }
+
+       /**
+        * * Cloak an arbitrary host. Will do detection to figure out what kind
+        * of host.
+        */
+       public String cloak(String host)
+       {
+               /* IPv6. */
+               if (host.contains(":"))
+               {
+                       /* Plexus' cloaking code has an IPv6 parsing bug where it stops scanning at the first ::. */
+                       int cutoff = host.indexOf("::");
+                       if (cutoff != -1)
+                       {
+                               host = host.substring(0, cutoff + 2);
+                       }
+
+                       try
+                       {
+                               return this.cloak(Inet6Address.getByName(host));
+                       }
+                       catch (UnknownHostException e)
+                       {
+                               return null;
+                       }
+                       /* IPv4 or host. */
+               }
+               else if (host.contains("."))
+               {
+                       for (char c : host.toCharArray())
+                       {
+                               if (c != '.' && !Character.isDigit(c)) /* Host. */ {
+                                       return this.cloakHost(host);
+                               }
+                       }
+                       /* IPv4. */
+                       try
+                       {
+                               return this.cloak(Inet4Address.getByName(host));
+                       }
+                       catch (UnknownHostException e)
+                       {
+                               return null;
+                       }
+                       /* Host */
+               }
+               else
+               {
+                       return this.cloakHost(host);
+               }
+       }
+
+       /**
+        * Cloak an internet address.
+        */
+       public String cloak(InetAddress addr)
+       {
+               if (addr instanceof Inet4Address)
+               {
+                       return this.cloakIPv4((Inet4Address) addr);
+               }
+               else if (addr instanceof Inet6Address)
+               {
+                       return this.cloakIPv6((Inet6Address) addr);
+               }
+               return null;
+       }
+
+       /**
+        * Cloak an IPv4 address.
+        *
+        * Cloak format: A.B.C.D -> ALPHA.BETA.GAMMA.IP
+        *
+        * ALPHA: downmix(md5(md5(keys[1]:A.B.C.D:keys[2])keys[0])) BETA:
+        * downmix(md5(md5(keys[2]:A.B.C:keys[0])keys[1])) GAMMA:
+        * downmix(md5(md5(keys[0]:A.B:keys[1])keys[2]))
+        */
+       public String cloakIPv4(Inet4Address addr)
+       {
+               /* Gather IP pieces. */
+               byte[] bytePieces = addr.getAddress();
+               int[] pieces = new int[bytePieces.length];
+               for (int i = 0; i < bytePieces.length; i++)
+               {
+                       pieces[i] = bytePieces[i] & 0xFF;
+               }
+
+               /* Generate alpha. */
+               String alphaInput = String.format("%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
+               String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
+
+               /* Generate beta. */
+               String betaInput = String.format("%d.%d.%d", pieces[0], pieces[1], pieces[2]);
+               String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
+
+               /* Generate gamma. */
+               String gammaInput = String.format("%d.%d", pieces[0], pieces[1]);
+               String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
+
+               return alpha + "." + beta + "." + gamma + ".IP";
+       }
+
+       /**
+        * Cloak an IPv6 address.
+        *
+        * Cloak format: a:b:c:d:e:f:g:h -> ALPHA:BETA:GAMMA:IP
+        *
+        * ALPHA: downmix(md5(md5(keys[1]:a:b:c:d:e:f:g:h:keys[2])keys[0]))
+        * BETA: downmix(md5(md5(keys[2]:a:b:c:d:e:f:g:keys[0])keys[1])) GAMMA:
+        * downmix(md5(md5(keys[0]:a:b:c:d:keys[1])keys[2]))
+        */
+       public String cloakIPv6(Inet6Address addr)
+       {
+               /* Gather IP pieces. */
+               byte[] bytePieces = addr.getAddress();
+               int[] pieces = new int[bytePieces.length / 2];
+               System.out.println("Byte length: " + bytePieces.length);
+               for (int i = 0; i < bytePieces.length; i += 2)
+               {
+                       pieces[i / 2] = ((bytePieces[i] & 0xFF) << 8) | (bytePieces[i + 1] & 0xFF);
+               }
+
+               /* Generate alpha. */
+               String alphaInput = String.format("%x:%x:%x:%x:%x:%x:%x:%x",
+                       pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7]);
+               String alpha = this.stage(alphaInput, this.keys[1], this.keys[2], this.keys[0]);
+
+               /* Generate beta. */
+               String betaInput = String.format("%x:%x:%x:%x:%x:%x:%x",
+                       pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6]);
+               String beta = this.stage(betaInput, this.keys[2], this.keys[0], this.keys[1]);
+
+               /* Generate gamma. */
+               String gammaInput = String.format("%x:%x:%x:%x", pieces[0], pieces[1], pieces[2], pieces[3]);
+               String gamma = this.stage(gammaInput, this.keys[0], this.keys[1], this.keys[2]);
+
+               return alpha + ":" + beta + ":" + gamma + ":IP";
+       }
+
+       /**
+        * Cloak a reverse DNS host.
+        *
+        * Cloak format: my.host.com -> PREFIX-ALPHA.host.com localhost ->
+        * PREFIX-ALPHA
+        *
+        * ALPHA: downmix(md5(md5(keys[0]:my.host.com:keys[1])keys[2]))
+        */
+       public String cloakHost(String host)
+       {
+               int dot = host.indexOf('.');
+               String alpha = this.stage(host, this.keys[0], this.keys[1], this.keys[2]);
+
+               if (dot == -1 || dot == host.length() - 1)
+               {
+                       return this.hostPrefix + alpha;
+               }
+               else
+               {
+                       return this.hostPrefix + alpha + host.substring(dot);
+               }
+       }
+
+       /**
+        * Perform a downmix(md5(md5(key1:host:key2)key3)) stage.
+        */
+       protected String stage(String input, String key1, String key2, String key3)
+       {
+               String salted = String.format("%s:%s:%s", key1, input, key2);
+               byte[] firstMD5 = this.md5sum(salted);
+               byte[] secondMD5 = this.md5sum(firstMD5, key3.getBytes());
+               return this.downmix(secondMD5);
+       }
+
+       /**
+        * Mix the 128 bits of the MD5 hash into a single 32-bit value.
+        */
+       protected String downmix(byte[] input)
+       {
+               byte[] res = new byte[4];
+               res[0] = (byte) (input[0] ^ input[1] ^ input[2] ^ input[3]);
+               res[1] = (byte) (input[4] ^ input[5] ^ input[6] ^ input[7]);
+               res[2] = (byte) (input[8] ^ input[9] ^ input[10] ^ input[11]);
+               res[3] = (byte) (input[12] ^ input[13] ^ input[14] ^ input[15]);
+
+               int r = (((int) res[0] & 0xFFFFFFFF) << 24)
+                       + (((int) res[1] & 0xFFFFFFFF) << 16)
+                       + (((int) res[2] & 0xFFFFFFFF) << 8)
+                       + ((int) res[3] & 0xFFFFFFFF);
+               return String.format("%X", r);
+       }
+
+       protected byte[] md5sum(String input)
+       {
+               return this.md5sum(input.getBytes());
+       }
+
+       protected byte[] md5sum(byte[]... input)
+       {
+               this.digest.reset();
+               for (byte[] b : input)
+               {
+                       this.digest.update(b);
+               }
+               return this.digest.digest();
+       }
+}