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
* </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
* @return encrypted data or null on failure
*/
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re,
DeliveryInstructions replyDI, ReplyCallback callback,
RatchetSKM keyManager) {
//
// TODO remove DI, just make it a boolean
if (ACKREQ_IN_ES && replyDI == null)
replyDI = new DeliveryInstructions();
byte rawTag[] = re.tag.getData();
@ -794,6 +794,31 @@ public final class ECIESAEADEngine {
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
*/

View File

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

View File

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

View File

@ -136,13 +136,19 @@ class RatchetPayload {
case BLOCK_NEXTKEY:
{
if (len != 35)
if (len != 3 && len != 35)
throw new IOException("Bad length for NEXTKEY: " + len);
boolean isReverse = (payload[i] & 0x01) != 0;
boolean isRequest = (payload[i] & 0x02) != 0;
boolean hasKey = (payload[i] & 0x01) != 0;
boolean isReverse = (payload[i] & 0x02) != 0;
boolean isRequest = (payload[i] & 0x04) != 0;
int id = (int) DataHelper.fromLong(payload, i + 1, 2);
byte[] data = new byte[32];
System.arraycopy(payload, i + 3, data, 0, 32);
byte[] data;
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);
cb.gotNextKey(nsk);
}
@ -352,17 +358,22 @@ class RatchetPayload {
}
public int getDataLength() {
return 35;
return next.getData() != null ? 35 : 3;
}
public int writeData(byte[] tgt, int off) {
if (next.isReverse())
if (next.getData() != null)
tgt[off] = 0x01;
if (next.isRequest())
if (next.isReverse())
tgt[off] |= 0x02;
if (next.isRequest())
tgt[off] |= 0x04;
DataHelper.toLong(tgt, off + 1, 2, next.getID());
System.arraycopy(next.getData(), 0, tgt, off + 3, 32);
return off + 35;
if (next.getData() != null) {
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.
* 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
@ -63,9 +63,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*
* 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
@ -110,8 +110,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private class CleanupEvent extends SimpleTimer2.TimedEvent {
public CleanupEvent() {
// wait until outbound expiration time to start
super(_context.simpleTimer2(), SESSION_TAG_DURATION_MS);
// wait until first expiration time to start
super(_context.simpleTimer2(), SESSION_PENDING_DURATION_MS);
}
public void timeReached() {
@ -491,6 +491,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
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
* the associated tag sets. this is very time consuming - iterating
@ -543,13 +551,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (state == null) {
// TODO this should really be after decrypt...
PublicKey pk = tagSet.getRemoteKey();
OutboundSession sess = getSession(pk);
if (sess != null) {
sess.firstTagConsumed(tagSet);
} else {
if (_log.shouldDebug())
_log.debug("First tag consumed but session is gone");
}
if (pk != null) {
OutboundSession sess = getSession(pk);
if (sess != null) {
sess.firstTagConsumed(tagSet);
} else {
if (_log.shouldDebug())
_log.debug("First tag consumed but session is gone");
}
} // else null for SingleTagSets
}
}
if (_log.shouldDebug()) {
@ -611,13 +621,12 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/
private int aggressiveExpire() {
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) {
if (ts.getExpiration() < now) {
iter.remove();
removed++;
}
@ -626,7 +635,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// outbound
int oremoved = 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();) {
OutboundSession sess = iter.next();
oremoved += sess.expireTags(now);
@ -753,7 +762,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
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();
@ -767,8 +775,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
int size = ts.size();
total += size;
buf.append("<li><b>ID: ").append(ts.getID());
buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()));
long expires = ts.getDate() - exp;
buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
.append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
if (expires > 0)
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else
@ -789,7 +798,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// outbound
totalSets = 0;
exp = now - SESSION_TAG_DURATION_MS;
Set<OutboundSession> outbound = getOutboundSessions();
for (OutboundSession sess : outbound) {
sets.clear();
@ -808,8 +816,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
for (RatchetTagSet ts : sets) {
int size = ts.remaining();
buf.append("<li><b>ID: ").append(ts.getID())
.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()));
long expires = ts.getDate() - exp;
.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
.append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
if (expires > 0)
buf.append(" <b>expires in:</b> ").append(DataHelper.formatDuration2(expires)).append(" with ");
else
@ -1130,7 +1139,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
synchronized (_tagSets) {
for (Iterator<RatchetTagSet> iter = _tagSets.iterator(); iter.hasNext(); ) {
RatchetTagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
if (set.getExpiration() <= now) {
iter.remove();
removed++;
}
@ -1139,7 +1148,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if ((now & 0x0f) == 0) {
for (Iterator<RatchetTagSet> iter = _unackedTagSets.iterator(); iter.hasNext(); ) {
RatchetTagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
if (set.getExpiration() <= now) {
iter.remove();
removed++;
}
@ -1156,7 +1165,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
while (!_tagSets.isEmpty()) {
RatchetTagSet set = _tagSets.get(0);
synchronized(set) {
if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
if (set.getExpiration() > now) {
RatchetSessionTag tag = set.consumeNext();
if (tag != null) {
set.setDate(now);
@ -1186,7 +1195,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
RatchetTagSet set = _tagSets.get(i);
if (!set.getAcked())
continue;
if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
if (set.getExpiration() > now) {
// or just add fixed number?
int sz = set.remaining();
tags += sz;
@ -1205,12 +1214,13 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
long last = 0;
synchronized (_tagSets) {
for (RatchetTagSet set : _tagSets) {
if (set.getDate() > last && set.remaining() > 0)
last = set.getDate();
long exp = set.getExpiration();
if (exp > last && set.remaining() > 0)
last = exp;
}
}
if (last > 0)
return last + SESSION_TAG_DURATION_MS;
return last;
return -1;
}

View File

@ -38,7 +38,7 @@ import net.i2p.util.Log;
class RatchetTagSet implements TagSetHandle {
private final SessionTagListener _lsnr;
private final PublicKey _remoteKey;
private final SessionKey _key;
protected final SessionKey _key;
private final HandshakeState _state;
// inbound only, else null
// 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 HKDF hkdf;
private final long _created;
private final long _timeout;
private long _date;
private final int _id;
private final int _originalSize;
@ -84,7 +85,7 @@ class RatchetTagSet implements TagSetHandle {
*/
public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data,
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,
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,
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,
PublicKey remoteKey, SessionKey rootKey, SessionKey data,
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,
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;
_state = state;
_remoteKey = remoteKey;
_key = rootKey;
_created = date;
_timeout = timeout;
_date = date;
_id = id;
_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() {
if (_sessionTags != null)
_sessionTags.clear();
@ -201,6 +228,7 @@ class RatchetTagSet implements TagSetHandle {
/**
* For inbound and outbound: last used time
* Expiration is getDate() + getTimeout().
*/
public long getDate() {
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() {
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 */
public int getOriginalSize() {
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();
}
}