forked from I2P_Developers/i2p.i2p
Ratchet: Validate NS datetime block; add NS key bloom filter
This commit is contained in:
@ -61,6 +61,8 @@ public final class ECIESAEADEngine {
|
|||||||
private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE;
|
private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE;
|
||||||
private static final byte[] NULLPK = new byte[KEYLEN];
|
private static final byte[] NULLPK = new byte[KEYLEN];
|
||||||
private static final int MAXPAD = 16;
|
private static final int MAXPAD = 16;
|
||||||
|
static final long MAX_NS_AGE = 5*60*1000;
|
||||||
|
private static final long MAX_NS_FUTURE = 2*60*1000;
|
||||||
// debug, send ACKREQ in every ES
|
// debug, send ACKREQ in every ES
|
||||||
private static final boolean ACKREQ_IN_ES = false;
|
private static final boolean ACKREQ_IN_ES = false;
|
||||||
|
|
||||||
@ -145,6 +147,8 @@ public final class ECIESAEADEngine {
|
|||||||
try {
|
try {
|
||||||
return x_decrypt(data, targetPrivateKey, keyManager);
|
return x_decrypt(data, targetPrivateKey, keyManager);
|
||||||
} catch (DataFormatException dfe) {
|
} catch (DataFormatException dfe) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("ECIES decrypt error", dfe);
|
||||||
throw dfe;
|
throw dfe;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
_log.error("ECIES decrypt error", e);
|
_log.error("ECIES decrypt error", e);
|
||||||
@ -176,11 +180,11 @@ public final class ECIESAEADEngine {
|
|||||||
HandshakeState state = key.getHandshakeState();
|
HandshakeState state = key.getHandshakeState();
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
if (shouldDebug)
|
if (shouldDebug)
|
||||||
_log.debug("Decrypting ES with tag: " + st.toBase64() + ": key: " + key.toBase64() + ": " + data.length + " bytes");
|
_log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key.toBase64() + ": " + data.length + " bytes");
|
||||||
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
|
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
|
||||||
} else if (data.length >= MIN_NSR_SIZE) {
|
} else if (data.length >= MIN_NSR_SIZE) {
|
||||||
if (shouldDebug)
|
if (shouldDebug)
|
||||||
_log.debug("Decrypting NSR with tag: " + st.toBase64() + ": key: " + key.toBase64() + ": " + data.length + " bytes");
|
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key.toBase64() + ": " + data.length + " bytes");
|
||||||
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
||||||
} else {
|
} else {
|
||||||
decrypted = null;
|
decrypted = null;
|
||||||
@ -270,6 +274,15 @@ public final class ECIESAEADEngine {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// bloom filter here based on ephemeral key
|
||||||
|
// or should we do it based on apparent elg2-encoded key
|
||||||
|
// at the very top, to prevent excess DH resource usage?
|
||||||
|
// But that would put everything in the bloom filter.
|
||||||
|
if (keyManager.isDuplicate(pk)) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Dup eph. key in IB NS: " + pk);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] bobPK = new byte[KEYLEN];
|
byte[] bobPK = new byte[KEYLEN];
|
||||||
state.getRemotePublicKey().getPublicKey(bobPK, 0);
|
state.getRemotePublicKey().getPublicKey(bobPK, 0);
|
||||||
@ -298,7 +311,13 @@ public final class ECIESAEADEngine {
|
|||||||
} catch (DataFormatException e) {
|
} catch (DataFormatException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new DataFormatException("Msg 1 payload error", e);
|
throw new DataFormatException("NS payload error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc.datetime == 0) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("No datetime block in IB NS");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tell the SKM
|
// tell the SKM
|
||||||
@ -874,12 +893,17 @@ public final class ECIESAEADEngine {
|
|||||||
remote = remoteKey;
|
remote = remoteKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void gotDateTime(long time) {
|
public void gotDateTime(long time) throws DataFormatException {
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("Got DATE block: " + DataHelper.formatTime(time));
|
_log.debug("Got DATE block: " + DataHelper.formatTime(time));
|
||||||
if (datetime != 0)
|
if (datetime != 0)
|
||||||
throw new IllegalArgumentException("Multiple DATETIME blocks");
|
throw new DataFormatException("Multiple DATETIME blocks");
|
||||||
datetime = time;
|
datetime = time;
|
||||||
|
long now = _context.clock().now();
|
||||||
|
if (time < now - MAX_NS_AGE ||
|
||||||
|
time > now + MAX_NS_FUTURE) {
|
||||||
|
throw new DataFormatException("Excess clock skew in IB NS: " + DataHelper.formatTime(time));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void gotOptions(byte[] options, boolean isHandshake) {
|
public void gotOptions(byte[] options, boolean isHandshake) {
|
||||||
|
@ -30,6 +30,7 @@ import net.i2p.data.PublicKey;
|
|||||||
import net.i2p.data.SessionKey;
|
import net.i2p.data.SessionKey;
|
||||||
import net.i2p.data.SessionTag;
|
import net.i2p.data.SessionTag;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.util.DecayingHashSet;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer2;
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
protected final I2PAppContext _context;
|
protected final I2PAppContext _context;
|
||||||
private volatile boolean _alive;
|
private volatile boolean _alive;
|
||||||
private final HKDF _hkdf;
|
private final HKDF _hkdf;
|
||||||
|
private final DecayingHashSet _replayFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let outbound session tags sit around for this long before expiring them.
|
* Let outbound session tags sit around for this long before expiring them.
|
||||||
@ -95,17 +97,25 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
|
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
|
||||||
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
|
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
|
||||||
_hkdf = new HKDF(context);
|
_hkdf = new HKDF(context);
|
||||||
|
_replayFilter = new DecayingHashSet(context, (int) ECIESAEADEngine.MAX_NS_AGE, 32, "Ratchet-NS");
|
||||||
// start the precalc of Elg2 keys if it wasn't already started
|
// start the precalc of Elg2 keys if it wasn't already started
|
||||||
context.eciesEngine().startup();
|
context.eciesEngine().startup();
|
||||||
_alive = true;
|
_alive = true;
|
||||||
new CleanupEvent();
|
new CleanupEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cannot be restarted
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
_alive = false;
|
_alive = false;
|
||||||
_inboundTagSets.clear();
|
_inboundTagSets.clear();
|
||||||
_outboundSessions.clear();
|
_outboundSessions.clear();
|
||||||
|
synchronized (_pendingOutboundSessions) {
|
||||||
|
_pendingOutboundSessions.clear();
|
||||||
|
}
|
||||||
|
_replayFilter.stopDecaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CleanupEvent extends SimpleTimer2.TimedEvent {
|
private class CleanupEvent extends SimpleTimer2.TimedEvent {
|
||||||
@ -159,6 +169,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if a dup
|
||||||
|
* @since 0.9.46
|
||||||
|
*/
|
||||||
|
boolean isDuplicate(PublicKey pk) {
|
||||||
|
return _replayFilter.add(pk.getData(), 0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inbound or outbound. Checks state.getRole() to determine.
|
* Inbound or outbound. Checks state.getRole() to determine.
|
||||||
* For outbound (NS sent), adds to list of pending inbound sessions and returns true.
|
* For outbound (NS sent), adds to list of pending inbound sessions and returns true.
|
||||||
@ -591,6 +609,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
OutboundSession old = _outboundSessions.putIfAbsent(sess.getTarget(), sess);
|
OutboundSession old = _outboundSessions.putIfAbsent(sess.getTarget(), sess);
|
||||||
boolean rv = old == null;
|
boolean rv = old == null;
|
||||||
if (!rv) {
|
if (!rv) {
|
||||||
|
// TODO fix
|
||||||
if (isInbound && old.getLastUsedDate() < _context.clock().now() - SESSION_TAG_DURATION_MS - (60*1000)) {
|
if (isInbound && old.getLastUsedDate() < _context.clock().now() - SESSION_TAG_DURATION_MS - (60*1000)) {
|
||||||
_outboundSessions.put(sess.getTarget(), sess);
|
_outboundSessions.put(sess.getTarget(), sess);
|
||||||
rv = true;
|
rv = true;
|
||||||
|
Reference in New Issue
Block a user