From 37d3204e433a887f2ba8e0776f056b7cde7e236c Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Wed, 16 Nov 2016 18:01:24 +0000
Subject: [PATCH] Router: Add methods to verify and track members of our
 family; use on sybil page

---
 .../src/net/i2p/router/web/SybilRenderer.java | 19 ++++-
 .../i2p/router/crypto/FamilyKeyCrypto.java    | 75 ++++++++++++++++++-
 2 files changed, 89 insertions(+), 5 deletions(-)

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
index 98671a146f..fb45e0cb27 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
 import java.math.BigInteger;
+import java.text.Collator;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -24,6 +25,7 @@ import net.i2p.data.router.RouterInfo;
 import net.i2p.data.router.RouterKeyGenerator;
 import net.i2p.router.RouterContext;
 import net.i2p.router.TunnelPoolSettings;
+import net.i2p.router.crypto.FamilyKeyCrypto;
 import net.i2p.router.peermanager.DBHistory;
 import net.i2p.router.peermanager.PeerProfile;
 import net.i2p.router.tunnel.pool.TunnelPool;
@@ -61,6 +63,8 @@ class SybilRenderer {
     private static final double POINTS_US24 = 25.0;
     private static final double POINTS_US16 = 10.0;
     private static final double POINTS_FAMILY = -2.0;
+    private static final double POINTS_BAD_OUR_FAMILY = 100.0;
+    private static final double POINTS_OUR_FAMILY = -100.0;
     private static final double MIN_CLOSE = 242.0;
     private static final double PAIR_DISTANCE_FACTOR = 2.0;
     private static final double OUR_KEY_FACTOR = 4.0;
@@ -370,6 +374,7 @@ class SybilRenderer {
 
     private static class FoofComparator implements Comparator<String>, Serializable {
          private final ObjectCounter<String> _o;
+         private final Collator _comp = Collator.getInstance();
          public FoofComparator(ObjectCounter<String> o) { _o = o;}
          public int compare(String l, String r) {
              // reverse by count
@@ -377,7 +382,7 @@ class SybilRenderer {
              if (rv != 0)
                  return rv;
              // foward by name
-             return l.compareTo(r);
+             return _comp.compare(l, r);
         }
     }
 
@@ -578,6 +583,8 @@ class SybilRenderer {
         }
         List<String> foo = new ArrayList<String>(oc.objects());
         Collections.sort(foo, new FoofComparator(oc));
+        FamilyKeyCrypto fkc = _context.router().getFamilyKeyCrypto();
+        String ourFamily = fkc != null ? fkc.getOurFamilyName() : null;
         boolean found = false;
         for (String s : foo) {
             int count = oc.count(s);
@@ -593,10 +600,16 @@ class SybilRenderer {
                 // limit display
                 //renderRouterInfo(buf, info, null, false, false);
                 double point = POINTS_FAMILY;
-                if (count > 1)
+                if (fkc != null && s.equals(ourFamily)) {
+                    if (fkc.verifyOurFamily(info))
+                        addPoints(points, info.getHash(), POINTS_OUR_FAMILY, "Our family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
+                    else
+                        addPoints(points, info.getHash(), POINTS_BAD_OUR_FAMILY, "Spoofed our family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
+                } else if (count > 1) {
                     addPoints(points, info.getHash(), point, "Same declared family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
-                else
+                } else {
                     addPoints(points, info.getHash(), point, "Declared family \"" + DataHelper.escapeHTML(s) + '"');
+                }
             }
         }
         if (!found)
diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
index cdb0811dc7..f1bfe2063a 100644
--- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
+++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
@@ -10,6 +10,7 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509CRL;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -44,6 +45,7 @@ public class FamilyKeyCrypto {
     private final Log _log;
     private final Map<Hash, String> _verified;
     private final Set<Hash> _negativeCache;
+    private final Set<Hash> _ourFamily;
     // following for verification only, otherwise null
     private final String _fname;
     private final SigningPrivateKey _privkey;
@@ -82,13 +84,15 @@ public class FamilyKeyCrypto {
         _fname = _context.getProperty(PROP_FAMILY_NAME);
         if (_fname != null) {
             if (_fname.contains("/") || _fname.contains("\\") ||
-                _fname.contains("..") || (new File(_fname)).isAbsolute())
-                throw new GeneralSecurityException("Illegal family name");
+                _fname.contains("..") || (new File(_fname)).isAbsolute() ||
+                _fname.length() <= 0)
+                throw new GeneralSecurityException("Illegal family name: " + _fname);
         }
         _privkey = (_fname != null) ? initialize() : null;
         _pubkey = (_privkey != null) ? _privkey.toPublic() : null;
         _verified = new ConcurrentHashMap<Hash, String>(4);
         _negativeCache = new ConcurrentHashSet<Hash>(4);
+        _ourFamily = (_privkey != null) ? new ConcurrentHashSet<Hash>(4) : Collections.<Hash>emptySet();
     }
     
     /** 
@@ -144,6 +148,35 @@ public class FamilyKeyCrypto {
         return rv;
     }
 
+    /** 
+     *  Do we have a valid family?
+     *  @since 0.9.28
+     */
+    public boolean hasFamily() {
+        return _pubkey != null;
+    }
+
+    /** 
+     *  Get verified members of our family.
+     *  Will not contain ourselves.
+     *
+     *  @return non-null, not a copy, do not modify
+     *  @since 0.9.28
+     */
+    public Set<Hash> getOurFamily() {
+        return _ourFamily;
+    }
+
+    /** 
+     *  Get our family name.
+     *
+     *  @return name or null
+     *  @since 0.9.28
+     */
+    public String getOurFamilyName() {
+        return _fname;
+    }
+
     /** 
      *  Verify the family signature in a RouterInfo.
      *  @return true if good sig or if no family specified at all
@@ -152,6 +185,44 @@ public class FamilyKeyCrypto {
         String name = ri.getOption(OPT_NAME);
         if (name == null)
             return true;
+        return verify(ri, name);
+    }
+
+    /** 
+     *  Verify the family in a RouterInfo matches ours and the signature is good.
+     *  Returns false if we don't have a family and sig, or they don't.
+     *  Returns false for ourselves.
+     *
+     *  @return true if family matches with good sig
+     *  @since 0.9.28
+     */
+    public boolean verifyOurFamily(RouterInfo ri) {
+        if (_pubkey == null)
+            return false;
+        String name = ri.getOption(OPT_NAME);
+        if (!_fname.equals(name))
+            return false;
+        Hash h = ri.getHash();
+        if (_ourFamily.contains(h))
+            return true;
+        if (h.equals(_context.routerHash()))
+            return false;
+        boolean rv = verify(ri, name);
+        if (rv) {
+            _ourFamily.add(h);
+            _log.logAlways(Log.INFO, "Found and verified member of our family (" + _fname + "): " + h);
+        } else {
+            if (_log.shouldWarn())
+                _log.warn("Found spoofed member of our family (" + _fname + "): " + h);
+        }
+        return rv;
+    }
+
+    /** 
+     *  Verify the family in a RouterInfo, name already retrieved
+     *  @since 0.9.28
+     */
+    private boolean verify(RouterInfo ri, String name) {
         Hash h = ri.getHash();
         String ssig = ri.getOption(OPT_SIG);
         if (ssig == null) {
-- 
GitLab