Ratchet: Updates

- Prep for prop. 154 with SingleTagSet
- Variable timeout for tagsets
- Start cleaner sooner
- Make key optional in next key block
- HTML debug output improvement
- log tweaks and javadocs
This commit is contained in:
zzz
2020-03-30 16:44:42 +00:00
parent b19b529afe
commit dee92b5290
7 changed files with 199 additions and 51 deletions

View File

@ -772,14 +772,14 @@ public final class ECIESAEADEngine {
* - 16 byte MAC * - 16 byte MAC
* </pre> * </pre>
* *
* @param target unused, this is AEAD encrypt only using the session key and tag * @param target only used if callback is non-null to register it
* @param replyDI non-null to request an ack, or null * @param replyDI non-null to request an ack, or null
* @return encrypted data or null on failure * @return encrypted data or null on failure
*/ */
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re, private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re,
DeliveryInstructions replyDI, ReplyCallback callback, DeliveryInstructions replyDI, ReplyCallback callback,
RatchetSKM keyManager) { RatchetSKM keyManager) {
// // TODO remove DI, just make it a boolean
if (ACKREQ_IN_ES && replyDI == null) if (ACKREQ_IN_ES && replyDI == null)
replyDI = new DeliveryInstructions(); replyDI = new DeliveryInstructions();
byte rawTag[] = re.tag.getData(); byte rawTag[] = re.tag.getData();
@ -794,6 +794,31 @@ public final class ECIESAEADEngine {
return encr; return encr;
} }
/**
* Create an Existing Session Message to an anonymous target
* using the given session key and tag, for netdb DSM/DSRM replies.
* Called from MessageWrapper.
*
* No datetime, no next key, no acks, no ack requests.
* n=0, ad=null.
*
* <pre>
* - 8 byte SessionTag
* - payload
* - 16 byte MAC
* </pre>
*
* @return encrypted data or null on failure
* @since 0.9.46
*/
public byte[] encrypt(CloveSet cloves, SessionKey key, RatchetSessionTag tag) {
byte rawTag[] = tag.getData();
byte[] payload = createPayload(cloves, 0, null, null, null);
byte encr[] = encryptAEADBlock(rawTag, payload, key, 0);
System.arraycopy(rawTag, 0, encr, 0, TAGLEN);
return encr;
}
/** /**
* No ad * No ad
*/ */

View File

