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
*/
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)) {

View File

@ -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<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 ///
@ -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("<h3 class=\"debug_inboundsessions\">Ratchet Inbound sessions</h3>" +
"<table>");
Map<SessionKey, Set<RatchetTagSet>> inboundSets = getRatchetTagSetsBySessionKey();
int total = 0;
int totalSets = 0;
long now = _context.clock().now();
long exp = now + SESSION_LIFETIME_MAX_MS;
Set<RatchetTagSet> sets = new TreeSet<RatchetTagSet>(new RatchetTagSetComparator());
for (Map.Entry<SessionKey, Set<RatchetTagSet>> e : inboundSets.entrySet()) {
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>" +
"<tr class=\"expiry\"><td colspan=\"2\"><ul>");
for (RatchetTagSet ts : sets) {
int size = ts.getTags().size();
int size = ts.size();
total += size;
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)
buf.append(" expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else
buf.append(" expired:</b> ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with ");
buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining</li>");
buf.append(" <b>expired:</b> ").append(DataHelper.formatDuration2(0 - expires)).append(" ago with ");
buf.append(size).append('/').append(ts.remaining()).append(" tags remaining</li>");
}
buf.append("</ul></td></tr>\n");
out.write(buf.toString());
@ -644,11 +683,12 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
"</table>" +
"<h3 class=\"debug_outboundsessions\">Ratchet Outbound sessions</h3>" +
"<table>");
total = 0;
// outbound
totalSets = 0;
exp = now + SESSION_TAG_DURATION_MS;
Set<OutboundSession> outbound = getOutboundSessions();
for (Iterator<OutboundSession> 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("</div></td>" +
"<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>");
for (Iterator<RatchetTagSet> 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("<li><b>ID: ").append(ts.getID())
.append(" Sent:</b> ").append(DataHelper.formatDuration2(now - ts.getDate())).append(" ago with ");
buf.append(size).append('/').append(ts.getOriginalSize()).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>");
.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()));
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");
out.write(buf.toString());
buf.setLength(0);
}
buf.append("<tr><th colspan=\"2\">Total outbound tags: ").append(total).append(" (")
.append(DataHelper.formatSize2(32*total)).append("B); sets: ").append(totalSets)
buf.append("<tr><th colspan=\"2\">Total sets: ").append(totalSets)
.append("; sessions: ").append(outbound.size())
.append("</th></tr>\n</table>");
@ -697,10 +739,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/
private static class RatchetTagSetComparator implements Comparator<RatchetTagSet>, 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();
}
}

View File

@ -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<byte[]> _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<RatchetSessionTag> getTags() {
private List<RatchetSessionTag> 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");