forked from I2P_Developers/i2p.i2p
NTCP2: Key factory
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
package net.i2p.router.transport.crypto;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.southernstorm.noise.crypto.Curve25519;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Try to keep DH pairs at the ready.
|
||||
* It's important to do this in a separate thread, because if we run out,
|
||||
* the pairs are generated in the NTCP Pumper thread,
|
||||
* and it can fall behind.
|
||||
*
|
||||
* @since 0.9.36 from DHSessionKeyFactory.PrecalcRunner
|
||||
*/
|
||||
public class X25519KeyFactory extends I2PThread {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final int _minSize;
|
||||
private final int _maxSize;
|
||||
private final int _calcDelay;
|
||||
private final LinkedBlockingQueue<KeyPair> _keys;
|
||||
private volatile boolean _isRunning;
|
||||
private long _checkDelay = 10 * 1000;
|
||||
|
||||
private final static String PROP_DH_PRECALC_MIN = "crypto.xdh.precalc.min";
|
||||
private final static String PROP_DH_PRECALC_MAX = "crypto.xdh.precalc.max";
|
||||
private final static String PROP_DH_PRECALC_DELAY = "crypto.xdh.precalc.delay";
|
||||
private final static int DEFAULT_DH_PRECALC_MIN = 20;
|
||||
private final static int DEFAULT_DH_PRECALC_MAX = 60;
|
||||
private final static int DEFAULT_DH_PRECALC_DELAY = 25;
|
||||
|
||||
public X25519KeyFactory(I2PAppContext ctx) {
|
||||
super("DH Precalc");
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(X25519KeyFactory.class);
|
||||
ctx.statManager().createRateStat("crypto.XDHGenerateTime", "How long it takes to create x and X", "Encryption", new long[] { 60*60*1000 });
|
||||
ctx.statManager().createRateStat("crypto.XDHUsed", "Need a DH from the queue", "Encryption", new long[] { 60*60*1000 });
|
||||
ctx.statManager().createRateStat("crypto.XDHReused", "Unused DH requeued", "Encryption", new long[] { 60*60*1000 });
|
||||
ctx.statManager().createRateStat("crypto.XDHEmpty", "DH queue empty", "Encryption", new long[] { 60*60*1000 });
|
||||
|
||||
// add to the defaults for every 128MB of RAM, up to 512MB
|
||||
long maxMemory = SystemVersion.getMaxMemory();
|
||||
int factor = (int) Math.max(1l, Math.min(4l, 1 + (maxMemory / (128*1024*1024l))));
|
||||
int defaultMin = DEFAULT_DH_PRECALC_MIN * factor;
|
||||
int defaultMax = DEFAULT_DH_PRECALC_MAX * factor;
|
||||
_minSize = ctx.getProperty(PROP_DH_PRECALC_MIN, defaultMin);
|
||||
_maxSize = ctx.getProperty(PROP_DH_PRECALC_MAX, defaultMax);
|
||||
_calcDelay = ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("XDH Precalc (minimum: " + _minSize + " max: " + _maxSize + ", delay: "
|
||||
+ _calcDelay + ")");
|
||||
_keys = new LinkedBlockingQueue<KeyPair>(_maxSize);
|
||||
if (!SystemVersion.isWindows())
|
||||
setPriority(Thread.NORM_PRIORITY - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that this stops the singleton precalc thread.
|
||||
* You don't want to do this if there are multiple routers in the JVM.
|
||||
* Fix this if you care. See Router.shutdown().
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public void shutdown() {
|
||||
_isRunning = false;
|
||||
this.interrupt();
|
||||
_keys.clear();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
while (_isRunning) {
|
||||
int startSize = getSize();
|
||||
// Adjust delay
|
||||
if (startSize <= (_minSize * 2 / 3) && _checkDelay > 1000)
|
||||
_checkDelay -= 1000;
|
||||
else if (startSize > (_minSize * 3 / 2) && _checkDelay < 60*1000)
|
||||
_checkDelay += 1000;
|
||||
if (startSize < _minSize) {
|
||||
// fill all the way up, do the check here so we don't
|
||||
// throw away one when full in addValues()
|
||||
while (getSize() < _maxSize && _isRunning) {
|
||||
long curStart = System.currentTimeMillis();
|
||||
if (!addKeys(precalc()))
|
||||
break;
|
||||
long curCalc = System.currentTimeMillis() - curStart;
|
||||
// for some relief...
|
||||
if (!interrupted()) {
|
||||
try {
|
||||
Thread.sleep(Math.min(200, Math.max(10, _calcDelay + (curCalc * 3))));
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_isRunning)
|
||||
break;
|
||||
try {
|
||||
Thread.sleep(_checkDelay);
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls a prebuilt keypair from the queue,
|
||||
* or if not available, construct a new one.
|
||||
*/
|
||||
public KeyPair getKeys() {
|
||||
_context.statManager().addRateData("crypto.XDHUsed", 1);
|
||||
KeyPair rv = _keys.poll();
|
||||
if (rv == null) {
|
||||
_context.statManager().addRateData("crypto.XDHEmpty", 1);
|
||||
rv = precalc();
|
||||
// stop sleeping, wake up, make some more
|
||||
this.interrupt();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private KeyPair precalc() {
|
||||
long start = System.currentTimeMillis();
|
||||
byte[] priv = new byte[32];
|
||||
do {
|
||||
_context.random().nextBytes(priv);
|
||||
// little endian, loop if too small
|
||||
// worth doing?
|
||||
} while (priv[31] == 0);
|
||||
byte[] pub = new byte[32];
|
||||
Curve25519.eval(pub, 0, priv, null);
|
||||
KeyPair rv = new KeyPair(new X25519PublicKey(pub), new X25519PrivateKey(priv));
|
||||
long end = System.currentTimeMillis();
|
||||
long diff = end - start;
|
||||
_context.statManager().addRateData("crypto.XDHGenerateTime", diff);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Took " + diff + "ms to generate local DH value");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an unused DH key builder
|
||||
* to be put back onto the queue for reuse.
|
||||
*/
|
||||
public void returnUnused(KeyPair kp) {
|
||||
_context.statManager().addRateData("crypto.XDHReused", 1);
|
||||
_keys.offer(kp);
|
||||
}
|
||||
|
||||
/** @return true if successful, false if full */
|
||||
private final boolean addKeys(KeyPair kp) {
|
||||
return _keys.offer(kp);
|
||||
}
|
||||
|
||||
private final int getSize() {
|
||||
return _keys.size();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package net.i2p.router.transport.crypto;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import com.southernstorm.noise.crypto.Curve25519;
|
||||
|
||||
/**
|
||||
* A PrivateKey we can stick in a KeyPair.
|
||||
* Raw data is accessible via getEncoded().
|
||||
* Also provides a toPublic() method.
|
||||
*
|
||||
* @since 0.9.36
|
||||
*/
|
||||
public class X25519PrivateKey implements PrivateKey {
|
||||
|
||||
private final byte[] _data;
|
||||
|
||||
/**
|
||||
* Montgomery representation, little-endian
|
||||
* @param data 32 bytes
|
||||
* @throws IllegalArgumentException if not 32 bytes
|
||||
*/
|
||||
public X25519PrivateKey(byte[] data) {
|
||||
if (data.length != 32)
|
||||
throw new IllegalArgumentException();
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public X25519PublicKey toPublic() {
|
||||
byte[] pub = new byte[32];
|
||||
Curve25519.eval(pub, 0, _data, null);
|
||||
return new X25519PublicKey(pub);
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw byte array, there is no encoding.
|
||||
* @return the data passed in
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return "X25519";
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return "raw";
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package net.i2p.router.transport.crypto;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* A PublicKey we can stick in a KeyPair.
|
||||
* Raw data is accessible via getEncoded().
|
||||
*
|
||||
* @since 0.9.36
|
||||
*/
|
||||
public class X25519PublicKey implements PublicKey {
|
||||
|
||||
private final byte[] _data;
|
||||
|
||||
/**
|
||||
* Montgomery representation, little-endian
|
||||
* @param data 32 bytes
|
||||
* @throws IllegalArgumentException if not 32 bytes
|
||||
*/
|
||||
public X25519PublicKey(byte[] data) {
|
||||
if (data.length != 32)
|
||||
throw new IllegalArgumentException();
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw byte array, there is no encoding.
|
||||
* @return the data passed in
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return "X25519";
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return "raw";
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user