propagate from branch 'i2p.i2p.zzz.test2' (head 6ccd9ca652057494bb2857e87636f18aadcd33f3)

to branch 'i2p.i2p' (head 376f751adc13923cdbf4f659c3f23ca957cf47b3)
This commit is contained in:
zzz
2014-09-23 13:06:36 +00:00
197 changed files with 4617 additions and 1989 deletions

View File

@ -86,7 +86,6 @@ public class I2PAppContext {
private SHA256Generator _sha;
protected Clock _clock; // overridden in RouterContext
private DSAEngine _dsa;
private RoutingKeyGenerator _routingKeyGenerator;
private RandomSource _random;
private KeyGenerator _keyGenerator;
protected KeyRing _keyRing; // overridden in RouterContext
@ -106,7 +105,6 @@ public class I2PAppContext {
private volatile boolean _shaInitialized;
protected volatile boolean _clockInitialized; // used in RouterContext
private volatile boolean _dsaInitialized;
private volatile boolean _routingKeyGeneratorInitialized;
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
protected volatile boolean _keyRingInitialized; // used in RouterContext
@ -126,7 +124,7 @@ public class I2PAppContext {
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object(), _lock4 = new Object(),
_lock5 = new Object(), _lock6 = new Object(), _lock7 = new Object(), _lock8 = new Object(),
_lock9 = new Object(), _lock10 = new Object(), _lock11 = new Object(), _lock12 = new Object(),
_lock13 = new Object(), _lock14 = new Object(), _lock15 = new Object(), _lock16 = new Object(),
_lock13 = new Object(), _lock14 = new Object(), _lock16 = new Object(),
_lock17 = new Object(), _lock18 = new Object(), _lock19 = new Object(), _lock20 = new Object();
/**
@ -851,19 +849,13 @@ public class I2PAppContext {
* may want to test out how things react when peers don't agree on
* how to skew.
*
* As of 0.9.16, returns null in I2PAppContext.
* You must be in RouterContext to get a generator.
*
* @return null always
*/
public RoutingKeyGenerator routingKeyGenerator() {
if (!_routingKeyGeneratorInitialized)
initializeRoutingKeyGenerator();
return _routingKeyGenerator;
}
private void initializeRoutingKeyGenerator() {
synchronized (_lock15) {
if (_routingKeyGenerator == null)
_routingKeyGenerator = new RoutingKeyGenerator(this);
_routingKeyGeneratorInitialized = true;
}
return null;
}
/**

View File

@ -0,0 +1,135 @@
package net.i2p.crypto;
import java.math.BigInteger;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import net.i2p.util.NativeBigInteger;
/**
* Used by KeyGenerator.getSigningPublicKey()
*
* Modified from
* http://stackoverflow.com/questions/15727147/scalar-multiplication-of-point-over-elliptic-curve
* Apparently public domain.
* Supported P-192 only.
* Added curve parameters to support all curves.
*
* @since 0.9.16
*/
class ECUtil {
private static final BigInteger TWO = new BigInteger("2");
private static final BigInteger THREE = new BigInteger("3");
public static ECPoint scalarMult(ECPoint p, BigInteger kin, EllipticCurve curve) {
ECPoint r = ECPoint.POINT_INFINITY;
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
BigInteger k = kin.mod(prime);
int length = k.bitLength();
byte[] binarray = new byte[length];
for (int i = 0; i <= length-1; i++) {
binarray[i] = k.mod(TWO).byteValue();
k = k.divide(TWO);
}
for (int i = length-1; i >= 0; i--) {
// i should start at length-1 not -2 because the MSB of binarry may not be 1
r = doublePoint(r, curve);
if (binarray[i] == 1)
r = addPoint(r, p, curve);
}
return r;
}
private static ECPoint addPoint(ECPoint r, ECPoint s, EllipticCurve curve) {
if (r.equals(s))
return doublePoint(r, curve);
else if (r.equals(ECPoint.POINT_INFINITY))
return s;
else if (s.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
BigInteger slope = (r.getAffineY().subtract(s.getAffineY())).multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(prime)).mod(prime);
slope = new NativeBigInteger(slope);
BigInteger xOut = (slope.modPow(TWO, prime).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(prime);
BigInteger yOut = s.getAffineY().negate().mod(prime);
yOut = yOut.add(slope.multiply(s.getAffineX().subtract(xOut))).mod(prime);
ECPoint out = new ECPoint(xOut, yOut);
return out;
}
private static ECPoint doublePoint(ECPoint r, EllipticCurve curve) {
if (r.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE);
slope = slope.add(curve.getA());
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
slope = slope.multiply((r.getAffineY().multiply(TWO)).modInverse(prime));
BigInteger xOut = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(prime);
BigInteger yOut = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(xOut))).mod(prime);
ECPoint out = new ECPoint(xOut, yOut);
return out;
}
/**
* P-192 test only.
* See KeyGenerator.main() for a test of all supported curves.
*/
/****
public static void main(String[] args) {
EllipticCurve P192 = ECConstants.P192_SPEC.getCurve();
BigInteger xs = new BigInteger("d458e7d127ae671b0c330266d246769353a012073e97acf8", 16);
BigInteger ys = new BigInteger("325930500d851f336bddc050cf7fb11b5673a1645086df3b", 16);
BigInteger xt = new BigInteger("f22c4395213e9ebe67ddecdd87fdbd01be16fb059b9753a4", 16);
BigInteger yt = new BigInteger("264424096af2b3597796db48f8dfb41fa9cecc97691a9c79", 16);
ECPoint S = new ECPoint(xs,ys);
ECPoint T = new ECPoint(xt,yt);
// Verifying addition
ECPoint Rst = addPoint(S, T, P192);
BigInteger xst = new BigInteger("48e1e4096b9b8e5ca9d0f1f077b8abf58e843894de4d0290", 16); // Specified value of x of point R for addition in NIST Routine example
System.out.println("x-coordinate of point Rst is : " + Rst.getAffineX());
System.out.println("y-coordinate of point Rst is : " + Rst.getAffineY());
if (Rst.getAffineX().equals(xst))
System.out.println("Adding is correct");
else
System.out.println("Adding FAIL");
//Verifying Doubling
BigInteger xr = new BigInteger("30c5bc6b8c7da25354b373dc14dd8a0eba42d25a3f6e6962", 16); // Specified value of x of point R for doubling in NIST Routine example
BigInteger yr = new BigInteger("0dde14bc4249a721c407aedbf011e2ddbbcb2968c9d889cf", 16);
ECPoint R2s = new ECPoint(xr, yr); // Specified value of y of point R for doubling in NIST Routine example
System.out.println("x-coordinate of point R2s is : " + R2s.getAffineX());
System.out.println("y-coordinate of point R2s is : " + R2s.getAffineY());
System.out.println("x-coordinate of calculated point is : " + doublePoint(S, P192).getAffineX());
System.out.println("y-coordinate of calculated point is : " + doublePoint(S, P192).getAffineY());
if (R2s.getAffineX().equals(doublePoint(S, P192).getAffineX()) &&
R2s.getAffineY().equals(doublePoint(S, P192).getAffineY()))
System.out.println("Doubling is correct");
else
System.out.println("Doubling FAIL");
xr = new BigInteger("1faee4205a4f669d2d0a8f25e3bcec9a62a6952965bf6d31", 16); // Specified value of x of point R for scalar Multiplication in NIST Routine example
yr = new BigInteger("5ff2cdfa508a2581892367087c696f179e7a4d7e8260fb06", 16); // Specified value of y of point R for scalar Multiplication in NIST Routine example
ECPoint Rds = new ECPoint(xr, yr);
BigInteger d = new BigInteger("a78a236d60baec0c5dd41b33a542463a8255391af64c74ee", 16);
ECPoint Rs = scalarMult(S, d, P192);
System.out.println("x-coordinate of point Rds is : " + Rds.getAffineX());
System.out.println("y-coordinate of point Rds is : " + Rds.getAffineY());
System.out.println("x-coordinate of calculated point is : " + Rs.getAffineX());
System.out.println("y-coordinate of calculated point is : " + Rs.getAffineY());
if (Rds.getAffineX().equals(Rs.getAffineX()) &&
Rds.getAffineY().equals(Rs.getAffineY()))
System.out.println("Scalar Multiplication is correct");
else
System.out.println("Scalar Multiplication FAIL");
}
****/
}

View File

@ -56,6 +56,9 @@ public class ElGamalEngine {
private final Log _log;
private final I2PAppContext _context;
private final YKGenerator _ykgen;
private static final BigInteger ELGPM1 = CryptoConstants.elgp.subtract(BigInteger.ONE);
/**
* The ElGamal engine should only be constructed and accessed through the
@ -171,10 +174,11 @@ public class ElGamalEngine {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to encrypt ElGamal block (" + diff + "ms)");
}
_context.statManager().addRateData("crypto.elGamal.encrypt", diff, 0);
_context.statManager().addRateData("crypto.elGamal.encrypt", diff);
return out;
}
/** Decrypt the data
* @param encrypted encrypted data, must be exactly 514 bytes
* Contains the two-part encrypted data starting at bytes 0 and 257.
@ -184,26 +188,26 @@ public class ElGamalEngine {
* @return unencrypted data or null on failure
*/
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
// actually it must be exactly 514 bytes or the arraycopy below will AIOOBE
if ((encrypted == null) || (encrypted.length > 514))
throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
if ((encrypted == null) || (encrypted.length != 514))
throw new IllegalArgumentException("Data to decrypt must be exactly 514 bytes");
long start = _context.clock().now();
byte[] ybytes = new byte[257];
byte[] dbytes = new byte[257];
System.arraycopy(encrypted, 0, ybytes, 0, 257);
System.arraycopy(encrypted, 257, dbytes, 0, 257);
BigInteger y = new NativeBigInteger(1, ybytes);
BigInteger d = new NativeBigInteger(1, dbytes);
BigInteger a = new NativeBigInteger(1, privateKey.getData());
BigInteger y1p = CryptoConstants.elgp.subtract(BigInteger.ONE).subtract(a);
BigInteger y1p = ELGPM1.subtract(a);
// we use this buf first for Y, then for D, then for the hash
byte[] buf = SimpleByteCache.acquire(257);
System.arraycopy(encrypted, 0, buf, 0, 257);
BigInteger y = new NativeBigInteger(1, buf);
BigInteger ya = y.modPow(y1p, CryptoConstants.elgp);
System.arraycopy(encrypted, 257, buf, 0, 257);
BigInteger d = new NativeBigInteger(1, buf);
BigInteger m = ya.multiply(d);
m = m.mod(CryptoConstants.elgp);
byte val[] = m.toByteArray();
int i = 0;
for (i = 0; i < val.length; i++)
int i;
for (i = 0; i < val.length; i++) {
if (val[i] != (byte) 0x00) break;
}
int payloadLen = val.length - i - 1 - Hash.HASH_LENGTH;
if (payloadLen < 0) {
@ -220,10 +224,10 @@ public class ElGamalEngine {
byte rv[] = new byte[payloadLen];
System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length);
byte[] calcHash = SimpleByteCache.acquire(Hash.HASH_LENGTH);
_context.sha().calculateHash(rv, 0, payloadLen, calcHash, 0);
boolean ok = DataHelper.eq(calcHash, 0, val, i + 1, Hash.HASH_LENGTH);
SimpleByteCache.release(calcHash);
// we reuse buf here for the calculated hash
_context.sha().calculateHash(rv, 0, payloadLen, buf, 0);
boolean ok = DataHelper.eq(buf, 0, val, i + 1, Hash.HASH_LENGTH);
SimpleByteCache.release(buf);
long end = _context.clock().now();
@ -233,7 +237,7 @@ public class ElGamalEngine {
_log.warn("Took too long to decrypt and verify ElGamal block (" + diff + "ms)");
}
_context.statManager().addRateData("crypto.elGamal.decrypt", diff, 0);
_context.statManager().addRateData("crypto.elGamal.decrypt", diff);
if (ok) {
//_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));

View File

@ -12,11 +12,25 @@ package net.i2p.crypto;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.ProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
@ -268,24 +282,56 @@ public class KeyGenerator {
}
/** Convert a SigningPrivateKey to a SigningPublicKey.
* DSA-SHA1 only.
* As of 0.9.16, supports all key types.
*
* @param priv a SigningPrivateKey object
* @return a SigningPublicKey object
* @throws IllegalArgumentException on bad key
* @throws IllegalArgumentException on bad key or unknown type
*/
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
if (priv.getType() != SigType.DSA_SHA1)
throw new IllegalArgumentException();
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
SigType type = priv.getType();
if (type == null)
throw new IllegalArgumentException("Unknown type");
try {
pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
} catch (InvalidKeyException ike) {
throw new IllegalArgumentException(ike);
switch (type.getBaseAlgorithm()) {
case DSA:
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
return pub;
case EC:
ECPrivateKey ecpriv = SigUtil.toJavaECKey(priv);
BigInteger s = ecpriv.getS();
ECParameterSpec spec = (ECParameterSpec) type.getParams();
EllipticCurve curve = spec.getCurve();
ECPoint g = spec.getGenerator();
ECPoint w = ECUtil.scalarMult(g, s, curve);
ECPublicKeySpec ecks = new ECPublicKeySpec(w, ecpriv.getParams());
KeyFactory eckf = KeyFactory.getInstance("EC");
ECPublicKey ecpub = (ECPublicKey) eckf.generatePublic(ecks);
return SigUtil.fromJavaKey(ecpub, type);
case RSA:
RSAPrivateKey rsapriv = SigUtil.toJavaRSAKey(priv);
BigInteger exp = ((RSAKeyGenParameterSpec)type.getParams()).getPublicExponent();
RSAPublicKeySpec rsaks = new RSAPublicKeySpec(rsapriv.getModulus(), exp);
KeyFactory rsakf = KeyFactory.getInstance("RSA");
RSAPublicKey rsapub = (RSAPublicKey) rsakf.generatePublic(rsaks);
return SigUtil.fromJavaKey(rsapub, type);
case EdDSA:
EdDSAPrivateKey epriv = SigUtil.toJavaEdDSAKey(priv);
EdDSAPublicKey epub = new EdDSAPublicKey(new EdDSAPublicKeySpec(epriv.getA(), epriv.getParams()));
return SigUtil.fromJavaKey(epub, type);
default:
throw new IllegalArgumentException("Unsupported algorithm");
}
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException("Conversion failed", gse);
}
return pub;
}
public static void main(String args[]) {
@ -322,14 +368,20 @@ public class KeyGenerator {
long stime = 0;
long vtime = 0;
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type);
//System.out.println("pubkey " + keys[0]);
SigningPublicKey pubkey = (SigningPublicKey) keys[0];
SigningPrivateKey privkey = (SigningPrivateKey) keys[1];
SigningPublicKey pubkey2 = getSigningPublicKey(privkey);
if (pubkey.equals(pubkey2))
System.out.println(type + " private-to-public test PASSED");
else
System.out.println(type + " private-to-public test FAILED");
//System.out.println("privkey " + keys[1]);
for (int i = 0; i < runs; i++) {
RandomSource.getInstance().nextBytes(src);
long start = System.nanoTime();
Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]);
Signature sig = DSAEngine.getInstance().sign(src, privkey);
long mid = System.nanoTime();
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]);
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
long end = System.nanoTime();
stime += mid - start;
vtime += end - mid;

View File

@ -345,7 +345,7 @@ public class SigUtil {
}
/**
* @deprecated unused
*
*/
public static RSAPrivateKey toJavaRSAKey(SigningPrivateKey pk)
throws GeneralSecurityException {
@ -358,7 +358,7 @@ public class SigUtil {
}
/**
* @deprecated unused
*
*/
public static SigningPublicKey fromJavaKey(RSAPublicKey pk, SigType type)
throws GeneralSecurityException {

View File

@ -24,20 +24,15 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@ -638,13 +633,17 @@ public class DataHelper {
* Integers are a fixed number of bytes (numBytes), stored as unsigned integers in network byte order.
* @param value value to write out, non-negative
* @param rawStream stream to write to
* @param numBytes number of bytes to write the number into (padding as necessary)
* @throws DataFormatException if value is negative
* @param numBytes number of bytes to write the number into, 1-8 (padding as necessary)
* @throws DataFormatException if value is negative or if numBytes not 1-8
* @throws IOException if there is an IO error writing to the stream
*/
public static void writeLong(OutputStream rawStream, int numBytes, long value)
throws DataFormatException, IOException {
if (value < 0) throw new DataFormatException("Value is negative (" + value + ")");
if (numBytes <= 0 || numBytes > 8)
// probably got the args backwards
throw new DataFormatException("Bad byte count " + numBytes);
if (value < 0)
throw new DataFormatException("Value is negative (" + value + ")");
for (int i = (numBytes - 1) * 8; i >= 0; i -= 8) {
byte cur = (byte) (value >> i);
rawStream.write(cur);
@ -667,7 +666,7 @@ public class DataHelper {
* @param value non-negative
*/
public static void toLong(byte target[], int offset, int numBytes, long value) throws IllegalArgumentException {
if (numBytes <= 0) throw new IllegalArgumentException("Invalid number of bytes");
if (numBytes <= 0 || numBytes > 8) throw new IllegalArgumentException("Invalid number of bytes");
if (value < 0) throw new IllegalArgumentException("Negative value not allowed");
for (int i = offset + numBytes - 1; i >= offset; i--) {
@ -1425,58 +1424,6 @@ public class DataHelper {
out.write(data);
}
/**
* Sort based on the Hash of the DataStructure.
* Warning - relatively slow.
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
* it cannot be changed.
* Why? Just because it has to be consistent so signing will work.
* How to spec as returning the same type as the param?
* DEPRECATED - Only used by RouterInfo.
*
* @return a new list
*/
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
if (dataStructures == null) return Collections.emptyList();
// This used to use Hash.toString(), which is insane, since a change to toString()
// would break the whole network. Now use Hash.toBase64().
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
// despite what you may read elsewhere.
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
//TreeMap<String, DataStructure> tm = new TreeMap();
//for (DataStructure struct : dataStructures) {
// tm.put(struct.calculateHash().toString(), struct);
//}
//for (DataStructure struct : tm.values()) {
// rv.add(struct);
//}
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
sortStructureList(rv);
return rv;
}
/**
* See above.
* DEPRECATED - Only used by RouterInfo.
*
* @since 0.9
*/
static void sortStructureList(List<? extends DataStructure> dataStructures) {
Collections.sort(dataStructures, new DataStructureComparator());
}
/**
* See sortStructures() comments.
* @since 0.8.3
*/
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
public int compare(DataStructure l, DataStructure r) {
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
}
}
/**
* NOTE: formatDuration2() recommended in most cases for readability
*/

View File

@ -11,6 +11,7 @@ package net.i2p.data;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.crypto.DSAEngine;
/**
@ -47,7 +48,7 @@ public abstract class DatabaseEntry extends DataStructureImpl {
protected volatile Signature _signature;
protected volatile Hash _currentRoutingKey;
protected volatile byte[] _routingKeyGenMod;
protected volatile long _routingKeyGenMod;
/**
* A common interface to the timestamp of the two subclasses.
@ -106,11 +107,15 @@ public abstract class DatabaseEntry extends DataStructureImpl {
* Get the routing key for the structure using the current modifier in the RoutingKeyGenerator.
* This only calculates a new one when necessary though (if the generator's key modifier changes)
*
* @throws IllegalStateException if not in RouterContext
*/
public Hash getRoutingKey() {
RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance();
byte[] mod = gen.getModData();
if (!Arrays.equals(mod, _routingKeyGenMod)) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext())
throw new IllegalStateException("Not in router context");
RoutingKeyGenerator gen = ctx.routingKeyGenerator();
long mod = gen.getLastChanged();
if (mod != _routingKeyGenMod) {
_currentRoutingKey = gen.getRoutingKey(getHash());
_routingKeyGenMod = mod;
}
@ -124,9 +129,16 @@ public abstract class DatabaseEntry extends DataStructureImpl {
_currentRoutingKey = key;
}
/**
* @throws IllegalStateException if not in RouterContext
*/
public boolean validateRoutingKey() {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
if (!ctx.isRouterContext())
throw new IllegalStateException("Not in router context");
RoutingKeyGenerator gen = ctx.routingKeyGenerator();
Hash destKey = getHash();
Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(destKey);
Hash rk = gen.getRoutingKey(destKey);
return rk.equals(getRoutingKey());
}

View File

@ -77,6 +77,13 @@ public class KeysAndCert extends DataStructureImpl {
_signingKey = key;
}
/**
* @since 0.9.16
*/
public byte[] getPadding() {
return _padding;
}
/**
* @throws IllegalStateException if was already set
* @since 0.9.12
@ -114,6 +121,8 @@ public class KeysAndCert extends DataStructureImpl {
_publicKey.writeBytes(out);
if (_padding != null)
out.write(_padding);
else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES)
throw new DataFormatException("No padding set");
_signingKey.writeTruncatedBytes(out);
_certificate.writeBytes(out);
}

View File

@ -50,6 +50,7 @@ public class PrivateKey extends SimpleDataStructure {
/** derives a new PublicKey object derived from the secret contents
* of this PrivateKey
* @return a PublicKey object
* @throws IllegalArgumentException on bad key
*/
public PublicKey toPublic() {
return KeyGenerator.getPublicKey(this);

View File

@ -1,11 +1,13 @@
package net.i2p.data;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.Map;
@ -24,6 +26,7 @@ import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureFileOutputStream;
/**
* This helper class reads and writes files in the
@ -48,11 +51,11 @@ public class PrivateKeyFile {
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
private final File file;
protected final File file;
private final I2PClient client;
private Destination dest;
private PrivateKey privKey;
private SigningPrivateKey signingPrivKey;
protected Destination dest;
protected PrivateKey privKey;
protected SigningPrivateKey signingPrivKey;
/**
* Create a new PrivateKeyFile, or modify an existing one, with various
@ -224,6 +227,16 @@ public class PrivateKeyFile {
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk) {
this(file, pubkey, spubkey, cert, pk, spk, null);
}
/**
* @param padding null OK, must be non-null if spubkey length < 128
* @throws IllegalArgumentException on mismatch of spubkey and spk types
* @since 0.9.16
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk, byte[] padding) {
if (spubkey.getType() != spk.getType())
throw new IllegalArgumentException("Signing key type mismatch");
this.file = file;
@ -232,6 +245,8 @@ public class PrivateKeyFile {
this.dest.setPublicKey(pubkey);
this.dest.setSigningPublicKey(spubkey);
this.dest.setCertificate(cert);
if (padding != null)
this.dest.setPadding(padding);
this.privKey = pk;
this.signingPrivKey = spk;
}
@ -241,9 +256,9 @@ public class PrivateKeyFile {
*/
public Destination createIfAbsent() throws I2PException, IOException, DataFormatException {
if(!this.file.exists()) {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
if (this.client != null)
this.client.createDestination(out);
else
@ -257,7 +272,10 @@ public class PrivateKeyFile {
return getDestination();
}
/** Also sets the local privKey and signingPrivKey */
/**
* If the destination is not set, read it in from the file.
* Also sets the local privKey and signingPrivKey.
*/
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
if (dest == null) {
I2PSession s = open();
@ -408,9 +426,9 @@ public class PrivateKeyFile {
}
public I2PSession open(Properties opts) throws I2PSessionException, IOException {
FileInputStream in = null;
InputStream in = null;
try {
in = new FileInputStream(this.file);
in = new BufferedInputStream(new FileInputStream(this.file));
I2PSession s = this.client.createSession(in, opts);
return s;
} finally {
@ -424,13 +442,12 @@ public class PrivateKeyFile {
* Copied from I2PClientImpl.createDestination()
*/
public void write() throws IOException, DataFormatException {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
this.dest.writeBytes(out);
this.privKey.writeBytes(out);
this.signingPrivKey.writeBytes(out);
out.flush();
} finally {
if (out != null) {
try { out.close(); } catch (IOException ioe) {}
@ -438,6 +455,23 @@ public class PrivateKeyFile {
}
}
/**
* Verify that the PublicKey matches the PrivateKey, and
* the SigningPublicKey matches the SigningPrivateKey.
*
* @return success
* @since 0.9.16
*/
public boolean validateKeyPairs() {
try {
if (!dest.getPublicKey().equals(KeyGenerator.getPublicKey(privKey)))
return false;
return dest.getSigningPublicKey().equals(KeyGenerator.getSigningPublicKey(signingPrivKey));
} catch (IllegalArgumentException iae) {
return false;
}
}
@Override
public String toString() {
StringBuilder s = new StringBuilder(128);

View File

@ -1,353 +0,0 @@
package net.i2p.data;
/*
* 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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import net.i2p.util.Addresses;
import net.i2p.util.OrderedProperties;
/**
* Defines a method of communicating with a router
*
* For efficiency, the options methods and structures here are unsynchronized.
* Initialize the structure with readBytes(), or call the setOptions().
* Don't change it after that.
*
* To ensure integrity of the RouterInfo, methods that change an element of the
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
*
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* If we implement expiration, or other use for the field, we must allow
* several releases for the change to propagate as it is backwards-incompatible.
* Restored as of 0.9.12.
*
* @author jrandom
*/
public class RouterAddress extends DataStructureImpl {
private short _cost;
private long _expiration;
private String _transportStyle;
private final Properties _options;
// cached values
private byte[] _ip;
private int _port;
public static final String PROP_HOST = "host";
public static final String PROP_PORT = "port";
public RouterAddress() {
_options = new OrderedProperties();
}
/**
* For efficiency when created by a Transport.
* @param options not copied; do not reuse or modify
* @param cost 0-255
* @since IPv6
*/
public RouterAddress(String style, OrderedProperties options, int cost) {
_transportStyle = style;
_options = options;
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
* Retrieve the weighted cost of this address, relative to other methods of
* contacting this router. The value 0 means free and 255 means really expensive.
* No value above 255 is allowed.
*
* Unused before 0.7.12
* @return 0-255
*/
public int getCost() {
return _cost;
}
/**
* Configure the weighted cost of using the address.
* No value negative or above 255 is allowed.
*
* WARNING - do not change cost on a published address or it will break the RI sig.
* There is no check here.
* Rarely used, use 3-arg constructor.
*
* NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12
*/
public void setCost(int cost) {
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
* Retrieve the date after which the address should not be used. If this
* is null, then the address never expires.
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12.
*
* @deprecated unused for now
* @return null for never, or a Date
*/
public Date getExpiration() {
//return _expiration;
if (_expiration > 0)
return new Date(_expiration);
return null;
}
/**
* Retrieve the date after which the address should not be used. If this
* is zero, then the address never expires.
*
* @deprecated unused for now
* @return 0 for never
* @since 0.9.12
*/
public long getExpirationTime() {
return _expiration;
}
/**
* Configure the expiration date of the address (null for no expiration)
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12, wait several more releases before using.
* TODO: Use for introducers
*
* Unused for now, always null
* @deprecated unused for now
*/
public void setExpiration(Date expiration) {
_expiration = expiration.getDate();
}
/**
* Retrieve the type of transport that must be used to communicate on this address.
*
*/
public String getTransportStyle() {
return _transportStyle;
}
/**
* Configure the type of transport that must be used to communicate on this address
*
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setTransportStyle(String transportStyle) {
if (_transportStyle != null)
throw new IllegalStateException();
_transportStyle = transportStyle;
}
/**
* Retrieve the transport specific options necessary for communication
*
* @deprecated use getOptionsMap()
* @return sorted, non-null, NOT a copy, do not modify
*/
public Properties getOptions() {
return _options;
}
/**
* Retrieve the transport specific options necessary for communication
*
* @return an unmodifiable view, non-null, sorted
* @since 0.8.13
*/
public Map<Object, Object> getOptionsMap() {
return Collections.unmodifiableMap(_options);
}
/**
* @since 0.8.13
*/
public String getOption(String opt) {
return _options.getProperty(opt);
}
/**
* Specify the transport specific options necessary for communication.
* Makes a copy.
* @param options non-null
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setOptions(Properties options) {
if (!_options.isEmpty())
throw new IllegalStateException();
_options.putAll(options);
}
/**
* Caching version of InetAddress.getByName(getOption("host")).getAddress(), which is slow.
* Caches numeric host names only.
* Will resolve but not cache resolution of DNS host names.
*
* @return IP or null
* @since 0.9.3
*/
public byte[] getIP() {
if (_ip != null)
return _ip;
byte[] rv = null;
String host = getHost();
if (host != null) {
rv = Addresses.getIP(host);
if (rv != null &&
(host.replaceAll("[0-9\\.]", "").length() == 0 ||
host.replaceAll("[0-9a-fA-F:]", "").length() == 0)) {
_ip = rv;
}
}
return rv;
}
/**
* Convenience, same as getOption("host").
* Does no parsing, so faster than getIP().
*
* @return host string or null
* @since IPv6
*/
public String getHost() {
return _options.getProperty(PROP_HOST);
}
/**
* Caching version of Integer.parseInt(getOption("port"))
* Caches valid ports 1-65535 only.
*
* @return 1-65535 or 0 if invalid
* @since 0.9.3
*/
public int getPort() {
if (_port != 0)
return _port;
String port = _options.getProperty(PROP_PORT);
if (port != null) {
try {
int rv = Integer.parseInt(port);
if (rv > 0 && rv <= 65535)
_port = rv;
} catch (NumberFormatException nfe) {}
}
return _port;
}
/**
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12, wait several more releases before using.
* @throws IllegalStateException if was already read in
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_transportStyle != null)
throw new IllegalStateException();
_cost = (short) DataHelper.readLong(in, 1);
_expiration = DataHelper.readLong(in, 8);
_transportStyle = DataHelper.readString(in);
// reduce Object proliferation
if (_transportStyle.equals("SSU"))
_transportStyle = "SSU";
else if (_transportStyle.equals("NTCP"))
_transportStyle = "NTCP";
DataHelper.readProperties(in, _options);
}
/**
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_transportStyle == null)
throw new DataFormatException("uninitialized");
DataHelper.writeLong(out, 1, _cost);
DataHelper.writeLong(out, 8, _expiration);
DataHelper.writeString(out, _transportStyle);
DataHelper.writeProperties(out, _options);
}
/**
* Transport, host, and port only.
* Never look at cost or other properties.
*/
@Override
public boolean equals(Object object) {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterAddress)) return false;
RouterAddress addr = (RouterAddress) object;
return
getPort() == addr.getPort() &&
DataHelper.eq(getHost(), addr.getHost()) &&
DataHelper.eq(_transportStyle, addr._transportStyle);
//DataHelper.eq(_options, addr._options) &&
//DataHelper.eq(_expiration, addr._expiration);
}
/**
* Everything, including Transport, host, port, options, and cost
* @param addr may be null
* @since IPv6
*/
public boolean deepEquals(RouterAddress addr) {
return
equals(addr) &&
_cost == addr._cost &&
_options.equals(addr._options);
}
/**
* Just use a few items for speed (expiration is always null).
* Never look at cost or other properties.
*/
@Override
public int hashCode() {
return DataHelper.hashCode(_transportStyle) ^
DataHelper.hashCode(getIP()) ^
getPort();
}
/**
* This is used on peers.jsp so sort options so it looks better.
* We don't just use OrderedProperties for _options because DataHelper.writeProperties()
* sorts also.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(128);
buf.append("[RouterAddress: ");
buf.append("\n\tType: ").append(_transportStyle);
buf.append("\n\tCost: ").append(_cost);
if (_expiration > 0)
buf.append("\n\tExpiration: ").append(new Date(_expiration));
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry<Object, Object> e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
buf.append("]");
return buf.toString();
}
}

View File

@ -1,37 +0,0 @@
package net.i2p.data;
/*
* 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.
*
*/
/**
* Defines the unique identifier of a router, including any certificate or
* public key.
*
* As of 0.9.9 this data structure is immutable after the two keys and the certificate
* are set; attempts to change them will throw an IllegalStateException.
*
* @author jrandom
*/
public class RouterIdentity extends KeysAndCert {
/**
* This router specified that they should not be used as a part of a tunnel,
* nor queried for the netDb, and that disclosure of their contact information
* should be limited.
*
*/
public boolean isHidden() {
return (_certificate != null) && (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_HIDDEN);
}
@Override
public boolean equals(Object o) {
return super.equals(o) && (o instanceof RouterIdentity);
}
}

View File

@ -1,699 +0,0 @@
package net.i2p.data;
/*
* 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.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SHA1;
import net.i2p.crypto.SHA1Hash;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SigType;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SystemVersion;
/**
* Defines the data that a router either publishes to the global routing table or
* provides to trusted peers.
*
* For efficiency, the methods and structures here are now unsynchronized.
* Initialize the RI with readBytes(), or call the setters and then sign() in a single thread.
* Don't change it after that.
*
* To ensure integrity of the RouterInfo, methods that change an element of the
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
*
* @author jrandom
*/
public class RouterInfo extends DatabaseEntry {
private RouterIdentity _identity;
private volatile long _published;
/**
* Addresses must be sorted by SHA256.
* When an RI is created, they are sorted in setAddresses().
* Save addresses in the order received so we need not resort.
*/
private final List<RouterAddress> _addresses;
/** may be null to save memory, no longer final */
private Set<Hash> _peers;
private final Properties _options;
private volatile boolean _validated;
private volatile boolean _isValid;
//private volatile String _stringified;
private volatile byte _byteified[];
private volatile int _hashCode;
private volatile boolean _hashCodeInitialized;
/** should we cache the byte and string versions _byteified ? **/
private boolean _shouldCache;
/** maybe we should check if we are floodfill? */
private static final boolean CACHE_ALL = SystemVersion.getMaxMemory() > 128*1024*1024l;
public static final String PROP_NETWORK_ID = "netId";
public static final String PROP_CAPABILITIES = "caps";
public static final char CAPABILITY_HIDDEN = 'H';
// Public string of chars which serve as bandwidth capacity markers
// NOTE: individual chars defined in Router.java
public static final String BW_CAPABILITY_CHARS = "KLMNO";
public RouterInfo() {
_addresses = new ArrayList<RouterAddress>(2);
_options = new OrderedProperties();
}
/**
* Used only by Router and PublishLocalRouterInfoJob.
* Copies ONLY the identity and peers.
* Does not copy published, addresses, options, or signature.
*/
public RouterInfo(RouterInfo old) {
this();
setIdentity(old.getIdentity());
//setPublished(old.getPublished());
//setAddresses(old.getAddresses());
setPeers(old.getPeers());
//setOptions(old.getOptions());
//setSignature(old.getSignature());
// copy over _byteified?
}
public long getDate() {
return _published;
}
protected KeysAndCert getKeysAndCert() {
return _identity;
}
public int getType() {
return KEY_TYPE_ROUTERINFO;
}
/**
* Retrieve the identity of the router represented
*
*/
public RouterIdentity getIdentity() {
return _identity;
}
/**
* Configure the identity of the router represented
*
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setIdentity(RouterIdentity ident) {
if (_signature != null)
throw new IllegalStateException();
_identity = ident;
// We only want to cache the bytes for our own RI, which is frequently written.
// To cache for all RIs doubles the RI memory usage.
// setIdentity() is only called when we are creating our own RI.
// Otherwise, the data is populated with readBytes().
_shouldCache = true;
}
/**
* Retrieve the approximate date on which the info was published
* (essentially a version number for the routerInfo structure, except that
* it also contains freshness information - whether or not the router is
* currently publishing its information). This should be used to help expire
* old routerInfo structures
*
*/
public long getPublished() {
return _published;
}
/**
* Date on which it was published, in milliseconds since Midnight GMT on Jan 01, 1970
*
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setPublished(long published) {
if (_signature != null)
throw new IllegalStateException();
_published = published;
}
/**
* Retrieve the set of RouterAddress structures at which this
* router can be contacted.
*
* @return unmodifiable view, non-null
*/
public Collection<RouterAddress> getAddresses() {
return Collections.unmodifiableCollection(_addresses);
}
/**
* Specify a set of RouterAddress structures at which this router
* can be contacted.
*
* Warning - Sorts the addresses here. Do not modify any address
* after calling this, as the sort order is based on the
* hash of the entire address structure.
*
* @param addresses may be null
* @throws IllegalStateException if RouterInfo is already signed or addresses previously set
*/
public void setAddresses(Collection<RouterAddress> addresses) {
if (_signature != null || !_addresses.isEmpty())
throw new IllegalStateException();
if (addresses != null) {
_addresses.addAll(addresses);
if (_addresses.size() > 1) {
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the addresses are stored in a Set, not a List.
DataHelper.sortStructureList(_addresses);
}
}
}
/**
* Retrieve a set of SHA-256 hashes of RouterIdentities from routers
* this router can be reached through.
*
* @deprecated Implemented here but unused elsewhere
*/
public Set<Hash> getPeers() {
if (_peers == null)
return Collections.emptySet();
return _peers;
}
/**
* Specify a set of SHA-256 hashes of RouterIdentities from routers
* this router can be reached through.
*
* @deprecated Implemented here but unused elsewhere
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setPeers(Set<Hash> peers) {
if (_signature != null)
throw new IllegalStateException();
if (peers == null || peers.isEmpty()) {
_peers = null;
return;
}
if (_peers == null)
_peers = new HashSet<Hash>(2);
synchronized (_peers) {
_peers.clear();
_peers.addAll(peers);
}
}
/**
* Retrieve a set of options or statistics that the router can expose.
*
* @deprecated use getOptionsMap()
* @return sorted, non-null, NOT a copy, do not modify!!!
*/
public Properties getOptions() {
return _options;
}
/**
* Retrieve a set of options or statistics that the router can expose.
*
* @return an unmodifiable view, non-null, sorted
* @since 0.8.13
*/
public Map<Object, Object> getOptionsMap() {
return Collections.unmodifiableMap(_options);
}
public String getOption(String opt) {
return _options.getProperty(opt);
}
/**
* Configure a set of options or statistics that the router can expose.
* Makes a copy.
*
* @param options if null, clears current options
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setOptions(Properties options) {
if (_signature != null)
throw new IllegalStateException();
_options.clear();
if (options != null)
_options.putAll(options);
}
/**
* Write out the raw payload of the routerInfo, excluding the signature. This
* caches the data in memory if possible.
*
* @throws DataFormatException if the data is somehow b0rked (missing props, etc)
*/
protected byte[] getBytes() throws DataFormatException {
if (_byteified != null) return _byteified;
if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!");
//long before = Clock.getInstance().now();
ByteArrayOutputStream out = new ByteArrayOutputStream(2*1024);
try {
_identity.writeBytes(out);
// avoid thrashing objects
//DataHelper.writeDate(out, new Date(_published));
DataHelper.writeLong(out, 8, _published);
int sz = _addresses.size();
if (sz <= 0 || isHidden()) {
// Do not send IP address to peers in hidden mode
DataHelper.writeLong(out, 1, 0);
} else {
DataHelper.writeLong(out, 1, sz);
for (RouterAddress addr : _addresses) {
addr.writeBytes(out);
}
}
// XXX: what about peers?
// answer: they're always empty... they're a placeholder for one particular
// method of trusted links, which isn't implemented in the router
// at the moment, and may not be later.
int psz = _peers == null ? 0 : _peers.size();
DataHelper.writeLong(out, 1, psz);
if (psz > 0) {
Collection<Hash> peers = _peers;
if (psz > 1)
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the hashes are stored in a Set, not a List.
peers = (Collection<Hash>) DataHelper.sortStructures(peers);
for (Hash peerHash : peers) {
peerHash.writeBytes(out);
}
}
DataHelper.writeProperties(out, _options);
} catch (IOException ioe) {
throw new DataFormatException("IO Error getting bytes", ioe);
}
byte data[] = out.toByteArray();
//if (_log.shouldLog(Log.DEBUG)) {
// long after = Clock.getInstance().now();
// _log.debug("getBytes() took " + (after - before) + "ms");
//}
if (CACHE_ALL || _shouldCache)
_byteified = data;
return data;
}
/**
* Determine whether this router info is authorized with a valid signature
*
*/
public boolean isValid() {
if (!_validated) doValidate();
return _isValid;
}
/**
* Same as isValid()
* @since 0.9
*/
@Override
public boolean verifySignature() {
return isValid();
}
/**
* which network is this routerInfo a part of. configured through the property
* PROP_NETWORK_ID
* @return -1 if unknown
*/
public int getNetworkId() {
String id = _options.getProperty(PROP_NETWORK_ID);
if (id != null) {
try {
return Integer.parseInt(id);
} catch (NumberFormatException nfe) {}
}
return -1;
}
/**
* what special capabilities this router offers
* @return non-null, empty string if none
*/
public String getCapabilities() {
String capabilities = _options.getProperty(PROP_CAPABILITIES);
if (capabilities != null)
return capabilities;
else
return "";
}
/**
* Is this a hidden node?
*/
public boolean isHidden() {
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
}
/**
* Return a string representation of this node's bandwidth tier,
* or "Unknown"
*/
public String getBandwidthTier() {
String bwTiers = BW_CAPABILITY_CHARS;
String bwTier = "Unknown";
String capabilities = getCapabilities();
// Iterate through capabilities, searching for known bandwidth tier
for (int i = 0; i < capabilities.length(); i++) {
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
bwTier = String.valueOf(capabilities.charAt(i));
break;
}
}
return (bwTier);
}
/**
* @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, ""+cap);
else if (caps.indexOf(cap) == -1)
_options.setProperty(PROP_CAPABILITIES, caps + cap);
}
/**
* @throws IllegalStateException if RouterInfo is already signed
*/
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
* values such as 1 or 2 hours are probably reasonable.
*
* @param maxAgeMs milliseconds between the current time and publish date to check
* @return true if it was published recently, false otherwise
*/
public boolean isCurrent(long maxAgeMs) {
long earliestExpire = Clock.getInstance().now() - maxAgeMs;
if (_published < earliestExpire)
return false;
return true;
}
/**
* Pull the first workable target address for the given transport.
* Use to check for any address. For all addresses, use getTargetAddresses(),
* which you probably want if you care about IPv6.
*/
public RouterAddress getTargetAddress(String transportStyle) {
for (RouterAddress addr : _addresses) {
if (addr.getTransportStyle().equals(transportStyle))
return addr;
}
return null;
}
/**
* For multiple addresses per-transport (IPv4 or IPv6)
* @return non-null
* @since 0.7.11
*/
public List<RouterAddress> getTargetAddresses(String transportStyle) {
List<RouterAddress> ret = new ArrayList<RouterAddress>(_addresses.size());
for (RouterAddress addr : _addresses) {
if(addr.getTransportStyle().equals(transportStyle))
ret.add(addr);
}
return ret;
}
/**
* Actually validate the signature
*/
private void doValidate() {
_isValid = super.verifySignature();
_validated = true;
if (!_isValid) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(RouterInfo.class);
byte data[] = null;
try {
data = getBytes();
} catch (DataFormatException dfe) {
log.error("Error validating", dfe);
return;
}
log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
+ (log.shouldLog(Log.WARN) ? ("]\n" + toString()) : ""),
new Exception("Signature failed"));
}
}
/**
* This does NOT validate the signature
*
* @throws IllegalStateException if RouterInfo was already read in
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
readBytes(in, false);
}
/**
* If verifySig is true,
* this validates the signature while reading in,
* and throws a DataFormatException if the sig is invalid.
* This is faster than reserializing to validate later.
*
* @throws IllegalStateException if RouterInfo was already read in
* @since 0.9
*/
public void readBytes(InputStream in, boolean verifySig) throws DataFormatException, IOException {
if (_signature != null)
throw new IllegalStateException();
_identity = new RouterIdentity();
_identity.readBytes(in);
// can't set the digest until we know the sig type
InputStream din;
MessageDigest digest;
if (verifySig) {
SigType type = _identity.getSigningPublicKey().getType();
if (type != SigType.EdDSA_SHA512_Ed25519) {
// This won't work for EdDSA
digest = _identity.getSigningPublicKey().getType().getDigestInstance();
// TODO any better way?
digest.update(_identity.toByteArray());
din = new DigestInputStream(in, digest);
} else {
digest = null;
din = in;
}
} else {
digest = null;
din = in;
}
// avoid thrashing objects
//Date when = DataHelper.readDate(in);
//if (when == null)
// _published = 0;
//else
// _published = when.getTime();
_published = DataHelper.readLong(din, 8);
int numAddresses = (int) DataHelper.readLong(din, 1);
for (int i = 0; i < numAddresses; i++) {
RouterAddress address = new RouterAddress();
address.readBytes(din);
_addresses.add(address);
}
int numPeers = (int) DataHelper.readLong(din, 1);
if (numPeers == 0) {
_peers = null;
} else {
_peers = new HashSet<Hash>(numPeers);
for (int i = 0; i < numPeers; i++) {
Hash peerIdentityHash = new Hash();
peerIdentityHash.readBytes(din);
_peers.add(peerIdentityHash);
}
}
DataHelper.readProperties(din, _options);
_signature = new Signature(_identity.getSigningPublicKey().getType());
_signature.readBytes(in);
if (verifySig) {
SigType type = _identity.getSigningPublicKey().getType();
if (type != SigType.EdDSA_SHA512_Ed25519) {
// This won't work for EdDSA
SimpleDataStructure hash = _identity.getSigningPublicKey().getType().getHashInstance();
hash.setData(digest.digest());
_isValid = DSAEngine.getInstance().verifySignature(_signature, hash, _identity.getSigningPublicKey());
_validated = true;
} else {
doValidate();
}
if (!_isValid) {
throw new DataFormatException("Bad sig");
}
}
//_log.debug("Read routerInfo: " + toString());
}
/**
* This does NOT validate the signature
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_identity == null) throw new DataFormatException("Missing identity");
if (_published < 0) throw new DataFormatException("Invalid published date: " + _published);
if (_signature == null) throw new DataFormatException("Signature is null");
//if (!isValid())
// throw new DataFormatException("Data is not valid");
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
baos.write(getBytes());
_signature.writeBytes(baos);
byte data[] = baos.toByteArray();
//_log.debug("Writing routerInfo [len=" + data.length + "]: " + toString());
out.write(data);
}
@Override
public boolean equals(Object object) {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterInfo)) return false;
RouterInfo info = (RouterInfo) object;
return
_published == info.getPublished()
&& DataHelper.eq(_signature, info.getSignature())
&& DataHelper.eq(_identity, info.getIdentity());
// Let's speed up the NetDB
//&& DataHelper.eq(_addresses, info.getAddresses())
//&& DataHelper.eq(_options, info.getOptions())
//&& DataHelper.eq(getPeers(), info.getPeers());
}
@Override
public int hashCode() {
if (!_hashCodeInitialized) {
_hashCode = DataHelper.hashCode(_identity) + (int) _published;
_hashCodeInitialized = true;
}
return _hashCode;
}
@Override
public String toString() {
//if (_stringified != null) return _stringified;
StringBuilder buf = new StringBuilder(1024);
buf.append("[RouterInfo: ");
buf.append("\n\tIdentity: ").append(_identity);
buf.append("\n\tSignature: ").append(_signature);
buf.append("\n\tPublished: ").append(new Date(_published));
if (_peers != null) {
buf.append("\n\tPeers (").append(_peers.size()).append("):");
for (Hash hash : _peers) {
buf.append("\n\t\tPeer hash: ").append(hash);
}
}
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry<Object, Object> e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
if (!_addresses.isEmpty()) {
buf.append("\n\tAddresses (").append(_addresses.size()).append("):");
for (RouterAddress addr : _addresses) {
buf.append("\n\t").append(addr);
}
}
buf.append("]");
String rv = buf.toString();
//_stringified = rv;
return rv;
}
/**
* Print out routerinfos from files specified on the command line.
* Exits 1 if any RI is invalid, fails signature, etc.
* @since 0.8
*/
public static void main(String[] args) {
if (args.length <= 0) {
System.err.println("Usage: RouterInfo file ...");
System.exit(1);
}
boolean fail = false;
for (int i = 0; i < args.length; i++) {
RouterInfo ri = new RouterInfo();
InputStream is = null;
try {
is = new java.io.FileInputStream(args[i]);
ri.readBytes(is);
if (ri.isValid()) {
System.out.println(ri.toString());
} else {
System.err.println("Router info " + args[i] + " is invalid");
fail = true;
}
} catch (Exception e) {
System.err.println("Error reading " + args[i] + ": " + e);
fail = true;
} finally {
if (is != null) {
try { is.close(); } catch (IOException ioe) {}
}
}
}
if (fail)
System.exit(1);
}
}

View File

@ -9,215 +9,40 @@ package net.i2p.data;
*
*/
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.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!
*
* Warning - API subject to change. Not for use outside the router.
* As of 0.9.16, this is essentially just an interface.
* Implementation moved to net.i2p.data.router.RouterKeyGenerator.
* No generator is available in I2PAppContext; you must be in RouterContext.
*
*/
public class RoutingKeyGenerator {
private final Log _log;
private final I2PAppContext _context;
public RoutingKeyGenerator(I2PAppContext context) {
_log = context.logManager().getLog(RoutingKeyGenerator.class);
_context = context;
// ensure non-null mod data
generateDateBasedModData();
}
public abstract class RoutingKeyGenerator {
/**
* Get the generator for this context.
*
* @return null in I2PAppContext; non-null in RouterContext.
*/
public static RoutingKeyGenerator getInstance() {
return I2PAppContext.getGlobalContext().routingKeyGenerator();
}
private volatile byte _currentModData[];
private volatile byte _nextModData[];
private volatile long _nextMidnight;
private volatile long _lastChanged;
private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
private static final String FORMAT = "yyyyMMdd";
private static final int LENGTH = FORMAT.length();
private final static SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT, Locale.US);
static {
// make sure GMT is set, azi2phelper Vuze plugin is disabling static JVM TZ setting in Router.java
_fmt.setCalendar(_cal);
}
/**
* The current (today's) mod data.
* Warning - not a copy, do not corrupt.
*
* @return non-null, 8 bytes
* The version of the current (today's) mod data.
* Use to determine if the routing key should be regenerated.
*/
public byte[] getModData() {
return _currentModData;
}
public abstract long getLastChanged();
/**
* 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 = new byte[LENGTH];
for (int i = 0; i < LENGTH; i++)
mod[i] = (byte)(modVal.charAt(i) & 0xFF);
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;
_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.
* Get the routing key for a key.
*
* @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);
}
/**
* 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);
}
public abstract Hash getRoutingKey(Hash origKey);
/****
public static void main(String args[]) {
Hash k1 = new Hash();
byte k1d[] = new byte[Hash.HASH_LENGTH];
RandomSource.getInstance().nextBytes(k1d);
k1.setData(k1d);
for (int i = 0; i < 10; i++) {
System.out.println("K1: " + k1);
Hash k1m = RoutingKeyGenerator.getInstance().getRoutingKey(k1);
System.out.println("MOD: " + new String(RoutingKeyGenerator.getInstance().getModData()));
System.out.println("K1M: " + k1m);
}
try {
Thread.sleep(2000);
} catch (Throwable t) { // nop
}
}
****/
}

View File

@ -75,8 +75,12 @@ public class SigningPrivateKey extends SimpleDataStructure {
return _type;
}
/** converts this signing private key to its public equivalent
* @return a SigningPublicKey object derived from this private key
/**
* Converts this signing private key to its public equivalent.
* As of 0.9.16, supports all key types.
*
* @return a SigningPublicKey object derived from this private key
* @throws IllegalArgumentException on bad key or unknown or unsupported type
*/
public SigningPublicKey toPublic() {
return KeyGenerator.getSigningPublicKey(this);

View File

@ -70,9 +70,12 @@ public class MessagePayloadMessage extends I2CPMessageImpl {
}
}
/**
* @throws UnsupportedOperationException always
*/
@Override
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
throw new RuntimeException("go away, we dont want any");
throw new UnsupportedOperationException();
}
/**

View File

@ -101,9 +101,12 @@ public class SendMessageMessage extends I2CPMessageImpl {
}
}
/**
* @throws UnsupportedOperationException always
*/
@Override
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
throw new RuntimeException("wtf, dont run me");
throw new UnsupportedOperationException();
}
/**