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 dd1ed582bb787e2716bd42ae6e387785e5018b15..fa23d207d4caffa3bff63dd787f5050e257402bb 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java @@ -13,7 +13,7 @@ class NextSessionKey extends PublicKey { private final boolean _isReverse, _isRequest; /** - * @param data may be null + * @param data non-null */ public NextSessionKey(byte[] data, int id, boolean isReverse, boolean isRequest) { super(EncType.ECIES_X25519, data); @@ -22,6 +22,19 @@ class NextSessionKey extends PublicKey { _isRequest = isRequest; } + /** + * Null data, for acks/requests only. + * Type will be ElG but doesn't matter. + * Don't call setData(). + * @since 0.9.46 + */ + public NextSessionKey(int id, boolean isReverse, boolean isRequest) { + super(); + _id = id; + _isReverse = isReverse; + _isRequest = isRequest; + } + public int getID() { return _id; } @@ -41,16 +54,28 @@ class NextSessionKey extends PublicKey { */ @Override public int hashCode() { - return super.hashCode(); + int rv = super.hashCode() ^ _id; + if (_isReverse) + rv ^= 1 << 31; + if (_isRequest) + rv ^= 1 << 30; + return rv; } /** - * Equals if keys are equal * @since 0.9.46 */ @Override public boolean equals(Object obj) { - return super.equals(obj); + if (obj == null) + return false; + if (!(obj instanceof NextSessionKey)) + return false; + NextSessionKey o = (NextSessionKey) obj; + return _id == o._id && + _isReverse == o._isReverse && + _isRequest == o._isRequest && + super.equals(o); } @Override 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 f0e4c505925e5962f435aad10f685d75961efd62..d290238fdc005e77a8904bbd01a69f58b8c5ec25 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetPayload.java @@ -141,14 +141,14 @@ class RatchetPayload { boolean isReverse = (payload[i] & 0x02) != 0; boolean isRequest = (payload[i] & 0x04) != 0; int id = (int) DataHelper.fromLong(payload, i + 1, 2); - byte[] data; + NextSessionKey nsk; if (hasKey) { - data = new byte[32]; + byte[] data = new byte[32]; System.arraycopy(payload, i + 3, data, 0, 32); + nsk = new NextSessionKey(data, id, isReverse, isRequest); } else { - data = null; + nsk = new NextSessionKey(id, isReverse, isRequest); } - NextSessionKey nsk = new NextSessionKey(data, id, isReverse, isRequest); cb.gotNextKey(nsk); } break; 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 a4c9865f640e9eabc751930c6deb7c5e57c85d9f..0a53e9ef4095dfcff95b07e532168f26f9b2e0c4 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -874,21 +874,24 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // next key private int _myOBKeyID = -1; - private int _hisOBKeyID = -1; private int _currentOBTagSetID; private int _myIBKeyID = -1; - private int _hisIBKeyID = -1; private int _currentIBTagSetID; private int _myIBKeySendCount; private KeyPair _myIBKeys; + private KeyPair _myOBKeys; private NextSessionKey _myIBKey; + // last received, may not have data, for dup check + private NextSessionKey _hisIBKey; + private NextSessionKey _hisOBKey; + // last received, with data + private NextSessionKey _hisIBKeyWithData; + private NextSessionKey _hisOBKeyWithData; private SessionKey _nextIBRootKey; - private static final String INFO_7 = "XDHRatchetTagSet"; + private static final String INFO_7 = "XDHRatchetTagSet"; private static final int MAX_FAILS = 2; private static final int MAX_SEND_ACKS = 8; - private static final int DEBUG_OB_NSR = 0x10001; - private static final int DEBUG_IB_NSR = 0x10002; private static final int MAX_SEND_REVERSE_KEY = 25; /** @@ -917,7 +920,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // This is an INBOUND NS, we make an OUTBOUND tagset for the NSR RatchetTagSet tagset = new RatchetTagSet(_hkdf, state, rk, tk, - _established, DEBUG_OB_NSR); + _established); _tagSets.add(tagset); _state = null; if (_log.shouldDebug()) @@ -927,7 +930,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // This is an OUTBOUND NS, we make an INBOUND tagset for the NSR RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state, rk, tk, - _established, DEBUG_IB_NSR, + _established, MIN_RCV_WINDOW_NSR, MAX_RCV_WINDOW_NSR); // store the state so we can find the right session when we receive the NSR _state = state; @@ -956,11 +959,11 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // We are Bob // This is an OUTBOUND NSR, we make an INBOUND tagset for ES RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ab), - now, 0, + now, 0, -1, MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); // and a pending outbound one RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba), - now, 0); + now, 0, -1); if (_log.shouldDebug()) { _log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset:\n" + tagset_ab); _log.debug("Pending OB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset:\n" + tagset_ba); @@ -973,16 +976,24 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // We are Alice // This is an INBOUND NSR, we make an OUTBOUND tagset for ES RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ab), - now, 0); + now, 0, -1); // and an inbound one RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ba), - now, 0, + now, 0, -1, MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); if (_log.shouldDebug()) { _log.debug("Update OB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset:\n" + tagset_ab); _log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset:\n" + tagset_ba); } synchronized (_tagSets) { + for (Iterator<RatchetTagSet> iter = _tagSets.iterator(); iter.hasNext(); ) { + RatchetTagSet set = iter.next(); + if (set.getID() == RatchetTagSet.DEBUG_OB_NSR) { + iter.remove(); + if (_log.shouldDebug()) + _log.debug("Removed OB NSR tagset:\n" + set); + } + } _tagSets.add(tagset_ab); _unackedTagSets.clear(); } @@ -1008,114 +1019,187 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (isReverse) { // this is about my outbound tag set, // and is an ack of new key sent - if (_hisIBKeyID != id) { - if (_hisIBKeyID != id - 1) { + if (isRequest) { + if (_log.shouldWarn()) + _log.warn("invalid req+rev in nextkey " + key); + return; + } + if (key.equals(_hisIBKey)) { + if (_log.shouldDebug()) + _log.debug("Got dup nextkey for OB " + key); + return; + } + int hisLastIBKeyID; + if (_hisIBKey == null) + hisLastIBKeyID = -1; + else + hisLastIBKeyID = _hisIBKey.getID(); + _hisIBKey = key; + if (hisLastIBKeyID != id) { + // got a new key, use it + if (hisLastIBKeyID != id - 1) { if (_log.shouldWarn()) - _log.warn("Got nextkey id OB: " + id + " expected " + (_hisIBKeyID + 1)); + _log.warn("Got nextkey for OB: " + key + " expected " + (hisLastIBKeyID + 1)); return; } if (!hasKey) { - // TODO get it from above if (_log.shouldWarn()) - _log.warn("Got nextkey OB w/o key but we don't have it " + id); + _log.warn("Got nextkey for OB w/o key but we don't have it " + key); return; } - int oldtsID = 1 + _myIBKeyID + id; - RatchetTagSet oldts = null; - for (RatchetTagSet ts : _tagSets) { - if (ts.getID() == oldtsID) { - oldts = ts; - break; + // save the new key with data + _hisIBKeyWithData = key; + } else { + if (hasKey) { + // got a old key id but new data? + if (_hisIBKeyWithData != null && _log.shouldWarn()) + _log.warn("Got nextkey for OB with data, didn't match previous " + key); + } else { + if (_hisIBKeyWithData == null || + _hisIBKeyWithData.getID() != key.getID()) { + if (_log.shouldWarn()) + _log.warn("Got nextkey for OB w/o key but we don't have it " + key); + return; } + // got a old key, use it + key = _hisIBKeyWithData; } - if (oldts == null) { - if (_log.shouldWarn()) - _log.warn("Got nextkey id OB " + id + " but can't find existing OB tagset " + oldtsID); - return; + } + + int oldtsID; + if (_myOBKeyID == -1 && hisLastIBKeyID == -1) + oldtsID = 0; + else + oldtsID = 1 + _myOBKeyID + hisLastIBKeyID; + RatchetTagSet oldts = null; + for (RatchetTagSet ts : _tagSets) { + if (ts.getID() == oldtsID) { + oldts = ts; + break; } - KeyPair nextKeys = oldts.getNextKeys(); - if (nextKeys == null) { + } + if (oldts == null) { + if (_log.shouldWarn()) + _log.warn("Got nextkey for OB " + key + " but can't find existing OB tagset " + oldtsID); + return; + } + KeyPair nextKeys = oldts.getNextKeys(); + if (nextKeys == null) { + if (oldtsID == 0 || (oldtsID & 0x01) != 0 || _myOBKeys == null) { if (_log.shouldWarn()) - _log.warn("Got nextkey id OB " + id + " but didn't send OB keys " + oldtsID); + _log.warn("Got nextkey for OB " + key + " but we didn't send OB keys " + oldtsID); return; } - // create new OB TS, delete old one - int newtsID = oldtsID + 1; - if (_log.shouldWarn()) - _log.warn("Got nextkey id, ratchet OB: " + id); - PublicKey pub = nextKeys.getPublic(); - PrivateKey priv = nextKeys.getPrivate(); - PrivateKey sharedSecret = ECIESAEADEngine.doDH(priv, key); - byte[] sk = new byte[32]; - _hkdf.calculate(sharedSecret.getData(), ZEROLEN, INFO_7, sk); - SessionKey ssk = new SessionKey(sk); - RatchetTagSet ts = new RatchetTagSet(_hkdf, oldts.getNextRootKey(), ssk, - _context.clock().now(), newtsID); - _tagSets.add(ts); - _tagSets.remove(oldts); - _myOBKeyID++; - _hisIBKeyID = id; - _currentOBTagSetID = newtsID; - if (_log.shouldWarn()) - _log.warn("Got nextkey id " + id + " ratchet to new OB ES TS:\n" + ts); + // reuse last keys for tsIDs 2,4,6,... + nextKeys = _myOBKeys; } else { - if (_log.shouldWarn()) - _log.warn("Got dup nextkey id for OB " + id); - } - if (isRequest) { - if (_log.shouldWarn()) - _log.warn("invalid req+rev in nextkey"); - // ignore + // new keys for tsIDs 0,1,3,5... + _myOBKeys = nextKeys; + _myOBKeyID++; } + // create new OB TS, delete old one + PublicKey pub = nextKeys.getPublic(); + PrivateKey priv = nextKeys.getPrivate(); + PrivateKey sharedSecret = ECIESAEADEngine.doDH(priv, key); + byte[] sk = new byte[32]; + _hkdf.calculate(sharedSecret.getData(), ZEROLEN, INFO_7, sk); + SessionKey ssk = new SessionKey(sk); + int newtsID = oldtsID + 1; + RatchetTagSet ts = new RatchetTagSet(_hkdf, oldts.getNextRootKey(), ssk, + _context.clock().now(), newtsID, _myOBKeyID); + _tagSets.add(ts); + _tagSets.remove(oldts); + _currentOBTagSetID = newtsID; + if (_log.shouldWarn()) + _log.warn("Got nextkey " + key + " ratchet to new OB ES TS:\n" + ts); } else { // this is about my inbound tag set - if (_hisOBKeyID != id) { - if (_hisOBKeyID != id - 1) { + if (key.equals(_hisOBKey)) { + if (_log.shouldDebug()) + _log.debug("Got dup nextkey for IB " + key); + return; + } + int hisLastOBKeyID; + if (_hisOBKey == null) + hisLastOBKeyID = -1; + else + hisLastOBKeyID = _hisOBKey.getID(); + _hisOBKey = key; + if (hisLastOBKeyID != id) { + // got a new key, use it + if (hisLastOBKeyID != id - 1) { if (_log.shouldWarn()) - _log.warn("Got nextkey id IB: " + id + " expected " + (_hisOBKeyID + 1)); + _log.warn("Got nextkey for IB: " + key + " expected " + (hisLastOBKeyID + 1)); return; } if (!hasKey) { if (_log.shouldWarn()) - _log.warn("Got nextkey IB w/o key but we don't have it " + id); + _log.warn("Got nextkey for IB w/o key but we don't have it " + key); return; } - if (_nextIBRootKey == null) { - if (_log.shouldWarn()) - _log.warn("Got nextkey IB but we don't have next root key " + id); - return; + // save the new key with data + _hisOBKeyWithData = key; + } else { + if (hasKey) { + // got a old key id but new data? + if (_hisOBKeyWithData != null && _log.shouldWarn()) + _log.warn("Got nextkey for IB with data, didn't match previous " + key); + } else { + if (_hisOBKeyWithData == null || + _hisOBKeyWithData.getID() != key.getID()) { + if (_log.shouldWarn()) + _log.warn("Got nextkey for IB w/o key but we don't have it " + key); + return; + } + // got a old key, use it + key = _hisOBKeyWithData; } - // TODO new key only needed every other time + } + if (_nextIBRootKey == null) { + // first IB ES tagset never used? + if (_log.shouldWarn()) + _log.warn("Got nextkey for IB but we don't have next root key " + key); + return; + } + int oldtsID; + if (_myIBKeyID == -1 && hisLastOBKeyID == -1) + oldtsID = 0; + else + oldtsID = 1 + _myIBKeyID + hisLastOBKeyID; + // generate or reuse reverse key + // store next key for sending via getReverseSendKey() + if ((oldtsID & 0x01) == 0) { + // new keys for 0,2,4,... + if (!isRequest && _log.shouldWarn()) + _log.warn("Got reverse w/o request, generating new key anyway " + key); _myIBKeys = _context.keyGenerator().generatePKIKeys(EncType.ECIES_X25519); - PrivateKey sharedSecret = ECIESAEADEngine.doDH(_myIBKeys.getPrivate(), key); - // store next key for sending via getReverseSendKey() _myIBKeyID++; - _hisOBKeyID = id; - int newtsID = 1 + _myIBKeyID + id; - _currentOBTagSetID = newtsID; - _myIBKeySendCount = 0; _myIBKey = new NextSessionKey(_myIBKeys.getPublic().getData(), _myIBKeyID, true, false); - // create new IB TS - byte[] sk = new byte[32]; - _hkdf.calculate(sharedSecret.getData(), ZEROLEN, INFO_7, sk); - SessionKey ssk = new SessionKey(sk); - RatchetTagSet ts = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, _nextIBRootKey, ssk, - _context.clock().now(), newtsID, - MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); - _nextIBRootKey = ts.getNextRootKey(); - if (_log.shouldWarn()) - _log.warn("Got nextkey id " + id + " ratchet to new IB ES TS:\n" + ts); } else { - if (_log.shouldWarn()) - _log.warn("Got dup nextkey id for IB " + id); - // find current OB TS, tell him to send ack if nec. - // create new IB TS if nec. - } - if (!isRequest) { - if (_log.shouldWarn()) - _log.warn("invalid fwd w/o req in nextkey"); - // ignore + // reuse keys for 1,3,5... + if (_myIBKeys == null) { + if (_log.shouldWarn()) + _log.warn("Got nextkey IB but we don't have old keys " + key); + return; + } + if (isRequest && _log.shouldWarn()) + _log.warn("Got reverse with request, using old key anyway " + key); + _myIBKey = new NextSessionKey(_myIBKeyID, true, false); } + PrivateKey sharedSecret = ECIESAEADEngine.doDH(_myIBKeys.getPrivate(), key); + int newtsID = oldtsID + 1; + _currentIBTagSetID = newtsID; + _myIBKeySendCount = 0; + // create new IB TS + byte[] sk = new byte[32]; + _hkdf.calculate(sharedSecret.getData(), ZEROLEN, INFO_7, sk); + SessionKey ssk = new SessionKey(sk); + RatchetTagSet ts = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, _nextIBRootKey, ssk, + _context.clock().now(), newtsID, _myIBKeyID, + MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); + _nextIBRootKey = ts.getNextRootKey(); + if (_log.shouldWarn()) + _log.warn("Got nextkey " + key + " ratchet to new IB ES TS:\n" + ts); } } } @@ -1148,8 +1232,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener for (RatchetTagSet obSet : _unackedTagSets) { if (obSet.getAssociatedKey().equals(sk)) { if (_log.shouldDebug()) - _log.debug("First tag received from IB ES " + set + - ", promoting OB ES " + obSet); + _log.debug("First tag received from IB ES\n" + set + + "\npromoting OB ES " + obSet); _unackedTagSets.clear(); _tagSets.clear(); _tagSets.add(obSet); @@ -1161,7 +1245,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } } if (_log.shouldDebug()) - _log.debug("First tag received from IB ES " + set + + _log.debug("First tag received from IB ES\n" + set + " but no corresponding OB ES set found, unacked size: " + _unackedTagSets.size() + " acked size: " + _tagSets.size()); } 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 d74f1a757cd62005d4152d5cb508f3134f812f64..ec169dc56df0cb4918a364effcfc0e2e1bdf7f54 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java @@ -54,6 +54,7 @@ class RatchetTagSet implements TagSetHandle { private final long _timeout; private long _date; private final int _id; + private final int _keyid; private final int _originalSize; private final int _maxSize; private boolean _acked; @@ -79,7 +80,12 @@ class RatchetTagSet implements TagSetHandle { private static final byte[] ZEROLEN = new byte[0]; private static final int TAGLEN = RatchetSessionTag.LENGTH; private static final int MAX = 65535; - private static final int LOW = 50; + private static final boolean TEST_RATCHET = false; + // 2 * max streaming window + private static final int LOW = TEST_RATCHET ? (MAX - 100) : 256; + static final int DEBUG_OB_NSR = 0x10001; + static final int DEBUG_IB_NSR = 0x10002; + static final int DEBUG_SINGLE_ES = 0x10003; /** * Outbound NSR Tagset @@ -87,8 +93,8 @@ class RatchetTagSet implements TagSetHandle { * @param date For outbound: creation time */ public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data, - long date, int id) { - this(hkdf, null, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, id, false, 0, 0); + long date) { + this(hkdf, null, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, DEBUG_OB_NSR, -2, false, 0, 0); } /** @@ -97,8 +103,8 @@ class RatchetTagSet implements TagSetHandle { * @param date For outbound: creation time */ public RatchetTagSet(HKDF hkdf, SessionKey rootKey, SessionKey data, - long date, int id) { - this(hkdf, null, null, null, rootKey, data, date, RatchetSKM.SESSION_TAG_DURATION_MS, id, false, 0, 0); + long date, int tagsetid, int keyid) { + this(hkdf, null, null, null, rootKey, data, date, RatchetSKM.SESSION_TAG_DURATION_MS, tagsetid, keyid, false, 0, 0); } /** @@ -107,8 +113,8 @@ class RatchetTagSet implements TagSetHandle { * @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) { - this(hkdf, lsnr, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, id, true, minSize, maxSize); + long date, int minSize, int maxSize) { + this(hkdf, lsnr, state, null, rootKey, data, date, RatchetSKM.SESSION_PENDING_DURATION_MS, DEBUG_IB_NSR, -2, true, minSize, maxSize); } /** @@ -118,8 +124,8 @@ 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, RatchetSKM.SESSION_LIFETIME_MAX_MS, id, true, minSize, maxSize); + long date, int tagsetid, int keyid, int minSize, int maxSize) { + this(hkdf, lsnr, null, remoteKey, rootKey, data, date, RatchetSKM.SESSION_LIFETIME_MAX_MS, tagsetid, keyid, true, minSize, maxSize); } @@ -128,7 +134,7 @@ class RatchetTagSet implements TagSetHandle { */ private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, PublicKey remoteKey, SessionKey rootKey, SessionKey data, - long date, long timeout, int id, boolean isInbound, int minSize, int maxSize) { + long date, long timeout, int tagsetid, int keyid, boolean isInbound, int minSize, int maxSize) { _lsnr = lsnr; _state = state; _remoteKey = remoteKey; @@ -137,7 +143,8 @@ class RatchetTagSet implements TagSetHandle { _created = date; _timeout = timeout; _date = date; - _id = id; + _id = tagsetid; + _keyid = keyid; _originalSize = minSize; _maxSize = maxSize; _nextRootKey = new byte[32]; @@ -178,7 +185,8 @@ class RatchetTagSet implements TagSetHandle { _created = date; _timeout = timeout; _date = date; - _id = 0x10003; + _id = DEBUG_SINGLE_ES; + _keyid = -3; _originalSize = 1; _maxSize = 1; _nextRootKey = null; @@ -315,11 +323,16 @@ class RatchetTagSet implements TagSetHandle { public NextSessionKey getNextKey() { if (_sessionTags != null || _state != null || remaining() > LOW) return null; - if (_nextKeys == null) { - _nextKeys = I2PAppContext.getGlobalContext().keyGenerator().generatePKIKeys(EncType.ECIES_X25519); - boolean isIB = _sessionTags != null; - // TODO request only needed every other time - _nextKey = new NextSessionKey(_nextKeys.getPublic().getData(), 0, isIB, !isIB); + if (_nextKey == null) { + boolean isFirst = _id == 0; + if (isFirst || (_id & 0x01) != 0) { + // new keys only needed first time and odd times + _nextKeys = I2PAppContext.getGlobalContext().keyGenerator().generatePKIKeys(EncType.ECIES_X25519); + _nextKey = new NextSessionKey(_nextKeys.getPublic().getData(), _keyid + 1, false, isFirst); + } else { + // even times, just send old ID + _nextKey = new NextSessionKey(_keyid, false, true); + } } return _nextKey; } @@ -553,7 +566,7 @@ class RatchetTagSet implements TagSetHandle { else buf.append("Outbound "); if (_state != null) - buf.append("NSR ").append(_state.hashCode()).append(' '); + buf.append("NSR "); else buf.append("ES "); buf.append("TagSet #").append(_tagSetID)