From 471b53698a99cce39efce4db39665a2b3d0e4d1e Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 1 Apr 2020 12:58:24 +0000 Subject: [PATCH] Ratchet: Validate NS datetime block; add NS key bloom filter --- .../crypto/ratchet/ECIESAEADEngine.java | 36 +++++++++++++++---- .../i2p/router/crypto/ratchet/RatchetSKM.java | 19 ++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java index 6b92a20f02..1a203eec00 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -61,6 +61,8 @@ public final class ECIESAEADEngine { private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE; private static final byte[] NULLPK = new byte[KEYLEN]; 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 private static final boolean ACKREQ_IN_ES = false; @@ -145,6 +147,8 @@ public final class ECIESAEADEngine { try { return x_decrypt(data, targetPrivateKey, keyManager); } catch (DataFormatException dfe) { + if (_log.shouldWarn()) + _log.warn("ECIES decrypt error", dfe); throw dfe; } catch (Exception e) { _log.error("ECIES decrypt error", e); @@ -176,11 +180,11 @@ public final class ECIESAEADEngine { HandshakeState state = key.getHandshakeState(); if (state == null) { 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); } else if (data.length >= MIN_NSR_SIZE) { 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); } else { decrypted = null; @@ -270,6 +274,15 @@ public final class ECIESAEADEngine { } 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]; state.getRemotePublicKey().getPublicKey(bobPK, 0); @@ -298,7 +311,13 @@ public final class ECIESAEADEngine { } catch (DataFormatException e) { throw 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 @@ -862,7 +881,7 @@ public final class ECIESAEADEngine { public PLCallback() { this(null, null); } - + /** * ES * @param keyManager only for ES, otherwise null @@ -874,12 +893,17 @@ public final class ECIESAEADEngine { remote = remoteKey; } - public void gotDateTime(long time) { + public void gotDateTime(long time) throws DataFormatException { if (_log.shouldDebug()) _log.debug("Got DATE block: " + DataHelper.formatTime(time)); if (datetime != 0) - throw new IllegalArgumentException("Multiple DATETIME blocks"); + throw new DataFormatException("Multiple DATETIME blocks"); 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) { diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java index 4c08ec57c2..2330c84db6 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -30,6 +30,7 @@ import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; import net.i2p.data.SessionTag; import net.i2p.router.RouterContext; +import net.i2p.router.util.DecayingHashSet; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; @@ -49,6 +50,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener protected final I2PAppContext _context; private volatile boolean _alive; private final HKDF _hkdf; + private final DecayingHashSet _replayFilter; /** * 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); _inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128); _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 context.eciesEngine().startup(); _alive = true; new CleanupEvent(); } + /** + * Cannot be restarted + */ @Override public void shutdown() { _alive = false; _inboundTagSets.clear(); _outboundSessions.clear(); + synchronized (_pendingOutboundSessions) { + _pendingOutboundSessions.clear(); + } + _replayFilter.stopDecaying(); } private class CleanupEvent extends SimpleTimer2.TimedEvent { @@ -159,6 +169,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener 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. * 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); boolean rv = old == null; if (!rv) { + // TODO fix if (isInbound && old.getLastUsedDate() < _context.clock().now() - SESSION_TAG_DURATION_MS - (60*1000)) { _outboundSessions.put(sess.getTarget(), sess); rv = true; -- GitLab