Ratchet: More updates

- Reply callback from ratchet to OCMOSJ (ES TODO)
- Store key ID in tagset (prep for next key)
- Move debug tagset ID from engine to tagset
- OCMOSJ minor cleanups
This commit is contained in:
zzz
2020-03-27 19:38:24 +00:00
parent acf3abb19b
commit 23634afbc9
8 changed files with 141 additions and 58 deletions

View File

@ -304,7 +304,7 @@ public final class ECIESAEADEngine {
// tell the SKM // tell the SKM
PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK); PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK);
keyManager.createSession(bob, state); keyManager.createSession(bob, state, null);
if (pc.cloveSet.isEmpty()) { if (pc.cloveSet.isEmpty()) {
if (_log.shouldWarn()) if (_log.shouldWarn())
@ -430,7 +430,7 @@ public final class ECIESAEADEngine {
// tell the SKM // tell the SKM
PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK); PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK);
keyManager.updateSession(bob, oldState, state); keyManager.updateSession(bob, oldState, state, null);
if (pc.cloveSet.isEmpty()) { if (pc.cloveSet.isEmpty()) {
if (_log.shouldWarn()) if (_log.shouldWarn())
@ -558,13 +558,15 @@ public final class ECIESAEADEngine {
* @param target public key to which the data should be encrypted. * @param target public key to which the data should be encrypted.
* @param priv local private key to encrypt with, from the leaseset * @param priv local private key to encrypt with, from the leaseset
* @param replyDI non-null to request an ack, or null * @param replyDI non-null to request an ack, or null
* @param callback may be null
* @return encrypted data or null on failure * @return encrypted data or null on failure
* *
*/ */
public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv,
RatchetSKM keyManager, DeliveryInstructions replyDI) { RatchetSKM keyManager, DeliveryInstructions replyDI,
ReplyCallback callback) {
try { try {
return x_encrypt(cloves, target, priv, keyManager, replyDI); return x_encrypt(cloves, target, priv, keyManager, replyDI, callback);
} catch (Exception e) { } catch (Exception e) {
_log.error("ECIES encrypt error", e); _log.error("ECIES encrypt error", e);
return null; return null;
@ -572,7 +574,8 @@ public final class ECIESAEADEngine {
} }
private byte[] x_encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, private byte[] x_encrypt(CloveSet cloves, PublicKey target, PrivateKey priv,
RatchetSKM keyManager, DeliveryInstructions replyDI) { RatchetSKM keyManager, DeliveryInstructions replyDI,
ReplyCallback callback) {
if (target.getType() != EncType.ECIES_X25519) if (target.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (Arrays.equals(target.getData(), NULLPK)) { if (Arrays.equals(target.getData(), NULLPK)) {
@ -586,7 +589,7 @@ public final class ECIESAEADEngine {
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("Encrypting as NS to " + target); _log.debug("Encrypting as NS to " + target);
// no ack in NS // no ack in NS
return encryptNewSession(cloves, target, priv, keyManager, null); return encryptNewSession(cloves, target, priv, keyManager, null, callback);
} }
HandshakeState state = re.key.getHandshakeState(); HandshakeState state = re.key.getHandshakeState();
@ -601,11 +604,11 @@ public final class ECIESAEADEngine {
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64()); _log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64());
// no ack in NSR // no ack in NSR
return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, null); return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, null, callback);
} }
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag.toBase64()); _log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag.toBase64());
byte rv[] = encryptExistingSession(cloves, target, re, replyDI); byte rv[] = encryptExistingSession(cloves, target, re, replyDI, callback);
return rv; return rv;
} }
@ -625,10 +628,12 @@ public final class ECIESAEADEngine {
* </pre> * </pre>
* *
* @param replyDI non-null to request an ack, or null * @param replyDI non-null to request an ack, or null
* @param callback may be null
* @return encrypted data or null on failure * @return encrypted data or null on failure
*/ */
private byte[] encryptNewSession(CloveSet cloves, PublicKey target, PrivateKey priv, private byte[] encryptNewSession(CloveSet cloves, PublicKey target, PrivateKey priv,
RatchetSKM keyManager, DeliveryInstructions replyDI) { RatchetSKM keyManager, DeliveryInstructions replyDI,
ReplyCallback callback) {
HandshakeState state; HandshakeState state;
try { try {
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread); state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread);
@ -667,7 +672,7 @@ public final class ECIESAEADEngine {
_log.debug("Elligator2 encoded eph. key: " + Base64.encode(enc, 0, 32)); _log.debug("Elligator2 encoded eph. key: " + Base64.encode(enc, 0, 32));
// tell the SKM // tell the SKM
keyManager.createSession(target, state); keyManager.createSession(target, state, callback);
return enc; return enc;
} }
@ -689,11 +694,12 @@ public final class ECIESAEADEngine {
* *
* @param state must have already been cloned * @param state must have already been cloned
* @param replyDI non-null to request an ack, or null * @param replyDI non-null to request an ack, or null
* @param callback may be null
* @return encrypted data or null on failure * @return encrypted data or null on failure
*/ */
private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state, private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state,
RatchetSessionTag currentTag, RatchetSKM keyManager, RatchetSessionTag currentTag, RatchetSKM keyManager,
DeliveryInstructions replyDI) { DeliveryInstructions replyDI, ReplyCallback callback) {
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("State before encrypt new session reply: " + state); _log.debug("State before encrypt new session reply: " + state);
byte[] tag = currentTag.getData(); byte[] tag = currentTag.getData();
@ -746,7 +752,7 @@ public final class ECIESAEADEngine {
return null; return null;
} }
// tell the SKM // tell the SKM
keyManager.updateSession(target, null, state); keyManager.updateSession(target, null, state, callback);
return enc; return enc;
} }
@ -765,7 +771,7 @@ public final class ECIESAEADEngine {
* @return encrypted data or null on failure * @return encrypted data or null on failure
*/ */
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re, private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re,
DeliveryInstructions replyDI) { DeliveryInstructions replyDI, ReplyCallback callback) {
// //
if (ACKREQ_IN_ES && replyDI == null) if (ACKREQ_IN_ES && replyDI == null)
replyDI = new DeliveryInstructions(); replyDI = new DeliveryInstructions();
@ -774,6 +780,9 @@ public final class ECIESAEADEngine {
SessionKeyAndNonce key = re.key; SessionKeyAndNonce key = re.key;
byte encr[] = encryptAEADBlock(rawTag, payload, key, key.getNonce()); byte encr[] = encryptAEADBlock(rawTag, payload, key, key.getNonce());
System.arraycopy(rawTag, 0, encr, 0, TAGLEN); System.arraycopy(rawTag, 0, encr, 0, TAGLEN);
if (callback != null) {
// TODO
}
return encr; return encr;
} }

