forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)
to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
This commit is contained in:
135
core/java/src/net/i2p/crypto/ECUtil.java
Normal file
135
core/java/src/net/i2p/crypto/ECUtil.java
Normal 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");
|
||||
}
|
||||
****/
|
||||
}
|
@ -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;
|
||||
|
@ -331,7 +331,7 @@ public class SigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused
|
||||
*
|
||||
*/
|
||||
public static RSAPrivateKey toJavaRSAKey(SigningPrivateKey pk)
|
||||
throws GeneralSecurityException {
|
||||
@ -344,7 +344,7 @@ public class SigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused
|
||||
*
|
||||
*/
|
||||
public static SigningPublicKey fromJavaKey(RSAPublicKey pk, SigType type)
|
||||
throws GeneralSecurityException {
|
||||
|
@ -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);
|
||||
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Reference in New Issue
Block a user