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 4065123da..23be4edc1 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedSKM.java @@ -146,13 +146,14 @@ public class MuxedSKM extends SessionKeyManager { return 0; } + /** + * ElG only + */ @Override public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) { EncType type = target.getType(); if (type == EncType.ELGAMAL_2048) return _elg.tagsDelivered(target, key, sessionTags); - if (type == EncType.ECIES_X25519) - return _ec.tagsDelivered(target, key, sessionTags); return null; } @@ -195,21 +196,23 @@ public class MuxedSKM extends SessionKeyManager { _ec.renderStatusHTML(out); } + /** + * ElG only + */ @Override public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) { EncType type = target.getType(); if (type == EncType.ELGAMAL_2048) _elg.failTags(target, key, ts); - else if (type == EncType.ECIES_X25519) - _ec.failTags(target, key, ts); } + /** + * ElG only + */ @Override public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) { EncType type = target.getType(); if (type == EncType.ELGAMAL_2048) _elg.tagsAcked(target, key, ts); - else if (type == EncType.ECIES_X25519) - _ec.tagsAcked(target, key, ts); } } 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 e6b37071d..dd1ed582b 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/NextSessionKey.java @@ -36,6 +36,23 @@ class NextSessionKey extends PublicKey { return _isRequest; } + /** + * @since 0.9.46 + */ + @Override + public int hashCode() { + return super.hashCode(); + } + + /** + * Equals if keys are equal + * @since 0.9.46 + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + @Override public String toString() { StringBuilder buf = new StringBuilder(64); 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 125a0d444..a4c9865f6 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -413,42 +413,11 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } /** - * Take note of the fact that the given sessionTags associated with the key for - * encryption to the target have been sent. Whether to use the tags immediately - * (i.e. assume they will be received) or to wait until an ack, is implementation dependent. - * - * - * @param sessionTags ignored, must be null - * @return the TagSetHandle. Caller MUST subsequently call failTags() or tagsAcked() - * with this handle. May be null. + * @throws UnsupportedOperationException always */ @Override public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) { - // TODO - if (!(key instanceof SessionKeyAndNonce)) { - if (_log.shouldWarn()) - _log.warn("Bad SK type"); - //TODO - return null; - } - SessionKeyAndNonce sk = (SessionKeyAndNonce) key; - // if this is ever null, this is racy and needs synch - OutboundSession sess = getSession(target); - if (sess == null) { - if (_log.shouldWarn()) - _log.warn("No session for delivered RatchetTagSet to target: " + toString(target)); - // TODO - createSession(target, key); - } else { - sess.setCurrentKey(key); - } - // TODO - RatchetTagSet set = new RatchetTagSet(_hkdf, key, key, _context.clock().now(), 0); - sess.addTags(set); - if (_log.shouldDebug()) - _log.debug("Tags delivered: " + set + - " target: " + toString(target) /** + ": " + sessionTags */ ); - return set; + throw new UnsupportedOperationException(); } /** @@ -457,55 +426,28 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * from corrupted tag sets and crashes * * @deprecated unused and rather drastic + * @throws UnsupportedOperationException always */ @Override @Deprecated public void failTags(PublicKey target) { - removeSession(target); + throw new UnsupportedOperationException(); } /** - * Mark these tags as invalid, since the peer - * has failed to ack them in time. + * @throws UnsupportedOperationException always */ @Override public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) { - OutboundSession sess = getSession(target); - if (sess == null) { - if (_log.shouldWarn()) - _log.warn("No session for failed RatchetTagSet: " + ts); - return; - } - if(!key.equals(sess.getCurrentKey())) { - if (_log.shouldWarn()) - _log.warn("Wrong session key (wanted " + sess.getCurrentKey() + ") for failed RatchetTagSet: " + ts); - return; - } - if (_log.shouldWarn()) - _log.warn("TagSet failed: " + ts); - sess.failTags((RatchetTagSet)ts); + throw new UnsupportedOperationException(); } /** - * Mark these tags as acked, start to use them (if we haven't already) - * If the set was previously failed, it will be added back in. + * @throws UnsupportedOperationException always */ @Override public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) { - OutboundSession sess = getSession(target); - if (sess == null) { - if (_log.shouldWarn()) - _log.warn("No session for acked RatchetTagSet: " + ts); - return; - } - if(!key.equals(sess.getCurrentKey())) { - if (_log.shouldWarn()) - _log.warn("Wrong session key (wanted " + sess.getCurrentKey() + ") for acked RatchetTagSet: " + ts); - return; - } - if (_log.shouldDebug()) - _log.debug("TagSet acked: " + ts); - sess.ackTags((RatchetTagSet)ts); + throw new UnsupportedOperationException(); } /** @@ -597,9 +539,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } if (_log.shouldDebug()) { if (state != null) - _log.debug("IB NSR Tag consumed: " + tag.toBase64() + " from: " + tagSet); + _log.debug("IB NSR Tag " + key.getNonce() + " consumed: " + tag.toBase64() + " from\n" + tagSet); else - _log.debug("IB ES Tag consumed: " + tag.toBase64() + " from: " + tagSet); + _log.debug("IB ES Tag " + key.getNonce() + " consumed: " + tag.toBase64() + " from\n" + tagSet); } } else { if (_log.shouldWarn()) @@ -840,7 +782,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener totalSets += sets.size(); buf.append("
Target public key: ").append(toString(sess.getTarget())).append("
" + "Established: ").append(DataHelper.formatDuration2(now - sess.getEstablishedDate())).append(" ago
" + - "Ack Received? ").append(sess.getAckReceived()).append("
" + "Last Used: ").append(DataHelper.formatDuration2(now - sess.getLastUsedDate())).append(" ago
"); SessionKey sk = sess.getCurrentKey(); if (sk != null) @@ -938,11 +879,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener private int _myIBKeyID = -1; private int _hisIBKeyID = -1; private int _currentIBTagSetID; + private int _myIBKeySendCount; + private KeyPair _myIBKeys; + private NextSessionKey _myIBKey; + private SessionKey _nextIBRootKey; + 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; /** * @param key may be null @@ -974,7 +921,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener _tagSets.add(tagset); _state = null; if (_log.shouldDebug()) - _log.debug("New OB Session, rk = " + rk + " tk = " + tk + " 1st tagset: " + tagset); + _log.debug("New OB Session, rk = " + rk + " tk = " + tk + " 1st tagset:\n" + tagset); } else { // We are Alice // This is an OUTBOUND NS, we make an INBOUND tagset for the NSR @@ -985,7 +932,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // store the state so we can find the right session when we receive the NSR _state = state; if (_log.shouldDebug()) - _log.debug("New IB Session, rk = " + rk + " tk = " + tk + " 1st tagset: " + tagset); + _log.debug("New IB Session, rk = " + rk + " tk = " + tk + " 1st tagset:\n" + tagset); } } @@ -1015,8 +962,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba), now, 0); if (_log.shouldDebug()) { - _log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); - _log.debug("Pending OB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset: " + tagset_ba); + _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); } synchronized (_tagSets) { _unackedTagSets.add(tagset_ba); @@ -1032,8 +979,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener now, 0, 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: " + tagset_ab); - _log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset: " + tagset_ba); + _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) { _tagSets.add(tagset_ab); @@ -1062,27 +1009,58 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // this is about my outbound tag set, // and is an ack of new key sent if (_hisIBKeyID != id) { - if (_log.shouldWarn()) - _log.warn("Got new key id, ratchet OB " + id); - if (_hisIBKeyID != id + 1) { + if (_hisIBKeyID != id - 1) { if (_log.shouldWarn()) - _log.warn("Got bad new key id OB? " + id); + _log.warn("Got nextkey id OB: " + id + " expected " + (_hisIBKeyID + 1)); + return; } - if (hasKey) { - KeyPair nextKeys = _context.keyGenerator().generatePKIKeys(EncType.ECIES_X25519); - PublicKey pub = nextKeys.getPublic(); - PrivateKey priv = nextKeys.getPrivate(); - PrivateKey sharedSecret = ECIESAEADEngine.doDH(priv, key); - // create new OB TS - // find current OB TS, and delete it - } else { + if (!hasKey) { // TODO get it from above if (_log.shouldWarn()) - _log.warn("Got nextkey w/o key but we don't have it " + id); + _log.warn("Got nextkey OB w/o key but we don't have it " + id); + return; } + int oldtsID = 1 + _myIBKeyID + id; + RatchetTagSet oldts = null; + for (RatchetTagSet ts : _tagSets) { + if (ts.getID() == oldtsID) { + oldts = ts; + break; + } + } + if (oldts == null) { + if (_log.shouldWarn()) + _log.warn("Got nextkey id OB " + id + " but can't find existing OB tagset " + oldtsID); + return; + } + KeyPair nextKeys = oldts.getNextKeys(); + if (nextKeys == null) { + if (_log.shouldWarn()) + _log.warn("Got nextkey id OB " + id + " but 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); } else { if (_log.shouldWarn()) - _log.warn("Got dup new key id for OB " + id); + _log.warn("Got dup nextkey id for OB " + id); } if (isRequest) { if (_log.shouldWarn()) @@ -1092,21 +1070,44 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } else { // this is about my inbound tag set if (_hisOBKeyID != id) { - if (_log.shouldWarn()) - _log.warn("Got new key id, ratchet IB " + id); - if (_hisOBKeyID != id + 1) { + if (_hisOBKeyID != id - 1) { if (_log.shouldWarn()) - _log.warn("Got bad new key id IB? " + id); + _log.warn("Got nextkey id IB: " + id + " expected " + (_hisOBKeyID + 1)); + return; } if (!hasKey) { if (_log.shouldWarn()) - _log.warn("Got nextkey w/o key but we don't have it " + id); + _log.warn("Got nextkey IB w/o key but we don't have it " + id); + return; } - // find current OB TS, tell him to send ack + if (_nextIBRootKey == null) { + if (_log.shouldWarn()) + _log.warn("Got nextkey IB but we don't have next root key " + id); + return; + } + // TODO new key only needed every other time + _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 new key id for IB " + id); + _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. } @@ -1119,6 +1120,19 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } } + /** + * Reverse key to send, or null + * @since 0.9.46 + */ + private NextSessionKey getReverseSendKey() { + if (_myIBKey == null) + return null; + if (_myIBKeySendCount > MAX_SEND_REVERSE_KEY) + return null; + _myIBKeySendCount++; + return _myIBKey; + } + /** * First tag was received for this inbound (ES) tagset. * Find the corresponding outbound (ES) tagset in _unackedTagSets, @@ -1129,6 +1143,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener void firstTagConsumed(RatchetTagSet set) { SessionKey sk = set.getAssociatedKey(); synchronized (_tagSets) { + // save next root key + _nextIBRootKey = set.getNextRootKey(); for (RatchetTagSet obSet : _unackedTagSets) { if (obSet.getAssociatedKey().equals(sk)) { if (_log.shouldDebug()) @@ -1165,33 +1181,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener return rv; } - /** - * got an ack for these tags - * For tagsets delivered after the session was acked, this is a nop - * because the tagset was originally placed directly on the acked list. - * If the set was previously failed, it will be added back in. - */ - void ackTags(RatchetTagSet set) { - synchronized (_tagSets) { - if (_unackedTagSets.remove(set)) { - // we could perhaps use it even if not previuosly in unacked, - // i.e. it was expired already, but _tagSets is a list not a set... - _tagSets.add(set); - } else if (!_tagSets.contains(set)) { - // add back (sucess after fail) - _tagSets.add(set); - if (_log.shouldWarn()) - _log.warn("Ack of unknown (previously failed?) tagset: " + set); - } else if (set.getAcked()) { - if (_log.shouldWarn()) - _log.warn("Dup ack of tagset: " + set); - } - _acked = true; - _consecutiveFailures = 0; - } - set.setAcked(); - } - /** didn't get an ack for these tags */ void failTags(RatchetTagSet set) { synchronized (_tagSets) { @@ -1215,28 +1204,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener return _currentKey; } - public void setCurrentKey(SessionKey key) { - _lastUsed = _context.clock().now(); - if (_currentKey != null) { - if (!_currentKey.equals(key)) { - synchronized (_tagSets) { - if (_log.shouldWarn()) { - int dropped = 0; - for (RatchetTagSet set : _tagSets) { - dropped += set.remaining(); - } - _log.warn("Rekeyed from " + _currentKey + " to " + key - + ": dropping " + dropped + " session tags", new Exception()); - } - _acked = false; - _tagSets.clear(); - } - } - } - _currentKey = key; - - } - public long getEstablishedDate() { return _established; } @@ -1285,8 +1252,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener set.setDate(now); SessionKeyAndNonce skn = set.consumeNextKey(); // TODO PN - // TODO reverse next key - return new RatchetEntry(tag, skn, set.getID(), 0, set.getNextKey(), null, getAcksToSend()); + return new RatchetEntry(tag, skn, set.getID(), 0, set.getNextKey(), + getReverseSendKey(), getAcksToSend()); } else if (_log.shouldInfo()) { _log.info("Removing empty " + set); } 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 246981e8e..d74f1a757 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java @@ -39,6 +39,9 @@ class RatchetTagSet implements TagSetHandle { private final SessionTagListener _lsnr; private final PublicKey _remoteKey; protected final SessionKey _key; + // debug only, to be removed + private final SessionKey _tagsetKey; + // NSR only, else null private final HandshakeState _state; // inbound only, else null // We use object for tags because we must do indexOfValueByValue() @@ -130,6 +133,7 @@ class RatchetTagSet implements TagSetHandle { _state = state; _remoteKey = remoteKey; _key = rootKey; + _tagsetKey = data; _created = date; _timeout = timeout; _date = date; @@ -170,6 +174,7 @@ class RatchetTagSet implements TagSetHandle { _state = null; _remoteKey = null; _key = rootKey; + _tagsetKey = null; _created = date; _timeout = timeout; _date = date; @@ -300,15 +305,15 @@ class RatchetTagSet implements TagSetHandle { } /** - * Next Forward Key if applicable (we're running low). - * Null if remaining is sufficient. + * Next Forward Key if applicable (outbound ES and we're running low). + * Null if NSR or inbound or remaining is sufficient. * Once non-null, will be constant for the remaining life of the tagset. * * @return key or null * @since 0.9.46 */ public NextSessionKey getNextKey() { - if (remaining() > LOW) + if (_sessionTags != null || _state != null || remaining() > LOW) return null; if (_nextKeys == null) { _nextKeys = I2PAppContext.getGlobalContext().keyGenerator().generatePKIKeys(EncType.ECIES_X25519); @@ -331,6 +336,17 @@ class RatchetTagSet implements TagSetHandle { return _nextKeys; } + /** + * Root key for the next DH ratchet. + * Should only be needed for ES, but valid for NSR also. + * + * @return key + * @since 0.9.46 + */ + public SessionKey getNextRootKey() { + return new SessionKey(_nextRootKey); + } + /** * tags still available * inbound only @@ -541,13 +557,17 @@ class RatchetTagSet implements TagSetHandle { else buf.append("ES "); buf.append("TagSet #").append(_tagSetID) - .append(" keyID #").append(_id) + .append(" ID #").append(_id) .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()); + buf.append("\nRoot Key: ").append(_key.toBase64()); + if (_tagsetKey != null) + buf.append("\nTagset Key: ").append(_tagsetKey.toBase64()); + if (_nextKey != null) + buf.append("\nNext Key: ").append(_nextKey); int sz = size(); buf.append("\nSize: ").append(sz) .append(" Orig: ").append(_originalSize)