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 955c8ccfc..72ba13439 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java
@@ -772,14 +772,14 @@ public final class ECIESAEADEngine {
* - 16 byte MAC
*
*
- * @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.
+ *
+ *
+ * - 8 byte SessionTag
+ * - payload
+ * - 16 byte MAC
+ *
+ *
+ * @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
*/
diff --git a/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java
index 4fc114e2f..5d09aadf1 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedEngine.java
@@ -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) {
diff --git a/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java b/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java
index eaa3057d8..03cd18d35 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java
@@ -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;
diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java
index 4d89e4229..8619b3ba3 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java
@@ -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;
}
}
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 2267f6c62..4c08ec57c 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java
@@ -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 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 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 sets = new TreeSet(new RatchetTagSetComparator());
for (Map.Entry> 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("ID: ").append(ts.getID());
- buf.append(" created: ").append(DataHelper.formatTime(ts.getCreated()));
- long expires = ts.getDate() - exp;
+ buf.append(" created: ").append(DataHelper.formatTime(ts.getCreated()))
+ .append(" last use: ").append(DataHelper.formatTime(ts.getDate()));
+ long expires = ts.getExpiration() - now;
if (expires > 0)
buf.append(" expires in: ").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 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("ID: ").append(ts.getID())
- .append(" created: ").append(DataHelper.formatTime(ts.getCreated()));
- long expires = ts.getDate() - exp;
+ .append(" created: ").append(DataHelper.formatTime(ts.getCreated()))
+ .append(" last use: ").append(DataHelper.formatTime(ts.getDate()));
+ long expires = ts.getExpiration() - now;
if (expires > 0)
buf.append(" expires in: ").append(DataHelper.formatDuration2(expires)).append(" with ");
else
@@ -1130,7 +1139,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
synchronized (_tagSets) {
for (Iterator 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 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;
}
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 e3be95308..bf2716404 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java
@@ -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 _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;
diff --git a/router/java/src/net/i2p/router/crypto/ratchet/SingleTagSet.java b/router/java/src/net/i2p/router/crypto/ratchet/SingleTagSet.java
new file mode 100644
index 000000000..970f2180c
--- /dev/null
+++ b/router/java/src/net/i2p/router/crypto/ratchet/SingleTagSet.java
@@ -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();
+ }
+}