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 955d5a0252..e704ba7ebb 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -125,7 +125,20 @@ public final class ECIESAEADEngine { * * @return decrypted data or null on failure */ - public CloveSet decrypt(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager) throws DataFormatException { + public CloveSet decrypt(byte data[], PrivateKey targetPrivateKey, + RatchetSKM keyManager) throws DataFormatException { + try { + return x_decrypt(data, targetPrivateKey, keyManager); + } catch (DataFormatException dfe) { + throw dfe; + } catch (Exception e) { + _log.error("ECIES decrypt error", e); + return null; + } + } + + private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey, + RatchetSKM keyManager) throws DataFormatException { if (targetPrivateKey.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); if (data == null) { @@ -514,6 +527,16 @@ public final class ECIESAEADEngine { */ public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, RatchetSKM keyManager) { + try { + return x_encrypt(cloves, target, priv, keyManager); + } catch (Exception e) { + _log.error("ECIES encrypt error", e); + return null; + } + } + + private byte[] x_encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, + RatchetSKM keyManager) { if (target.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); if (Arrays.equals(target.getData(), NULLPK)) { 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 2fe3730e7b..35c596a316 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -31,7 +31,7 @@ import net.i2p.data.SessionKey; import net.i2p.data.SessionTag; import net.i2p.router.RouterContext; import net.i2p.util.Log; -import net.i2p.util.SimpleTimer; +import net.i2p.util.SimpleTimer2; /** * @@ -97,7 +97,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // start the precalc of Elg2 keys if it wasn't already started context.eciesEngine().startup(); _alive = true; - _context.simpleTimer2().addEvent(new CleanupEvent(), 60*1000); + new CleanupEvent(); } @Override @@ -107,12 +107,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener _outboundSessions.clear(); } - private class CleanupEvent implements SimpleTimer.TimedEvent { + private class CleanupEvent extends SimpleTimer2.TimedEvent { + public CleanupEvent() { + // wait until outbound expiration time to start + super(_context.simpleTimer2(), SESSION_TAG_DURATION_MS); + } + public void timeReached() { if (!_alive) return; - // TODO - _context.simpleTimer2().addEvent(this, 60*1000); + aggressiveExpire(); + schedule(60*1000); } } @@ -520,6 +525,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener HandshakeState state = tagSet.getHandshakeState(); synchronized(tagSet) { key = tagSet.consume(tag); + if (key != null) + tagSet.setDate(_context.clock().now()); } if (key == null) { if (_log.shouldDebug()) @@ -563,7 +570,35 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * @return number of tag sets expired (bogus as it overcounts inbound) */ private int aggressiveExpire() { - return 0; + long now = _context.clock().now(); + long exp = now - SESSION_LIFETIME_MAX_MS; + + // inbound + int removed = 0; + for (Iterator iter = _inboundTagSets.values().iterator(); iter.hasNext();) { + RatchetTagSet ts = iter.next(); + if (ts.getDate() < exp) { + iter.remove(); + removed++; + } + } + if (removed > 0 && _log.shouldInfo()) + _log.info("Expired inbound: " + removed); + + // outbound + int oremoved = 0; + exp = now - (SESSION_LIFETIME_MAX_MS / 2); + for (Iterator iter = _outboundSessions.values().iterator(); iter.hasNext();) { + OutboundSession sess = iter.next(); + oremoved += sess.expireTags(); + if (sess.getLastUsedDate() < exp) { + iter.remove(); + oremoved++; + } + } + if (oremoved > 0 && _log.shouldInfo()) + _log.info("Expired outbound: " + oremoved); + return removed + oremoved; } /// begin SessionTagListener /// @@ -607,12 +642,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener @Override public void renderStatusHTML(Writer out) throws IOException { StringBuilder buf = new StringBuilder(1024); + + // inbound buf.append("

Ratchet Inbound sessions

" + ""); Map> inboundSets = getRatchetTagSetsBySessionKey(); int total = 0; int totalSets = 0; long now = _context.clock().now(); + long exp = now + SESSION_LIFETIME_MAX_MS; Set sets = new TreeSet(new RatchetTagSetComparator()); for (Map.Entry> e : inboundSets.entrySet()) { SessionKey skey = e.getKey(); @@ -623,15 +661,16 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener "" + "\n"); out.write(buf.toString()); @@ -644,11 +683,12 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener "
Sets: ").append(sets.size()).append("
    "); for (RatchetTagSet ts : sets) { - int size = ts.getTags().size(); + int size = ts.size(); total += size; buf.append("
  • ID: ").append(ts.getID()); - long expires = ts.getDate() - now; + buf.append(" created: ").append(DataHelper.formatTime(ts.getCreated())); + long expires = exp - ts.getDate(); if (expires > 0) - buf.append(" expires in: ").append(DataHelper.formatDuration2(expires)).append(" with "); + buf.append(" expires in: ").append(DataHelper.formatDuration2(expires)).append(" with "); else - buf.append(" expired: ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with "); - buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining
  • "); + buf.append(" expired: ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with "); + buf.append(size).append('/').append(ts.remaining()).append(" tags remaining"); } buf.append("
" + "

Ratchet Outbound sessions

" + ""); - total = 0; + + // outbound totalSets = 0; + exp = now + SESSION_TAG_DURATION_MS; Set outbound = getOutboundSessions(); - for (Iterator iter = outbound.iterator(); iter.hasNext();) { - OutboundSession sess = iter.next(); + for (OutboundSession sess : outbound) { sets.clear(); sets.addAll(sess.getTagSets()); totalSets += sets.size(); @@ -662,20 +702,22 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener buf.append("" + "" + "\n"); out.write(buf.toString()); buf.setLength(0); } - buf.append("\n
# Sets: ").append(sess.getTagSets().size()).append("
    "); - for (Iterator siter = sets.iterator(); siter.hasNext();) { - RatchetTagSet ts = siter.next(); - int size = ts.getTags().size(); - total += size; + for (RatchetTagSet ts : sets) { + int size = ts.remaining(); buf.append("
  • ID: ").append(ts.getID()) - .append(" Sent: ").append(DataHelper.formatDuration2(now - ts.getDate())).append(" ago with "); - buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining; acked? ").append(ts.getAcked()).append("
  • "); + .append(" created: ").append(DataHelper.formatTime(ts.getCreated())); + long expires = exp - ts.getDate(); + if (expires > 0) + buf.append(" expires in: ").append(DataHelper.formatDuration2(expires)).append(" with "); + else + buf.append(" expired: ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with "); + buf.append(size).append(" tags remaining; acked? ").append(ts.getAcked()).append(""); } buf.append("
Total outbound tags: ").append(total).append(" (") - .append(DataHelper.formatSize2(32*total)).append("B); sets: ").append(totalSets) + buf.append("
Total sets: ").append(totalSets) .append("; sessions: ").append(outbound.size()) .append("
"); @@ -697,10 +739,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener */ private static class RatchetTagSetComparator implements Comparator, Serializable { public int compare(RatchetTagSet l, RatchetTagSet r) { - int rv = (int) (l.getDate() - r.getDate()); - if (rv != 0) - return rv; - return l.hashCode() - r.hashCode(); + return l.getID() - r.getID(); } } @@ -884,7 +923,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (_log.shouldWarn()) { int dropped = 0; for (RatchetTagSet set : _tagSets) { - dropped += set.getTags().size(); + dropped += set.remaining(); } _log.warn("Rekeyed from " + _currentKey + " to " + key + ": dropping " + dropped + " session tags", new Exception()); @@ -944,6 +983,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (set.getDate() + SESSION_TAG_DURATION_MS > now) { RatchetSessionTag tag = set.consumeNext(); if (tag != null) { + set.setDate(now); SessionKeyAndNonce skn = set.consumeNextKey(); return new RatchetEntry(tag, skn); } else if (_log.shouldInfo()) { @@ -970,8 +1010,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (!set.getAcked()) continue; if (set.getDate() + SESSION_TAG_DURATION_MS > now) { -/////////// just add fixed number? - int sz = set.getTags().size(); + // or just add fixed number? + int sz = set.remaining(); tags += sz; } } @@ -988,7 +1028,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener long last = 0; synchronized (_tagSets) { for (RatchetTagSet set : _tagSets) { - if ( (set.getDate() > last) && (!set.getTags().isEmpty()) ) + if (set.getDate() > last && set.remaining() > 0) last = set.getDate(); } } diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java index 105be61e78..ae504c3219 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java @@ -2,7 +2,6 @@ package net.i2p.router.crypto.ratchet; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -14,6 +13,7 @@ import net.i2p.I2PAppContext; import net.i2p.crypto.HKDF; import net.i2p.crypto.TagSetHandle; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; /** @@ -38,7 +38,8 @@ class RatchetTagSet implements TagSetHandle { // We use byte[] for key to save space, because we don't need indexOfValueByValue() private final SparseArray _sessionKeys; private final HKDF hkdf; - private final long _date; + private final long _created; + private long _date; private final int _id; private final int _originalSize; private final int _maxSize; @@ -57,7 +58,8 @@ class RatchetTagSet implements TagSetHandle { private static final String INFO_4 = "SessionTagKeyGen"; private static final String INFO_5 = "SymmetricRatchet"; private static final byte[] ZEROLEN = new byte[0]; - private static final int TAGLEN = 8; + private static final int TAGLEN = RatchetSessionTag.LENGTH; + private static final int MAX = 65535; /** * Outbound NSR Tagset @@ -82,7 +84,7 @@ class RatchetTagSet implements TagSetHandle { /** * Inbound NSR Tagset * - * @param date For inbound: when the TagSet will expire + * @param date For inbound: creation time */ public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data, long date, int id, int minSize, int maxSize) { @@ -92,7 +94,7 @@ class RatchetTagSet implements TagSetHandle { /** * Inbound ES Tagset * - * @param date For inbound: when the TagSet will expire + * @param date For inbound: creation time */ public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, SessionKey rootKey, SessionKey data, long date, int id, int minSize, int maxSize) { @@ -101,13 +103,14 @@ class RatchetTagSet implements TagSetHandle { /** - * @param date For inbound: when the TagSet will expire; for outbound: creation time + * @param date For inbound and outbound: creation time */ private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data, long date, int id, boolean isInbound, int minSize, int maxSize) { _lsnr = lsnr; _state = state; _key = rootKey; + _created = date; _date = date; _id = id; _originalSize = minSize; @@ -161,27 +164,64 @@ class RatchetTagSet implements TagSetHandle { } /** - * For inbound: when the TagSet will expire; for outbound: creation time + * For inbound and outbound: last used time */ public long getDate() { return _date; } - /** for debugging */ - public int getOriginalSize() { - return 0; + /** + * For inbound and outbound: last used time + */ + public void setDate(long when) { + _date = when; } + /** + * For inbound and outbound: creation time + */ + public long getCreated() { + return _created; + } + + /** for debugging */ + public int getOriginalSize() { + return _originalSize; + } + + /** + * unused tags generated + * @return 0 for outbound + */ public int size() { return _sessionTags != null ? _sessionTags.size() : 0; } + /** + * tags remaining + * @return 0 - 65535 + */ + public int remaining() { + int nextKey; + if (_sessionTags != null) { + // IB + if (_sessionTags.size() <= 0) + nextKey = 0; + else + nextKey = _sessionTags.keyAt(0); + } else { + // OB + nextKey = _lastTag + 1; + } + return MAX - nextKey; + } + /** * tags still available * inbound only * testing only */ - public List getTags() { + private List getTags() { if (_sessionTags == null) return Collections.emptyList(); int sz = _sessionTags.size(); @@ -197,7 +237,7 @@ class RatchetTagSet implements TagSetHandle { * inbound only * testing only */ - public RatchetSessionTag getFirstTag() { + private RatchetSessionTag getFirstTag() { if (_sessionTags == null) throw new IllegalStateException("Outbound tagset"); if (_sessionTags.size() <= 0) @@ -343,10 +383,14 @@ class RatchetTagSet implements TagSetHandle { buf.append("NSR "); else buf.append("ES "); - buf.append("TagSet #").append(_id).append(" created: ").append(new Date(_date)); + buf.append("TagSet #").append(_id) + .append(" created: ").append(DataHelper.formatTime(_created)) + .append(" last use: ").append(DataHelper.formatTime(_date)); int sz = size(); - buf.append(" Size: ").append(sz); - buf.append('/').append(getOriginalSize()); + buf.append(" Size: ").append(sz) + .append(" Orig: ").append(_originalSize) + .append(" Max: ").append(_maxSize) + .append(" Remaining: ").append(remaining()); buf.append(" Acked? ").append(_acked); if (_sessionTags != null) { buf.append(" Inbound");