From dee92b52903c70fd39fcb80303bce3cf6fa31410 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 30 Mar 2020 16:44:42 +0000 Subject: [PATCH] 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 --- .../crypto/ratchet/ECIESAEADEngine.java | 29 +++++++- .../router/crypto/ratchet/MuxedEngine.java | 8 +-- .../router/crypto/ratchet/NextSessionKey.java | 3 + .../router/crypto/ratchet/RatchetPayload.java | 31 ++++++--- .../i2p/router/crypto/ratchet/RatchetSKM.java | 66 +++++++++++-------- .../router/crypto/ratchet/RatchetTagSet.java | 60 +++++++++++++++-- .../router/crypto/ratchet/SingleTagSet.java | 53 +++++++++++++++ 7 files changed, 199 insertions(+), 51 deletions(-) create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/SingleTagSet.java 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 955c8ccfc0..72ba13439b 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 * </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 */ 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 4fc114e2f5..5d09aadf1a 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 eaa3057d8d..03cd18d35d 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 4d89e4229e..8619b3ba36 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 2267f6c626..4c08ec57c2 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<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; } 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 e3be95308f..bf2716404d 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<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; 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 0000000000..970f2180c6 --- /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(); + } +} -- GitLab