@ -48,12 +48,12 @@ final class MuxedEngine {
try { try {
rv = _context.garlicMessageParser().readCloveSet(dec, 0); rv = _context.garlicMessageParser().readCloveSet(dec, 0);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldWarn()) if (_log.shouldInfo())
_log.warn("ElG decrypt failed, trying ECIES", dfe); _log.info("ElG decrypt failed, trying ECIES", dfe);
} }
} else { } else {
if (_log.shouldWarn()) //if (_log.shouldDebug())
_log.warn("ElG decrypt failed, trying ECIES"); // _log.debug("ElG decrypt failed, trying ECIES");
} }
} }
if (rv == null) { if (rv == null) {

View File

@ -12,6 +12,9 @@ class NextSessionKey extends PublicKey {
private final int _id; private final int _id;
private final boolean _isReverse, _isRequest; private final boolean _isReverse, _isRequest;
/**
* @param data may be null
*/
public NextSessionKey(byte[] data, int id, boolean isReverse, boolean isRequest) { public NextSessionKey(byte[] data, int id, boolean isReverse, boolean isRequest) {
super(EncType.ECIES_X25519, data); super(EncType.ECIES_X25519, data);
_id = id; _id = id;

View File

@ -136,13 +136,19 @@ class RatchetPayload {
case BLOCK_NEXTKEY: case BLOCK_NEXTKEY:
{ {
if (len != 35) if (len != 3 && len != 35)
throw new IOException("Bad length for NEXTKEY: " + len); throw new IOException("Bad length for NEXTKEY: " + len);
boolean isReverse = (payload[i] & 0x01) != 0; boolean hasKey = (payload[i] & 0x01) != 0;
boolean isRequest = (payload[i] & 0x02) != 0; boolean isReverse = (payload[i] & 0x02) != 0;
boolean isRequest = (payload[i] & 0x04) != 0;
int id = (int) DataHelper.fromLong(payload, i + 1, 2); int id = (int) DataHelper.fromLong(payload, i + 1, 2);
byte[] data = new byte[32]; byte[] data;
System.arraycopy(payload, i + 3, data, 0, 32); if (hasKey) {
data = new byte[32];
System.arraycopy(payload, i + 3, data, 0, 32);
} else {
data = null;
}
NextSessionKey nsk = new NextSessionKey(data, id, isReverse, isRequest); NextSessionKey nsk = new NextSessionKey(data, id, isReverse, isRequest);
cb.gotNextKey(nsk); cb.gotNextKey(nsk);
} }
@ -352,17 +358,22 @@ class RatchetPayload {
} }
public int getDataLength() { public int getDataLength() {
return 35; return next.getData() != null ? 35 : 3;
} }
public int writeData(byte[] tgt, int off) { public int writeData(byte[] tgt, int off) {
if (next.isReverse()) if (next.getData() != null)
tgt[off] = 0x01; tgt[off] = 0x01;
if (next.isRequest()) if (next.isReverse())
tgt[off] |= 0x02; tgt[off] |= 0x02;
if (next.isRequest())
tgt[off] |= 0x04;
DataHelper.toLong(tgt, off + 1, 2, next.getID()); DataHelper.toLong(tgt, off + 1, 2, next.getID());
System.arraycopy(next.getData(), 0, tgt, off + 3, 32); if (next.getData() != null) {
return off + 35; System.arraycopy(next.getData(), 0, tgt, off + 3, 32);
return off + 35;
}
return off + 3;
} }
} }

View File

@ -54,7 +54,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* Let outbound session tags sit around for this long before expiring them. * Let outbound session tags sit around for this long before expiring them.
* Inbound tag expiration is set by SESSION_LIFETIME_MAX_MS * Inbound tag expiration is set by SESSION_LIFETIME_MAX_MS
*/ */
private final static long SESSION_TAG_DURATION_MS = 12 * 60 * 1000; final static long SESSION_TAG_DURATION_MS = 12 * 60 * 1000;
/** /**
* Keep unused inbound session tags around for this long (a few minutes longer than * Keep unused inbound session tags around for this long (a few minutes longer than
@ -63,9 +63,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* *
* This is also the max idle time for an outbound session. * This is also the max idle time for an outbound session.
*/ */
private final static long SESSION_LIFETIME_MAX_MS = SESSION_TAG_DURATION_MS + 3 * 60 * 1000; final static long SESSION_LIFETIME_MAX_MS = SESSION_TAG_DURATION_MS + 3 * 60 * 1000;
private final static long SESSION_PENDING_DURATION_MS = 5 * 60 * 1000; final static long SESSION_PENDING_DURATION_MS = 5 * 60 * 1000;
/** /**
* Time to send more if we are this close to expiration * Time to send more if we are this close to expiration
@ -110,8 +110,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private class CleanupEvent extends SimpleTimer2.TimedEvent { private class CleanupEvent extends SimpleTimer2.TimedEvent {
public CleanupEvent() { public CleanupEvent() {
// wait until outbound expiration time to start // wait until first expiration time to start
super(_context.simpleTimer2(), SESSION_TAG_DURATION_MS); super(_context.simpleTimer2(), SESSION_PENDING_DURATION_MS);
} }
public void timeReached() { public void timeReached() {
@ -491,6 +491,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* One time session
* @param expire time from now
*/
public void tagsReceived(SessionKey key, RatchetSessionTag tag, long expire) {
new SingleTagSet(this, key, tag, _context.clock().now(), expire);
}
/** /**
* remove a bunch of arbitrarily selected tags, then drop all of * remove a bunch of arbitrarily selected tags, then drop all of
* the associated tag sets. this is very time consuming - iterating * the associated tag sets. this is very time consuming - iterating
@ -543,13 +551,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (state == null) { if (state == null) {
// TODO this should really be after decrypt... // TODO this should really be after decrypt...
PublicKey pk = tagSet.getRemoteKey(); PublicKey pk = tagSet.getRemoteKey();
OutboundSession sess = getSession(pk); if (pk != null) {
if (sess != null) { OutboundSession sess = getSession(pk);
sess.firstTagConsumed(tagSet); if (sess != null) {
} else { sess.firstTagConsumed(tagSet);
if (_log.shouldDebug()) } else {
_log.debug("First tag consumed but session is gone"); if (_log.shouldDebug())
} _log.debug("First tag consumed but session is gone");
}
} // else null for SingleTagSets
} }
} }
if (_log.shouldDebug()) { if (_log.shouldDebug()) {
@ -611,13 +621,12 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/ */
private int aggressiveExpire() { private int aggressiveExpire() {
long now = _context.clock().now(); long now = _context.clock().now();
long exp = now - SESSION_LIFETIME_MAX_MS;
// inbound // inbound
int removed = 0; int removed = 0;
for (Iterator<RatchetTagSet> iter = _inboundTagSets.values().iterator(); iter.hasNext();) { for (Iterator<RatchetTagSet> iter = _inboundTagSets.values().iterator(); iter.hasNext();) {
RatchetTagSet ts = iter.next(); RatchetTagSet ts = iter.next();
if (ts.getDate() < exp) { if (ts.getExpiration() < now) {
iter.remove(); iter.remove();
removed++; removed++;
} }
@ -626,7 +635,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// outbound // outbound
int oremoved = 0; int oremoved = 0;
int cremoved = 0; int cremoved = 0;
exp = now - (SESSION_LIFETIME_MAX_MS / 2); long exp = now - (SESSION_LIFETIME_MAX_MS / 2);
for (Iterator<OutboundSession> iter = _outboundSessions.values().iterator(); iter.hasNext();) { for (Iterator<OutboundSession> iter = _outboundSessions.values().iterator(); iter.hasNext();) {
OutboundSession sess = iter.next(); OutboundSession sess = iter.next();
oremoved += sess.expireTags(now); oremoved += sess.expireTags(now);
@ -753,7 +762,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
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();
@ -767,8 +775,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
int size = ts.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());
buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated())); buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
long expires = ts.getDate() - exp; .append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
if (expires > 0) if (expires > 0)
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with "); buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else else
@ -789,7 +798,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// outbound // outbound
totalSets = 0; totalSets = 0;
exp = now - SESSION_TAG_DURATION_MS;
Set<OutboundSession> outbound = getOutboundSessions(); Set<OutboundSession> outbound = getOutboundSessions();
for (OutboundSession sess : outbound) { for (OutboundSession sess : outbound) {
sets.clear(); sets.clear();
@ -808,8 +816,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
for (RatchetTagSet ts : sets) { for (RatchetTagSet ts : sets) {
int size = ts.remaining(); int size = ts.remaining();
buf.append("<li><b>ID: ").append(ts.getID()) buf.append("<li><b>ID: ").append(ts.getID())
.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated())); .append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
long expires = ts.getDate() - exp; .append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
if (expires > 0) if (expires > 0)
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with "); buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else else
@ -1130,7 +1139,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
synchronized (_tagSets) { synchronized (_tagSets) {
for (Iterator<RatchetTagSet> iter = _tagSets.iterator(); iter.hasNext(); ) { for (Iterator<RatchetTagSet> iter = _tagSets.iterator(); iter.hasNext(); ) {
RatchetTagSet set = iter.next(); RatchetTagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) { if (set.getExpiration() <= now) {
iter.remove(); iter.remove();
removed++; removed++;
} }
@ -1139,7 +1148,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if ((now & 0x0f) == 0) { if ((now & 0x0f) == 0) {
for (Iterator<RatchetTagSet> iter = _unackedTagSets.iterator(); iter.hasNext(); ) { for (Iterator<RatchetTagSet> iter = _unackedTagSets.iterator(); iter.hasNext(); ) {
RatchetTagSet set = iter.next(); RatchetTagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) { if (set.getExpiration() <= now) {
iter.remove(); iter.remove();
removed++; removed++;
} }
@ -1156,7 +1165,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
while (!_tagSets.isEmpty()) { while (!_tagSets.isEmpty()) {
RatchetTagSet set = _tagSets.get(0); RatchetTagSet set = _tagSets.get(0);
synchronized(set) { synchronized(set) {
if (set.getDate() + SESSION_TAG_DURATION_MS > now) { if (set.getExpiration() > now) {
RatchetSessionTag tag = set.consumeNext(); RatchetSessionTag tag = set.consumeNext();
if (tag != null) { if (tag != null) {
set.setDate(now); set.setDate(now);
@ -1186,7 +1195,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
RatchetTagSet set = _tagSets.get(i); RatchetTagSet set = _tagSets.get(i);
if (!set.getAcked()) if (!set.getAcked())
continue; continue;
if (set.getDate() + SESSION_TAG_DURATION_MS > now) { if (set.getExpiration() > now) {
// or just add fixed number? // or just add fixed number?
int sz = set.remaining(); int sz = set.remaining();
tags += sz; tags += sz;
@ -1205,12 +1214,13 @@ 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.remaining() > 0) long exp = set.getExpiration();
last = set.getDate(); if (exp > last && set.remaining() > 0)
last = exp;
} }
} }
if (last > 0) if (last > 0)
return last + SESSION_TAG_DURATION_MS; return last;
return -1; return -1;
} }

View File

@ -38,7 +38,7 @@ import net.i2p.util.Log;
class RatchetTagSet implements TagSetHandle { class RatchetTagSet implements TagSetHandle {
private final SessionTagListener _lsnr; private final SessionTagListener _lsnr;
private final PublicKey _remoteKey; private final PublicKey _remoteKey;
private final SessionKey _key; protected final SessionKey _key;
private final HandshakeState _state; private final HandshakeState _state;
// inbound only, else null // inbound only, else null
// We use object for tags because we must do indexOfValueByValue() // We use object for tags because we must do indexOfValueByValue()
@ -48,6 +48,7 @@ class RatchetTagSet implements TagSetHandle {
private final SparseArray<byte[]> _sessionKeys; private final SparseArray<byte[]> _sessionKeys;
private final HKDF hkdf; private final HKDF hkdf;
private final long _created; private final long _created;
private final long _timeout;
private long _date; private long _date;
private final int _id; private final int _id;
private final int _originalSize; private final int _originalSize;
@ -84,7 +85,7 @@ class RatchetTagSet implements TagSetHandle {
*/ */
public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data, public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id) { long date, int id) {
this(hkdf, null, state, null, rootKey, data, date, id, false, 0, 0); this(hkdf, null, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, id, false, 0, 0);
} }
/** /**
@ -94,7 +95,7 @@ class RatchetTagSet implements TagSetHandle {
*/ */
public RatchetTagSet(HKDF hkdf, SessionKey rootKey, SessionKey data, public RatchetTagSet(HKDF hkdf, SessionKey rootKey, SessionKey data,
long date, int id) { long date, int id) {
this(hkdf, null, null, null, rootKey, data, date, id, false, 0, 0); this(hkdf, null, null, null, rootKey, data, date, RatchetSKM.SESSION_TAG_DURATION_MS, id, false, 0, 0);
} }
/** /**
@ -104,7 +105,7 @@ class RatchetTagSet implements TagSetHandle {
*/ */
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) {
this(hkdf, lsnr, state, null, rootKey, data, date, id, true, minSize, maxSize); this(hkdf, lsnr, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, id, true, minSize, maxSize);
} }
/** /**
@ -115,7 +116,7 @@ class RatchetTagSet implements TagSetHandle {
public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr,
PublicKey remoteKey, SessionKey rootKey, SessionKey data, PublicKey remoteKey, SessionKey rootKey, SessionKey data,
long date, int id, int minSize, int maxSize) { long date, int id, int minSize, int maxSize) {
this(hkdf, lsnr, null, remoteKey, rootKey, data, date, id, true, minSize, maxSize); this(hkdf, lsnr, null, remoteKey, rootKey, data, date, RatchetSKM.SESSION_LIFETIME_MAX_MS, id, true, minSize, maxSize);
} }
@ -124,12 +125,13 @@ class RatchetTagSet implements TagSetHandle {
*/ */
private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state,
PublicKey remoteKey, SessionKey rootKey, SessionKey data, PublicKey remoteKey, SessionKey rootKey, SessionKey data,
long date, int id, boolean isInbound, int minSize, int maxSize) { long date, long timeout, int id, boolean isInbound, int minSize, int maxSize) {
_lsnr = lsnr; _lsnr = lsnr;
_state = state; _state = state;
_remoteKey = remoteKey; _remoteKey = remoteKey;
_key = rootKey; _key = rootKey;
_created = date; _created = date;
_timeout = timeout;
_date = date; _date = date;
_id = id; _id = id;
_originalSize = minSize; _originalSize = minSize;
@ -159,6 +161,31 @@ class RatchetTagSet implements TagSetHandle {
} }
} }
/**
* For SingleTagSet
* @since 0.9.46
*/
protected RatchetTagSet(SessionTagListener lsnr, SessionKey rootKey, long date, long timeout) {
_lsnr = lsnr;
_state = null;
_remoteKey = null;
_key = rootKey;
_created = date;
_timeout = timeout;
_date = date;
_id = 0x10003;
_originalSize = 1;
_maxSize = 1;
_nextRootKey = null;
_sesstag_ck = null;
_sesstag_constant = null;
_symmkey_ck = null;
_symmkey_constant = null;
hkdf = null;
_sessionTags = null;
_sessionKeys = null;
}
public void clear() { public void clear() {
if (_sessionTags != null) if (_sessionTags != null)
_sessionTags.clear(); _sessionTags.clear();
@ -201,6 +228,7 @@ class RatchetTagSet implements TagSetHandle {
/** /**
* For inbound and outbound: last used time * For inbound and outbound: last used time
* Expiration is getDate() + getTimeout().
*/ */
public long getDate() { public long getDate() {
return _date; return _date;
@ -214,12 +242,30 @@ class RatchetTagSet implements TagSetHandle {
} }
/** /**
* For inbound and outbound: creation time * For inbound and outbound: creation time, for debugging only
*/ */
public long getCreated() { public long getCreated() {
return _created; return _created;
} }
/**
* For inbound and outbound: Idle timeout interval.
* Expiration is getDate() + getTimeout().
* @since 0.9.46
*/
public long getTimeout() {
return _timeout;
}
/**
* For inbound and outbound: Expiration.
* Expiration is getDate() + getTimeout().
* @since 0.9.46
*/
public synchronized long getExpiration() {
return _date + _timeout;
}
/** for debugging */ /** for debugging */
public int getOriginalSize() { public int getOriginalSize() {
return _originalSize; return _originalSize;

View File

@ -0,0 +1,53 @@
package net.i2p.router.crypto.ratchet;
import net.i2p.data.SessionKey;
/**
* Inbound ES tagset with a single tag and key.
* Nonce is 0.
* For receiving DSM/DSRM replies.
*
* @since 0.9.46
*/
class SingleTagSet extends RatchetTagSet {
private final RatchetSessionTag _tag;
private boolean _isUsed;
/**
* For outbound Existing Session
*/
public SingleTagSet(SessionTagListener lsnr, SessionKey key, RatchetSessionTag tag, long date, long timeout) {
super(lsnr, key, date, timeout);
_tag = tag;
lsnr.addTag(tag, this);
}
@Override
public int size() {
return _isUsed ? 0 : 1;
}
@Override
public int remaining() {
return _isUsed ? 0 : 1;
}
@Override
public SessionKeyAndNonce consume(RatchetSessionTag tag) {
if (_isUsed || !tag.equals(_tag))
return null;
_isUsed = true;
return new SessionKeyAndNonce(_key.getData(), 0);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(64);
buf.append("[SingleTagSet: 0 ");
buf.append(_tag.toBase64());
buf.append(' ').append(_key.toBase64());
buf.append(']');
return buf.toString();
}
}