forked from I2P_Developers/i2p.i2p
Router:
- Change addCapabilities() to getCapabilities() - Add netdb family sign/verify utility (ticket #1510) (verify not yet used) RouterInfo: - Remove addCapability() and delCapability() StatPublisher: - Remove Service interface, not required - Consolidate getCapabilities() and network ID here - Add family signatures - Remove unused coreVersion and stat_uptime (as of 0.9.24)
This commit is contained in:
@@ -434,44 +434,6 @@ public class RouterInfo extends DatabaseEntry {
|
||||
return (bwTier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning, must be called AFTER setOptions().
|
||||
*
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
*/
|
||||
public void addCapability(char cap) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
String caps = _options.getProperty(PROP_CAPABILITIES);
|
||||
if (caps == null)
|
||||
_options.setProperty(PROP_CAPABILITIES, String.valueOf(cap));
|
||||
else if (caps.indexOf(cap) == -1)
|
||||
_options.setProperty(PROP_CAPABILITIES, caps + cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if RouterInfo is already signed
|
||||
* @deprecated unused
|
||||
*/
|
||||
public void delCapability(char cap) {
|
||||
if (_signature != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
String caps = _options.getProperty(PROP_CAPABILITIES);
|
||||
int idx;
|
||||
if (caps == null) {
|
||||
return;
|
||||
} else if ((idx = caps.indexOf(cap)) == -1) {
|
||||
return;
|
||||
} else {
|
||||
StringBuilder buf = new StringBuilder(caps);
|
||||
while ( (idx = buf.indexOf(""+cap)) != -1)
|
||||
buf.deleteCharAt(idx);
|
||||
_options.setProperty(PROP_CAPABILITIES, buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the router was published recently (within the given age milliseconds).
|
||||
* The age should be large enough to take into consideration any clock fudge factor, so
|
||||
|
@@ -10,6 +10,7 @@ package net.i2p.router;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,6 +36,7 @@ import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade.Status;
|
||||
import net.i2p.router.crypto.FamilyKeyCrypto;
|
||||
import net.i2p.router.message.GarlicMessageHandler;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
@@ -88,6 +90,8 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
private final EventLog _eventLog;
|
||||
private final Object _stateLock = new Object();
|
||||
private State _state = State.UNINITIALIZED;
|
||||
private FamilyKeyCrypto _familyKeyCrypto;
|
||||
private boolean _familyKeyCryptoFail;
|
||||
|
||||
public final static String PROP_CONFIG_FILE = "router.configLocation";
|
||||
|
||||
@@ -830,7 +834,7 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
* has changed.
|
||||
*/
|
||||
private void locked_rebuildRouterInfo(boolean blockingRebuild) {
|
||||
RouterInfo ri = null;
|
||||
RouterInfo ri;
|
||||
if (_routerInfo != null)
|
||||
ri = new RouterInfo(_routerInfo);
|
||||
else
|
||||
@@ -839,12 +843,10 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
try {
|
||||
ri.setPublished(_context.clock().now());
|
||||
Properties stats = _context.statPublisher().publishStatistics();
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, NETWORK_ID+"");
|
||||
|
||||
ri.setOptions(stats);
|
||||
ri.setAddresses(_context.commSystem().createAddresses());
|
||||
|
||||
addCapabilities(ri);
|
||||
SigningPrivateKey key = _context.keyManager().getSigningPrivateKey();
|
||||
if (key == null) {
|
||||
_log.log(Log.CRIT, "Internal error - signing private key not known? Impossible?");
|
||||
@@ -864,6 +866,32 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Family Key Crypto Signer / Verifier.
|
||||
* Not for external use.
|
||||
* If family key is set, first call Will take a while to generate keys.
|
||||
* Warning - risk of deadlock - do not call while holding locks
|
||||
* (other than routerInfoLock)
|
||||
*
|
||||
* @return null on initialization failure
|
||||
* @since 0.9.24
|
||||
*/
|
||||
public FamilyKeyCrypto getFamilyKeyCrypto() {
|
||||
synchronized (_routerInfoLock) {
|
||||
if (_familyKeyCrypto == null) {
|
||||
if (!_familyKeyCryptoFail) {
|
||||
try {
|
||||
_familyKeyCrypto = new FamilyKeyCrypto(_context);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
_log.error("Failed to initialize family key crypto", gse);
|
||||
_familyKeyCryptoFail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _familyKeyCrypto;
|
||||
}
|
||||
|
||||
// publicize our ballpark capacity
|
||||
public static final char CAPABILITY_BW12 = 'K';
|
||||
public static final char CAPABILITY_BW32 = 'L';
|
||||
@@ -887,14 +915,11 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
|
||||
/**
|
||||
* For building our RI. Not for external use.
|
||||
* This does not publish the ri.
|
||||
* This does not use anything in the ri (i.e. it can be freshly constructed)
|
||||
*
|
||||
* TODO just return a string instead of passing in the RI? See PublishLocalRouterInfoJob.
|
||||
*
|
||||
* @param ri an unpublished ri we are generating.
|
||||
* @return a capabilities string to be added to the RI
|
||||
*/
|
||||
public void addCapabilities(RouterInfo ri) {
|
||||
String getCapabilities() {
|
||||
StringBuilder rv = new StringBuilder(4);
|
||||
int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
|
||||
_context.bandwidthLimiter().getOutboundKBytesPerSecond());
|
||||
bwLim = (int)(bwLim * getSharePercentage());
|
||||
@@ -903,40 +928,40 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
|
||||
String force = _context.getProperty(PROP_FORCE_BWCLASS);
|
||||
if (force != null && force.length() > 0) {
|
||||
ri.addCapability(force.charAt(0));
|
||||
rv.append(force.charAt(0));
|
||||
} else if (bwLim < 12) {
|
||||
ri.addCapability(CAPABILITY_BW12);
|
||||
rv.append(CAPABILITY_BW12);
|
||||
} else if (bwLim <= 48) {
|
||||
ri.addCapability(CAPABILITY_BW32);
|
||||
rv.append(CAPABILITY_BW32);
|
||||
} else if (bwLim <= 64) {
|
||||
ri.addCapability(CAPABILITY_BW64);
|
||||
rv.append(CAPABILITY_BW64);
|
||||
} else if (bwLim <= 128) {
|
||||
ri.addCapability(CAPABILITY_BW128);
|
||||
rv.append(CAPABILITY_BW128);
|
||||
} else if (bwLim <= 256) {
|
||||
ri.addCapability(CAPABILITY_BW256);
|
||||
rv.append(CAPABILITY_BW256);
|
||||
} else if (bwLim <= 2000) { // TODO adjust threshold
|
||||
// 512 supported as of 0.9.18;
|
||||
// Add 256 as well for compatibility
|
||||
ri.addCapability(CAPABILITY_BW512);
|
||||
ri.addCapability(CAPABILITY_BW256);
|
||||
rv.append(CAPABILITY_BW512);
|
||||
rv.append(CAPABILITY_BW256);
|
||||
} else {
|
||||
// Unlimited supported as of 0.9.18;
|
||||
// Add 256 as well for compatibility
|
||||
ri.addCapability(CAPABILITY_BW_UNLIMITED);
|
||||
ri.addCapability(CAPABILITY_BW256);
|
||||
rv.append(CAPABILITY_BW_UNLIMITED);
|
||||
rv.append(CAPABILITY_BW256);
|
||||
}
|
||||
|
||||
// if prop set to true, don't tell people we are ff even if we are
|
||||
if (_context.netDb().floodfillEnabled() &&
|
||||
!_context.getBooleanProperty("router.hideFloodfillParticipant"))
|
||||
ri.addCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL);
|
||||
rv.append(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL);
|
||||
|
||||
if(_context.getBooleanProperty(PROP_HIDDEN))
|
||||
ri.addCapability(RouterInfo.CAPABILITY_HIDDEN);
|
||||
rv.append(RouterInfo.CAPABILITY_HIDDEN);
|
||||
|
||||
if (_context.getBooleanProperty(PROP_FORCE_UNREACHABLE)) {
|
||||
ri.addCapability(CAPABILITY_UNREACHABLE);
|
||||
return;
|
||||
rv.append(CAPABILITY_UNREACHABLE);
|
||||
return rv.toString();
|
||||
}
|
||||
switch (_context.commSystem().getStatus()) {
|
||||
case OK:
|
||||
@@ -946,13 +971,13 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
case IPV4_DISABLED_IPV6_OK:
|
||||
case IPV4_UNKNOWN_IPV6_OK:
|
||||
case IPV4_SNAT_IPV6_OK:
|
||||
ri.addCapability(CAPABILITY_REACHABLE);
|
||||
rv.append(CAPABILITY_REACHABLE);
|
||||
break;
|
||||
|
||||
case DIFFERENT:
|
||||
case REJECT_UNSOLICITED:
|
||||
case IPV4_DISABLED_IPV6_FIREWALLED:
|
||||
ri.addCapability(CAPABILITY_UNREACHABLE);
|
||||
rv.append(CAPABILITY_UNREACHABLE);
|
||||
break;
|
||||
|
||||
case DISCONNECTED:
|
||||
@@ -966,6 +991,7 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
// no explicit capability
|
||||
break;
|
||||
}
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1178,7 +1204,6 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
try { _context.clientManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the client manager", t); }
|
||||
try { _context.namingService().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the naming service", t); }
|
||||
try { _context.jobQueue().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the job queue", t); }
|
||||
try { _context.statPublisher().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the stats publisher", t); }
|
||||
try { _context.tunnelManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the tunnel manager", t); }
|
||||
try { _context.tunnelDispatcher().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the tunnel dispatcher", t); }
|
||||
try { _context.netDb().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the networkDb", t); }
|
||||
|
@@ -9,6 +9,7 @@ package net.i2p.router;
|
||||
*/
|
||||
|
||||
import java.io.Writer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
@@ -16,6 +17,9 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.crypto.FamilyKeyCrypto;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
@@ -25,15 +29,12 @@ import net.i2p.util.Log;
|
||||
* Publishes some statistics about the router in the netDB.
|
||||
*
|
||||
*/
|
||||
public class StatisticsManager implements Service {
|
||||
public class StatisticsManager {
|
||||
private final Log _log;
|
||||
private final RouterContext _context;
|
||||
|
||||
public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings";
|
||||
private static final String PROP_CONTACT_NAME = "netdb.contact";
|
||||
private static final String PROP_FAMILY_NAME = "netdb.family.name";
|
||||
private static final String PROP_FAMILY_KEY = "netdb.family.key";
|
||||
private static final String PROP_FAMILY_SIG = "netdb.family.sig";
|
||||
/** enhance anonymity by only including build stats one out of this many times */
|
||||
private static final int RANDOM_INCLUDE_STATS = 16;
|
||||
|
||||
@@ -47,20 +48,32 @@ public class StatisticsManager implements Service {
|
||||
_log = context.logManager().getLog(StatisticsManager.class);
|
||||
}
|
||||
|
||||
/** noop */
|
||||
public void shutdown() {}
|
||||
|
||||
/** noop */
|
||||
public void restart() {}
|
||||
|
||||
/** noop */
|
||||
public void startup() {}
|
||||
|
||||
/** Retrieve a snapshot of the statistics that should be published */
|
||||
/**
|
||||
* Retrieve a snapshot of the statistics that should be published.
|
||||
*
|
||||
* This includes all standard options (as of 0.9.24, network ID and caps)
|
||||
*/
|
||||
public Properties publishStatistics() {
|
||||
// if hash is null, will be caught in fkc.sign()
|
||||
return publishStatistics(_context.routerHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a snapshot of the statistics that should be published.
|
||||
*
|
||||
* This includes all standard options (as of 0.9.24, network ID and caps)
|
||||
*
|
||||
* @param current router hash, non-null
|
||||
* @since 0.9.24
|
||||
*/
|
||||
public Properties publishStatistics(Hash h) {
|
||||
Properties stats = new Properties();
|
||||
stats.setProperty("router.version", RouterVersion.VERSION);
|
||||
stats.setProperty("coreVersion", CoreVersion.VERSION);
|
||||
// scheduled for removal, never used
|
||||
if (CoreVersion.VERSION.equals("0.9.23"))
|
||||
stats.setProperty("coreVersion", CoreVersion.VERSION);
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, Integer.toString(Router.NETWORK_ID));
|
||||
stats.setProperty(RouterInfo.PROP_CAPABILITIES, _context.router().getCapabilities());
|
||||
|
||||
// No longer expose, to make build tracking more expensive
|
||||
// stats.setProperty("router.id", RouterVersion.ID);
|
||||
@@ -152,8 +165,10 @@ public class StatisticsManager implements Service {
|
||||
//includeRate("tunnel.acceptLoad", stats, new long[] { 10*60*1000 });
|
||||
}
|
||||
|
||||
// So that we will still get build requests
|
||||
stats.setProperty("stat_uptime", "90m");
|
||||
// So that we will still get build requests - not required since 0.7.9 2010-01-12
|
||||
// scheduled for removal
|
||||
if (CoreVersion.VERSION.equals("0.9.23"))
|
||||
stats.setProperty("stat_uptime", "90m");
|
||||
if (FloodfillNetworkDatabaseFacade.isFloodfill(_context.router().getRouterInfo())) {
|
||||
int ri = _context.router().getUptime() > 30*60*1000 ?
|
||||
_context.netDb().getKnownRouters() :
|
||||
@@ -168,16 +183,32 @@ public class StatisticsManager implements Service {
|
||||
String contact = _context.getProperty(PROP_CONTACT_NAME);
|
||||
if (contact != null)
|
||||
stats.setProperty("contact", contact);
|
||||
String family = _context.getProperty(PROP_FAMILY_NAME);
|
||||
|
||||
String family = _context.getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME);
|
||||
if (family != null) {
|
||||
stats.setProperty("family", family);
|
||||
// TODO
|
||||
//String key = _context.getProperty(PROP_FAMILY_KEY);
|
||||
//if (key != null) {
|
||||
// get privkey
|
||||
// sign something
|
||||
// add b64 sig
|
||||
//}
|
||||
stats.setProperty(FamilyKeyCrypto.OPT_NAME, family);
|
||||
String sig = null;
|
||||
RouterInfo oldRI = _context.router().getRouterInfo();
|
||||
if (oldRI != null) {
|
||||
// don't do it if family changed
|
||||
if (family.equals(oldRI.getOption(FamilyKeyCrypto.OPT_NAME))) {
|
||||
// copy over the signature
|
||||
sig = oldRI.getOption(FamilyKeyCrypto.OPT_SIG);
|
||||
if (sig != null)
|
||||
stats.setProperty(FamilyKeyCrypto.OPT_SIG, sig);
|
||||
}
|
||||
}
|
||||
if (sig == null) {
|
||||
FamilyKeyCrypto fkc = _context.router().getFamilyKeyCrypto();
|
||||
if (fkc != null) {
|
||||
try {
|
||||
sig = fkc.sign(family, h);
|
||||
stats.setProperty(FamilyKeyCrypto.OPT_SIG, sig);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
_log.error("Failed to sign router family", gse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
|
324
router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
Normal file
324
router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
Normal file
@@ -0,0 +1,324 @@
|
||||
package net.i2p.router.crypto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.crypto.CertUtil;
|
||||
import net.i2p.crypto.KeyStoreUtil;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.crypto.SigUtil;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.StatisticsManager;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
* Utilities for creating, storing, retrieving the signing keys for
|
||||
* the netdb family feature
|
||||
*
|
||||
* @since 0.9.24
|
||||
*/
|
||||
public class FamilyKeyCrypto {
|
||||
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final Map<Hash, String> _verified;
|
||||
private final Set<String> _negativeCache;
|
||||
// following for verification only, otherwise null
|
||||
private final String _fname;
|
||||
private final SigningPrivateKey _privkey;
|
||||
|
||||
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
|
||||
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";
|
||||
private static final String CERT_DIR = "certificates/family";
|
||||
public static final String OPT_NAME = "family";
|
||||
public static final String OPT_SIG = "family.sig";
|
||||
|
||||
|
||||
/**
|
||||
* For signing and verification.
|
||||
*
|
||||
* If the context property netdb.family.name is set, this can be used for signing,
|
||||
* else only for verification.
|
||||
*/
|
||||
public FamilyKeyCrypto(RouterContext context) throws GeneralSecurityException {
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(FamilyKeyCrypto.class);
|
||||
_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");
|
||||
}
|
||||
_privkey = (_fname != null) ? initialize() : null;
|
||||
_verified = new ConcurrentHashMap<Hash, String>(4);
|
||||
_negativeCache = new ConcurrentHashSet<String>(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create (if necessary) and load the key store, then run.
|
||||
*/
|
||||
private SigningPrivateKey initialize() throws GeneralSecurityException {
|
||||
File dir = new SecureDirectory(_context.getConfigDir(), KS_DIR);
|
||||
File keyStore = new File(dir, KEYSTORE_PREFIX + _fname + KEYSTORE_SUFFIX);
|
||||
verifyKeyStore(keyStore);
|
||||
return getPrivKey(keyStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the caches
|
||||
*/
|
||||
public void shutdown() {
|
||||
_verified.clear();
|
||||
_negativeCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must add family to RI also.
|
||||
* throws on all errors
|
||||
*
|
||||
* @param family non-null, must match that we were initialized with or will throw GSE
|
||||
* @param h non-null
|
||||
* @return non-null base 64 signature string to be added to the RI
|
||||
* @throws GeneralSecurityException on null hash, null or changed family, or signing error
|
||||
*/
|
||||
public String sign(String family, Hash h) throws GeneralSecurityException {
|
||||
if (_privkey == null)
|
||||
throw new GeneralSecurityException("family name set, must restart router");
|
||||
if (h == null)
|
||||
throw new GeneralSecurityException("null router hash");
|
||||
if (!_fname.equals(family))
|
||||
throw new GeneralSecurityException("family name changed, must restart router");
|
||||
byte[] nb = DataHelper.getUTF8(_fname);
|
||||
int len = nb.length + Hash.HASH_LENGTH;
|
||||
byte[] b = new byte[len];
|
||||
System.arraycopy(nb, 0, b, 0, nb.length);
|
||||
System.arraycopy(h.getData(), 0, b, nb.length, Hash.HASH_LENGTH);
|
||||
Signature sig = _context.dsa().sign(b, _privkey);
|
||||
if (sig == null)
|
||||
throw new GeneralSecurityException("sig failed");
|
||||
return sig.toBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the family signature in a RouterInfo.
|
||||
* @return true if good sig or if no family specified at all
|
||||
*/
|
||||
public boolean verify(RouterInfo ri) {
|
||||
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 nameAndSig = _verified.get(h);
|
||||
String riNameAndSig = name + ssig;
|
||||
if (nameAndSig != null) {
|
||||
if (nameAndSig.equals(riNameAndSig))
|
||||
return true;
|
||||
// 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;
|
||||
}
|
||||
byte[] bsig = Base64.decode(ssig);
|
||||
if (bsig == null)
|
||||
return false;
|
||||
Signature sig;
|
||||
try {
|
||||
sig = new Signature(spk.getType(), bsig);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// wrong size (type mismatch)
|
||||
return false;
|
||||
}
|
||||
byte[] nb = DataHelper.getUTF8(_fname);
|
||||
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);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success if it exists and we have a password, or it was created successfully.
|
||||
* @throws GeneralSecurityException on keystore error
|
||||
*/
|
||||
private void verifyKeyStore(File ks) throws GeneralSecurityException {
|
||||
if (ks.exists()) {
|
||||
if (_context.getProperty(PROP_KEY_PASSWORD) == null) {
|
||||
String s ="Family key error, must set " + PROP_KEY_PASSWORD + " in " +
|
||||
(new File(_context.getConfigDir(), "router.config")).getAbsolutePath();
|
||||
_log.error(s);
|
||||
throw new GeneralSecurityException(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
File dir = ks.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||
if (!sdir.mkdirs()) {
|
||||
String s ="Family key error, must set " + PROP_KEY_PASSWORD + " in " +
|
||||
(new File(_context.getConfigDir(), "router.config")).getAbsolutePath();
|
||||
_log.error(s);
|
||||
throw new GeneralSecurityException(s);
|
||||
}
|
||||
}
|
||||
createKeyStore(ks);
|
||||
|
||||
// Now read it back out of the new keystore and save it in ascii form
|
||||
// where the clients can get to it.
|
||||
exportCert(ks);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call out to keytool to create a new keystore with a keypair in it.
|
||||
* Trying to do this programatically is a nightmare, requiring either BouncyCastle
|
||||
* libs or using proprietary Sun libs, and it's a huge mess.
|
||||
* If successful, stores the keystore password and key password in router.config.
|
||||
*
|
||||
* @throws GeneralSecurityException on all errors
|
||||
*/
|
||||
private void createKeyStore(File ks) throws GeneralSecurityException {
|
||||
// make a random 48 character password (30 * 8 / 5)
|
||||
String keyPassword = KeyStoreUtil.randomString();
|
||||
// and one for the cname
|
||||
String cname = _fname + ".family.i2p.net";
|
||||
|
||||
boolean success = KeyStoreUtil.createKeys(ks, 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_KEY_PASSWORD, keyPassword);
|
||||
changes.put(PROP_FAMILY_NAME, _fname);
|
||||
_context.router().saveConfig(changes, null);
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
_log.logAlways(Log.INFO, "Created new private key for netdb family \"" + _fname +
|
||||
"\" in keystore: " + ks.getAbsolutePath() + "\n" +
|
||||
"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_KEY_PASSWORD + '=' + keyPassword);
|
||||
|
||||
} else {
|
||||
String s = "Failed to create NetDb family keystore.\n" +
|
||||
"This is for the Sun/Oracle keytool, others may be incompatible.\n" +
|
||||
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath();
|
||||
_log.error(s);
|
||||
throw new GeneralSecurityException(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the cert back OUT of the keystore and save it as ascii
|
||||
* so the clients can get to it.
|
||||
*/
|
||||
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 name = _fname.replace("@", "_at_") + CERT_SUFFIX;
|
||||
File out = new File(sdir, name);
|
||||
boolean success = KeyStoreUtil.exportCert(ks, ksPass, _fname, out);
|
||||
if (success) {
|
||||
_log.logAlways(Log.INFO, "Created new public key certificate for netdb family \"" + _fname +
|
||||
"\" in file: " + out.getAbsolutePath() + "\n" +
|
||||
"The certificate will be associated with your router identity.\n" +
|
||||
"Copy the certificate to the directory $I2P/" + CERT_DIR + " for each of the other routers in the family.\n" +
|
||||
"Give this certificate to an I2P developer for inclusion in the next I2P release.");
|
||||
} else {
|
||||
_log.error("Error getting SSL cert to save as ASCII");
|
||||
}
|
||||
} else {
|
||||
_log.error("Error saving ASCII SSL keys");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a public key from a cert.
|
||||
*
|
||||
* @return null on all errors
|
||||
*/
|
||||
private SigningPublicKey loadCert(String familyName) {
|
||||
if (familyName.contains("/") || familyName.contains("\\") ||
|
||||
familyName.contains("..") || (new File(familyName)).isAbsolute())
|
||||
return null;
|
||||
familyName = familyName.replace("@", "_at_");
|
||||
File dir = new File(_context.getBaseDir(), CERT_DIR);
|
||||
File file = new File(dir, familyName + CERT_SUFFIX);
|
||||
if (!file.exists())
|
||||
return null;
|
||||
try {
|
||||
PublicKey pk = CertUtil.loadKey(file);
|
||||
return SigUtil.fromJavaKey(pk);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
_log.error("Error loading family key " + familyName, gse);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading family key " + familyName, ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private key from the keystore
|
||||
* @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 keyPass = _context.getProperty(PROP_KEY_PASSWORD);
|
||||
if (keyPass == null)
|
||||
throw new GeneralSecurityException("No key password, set " + PROP_KEY_PASSWORD +
|
||||
" in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
||||
try {
|
||||
PrivateKey pk = KeyStoreUtil.getPrivateKey(ks, ksPass, _fname, keyPass);
|
||||
if (pk == null)
|
||||
throw new GeneralSecurityException("Family key not found: " + _fname);
|
||||
return SigUtil.fromJavaKey(pk);
|
||||
} catch (IOException ioe) {
|
||||
throw new GeneralSecurityException("Error loading family key " + _fname, ioe);
|
||||
}
|
||||
}
|
||||
}
|
@@ -82,9 +82,6 @@ public class PublishLocalRouterInfoJob extends JobImpl {
|
||||
List<RouterAddress> newAddrs = getContext().commSystem().createAddresses();
|
||||
int count = _runCount.incrementAndGet();
|
||||
RouterInfo ri = new RouterInfo(oldRI);
|
||||
// this will get overwritten by setOptions() below, must restore it below
|
||||
getContext().router().addCapabilities(ri);
|
||||
String caps = ri.getCapabilities();
|
||||
if (_notFirstTime && (count % 4) != 0 && oldAddrs.size() == newAddrs.size()) {
|
||||
// 3 times out of 4, we don't republish if everything is the same...
|
||||
// If something changed, including the cost, then publish,
|
||||
@@ -116,9 +113,6 @@ public class PublishLocalRouterInfoJob extends JobImpl {
|
||||
}
|
||||
ri.setPublished(getContext().clock().now());
|
||||
Properties stats = getContext().statPublisher().publishStatistics();
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, String.valueOf(Router.NETWORK_ID));
|
||||
// restore caps generated above
|
||||
stats.setProperty(RouterInfo.PROP_CAPABILITIES, caps);
|
||||
ri.setOptions(stats);
|
||||
ri.setAddresses(newAddrs);
|
||||
|
||||
|
@@ -98,10 +98,6 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
OutputStream fos1 = null;
|
||||
try {
|
||||
info.setAddresses(getContext().commSystem().createAddresses());
|
||||
Properties stats = getContext().statPublisher().publishStatistics();
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, Router.NETWORK_ID+"");
|
||||
getContext().router().addCapabilities(info);
|
||||
info.setOptions(stats);
|
||||
// not necessary, in constructor
|
||||
//info.setPeers(new HashSet());
|
||||
info.setPublished(getCurrentPublishDate(getContext()));
|
||||
@@ -126,6 +122,8 @@ public class CreateRouterInfoJob extends JobImpl {
|
||||
padding = null;
|
||||
}
|
||||
info.setIdentity(ident);
|
||||
Properties stats = getContext().statPublisher().publishStatistics(ident.getHash());
|
||||
info.setOptions(stats);
|
||||
|
||||
info.sign(signingPrivKey);
|
||||
|
||||
|
@@ -31,6 +31,7 @@ import net.i2p.data.router.RouterPrivateKeyFile;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.FamilyKeyCrypto;
|
||||
import net.i2p.router.networkdb.kademlia.PersistentDataStore;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -98,7 +99,13 @@ class LoadRouterInfoJob extends JobImpl {
|
||||
throw new DataFormatException("Our RouterInfo has a bad signature");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Reading in routerInfo from " + rif.getAbsolutePath() + " and it has " + info.getAddresses().size() + " addresses");
|
||||
_us = info;
|
||||
// don't reuse if family name changed
|
||||
if (DataHelper.eq(info.getOption(FamilyKeyCrypto.OPT_NAME),
|
||||
getContext().getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME))) {
|
||||
_us = info;
|
||||
} else {
|
||||
_log.logAlways(Log.WARN, "NetDb family name changed");
|
||||
}
|
||||
}
|
||||
|
||||
if (keys2Exist || keysExist) {
|
||||
|
@@ -118,10 +118,8 @@ class RebuildRouterInfoJob extends JobImpl {
|
||||
|
||||
try {
|
||||
info.setAddresses(getContext().commSystem().createAddresses());
|
||||
Properties stats = getContext().statPublisher().publishStatistics();
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, ""+Router.NETWORK_ID);
|
||||
Properties stats = getContext().statPublisher().publishStatistics(info.getHash());
|
||||
info.setOptions(stats);
|
||||
getContext().router().addCapabilities(info);
|
||||
// info.setPeers(new HashSet()); // this would have the trusted peers
|
||||
info.setPublished(CreateRouterInfoJob.getCurrentPublishDate(getContext()));
|
||||
|
||||
|
@@ -36,7 +36,6 @@ public class StartupJob extends JobImpl {
|
||||
public void runJob() {
|
||||
if (!SystemVersion.isAndroid())
|
||||
getContext().jobQueue().addJob(new LoadClientAppsJob(getContext()));
|
||||
getContext().statPublisher().startup();
|
||||
getContext().jobQueue().addJob(new LoadRouterInfoJob(getContext()));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user