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 e704ba7ebb..1aab537b36 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -161,11 +161,11 @@ public final class ECIESAEADEngine { HandshakeState state = key.getHandshakeState(); if (state == null) { if (shouldDebug) - _log.debug("Decrypting ES with tag: " + st + ": key: " + key.toBase64() + ": " + data.length + " bytes"); + _log.debug("Decrypting ES with tag: " + st.toBase64() + ": key: " + key.toBase64() + ": " + data.length + " bytes"); decrypted = decryptExistingSession(tag, data, key, targetPrivateKey); } else if (data.length >= MIN_NSR_SIZE) { if (shouldDebug) - _log.debug("Decrypting NSR with tag: " + st + ": key: " + key.toBase64() + ": " + data.length + " bytes"); + _log.debug("Decrypting NSR with tag: " + st.toBase64() + ": key: " + key.toBase64() + ": " + data.length + " bytes"); decrypted = decryptNewSessionReply(tag, data, state, keyManager); } else { decrypted = null; @@ -562,11 +562,11 @@ public final class ECIESAEADEngine { return null; } if (_log.shouldDebug()) - _log.debug("Encrypting as NSR to " + target + " with tag " + re.tag); + _log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64()); return encryptNewSessionReply(cloves, target, state, re.tag, keyManager); } if (_log.shouldDebug()) - _log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag); + _log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag.toBase64()); byte rv[] = encryptExistingSession(cloves, target, re.key, re.tag); return rv; } @@ -722,7 +722,7 @@ public final class ECIESAEADEngine { /** * No ad */ - final byte[] encryptAEADBlock(byte data[], SessionKey key, long n) { + private final byte[] encryptAEADBlock(byte data[], SessionKey key, long n) { return encryptAEADBlock(null, data, key, n); } diff --git a/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java b/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java index bd4d402860..4065123da4 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java @@ -176,8 +176,7 @@ public class MuxedSKM extends SessionKeyManager { public SessionKey consumeTag(SessionTag tag) { SessionKey rv = _elg.consumeTag(tag); if (rv == null) { - byte[] stag = new byte[8]; - System.arraycopy(tag.getData(), 0, stag, 0, 8); + long stag = RatchetPayload.fromLong8(tag.getData(), 0); RatchetSessionTag rstag = new RatchetSessionTag(stag); rv = _ec.consumeTag(rstag); } 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 b446cf159e..024562eae5 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -178,9 +178,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener boolean rv = addSession(sess); if (_log.shouldInfo()) { if (rv) - _log.info("New OB session as Bob. Alice: " + toString(target)); + _log.info("New OB session " + state.hashCode() + " as Bob. Alice: " + toString(target)); else - _log.info("Dup OB session as Bob. Alice: " + toString(target)); + _log.info("Dup OB session " + state.hashCode() + " as Bob. Alice: " + toString(target)); } return rv; } else { @@ -191,14 +191,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (pending != null) { pending.add(sess); if (_log.shouldInfo()) - _log.info("Another new OB session as Alice, total now: " + pending.size() + + _log.info("Another new OB session " + state.hashCode() + " as Alice, total now: " + pending.size() + ". Bob: " + toString(target)); } else { pending = new ArrayList(4); pending.add(sess); _pendingOutboundSessions.put(target, pending); if (_log.shouldInfo()) - _log.info("First new OB session as Alice. Bob: " + toString(target)); + _log.info("First new OB session " + state.hashCode() + " as Alice. Bob: " + toString(target)); } } return true; @@ -220,23 +220,25 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener boolean isInbound = state.getRole() == HandshakeState.RESPONDER; if (isInbound) { // we are Bob, NSR sent + if (_log.shouldInfo()) + _log.info("Session " + state.hashCode() + " update as Bob. Alice: " + toString(target)); OutboundSession sess = getSession(target); if (sess == null) { if (_log.shouldDebug()) - _log.debug("Update session but no session found for " + target); + _log.debug("Update Bob session but no session found for " + target); // TODO can we recover? return false; } sess.updateSession(state); - if (_log.shouldInfo()) - _log.info("Session update as Bob. Alice: " + toString(target)); } else { // we are Alice, NSR received + if (_log.shouldInfo()) + _log.info("Session " + oldState.hashCode() + " to " + state.hashCode() + " update as Alice. Bob: " + toString(target)); synchronized (_pendingOutboundSessions) { List pending = _pendingOutboundSessions.get(target); if (pending == null) { if (_log.shouldDebug()) - _log.debug("Update session but no sessions found for " + target); + _log.debug("Update Alice session but no pending sessions for " + target); // TODO can we recover? return false; } @@ -250,7 +252,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener boolean ok = addSession(sess); if (_log.shouldDebug()) { if (ok) - _log.debug("Update session from NSR to ES for " + target); + _log.debug("Update Alice session from NSR to ES for " + target); else _log.debug("Session already updated from NSR to ES for " + target); } @@ -266,16 +268,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } } } - _pendingOutboundSessions.remove(target); - if (!found) { + if (found) { + _pendingOutboundSessions.remove(target); + } else { if (_log.shouldDebug()) - _log.debug("Update session but no session found (out of " + pending.size() + ") for " + target); + _log.debug("Update Alice session but no session found (out of " + pending.size() + ") for " + target); // TODO can we recover? return false; } } - if (_log.shouldInfo()) - _log.info("Session update as Alice. Bob: " + toString(target)); } return true; } @@ -520,8 +521,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener SessionKeyAndNonce key; tagSet = _inboundTagSets.remove(tag); if (tagSet == null) { - if (_log.shouldDebug()) - _log.debug("IB tag not found: " + tag.toBase64()); + //if (_log.shouldDebug()) + // _log.debug("IB tag not found: " + tag.toBase64()); return null; } boolean firstInbound; @@ -535,14 +536,22 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener HandshakeState state = tagSet.getHandshakeState(); if (firstInbound) { if (state == null) { - // TODO + // 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 (_log.shouldDebug()) { if (state != null) - _log.debug("IB NSR Tag consumed: " + tag + " from: " + tagSet); + _log.debug("IB NSR Tag consumed: " + tag.toBase64() + " from: " + tagSet); else - _log.debug("IB ES Tag consumed: " + tag + " from: " + tagSet); + _log.debug("IB ES Tag consumed: " + tag.toBase64() + " from: " + tagSet); } } else { if (_log.shouldWarn()) @@ -838,12 +847,21 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state, rk, tk, _established, _rcvTagSetID.getAndIncrement(), 5, 5); + // store the IB tagset as OB so we can lookup the state + // TODO just store the state _unackedTagSets.add(tagset); if (_log.shouldDebug()) _log.debug("New IB Session, rk = " + rk + " tk = " + tk + " 1st tagset: " + tagset); } } + /** + * Inbound or outbound. Checks state.getRole() to determine. + * For outbound (NSR rcvd by Alice), sets session to transition to ES mode outbound. + * For inbound (NSR sent by Bob), sets up inbound ES tagset. + * + * @param state current state + */ void updateSession(HandshakeState state) { byte[] ck = state.getChainingKey(); byte[] k_ab = new byte[32]; @@ -855,7 +873,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (isInbound) { // We are Bob // This is an OUTBOUND NSR, we make an INBOUND tagset for ES - RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, rk, new SessionKey(k_ab), + RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ab), now, _rcvTagSetID.getAndIncrement(), 5, 5); // and a pending outbound one RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba), @@ -873,7 +891,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ab), now, _sentTagSetID.getAndIncrement()); // and an inbound one - RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, rk, new SessionKey(k_ba), + RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ba), now, _rcvTagSetID.getAndIncrement(), 5, 5); if (_log.shouldDebug()) { _log.debug("Update OB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); @@ -888,9 +906,37 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } /** - * @return list of RatchetTagSet objects + * First tag was received for this inbound (ES) tagset. + * Find the corresponding outbound (ES) tagset in _unackedTagSets, + * move it to _tagSets, and remove all others. + * + * @param set the inbound tagset + */ + void firstTagConsumed(RatchetTagSet set) { + SessionKey sk = set.getAssociatedKey(); + synchronized (_tagSets) { + 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); + _unackedTagSets.clear(); + _tagSets.clear(); + _tagSets.add(obSet); + return; + } + } + if (_log.shouldDebug()) + _log.debug("First tag received from IB ES " + set + + " but no corresponding OB ES set found, unacked size: " + _unackedTagSets.size() + + " acked size: " + _tagSets.size()); + } + } + + /** * This is used only by renderStatusHTML(). * It includes both acked and unacked RatchetTagSets. + * @return list of RatchetTagSet objects */ List getTagSets() { List rv; diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSessionTag.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSessionTag.java index 936cb15ac5..539b4a41b9 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSessionTag.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSessionTag.java @@ -15,6 +15,10 @@ public class RatchetSessionTag { private final long _data; + public RatchetSessionTag(long val) { + _data = val; + } + public RatchetSessionTag(byte val[]) { if (val.length != LENGTH) throw new IllegalArgumentException(); 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 9623c70a21..66ccc03e32 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java @@ -7,13 +7,16 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import com.southernstorm.noise.protocol.DHState; import com.southernstorm.noise.protocol.HandshakeState; import net.i2p.I2PAppContext; +import net.i2p.crypto.EncType; import net.i2p.crypto.HKDF; import net.i2p.crypto.TagSetHandle; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; import net.i2p.util.Log; @@ -32,6 +35,7 @@ import net.i2p.util.Log; */ class RatchetTagSet implements TagSetHandle { private final SessionTagListener _lsnr; + private final PublicKey _remoteKey; private final SessionKey _key; private final HandshakeState _state; // We use object for tags because we must do indexOfValueByValue() @@ -69,7 +73,7 @@ class RatchetTagSet implements TagSetHandle { */ public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data, long date, int id) { - this(hkdf, null, state, rootKey, data, date, id, false, 0, 0); + this(hkdf, null, state, null, rootKey, data, date, id, false, 0, 0); } /** @@ -79,7 +83,7 @@ class RatchetTagSet implements TagSetHandle { */ public RatchetTagSet(HKDF hkdf, SessionKey rootKey, SessionKey data, long date, int id) { - this(hkdf, null, null, rootKey, data, date, id, false, 0, 0); + this(hkdf, null, null, null, rootKey, data, date, id, false, 0, 0); } /** @@ -89,7 +93,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, rootKey, data, date, id, true, minSize, maxSize); + this(hkdf, lsnr, state, null, rootKey, data, date, id, true, minSize, maxSize); } /** @@ -97,19 +101,22 @@ class RatchetTagSet implements TagSetHandle { * * @param date For inbound: creation time */ - public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, SessionKey rootKey, SessionKey data, + public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, + PublicKey remoteKey, SessionKey rootKey, SessionKey data, long date, int id, int minSize, int maxSize) { - this(hkdf, lsnr, null, rootKey, data, date, id, true, minSize, maxSize); + this(hkdf, lsnr, null, remoteKey, rootKey, data, date, id, true, minSize, maxSize); } /** * @param date For inbound and outbound: creation time */ - private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data, + private RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, + PublicKey remoteKey, SessionKey rootKey, SessionKey data, long date, int id, boolean isInbound, int minSize, int maxSize) { _lsnr = lsnr; _state = state; + _remoteKey = remoteKey; _key = rootKey; _created = date; _date = date; @@ -149,7 +156,24 @@ class RatchetTagSet implements TagSetHandle { } /** - * The identifier for the session.. + * The far-end's public key. + * Valid for NSR and inbound ES tagsets. + * Returns null for outbound ES tagsets. + */ + public PublicKey getRemoteKey() { + if (_state != null) { + DHState kp = _state.getRemotePublicKey(); + if (kp != null) { + byte[] rv = new byte[32]; + kp.getPublicKey(rv, 0); + return new PublicKey(EncType.ECIES_X25519, rv); + } + } + return _remoteKey; + } + + /** + * The identifier for the session. * Not used for cryptographic operations after setup. */ public SessionKey getAssociatedKey() { @@ -386,21 +410,28 @@ class RatchetTagSet implements TagSetHandle { @Override public String toString() { StringBuilder buf = new StringBuilder(256); + if (_sessionTags != null) + buf.append("Inbound "); + else + buf.append("Outbound "); if (_state != null) - buf.append("NSR "); + buf.append("NSR ").append(_state.hashCode()).append(' '); else buf.append("ES "); buf.append("TagSet #").append(_id) - .append(" created: ").append(DataHelper.formatTime(_created)) - .append(" last use: ").append(DataHelper.formatTime(_date)); + .append("\nCreated: ").append(DataHelper.formatTime(_created)) + .append("\nLast use: ").append(DataHelper.formatTime(_date)); + PublicKey pk = getRemoteKey(); + if (pk != null) + buf.append("\nRemote Public Key: ").append(pk.toBase64()); + buf.append("\nRoot Symmetr. Key: ").append(_key.toBase64()); int sz = size(); - buf.append(" Size: ").append(sz) + buf.append("\nSize: ").append(sz) .append(" Orig: ").append(_originalSize) .append(" Max: ").append(_maxSize) .append(" Remaining: ").append(remaining()); buf.append(" Acked? ").append(_acked); if (_sessionTags != null) { - buf.append(" Inbound"); for (int i = 0; i < sz; i++) { int n = _sessionTags.keyAt(i); RatchetSessionTag tag = _sessionTags.valueAt(i); @@ -413,8 +444,6 @@ class RatchetTagSet implements TagSetHandle { buf.append("\tdeferred"); } } - } else { - buf.append(" Outbound"); } return buf.toString(); }