From 2852383e4e0e4bc7474a80bc0ffdeacfcf5b11e1 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Fri, 11 Dec 2015 15:10:08 +0000
Subject: [PATCH] Router: Fix family verification after testing, partially hook
 into netdb store() Always use our pubkey to verify our family Rework caching
 strategy

---
 .../i2p/router/crypto/FamilyKeyCrypto.java    | 63 +++++++++++++------
 .../KademliaNetworkDatabaseFacade.java        | 10 +++
 2 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
index c003536745..8af392f1c6 100644
--- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
+++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
@@ -41,19 +41,21 @@ public class FamilyKeyCrypto {
     private final RouterContext _context;
     private final Log _log;
     private final Map<Hash, String> _verified;
-    private final Set<String> _negativeCache;
+    private final Set<Hash> _negativeCache;
     // following for verification only, otherwise null
     private final String _fname;
     private final SigningPrivateKey _privkey;
+    private final SigningPublicKey _pubkey;
 
     private static final String PROP_KEYSTORE_PASSWORD = "netdb.family.keystorePassword";
     public static final String PROP_FAMILY_NAME = "netdb.family.name";
-    private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
     private static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword";
     private static final String CERT_SUFFIX = ".crt";
     private static final String KEYSTORE_PREFIX = "family-";
     private static final String KEYSTORE_SUFFIX = ".ks";
     private static final int DEFAULT_KEY_VALID_DAYS = 3652;  // 10 years
+    // Note that we can't use RSA here, as the b64 sig would exceed the 256 char limit for a Mapping
+    // Note that we can't use EdDSA here, as keystore doesn't know how, and encoding/decoding is unimplemented
     private static final String DEFAULT_KEY_ALGORITHM = SigType.ECDSA_SHA256_P256.isAvailable() ? "EC" : "DSA";
     private static final int DEFAULT_KEY_SIZE = SigType.ECDSA_SHA256_P256.isAvailable() ? 256 : 1024;
     private static final String KS_DIR = "keystore";
@@ -78,8 +80,9 @@ public class FamilyKeyCrypto {
                 throw new GeneralSecurityException("Illegal family name");
         }
         _privkey = (_fname != null) ? initialize() : null;
+        _pubkey = (_privkey != null) ? _privkey.toPublic() : null;
         _verified = new ConcurrentHashMap<Hash, String>(4);
-        _negativeCache = new ConcurrentHashSet<String>(4);
+        _negativeCache = new ConcurrentHashSet<Hash>(4);
     }
     
     /** 
@@ -135,10 +138,13 @@ public class FamilyKeyCrypto {
         String name = ri.getOption(OPT_NAME);
         if (name == null)
             return true;
-        String ssig = ri.getOption("OPT_SIG");
-        if (ssig == null)
-            return false;
         Hash h = ri.getHash();
+        String ssig = ri.getOption(OPT_SIG);
+        if (ssig == null) {
+            if (_log.shouldInfo())
+                _log.info("No sig for " + h + ' ' + name);
+            return false;
+        }
         String nameAndSig = _verified.get(h);
         String riNameAndSig = name + ssig;
         if (nameAndSig != null) {
@@ -147,30 +153,49 @@ public class FamilyKeyCrypto {
             // name or sig changed
             _verified.remove(h);
         }
-        if (_negativeCache.contains(name))
-            return false;
-        SigningPublicKey spk = loadCert(name);
-        if (spk == null) {
-            _negativeCache.add(name);
-            return false;
+        SigningPublicKey spk;
+        if (name.equals(_fname)) {
+            // us
+            spk = _pubkey;
+        } else {
+            if (_negativeCache.contains(h))
+                return false;
+            spk = loadCert(name);
+            if (spk == null) {
+                _negativeCache.add(h);
+                if (_log.shouldInfo())
+                    _log.info("No cert for " + h + ' ' + name);
+                return false;
+            }
         }
         byte[] bsig = Base64.decode(ssig);
-        if (bsig == null)
+        if (bsig == null) {
+            _negativeCache.add(h);
+            if (_log.shouldInfo())
+                _log.info("Bad sig for " + h + ' ' + name + ' ' + ssig);
             return false;
+        }
         Signature sig;
         try {
             sig = new Signature(spk.getType(), bsig);
         } catch (IllegalArgumentException iae) {
             // wrong size (type mismatch)
+            _negativeCache.add(h);
+            if (_log.shouldInfo())
+                _log.info("Bad sig for " + ri, iae);
             return false;
         }
-        byte[] nb = DataHelper.getUTF8(_fname);
+        byte[] nb = DataHelper.getUTF8(name);
         byte[] b = new byte[nb.length + Hash.HASH_LENGTH];
         System.arraycopy(nb, 0, b, 0, nb.length);
         System.arraycopy(ri.getHash().getData(), 0, b, nb.length, Hash.HASH_LENGTH);
         boolean rv = _context.dsa().verifySignature(sig, b, spk);
         if (rv)
             _verified.put(h, riNameAndSig);
+        else
+            _negativeCache.add(h);
+        if (_log.shouldInfo())
+            _log.info("Verified? " + rv + " for " + h + ' ' + name + ' ' + ssig);
         return rv;
     }
 
@@ -220,14 +245,14 @@ public class FamilyKeyCrypto {
         // and one for the cname
         String cname = _fname + ".family.i2p.net";
 
-        boolean success = KeyStoreUtil.createKeys(ks, DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family",
+        boolean success = KeyStoreUtil.createKeys(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family",
                                                   DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM,
                                                   DEFAULT_KEY_SIZE, keyPassword);
         if (success) {
             success = ks.exists();
             if (success) {
                 Map<String, String> changes = new HashMap<String, String>();
-                changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+                changes.put(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
                 changes.put(PROP_KEY_PASSWORD, keyPassword);
                 changes.put(PROP_FAMILY_NAME, _fname);
                 _context.router().saveConfig(changes, null);
@@ -239,7 +264,7 @@ public class FamilyKeyCrypto {
                            "Copy the keystore to the other routers in the family,\n" +
                            "and add the following entries to their router.config file:\n" +
                            PROP_FAMILY_NAME + '=' + _fname + '\n' +
-                           PROP_KEYSTORE_PASSWORD + '=' + DEFAULT_KEYSTORE_PASSWORD + '\n' +
+                           PROP_KEYSTORE_PASSWORD + '=' + KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD + '\n' +
                            PROP_KEY_PASSWORD + '=' + keyPassword);
 
         } else {
@@ -259,7 +284,7 @@ public class FamilyKeyCrypto {
     private void exportCert(File ks) {
         File sdir = new SecureDirectory(_context.getConfigDir(), CERT_DIR);
         if (sdir.exists() || sdir.mkdirs()) {
-            String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+            String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
             String name = _fname.replace("@", "_at_") + CERT_SUFFIX;
             File out = new File(sdir, name);
             boolean success = KeyStoreUtil.exportCert(ks, ksPass, _fname, out);
@@ -307,7 +332,7 @@ public class FamilyKeyCrypto {
      * @return non-null, throws on all errors
      */
     private SigningPrivateKey getPrivKey(File ks) throws GeneralSecurityException {
-        String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
+        String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
         String keyPass = _context.getProperty(PROP_KEY_PASSWORD);
         if (keyPass == null)
             throw new GeneralSecurityException("No key password, set " + PROP_KEY_PASSWORD +
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
index 794638708b..09fe911b20 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java
@@ -40,6 +40,7 @@ import net.i2p.router.Job;
 import net.i2p.router.NetworkDatabaseFacade;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
+import net.i2p.router.crypto.FamilyKeyCrypto;
 import net.i2p.router.networkdb.PublishLocalRouterInfoJob;
 import net.i2p.router.networkdb.reseed.ReseedChecker;
 import net.i2p.router.peermanager.PeerProfile;
@@ -894,6 +895,15 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
                 _log.warn("Bad network: " + routerInfo);
             return "Not in our network";
         }
+        FamilyKeyCrypto fkc = _context.router().getFamilyKeyCrypto();
+        if (fkc != null) {
+            boolean validFamily = fkc.verify(routerInfo);
+            if (!validFamily) {
+                if (_log.shouldWarn())
+                    _log.warn("Bad family sig: " + routerInfo);
+            }
+            // todo store in RI
+        }
         return validate(routerInfo);
     }
 
-- 
GitLab