View File

@ -15,7 +15,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.southernstorm.noise.protocol.HandshakeState; import com.southernstorm.noise.protocol.HandshakeState;
@ -48,9 +47,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private final ConcurrentHashMap<RatchetSessionTag, RatchetTagSet> _inboundTagSets; private final ConcurrentHashMap<RatchetSessionTag, RatchetTagSet> _inboundTagSets;
protected final I2PAppContext _context; protected final I2PAppContext _context;
private volatile boolean _alive; private volatile boolean _alive;
/** for debugging */
private final AtomicInteger _rcvTagSetID = new AtomicInteger();
private final AtomicInteger _sentTagSetID = new AtomicInteger();
private final HKDF _hkdf; private final HKDF _hkdf;
/** /**
@ -168,15 +164,16 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* For inbound (NS rcvd), if no other pending outbound sessions, creates one * For inbound (NS rcvd), if no other pending outbound sessions, creates one
* and returns true, or false if one already exists. * and returns true, or false if one already exists.
* *
* @param callback null for inbound, may be null for outbound
*/ */
boolean createSession(PublicKey target, HandshakeState state) { boolean createSession(PublicKey target, HandshakeState state, ReplyCallback callback) {
EncType type = target.getType(); EncType type = target.getType();
if (type != EncType.ECIES_X25519) if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException("Bad public key type " + type); throw new IllegalArgumentException("Bad public key type " + type);
OutboundSession sess = new OutboundSession(target, null, state, callback);
boolean isInbound = state.getRole() == HandshakeState.RESPONDER; boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
if (isInbound) { if (isInbound) {
// we are Bob, NS received // we are Bob, NS received
OutboundSession sess = new OutboundSession(target, null, state);
boolean rv = addSession(sess, true); boolean rv = addSession(sess, true);
if (_log.shouldInfo()) { if (_log.shouldInfo()) {
if (rv) if (rv)
@ -187,7 +184,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
return rv; return rv;
} else { } else {
// we are Alice, NS sent // we are Alice, NS sent
OutboundSession sess = new OutboundSession(target, null, state);
synchronized (_pendingOutboundSessions) { synchronized (_pendingOutboundSessions) {
List<OutboundSession> pending = _pendingOutboundSessions.get(target); List<OutboundSession> pending = _pendingOutboundSessions.get(target);
if (pending != null) { if (pending != null) {
@ -215,7 +211,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* @param oldState null for inbound, pre-clone for outbound * @param oldState null for inbound, pre-clone for outbound
* @return true if this was the first NSR received * @return true if this was the first NSR received
*/ */
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state) { boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state, ReplyCallback callback) {
EncType type = target.getType(); EncType type = target.getType();
if (type != EncType.ECIES_X25519) if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException("Bad public key type " + type); throw new IllegalArgumentException("Bad public key type " + type);
@ -231,7 +227,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// TODO can we recover? // TODO can we recover?
return false; return false;
} }
sess.updateSession(state); sess.updateSession(state, callback);
} else { } else {
// we are Alice, NSR received // we are Alice, NSR received
if (_log.shouldInfo()) if (_log.shouldInfo())
@ -251,7 +247,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (oldState.equals(pstate)) { if (oldState.equals(pstate)) {
if (!found) { if (!found) {
found = true; found = true;
sess.updateSession(state); sess.updateSession(state, null);
boolean ok = addSession(sess, false); boolean ok = addSession(sess, false);
if (_log.shouldDebug()) { if (_log.shouldDebug()) {
if (ok) if (ok)
@ -407,13 +403,13 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (sess == null) { if (sess == null) {
if (_log.shouldWarn()) if (_log.shouldWarn())
_log.warn("No session for delivered RatchetTagSet to target: " + toString(target)); _log.warn("No session for delivered RatchetTagSet to target: " + toString(target));
/////////// // TODO
createSession(target, key); createSession(target, key);
} else { } else {
sess.setCurrentKey(key); sess.setCurrentKey(key);
} }
/////////// // TODO
RatchetTagSet set = new RatchetTagSet(_hkdf, key, key, _context.clock().now(), _sentTagSetID.incrementAndGet()); RatchetTagSet set = new RatchetTagSet(_hkdf, key, key, _context.clock().now(), 0);
sess.addTags(set); sess.addTags(set);
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("Tags delivered: " + set + _log.debug("Tags delivered: " + set +
@ -812,6 +808,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private class OutboundSession { private class OutboundSession {
private final PublicKey _target; private final PublicKey _target;
private final HandshakeState _state; private final HandshakeState _state;
private final ReplyCallback _NScallback;
private ReplyCallback _NSRcallback;
private SessionKey _currentKey; private SessionKey _currentKey;
private final long _established; private final long _established;
private long _lastUsed; private long _lastUsed;
@ -843,10 +841,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private int _consecutiveFailures; private int _consecutiveFailures;
private static final int MAX_FAILS = 2; private static final int MAX_FAILS = 2;
private static final int DEBUG_OB_NSR = 0x10001;
private static final int DEBUG_IB_NSR = 0x10002;
public OutboundSession(PublicKey target, SessionKey key, HandshakeState state) { /**
* @param key may be null
* @param callback may be null. Always null for IB.
*/
public OutboundSession(PublicKey target, SessionKey key, HandshakeState state, ReplyCallback callback) {
_target = target; _target = target;
_currentKey = key; _currentKey = key;
_NScallback = callback;
_established = _context.clock().now(); _established = _context.clock().now();
_lastUsed = _established; _lastUsed = _established;
_unackedTagSets = new HashSet<RatchetTagSet>(4); _unackedTagSets = new HashSet<RatchetTagSet>(4);
@ -863,7 +868,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// This is an INBOUND NS, we make an OUTBOUND tagset for the NSR // This is an INBOUND NS, we make an OUTBOUND tagset for the NSR
RatchetTagSet tagset = new RatchetTagSet(_hkdf, state, RatchetTagSet tagset = new RatchetTagSet(_hkdf, state,
rk, tk, rk, tk,
_established, _sentTagSetID.getAndIncrement()); _established, DEBUG_OB_NSR);
_tagSets.add(tagset); _tagSets.add(tagset);
_state = null; _state = null;
if (_log.shouldDebug()) if (_log.shouldDebug())
@ -873,7 +878,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// This is an OUTBOUND NS, we make an INBOUND tagset for the NSR // This is an OUTBOUND NS, we make an INBOUND tagset for the NSR
RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state, RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state,
rk, tk, rk, tk,
_established, _rcvTagSetID.getAndIncrement(), _established, DEBUG_IB_NSR,
MIN_RCV_WINDOW_NSR, MAX_RCV_WINDOW_NSR); MIN_RCV_WINDOW_NSR, MAX_RCV_WINDOW_NSR);
// store the state so we can find the right session when we receive the NSR // store the state so we can find the right session when we receive the NSR
_state = state; _state = state;
@ -888,8 +893,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
* For inbound (NSR sent by Bob), sets up inbound ES tagset. * For inbound (NSR sent by Bob), sets up inbound ES tagset.
* *
* @param state current state * @param state current state
* @param callback only for inbound (NSR sent by Bob), may be null
*/ */
void updateSession(HandshakeState state) { void updateSession(HandshakeState state, ReplyCallback callback) {
byte[] ck = state.getChainingKey(); byte[] ck = state.getChainingKey();
byte[] k_ab = new byte[32]; byte[] k_ab = new byte[32];
byte[] k_ba = new byte[32]; byte[] k_ba = new byte[32];
@ -901,26 +907,27 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
// We are Bob // We are Bob
// This is an OUTBOUND NSR, we make an INBOUND tagset for ES // 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), RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ab),
now, _rcvTagSetID.getAndIncrement(), now, 0,
MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES);
// and a pending outbound one // and a pending outbound one
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba), RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba),
now, _sentTagSetID.getAndIncrement()); now, 0);
if (_log.shouldDebug()) { if (_log.shouldDebug()) {
_log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); _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("Pending OB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset: " + tagset_ba);
} }
synchronized (_tagSets) { synchronized (_tagSets) {
_unackedTagSets.add(tagset_ba); _unackedTagSets.add(tagset_ba);
_NSRcallback = callback;
} }
} else { } else {
// We are Alice // We are Alice
// This is an INBOUND NSR, we make an OUTBOUND tagset for ES // This is an INBOUND NSR, we make an OUTBOUND tagset for ES
RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ab), RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ab),
now, _sentTagSetID.getAndIncrement()); now, 0);
// and an inbound one // and an inbound one
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ba), RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ba),
now, _rcvTagSetID.getAndIncrement(), now, 0,
MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES);
if (_log.shouldDebug()) { if (_log.shouldDebug()) {
_log.debug("Update OB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); _log.debug("Update OB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab);
@ -932,6 +939,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
} }
// We can't destroy the original state, as more NSRs may come in // We can't destroy the original state, as more NSRs may come in
//_state.destroy(); //_state.destroy();
// Bob received the NS, call the callback
if (_NScallback != null)
_NScallback.onReply();
} }
// kills the keys for future NSRs // kills the keys for future NSRs
//state.destroy(); //state.destroy();
@ -955,6 +965,10 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
_unackedTagSets.clear(); _unackedTagSets.clear();
_tagSets.clear(); _tagSets.clear();
_tagSets.add(obSet); _tagSets.add(obSet);
if (_NSRcallback != null) {
_NSRcallback.onReply();
_NSRcallback = null;
}
return; return;
} }
} }

View File

@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.southernstorm.noise.protocol.DHState; import com.southernstorm.noise.protocol.DHState;
import com.southernstorm.noise.protocol.HandshakeState; import com.southernstorm.noise.protocol.HandshakeState;
@ -60,6 +61,9 @@ class RatchetTagSet implements TagSetHandle {
private KeyPair _nextKeys; private KeyPair _nextKeys;
private NextSessionKey _nextKey; private NextSessionKey _nextKey;
private boolean _nextKeyAcked; private boolean _nextKeyAcked;
/** for debugging */
private static final AtomicInteger __tagSetID = new AtomicInteger();
private final int _tagSetID = __tagSetID.incrementAndGet();
private static final String INFO_1 = "KDFDHRatchetStep"; private static final String INFO_1 = "KDFDHRatchetStep";
private static final String INFO_2 = "TagAndKeyGenKeys"; private static final String INFO_2 = "TagAndKeyGenKeys";
@ -432,7 +436,7 @@ class RatchetTagSet implements TagSetHandle {
*/ */
public boolean getAcked() { return _acked; } public boolean getAcked() { return _acked; }
/** for debugging */ /** the Key ID */
public int getID() { public int getID() {
return _id; return _id;
} }
@ -448,7 +452,8 @@ class RatchetTagSet implements TagSetHandle {
buf.append("NSR ").append(_state.hashCode()).append(' '); buf.append("NSR ").append(_state.hashCode()).append(' ');
else else
buf.append("ES "); buf.append("ES ");
buf.append("TagSet #").append(_id) buf.append("TagSet #").append(_tagSetID)
.append(" keyID #").append(_id)
.append("\nCreated: ").append(DataHelper.formatTime(_created)) .append("\nCreated: ").append(DataHelper.formatTime(_created))
.append("\nLast use: ").append(DataHelper.formatTime(_date)); .append("\nLast use: ").append(DataHelper.formatTime(_date));
PublicKey pk = getRemoteKey(); PublicKey pk = getRemoteKey();

View File

@ -0,0 +1,20 @@
package net.i2p.router.crypto.ratchet;
/**
* ECIES will call this back if an ack was requested and received.
*
* @since 0.9.46
*/
public interface ReplyCallback {
/**
* When does this callback expire?
* @return java time
*/
public long getExpiration();
/**
* A reply was received.
*/
public void onReply();
}

View File

@ -1,7 +1,5 @@
package net.i2p.router.crypto.ratchet; package net.i2p.router.crypto.ratchet;
import net.i2p.data.SessionTag;
/** /**
* Something that looks for SessionTags. * Something that looks for SessionTags.
* *

View File

@ -32,6 +32,7 @@ import net.i2p.router.LeaseSetKeys;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.MuxedSKM;
import net.i2p.router.crypto.ratchet.RatchetSKM; import net.i2p.router.crypto.ratchet.RatchetSKM;
import net.i2p.router.crypto.ratchet.ReplyCallback;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -246,20 +247,21 @@ public class GarlicMessageBuilder {
/** /**
* ECIES_X25519 only. * ECIES_X25519 only.
* Called by GarlicMessageBuilder only. * Called by OCMJH only.
* *
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param target public key of the location being garlic routed to (may be null if we * @param target public key of the location being garlic routed to (may be null if we
* know the encryptKey and encryptTag) * know the encryptKey and encryptTag)
* @param replyDI non-null to request an ack, or null * @param replyDI non-null to request an ack, or null
* @param callback may be null
* @return null if expired or on other errors * @return null if expired or on other errors
* @throws IllegalArgumentException on error * @throws IllegalArgumentException on error
* @since 0.9.44 * @since 0.9.44
*/ */
static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config, static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config,
PublicKey target, Hash from, SessionKeyManager skm, PublicKey target, Hash from, SessionKeyManager skm,
DeliveryInstructions replyDI) { DeliveryInstructions replyDI, ReplyCallback callback) {
PublicKey key = config.getRecipientPublicKey(); PublicKey key = config.getRecipientPublicKey();
if (key.getType() != EncType.ECIES_X25519) if (key.getType() != EncType.ECIES_X25519)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@ -289,7 +291,7 @@ public class GarlicMessageBuilder {
log.warn("No SKM for " + from.toBase32()); log.warn("No SKM for " + from.toBase32());
return null; return null;
} }
byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, replyDI); byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, replyDI, callback);
if (encData == null) { if (encData == null) {
if (log.shouldWarn()) if (log.shouldWarn())
log.warn("Encrypt fail for " + from.toBase32()); log.warn("Encrypt fail for " + from.toBase32());

View File

@ -30,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.LeaseSetKeys; import net.i2p.router.LeaseSetKeys;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo; import net.i2p.router.TunnelInfo;
import net.i2p.router.crypto.ratchet.ReplyCallback;
import net.i2p.router.networkdb.kademlia.MessageWrapper; import net.i2p.router.networkdb.kademlia.MessageWrapper;
import net.i2p.util.Log; import net.i2p.util.Log;
@ -108,12 +109,14 @@ class OutboundClientMessageJobHelper {
* @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null * @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null
* @param requireAck if true, bundle replyToken in an ack clove * @param requireAck if true, bundle replyToken in an ack clove
* @param bundledReplyLeaseSet may be null; if non-null, put it in a clove * @param bundledReplyLeaseSet may be null; if non-null, put it in a clove
* @param callback only for ECIES, may be null
* @return garlic, or null if no tunnels were found (or other errors) * @return garlic, or null if no tunnels were found (or other errors)
*/ */
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel,
int tagsToSendOverride, int lowTagsOverride, SessionKey wrappedKey, int tagsToSendOverride, int lowTagsOverride, SessionKey wrappedKey,
Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) { Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet,
ReplyCallback callback) {
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from); SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
if (skm == null) if (skm == null)
@ -147,7 +150,7 @@ class OutboundClientMessageJobHelper {
} else { } else {
di = null; di = null;
} }
msg = GarlicMessageBuilder.buildECIESMessage(ctx, config, recipientPK, from, skm, di); msg = GarlicMessageBuilder.buildECIESMessage(ctx, config, recipientPK, from, skm, di, callback);
} else { } else {
// no use sending tags unless we have a reply token set up already // no use sending tags unless we have a reply token set up already
int tagsToSend = replyToken >= 0 ? (tagsToSendOverride > 0 ? tagsToSendOverride : skm.getTagsToSend()) : 0; int tagsToSend = replyToken >= 0 ? (tagsToSendOverride > 0 ? tagsToSendOverride : skm.getTagsToSend()) : 0;

View File

@ -38,6 +38,7 @@ import net.i2p.router.ReplyJob;
import net.i2p.router.Router; import net.i2p.router.Router;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo; import net.i2p.router.TunnelInfo;
import net.i2p.router.crypto.ratchet.ReplyCallback;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -655,12 +656,18 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
// Per-message flag > 0 overrides per-session option // Per-message flag > 0 overrides per-session option
int tagsToSend = SendMessageOptions.getTagsToSend(sendFlags); int tagsToSend = SendMessageOptions.getTagsToSend(sendFlags);
ReplyCallback callback;
if (wantACK && _encryptionKey.getType() == EncType.ECIES_X25519) {
callback = new ECIESReplyCallback(replyLeaseSet);
} else {
callback = null;
}
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token, GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
_overallExpiration, _encryptionKey, _overallExpiration, _encryptionKey,
clove, _from.calculateHash(), clove, _from.calculateHash(),
_to, _inTunnel, tagsToSend, _to, _inTunnel, tagsToSend,
tagsRequired, sessKey, tags, tagsRequired, sessKey, tags,
wantACK, replyLeaseSet); wantACK, replyLeaseSet, callback);
if (msg == null) { if (msg == null) {
// set to null if there are no tunnels to ack the reply back through // set to null if there are no tunnels to ack the reply back through
// (should we always fail for this? or should we send it anyway, even if // (should we always fail for this? or should we send it anyway, even if
@ -675,9 +682,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
//if (_log.shouldLog(Log.DEBUG)) //if (_log.shouldLog(Log.DEBUG))
// _log.debug(getJobId() + ": send() - token expected " + token + " to " + _toString); // _log.debug(getJobId() + ": send() - token expected " + token + " to " + _toString);
SendSuccessJob onReply = null; SendSuccessJob onReply;
SendTimeoutJob onFail = null; SendTimeoutJob onFail;
ReplySelector selector = null; ReplySelector selector;
if (wantACK && _encryptionKey.getType() == EncType.ELGAMAL_2048) { if (wantACK && _encryptionKey.getType() == EncType.ELGAMAL_2048) {
TagSetHandle tsh = null; TagSetHandle tsh = null;
@ -686,10 +693,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
if (skm != null) if (skm != null)
tsh = skm.tagsDelivered(_encryptionKey, sessKey, tags); tsh = skm.tagsDelivered(_encryptionKey, sessKey, tags);
} }
onFail = new SendTimeoutJob(getContext(), sessKey, tsh); onFail = new SendTimeoutJob(sessKey, tsh);
onReply = new SendSuccessJob(getContext(), sessKey, tsh, replyLeaseSet, onFail); onReply = new SendSuccessJob(sessKey, tsh, replyLeaseSet, onFail);
long expiration = Math.max(_overallExpiration, _start + REPLY_TIMEOUT_MS_MIN); long expiration = Math.max(_overallExpiration, _start + REPLY_TIMEOUT_MS_MIN);
selector = new ReplySelector(token, expiration); selector = new ReplySelector(token, expiration);
} else {
onReply = null;
onFail = null;
selector = null;
} }
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
@ -698,7 +709,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ _lease.getTunnelId() + " on " + _lease.getTunnelId() + " on "
+ _lease.getGateway()); + _lease.getGateway());
DispatchJob dispatchJob = new DispatchJob(getContext(), msg, selector, onReply, onFail); DispatchJob dispatchJob = new DispatchJob(msg, selector, onReply, onFail);
//if (false) // dispatch may take 100+ms, so toss it in its own job //if (false) // dispatch may take 100+ms, so toss it in its own job
// getContext().jobQueue().addJob(dispatchJob); // getContext().jobQueue().addJob(dispatchJob);
//else //else
@ -723,9 +734,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* @param success non-null if sel non-null * @param success non-null if sel non-null
* @param timeout non-null if sel non-null * @param timeout non-null if sel non-null
*/ */
public DispatchJob(RouterContext ctx, GarlicMessage msg, ReplySelector sel, public DispatchJob(GarlicMessage msg, ReplySelector sel,
SendSuccessJob success, SendTimeoutJob timeout) { SendSuccessJob success, SendTimeoutJob timeout) {
super(ctx); super(OutboundClientMessageOneShotJob.this.getContext());
_msg = msg; _msg = msg;
_selector = sel; _selector = sel;
_replyFound = success; _replyFound = success;
@ -1014,11 +1025,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* @param key may be null * @param key may be null
* @param tags may be null * @param tags may be null
* @param ls the delivered leaseset or null * @param ls the delivered leaseset or null
* @param timeout will be cancelled when this is run * @param timeout will be cancelled when this is run, may be null
*/ */
public SendSuccessJob(RouterContext enclosingContext, SessionKey key, public SendSuccessJob(SessionKey key,
TagSetHandle tags, LeaseSet ls, SendTimeoutJob timeout) { TagSetHandle tags, LeaseSet ls, SendTimeoutJob timeout) {
super(enclosingContext); super(OutboundClientMessageOneShotJob.this.getContext());
_key = key; _key = key;
_tags = tags; _tags = tags;
_deliveredLS = ls; _deliveredLS = ls;
@ -1064,7 +1075,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
skm.tagsAcked(_encryptionKey, _key, _tags); skm.tagsAcked(_encryptionKey, _key, _tags);
} }
} }
getContext().jobQueue().removeJob(_replyTimeout); if (_replyTimeout != null)
getContext().jobQueue().removeJob(_replyTimeout);
long sendTime = getContext().clock().now() - _start; long sendTime = getContext().clock().now() - _start;
if (old == Result.FAIL) { if (old == Result.FAIL) {
@ -1110,6 +1122,26 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
public void setMessage(I2NPMessage msg) {} public void setMessage(I2NPMessage msg) {}
} }
/**
* For ECIES only.
*
* @since 0.9.46
*/
private class ECIESReplyCallback extends SendSuccessJob implements ReplyCallback {
public ECIESReplyCallback(LeaseSet ls) {
super(null, null, ls, null);
}
public long getExpiration() {
// same as SendTimeoutJob
return Math.max(_overallExpiration, _start + REPLY_TIMEOUT_MS_MIN);
}
public void onReply() {
runJob();
}
}
/** /**
* Fired after the basic timeout for sending through the given tunnel has been reached. * Fired after the basic timeout for sending through the given tunnel has been reached.
@ -1127,8 +1159,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* @param key may be null * @param key may be null
* @param tags may be null * @param tags may be null
*/ */
public SendTimeoutJob(RouterContext enclosingContext, SessionKey key, TagSetHandle tags) { public SendTimeoutJob(SessionKey key, TagSetHandle tags) {
super(enclosingContext); super(OutboundClientMessageOneShotJob.this.getContext());
_key = key; _key = key;
_tags = tags; _tags = tags;
} }