package net.i2p.data.router;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.util.ConvertToHash;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/**
* Component to manage the munging of hashes into routing keys - given a hash,
* perform some consistent transformation against it and return the result.
* This transformation is fed by the current "mod data".
*
* Right now the mod data is the current date (GMT) as a string: "yyyyMMdd",
* and the transformation takes the original hash, appends the bytes of that mod data,
* then returns the SHA256 of that concatenation.
*
* Do we want this to simply do the XOR of the SHA256 of the current mod data and
* the key? does that provide the randomization we need? It'd save an SHA256 op.
* Bah, too much effort to think about for so little gain. Other algorithms may come
* into play layer on about making periodic updates to the routing key for data elements
* to mess with Sybil. This may be good enough though.
*
* Also - the method generateDateBasedModData() should be called after midnight GMT
* once per day to generate the correct routing keys!
*
* @since 0.9.16 moved from net.i2p.data.RoutingKeyGenerator..
*
*/
public class RouterKeyGenerator extends RoutingKeyGenerator {
private final Log _log;
private final I2PAppContext _context;
public RouterKeyGenerator(I2PAppContext context) {
_log = context.logManager().getLog(RoutingKeyGenerator.class);
_context = context;
// make sure GMT is set, azi2phelper Vuze plugin is disabling static JVM TZ setting in Router.java
_fmt.setCalendar(_cal);
// ensure non-null mod data
generateDateBasedModData();
}
private volatile byte _currentModData[];
private volatile byte _nextModData[];
private volatile long _nextMidnight;
private volatile long _lastChanged;
private final Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
private static final String FORMAT = "yyyyMMdd";
private static final int LENGTH = FORMAT.length();
private final SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT, Locale.US);
/**
* The current (today's) mod data.
* Warning - not a copy, do not corrupt.
*
* @return non-null, 8 bytes
*/
public byte[] getModData() {
return _currentModData;
}
/**
* Tomorrow's mod data.
* Warning - not a copy, do not corrupt.
* For debugging use only.
*
* @return non-null, 8 bytes
* @since 0.9.10
*/
public byte[] getNextModData() {
return _nextModData;
}
public long getLastChanged() {
return _lastChanged;
}
/**
* How long until midnight (ms)
*
* @return could be slightly negative
* @since 0.9.10 moved from UpdateRoutingKeyModifierJob
*/
public long getTimeTillMidnight() {
return _nextMidnight - _context.clock().now();
}
/**
* Set _cal to midnight for the time given.
* Caller must synch.
* @since 0.9.10
*/
private void setCalToPreviousMidnight(long now) {
_cal.setTime(new Date(now));
_cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround
_cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MILLISECOND, 0);
}
/**
* Generate mod data from _cal.
* Caller must synch.
* @since 0.9.10
*/
private byte[] generateModDataFromCal() {
Date today = _cal.getTime();
String modVal = _fmt.format(today);
if (modVal.length() != LENGTH)
throw new IllegalStateException();
byte[] mod = DataHelper.getASCII(modVal);
return mod;
}
/**
* Update the current modifier data with some bytes derived from the current
* date (yyyyMMdd in GMT)
*
* @return true if changed
*/
public synchronized boolean generateDateBasedModData() {
long now = _context.clock().now();
setCalToPreviousMidnight(now);
byte[] mod = generateModDataFromCal();
boolean changed = !Arrays.equals(_currentModData, mod);
if (changed) {
// add a day and store next midnight and mod data for convenience
_cal.add(Calendar.DATE, 1);
_nextMidnight = _cal.getTime().getTime();
byte[] next = generateModDataFromCal();
_currentModData = mod;
_nextModData = next;
// ensure version is bumped
if (_lastChanged == now)
now++;
_lastChanged = now;
if (_log.shouldLog(Log.INFO))
_log.info("Routing modifier generated: " + HexDump.dump(mod));
}
return changed;
}
/**
* Generate a modified (yet consistent) hash from the origKey by generating the
* SHA256 of the targetKey with the current modData appended to it
*
* This makes Sybil's job a lot harder, as she needs to essentially take over the
* whole keyspace.
*
* @throws IllegalArgumentException if origKey is null
*/
public Hash getRoutingKey(Hash origKey) {
return getKey(origKey, _currentModData);
}
/**
* Get the routing key using tomorrow's modData, not today's
*
* @since 0.9.10
*/
public Hash getNextRoutingKey(Hash origKey) {
return getKey(origKey, _nextModData);
}
/**
* Get the routing key for the specified date, not today's
*
* @param time Java time
* @since 0.9.28
*/
public Hash getRoutingKey(Hash origKey, long time) {
String modVal;
synchronized(this) {
modVal = _fmt.format(time);
}
if (modVal.length() != LENGTH)
throw new IllegalStateException();
byte[] mod = DataHelper.getASCII(modVal);
return getKey(origKey, mod);
}
/**
* Generate a modified (yet consistent) hash from the origKey by generating the
* SHA256 of the targetKey with the specified modData appended to it
*
* @throws IllegalArgumentException if origKey is null
*/
private static Hash getKey(Hash origKey, byte[] modData) {
if (origKey == null) throw new IllegalArgumentException("Original key is null");
byte modVal[] = new byte[Hash.HASH_LENGTH + LENGTH];
System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH);
System.arraycopy(modData, 0, modVal, Hash.HASH_LENGTH, LENGTH);
return SHA256Generator.getInstance().calculateHash(modVal);
}
/**
* @since 0.9.29
*/
public static void main(String args[]) {
if (args.length <= 0) {
System.err.println("Usage: RouterKeyGenerator [-days] [+days] hash|hostname|destination...");
System.exit(1);
}
long now = System.currentTimeMillis();
int st = 0;
if (args.length > 1 && (args[0].startsWith("+") || args[0].startsWith("-"))) {
now += Integer.parseInt(args[0]) * 24*60*60*1000L;
st++;
}
RouterKeyGenerator rkg = new RouterKeyGenerator(I2PAppContext.getGlobalContext());
System.out.println("Date: " + rkg._fmt.format(now) + '\n' +
"Hash Routing Key\n" +
"---- -----------");
for (int i = st; i < args.length; i++) {
String s = args[i];
String sp = " ";
if (s.length() < 44)
sp = " ".substring(0, 45 - s.length());
Hash h = ConvertToHash.getHash(s);
if (h == null) {
System.out.println(s + sp + "Bad hash");
continue;
}
Hash rkey = rkg.getRoutingKey(h, now);
System.out.println(s + sp + rkey.toBase64());
}
}
}