Ratchet: Implement expiration

Store creation and last-used in tagset
Catch all decrypt/encrypt exceptions
Debug page improvements
This commit is contained in:
zzz
2019-11-03 16:51:51 +00:00
parent 3ba48fda86
commit b5f6c58a0b
3 changed files with 153 additions and 46 deletions

View File

@ -125,7 +125,20 @@ public final class ECIESAEADEngine {
* *
* @return decrypted data or null on failure * @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) if (targetPrivateKey.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (data == null) { if (data == null) {
@ -514,6 +527,16 @@ public final class ECIESAEADEngine {
*/ */
public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv,
RatchetSKM keyManager) { 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) if (target.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (Arrays.equals(target.getData(), NULLPK)) { if (Arrays.equals(target.getData(), NULLPK)) {

View File

@ -31,7 +31,7 @@ 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.util.Log; 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 // start the precalc of Elg2 keys if it wasn't already started
context.eciesEngine().startup(); context.eciesEngine().startup();
_alive = true; _alive = true;
_context.simpleTimer2().addEvent(new CleanupEvent(), 60*1000); new CleanupEvent();
} }
@Override @Override
@ -107,12 +107,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
_outboundSessions.clear(); _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() { public void timeReached() {
if (!_alive) if (!_alive)
return; return;
// TODO aggressiveExpire();
_context.simpleTimer2().addEvent(this, 60*1000); schedule(60*1000);
} }
} }
@ -520,6 +525,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
HandshakeState state = tagSet.getHandshakeState(); HandshakeState state = tagSet.getHandshakeState();
synchronized(tagSet) { synchronized(tagSet) {
key = tagSet.consume(tag); key = tagSet.consume(tag);
if (key != null)
tagSet.setDate(_context.clock().now());
} }
if (key == null) { if (key == null) {
if (_log.shouldDebug()) 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) * @return number of tag sets expired (bogus as it overcounts inbound)
*/ */
private int aggressiveExpire() { private int aggressiveExpire() {
return 0; long now = _context.clock().now();
long exp = now - SESSION_LIFETIME_MAX_MS;
// inbound
int removed = 0;
for (Iterator<RatchetTagSet> 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<OutboundSession> 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 /// /// begin SessionTagListener ///
@ -607,12 +642,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
@Override @Override
public void renderStatusHTML(Writer out) throws IOException { public void renderStatusHTML(Writer out) throws IOException {
StringBuilder buf = new StringBuilder(1024); StringBuilder buf = new StringBuilder(1024);
// inbound
buf.append("<h3 class=\"debug_inboundsessions\">Ratchet Inbound sessions</h3>" + buf.append("<h3 class=\"debug_inboundsessions\">Ratchet Inbound sessions</h3>" +
"<table>"); "<table>");
Map<SessionKey, Set<RatchetTagSet>> inboundSets = getRatchetTagSetsBySessionKey(); Map<SessionKey, Set<RatchetTagSet>> inboundSets = getRatchetTagSetsBySessionKey();
int total = 0; int total = 0;
int totalSets = 0; int totalSets = 0;
long now = _context.clock().now(); long now = _context.clock().now();
long exp = now + SESSION_LIFETIME_MAX_MS;
Set<RatchetTagSet> sets = new TreeSet<RatchetTagSet>(new RatchetTagSetComparator()); Set<RatchetTagSet> sets = new TreeSet<RatchetTagSet>(new RatchetTagSetComparator());
for (Map.Entry<SessionKey, Set<RatchetTagSet>> e : inboundSets.entrySet()) { for (Map.Entry<SessionKey, Set<RatchetTagSet>> e : inboundSets.entrySet()) {
SessionKey skey = e.getKey(); SessionKey skey = e.getKey();
@ -623,15 +661,16 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
"<td><b>Sets:</b> ").append(sets.size()).append("</td></tr>" + "<td><b>Sets:</b> ").append(sets.size()).append("</td></tr>" +
"<tr class=\"expiry\"><td colspan=\"2\"><ul>"); "<tr class=\"expiry\"><td colspan=\"2\"><ul>");
for (RatchetTagSet ts : sets) { for (RatchetTagSet ts : sets) {
int size = ts.getTags().size(); int size = ts.size();
total += size; total += size;
buf.append("<li><b>ID: ").append(ts.getID()); buf.append("<li><b>ID: ").append(ts.getID());
long expires = ts.getDate() - now; buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()));
long expires = exp - ts.getDate();
if (expires > 0) if (expires > 0)
buf.append(" expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with "); buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else else
buf.append(" expired:</b> ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with "); buf.append(" <b>expired:</b> ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with ");
buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining</li>"); buf.append(size).append('/').append(ts.remaining()).append(" tags remaining</li>");
} }
buf.append("</ul></td></tr>\n"); buf.append("</ul></td></tr>\n");
out.write(buf.toString()); out.write(buf.toString());
@ -644,11 +683,12 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
"</table>" + "</table>" +
"<h3 class=\"debug_outboundsessions\">Ratchet Outbound sessions</h3>" + "<h3 class=\"debug_outboundsessions\">Ratchet Outbound sessions</h3>" +
"<table>"); "<table>");
total = 0;
// outbound
totalSets = 0; totalSets = 0;
exp = now + SESSION_TAG_DURATION_MS;
Set<OutboundSession> outbound = getOutboundSessions(); Set<OutboundSession> outbound = getOutboundSessions();
for (Iterator<OutboundSession> iter = outbound.iterator(); iter.hasNext();) { for (OutboundSession sess : outbound) {
OutboundSession sess = iter.next();
sets.clear(); sets.clear();
sets.addAll(sess.getTagSets()); sets.addAll(sess.getTagSets());
totalSets += sets.size(); totalSets += sets.size();
@ -662,20 +702,22 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
buf.append("</div></td>" + buf.append("</div></td>" +
"<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" + "<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>"); "<tr><td colspan=\"2\"><ul>");
for (Iterator<RatchetTagSet> siter = sets.iterator(); siter.hasNext();) { for (RatchetTagSet ts : sets) {
RatchetTagSet ts = siter.next(); int size = ts.remaining();
int size = ts.getTags().size();
total += size;
buf.append("<li><b>ID: ").append(ts.getID()) buf.append("<li><b>ID: ").append(ts.getID())
.append(" Sent:</b> ").append(DataHelper.formatDuration2(now - ts.getDate())).append(" ago with "); .append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()));
buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>"); long expires = exp - ts.getDate();
if (expires > 0)
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else
buf.append(" <b>expired:</b> ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with ");
buf.append(size).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>");
} }
buf.append("</ul></td></tr>\n"); buf.append("</ul></td></tr>\n");
out.write(buf.toString()); out.write(buf.toString());
buf.setLength(0); buf.setLength(0);
} }
buf.append("<tr><th colspan=\"2\">Total outbound tags: ").append(total).append(" (") buf.append("<tr><th colspan=\"2\">Total sets: ").append(totalSets)
.append(DataHelper.formatSize2(32*total)).append("B); sets: ").append(totalSets)
.append("; sessions: ").append(outbound.size()) .append("; sessions: ").append(outbound.size())
.append("</th></tr>\n</table>"); .append("</th></tr>\n</table>");
@ -697,10 +739,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/ */
private static class RatchetTagSetComparator implements Comparator<RatchetTagSet>, Serializable { private static class RatchetTagSetComparator implements Comparator<RatchetTagSet>, Serializable {
public int compare(RatchetTagSet l, RatchetTagSet r) { public int compare(RatchetTagSet l, RatchetTagSet r) {
int rv = (int) (l.getDate() - r.getDate()); return l.getID() - r.getID();
if (rv != 0)
return rv;
return l.hashCode() - r.hashCode();
} }
} }
@ -884,7 +923,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (_log.shouldWarn()) { if (_log.shouldWarn()) {
int dropped = 0; int dropped = 0;
for (RatchetTagSet set : _tagSets) { for (RatchetTagSet set : _tagSets) {
dropped += set.getTags().size(); dropped += set.remaining();
} }
_log.warn("Rekeyed from " + _currentKey + " to " + key _log.warn("Rekeyed from " + _currentKey + " to " + key
+ ": dropping " + dropped + " session tags", new Exception()); + ": 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) { if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
RatchetSessionTag tag = set.consumeNext(); RatchetSessionTag tag = set.consumeNext();
if (tag != null) { if (tag != null) {
set.setDate(now);
SessionKeyAndNonce skn = set.consumeNextKey(); SessionKeyAndNonce skn = set.consumeNextKey();
return new RatchetEntry(tag, skn); return new RatchetEntry(tag, skn);
} else if (_log.shouldInfo()) { } else if (_log.shouldInfo()) {
@ -970,8 +1010,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (!set.getAcked()) if (!set.getAcked())
continue; continue;
if (set.getDate() + SESSION_TAG_DURATION_MS > now) { if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
/////////// just add fixed number? // or just add fixed number?
int sz = set.getTags().size(); int sz = set.remaining();
tags += sz; tags += sz;
} }
} }
@ -988,7 +1028,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
long last = 0; long last = 0;
synchronized (_tagSets) { synchronized (_tagSets) {
for (RatchetTagSet set : _tagSets) { for (RatchetTagSet set : _tagSets) {
if ( (set.getDate() > last) && (!set.getTags().isEmpty()) ) if (set.getDate() > last && set.remaining() > 0)
last = set.getDate(); last = set.getDate();
} }
} }

View File

@ -2,7 +2,6 @@ package net.i2p.router.crypto.ratchet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -14,6 +13,7 @@ import net.i2p.I2PAppContext;
import net.i2p.crypto.HKDF; import net.i2p.crypto.HKDF;
import net.i2p.crypto.TagSetHandle; import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey; 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() // We use byte[] for key to save space, because we don't need indexOfValueByValue()
private final SparseArray<byte[]> _sessionKeys; private final SparseArray<byte[]> _sessionKeys;
private final HKDF hkdf; private final HKDF hkdf;
private final long _date; private final long _created;
private long _date;
private final int _id; private final int _id;
private final int _originalSize; private final int _originalSize;
private final int _maxSize; private final int _maxSize;
@ -57,7 +58,8 @@ class RatchetTagSet implements TagSetHandle {
private static final String INFO_4 = "SessionTagKeyGen"; private static final String INFO_4 = "SessionTagKeyGen";
private static final String INFO_5 = "SymmetricRatchet"; private static final String INFO_5 = "SymmetricRatchet";
private static final byte[] ZEROLEN = new byte[0]; 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 * Outbound NSR Tagset
@ -82,7 +84,7 @@ class RatchetTagSet implements TagSetHandle {
/** /**
* Inbound NSR Tagset * 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, public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id, int minSize, int maxSize) { long date, int id, int minSize, int maxSize) {
@ -92,7 +94,7 @@ class RatchetTagSet implements TagSetHandle {
/** /**
* Inbound ES Tagset * 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, public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, SessionKey rootKey, SessionKey data,
long date, int id, int minSize, int maxSize) { 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, private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id, boolean isInbound, int minSize, int maxSize) { long date, int id, boolean isInbound, int minSize, int maxSize) {
_lsnr = lsnr; _lsnr = lsnr;
_state = state; _state = state;
_key = rootKey; _key = rootKey;
_created = date;
_date = date; _date = date;
_id = id; _id = id;
_originalSize = minSize; _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() { public long getDate() {
return _date; return _date;
} }
/** for debugging */ /**
public int getOriginalSize() { * For inbound and outbound: last used time
return 0; */
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() { public int size() {
return _sessionTags != null ? _sessionTags.size() : 0; 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 * tags still available
* inbound only * inbound only
* testing only * testing only
*/ */
public List<RatchetSessionTag> getTags() { private List<RatchetSessionTag> getTags() {
if (_sessionTags == null) if (_sessionTags == null)
return Collections.emptyList(); return Collections.emptyList();
int sz = _sessionTags.size(); int sz = _sessionTags.size();
@ -197,7 +237,7 @@ class RatchetTagSet implements TagSetHandle {
* inbound only * inbound only
* testing only * testing only
*/ */
public RatchetSessionTag getFirstTag() { private RatchetSessionTag getFirstTag() {
if (_sessionTags == null) if (_sessionTags == null)
throw new IllegalStateException("Outbound tagset"); throw new IllegalStateException("Outbound tagset");
if (_sessionTags.size() <= 0) if (_sessionTags.size() <= 0)
@ -343,10 +383,14 @@ class RatchetTagSet implements TagSetHandle {
buf.append("NSR "); buf.append("NSR ");
else else
buf.append("ES "); 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(); int sz = size();
buf.append(" Size: ").append(sz); buf.append(" Size: ").append(sz)
buf.append('/').append(getOriginalSize()); .append(" Orig: ").append(_originalSize)
.append(" Max: ").append(_maxSize)
.append(" Remaining: ").append(remaining());
buf.append(" Acked? ").append(_acked); buf.append(" Acked? ").append(_acked);
if (_sessionTags != null) { if (_sessionTags != null) {
buf.append(" Inbound"); buf.append(" Inbound");