Ratchet: Bob transition to ES

Store PK in RTS
New RST constructor
Log tweaks (lots)
This commit is contained in:
zzz
2019-11-05 14:13:35 +00:00
parent 3ae5b90c98
commit 4d1d11d1d4
5 changed files with 121 additions and 43 deletions

View File

@ -161,11 +161,11 @@ public final class ECIESAEADEngine {
HandshakeState state = key.getHandshakeState(); HandshakeState state = key.getHandshakeState();
if (state == null) { if (state == null) {
if (shouldDebug) 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); decrypted = decryptExistingSession(tag, data, key, targetPrivateKey);
} else if (data.length >= MIN_NSR_SIZE) { } else if (data.length >= MIN_NSR_SIZE) {
if (shouldDebug) 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); decrypted = decryptNewSessionReply(tag, data, state, keyManager);
} else { } else {
decrypted = null; decrypted = null;
@ -562,11 +562,11 @@ public final class ECIESAEADEngine {
return null; return null;
} }
if (_log.shouldDebug()) 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); return encryptNewSessionReply(cloves, target, state, re.tag, keyManager);
} }
if (_log.shouldDebug()) 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); byte rv[] = encryptExistingSession(cloves, target, re.key, re.tag);
return rv; return rv;
} }
@ -722,7 +722,7 @@ public final class ECIESAEADEngine {
/** /**
* No ad * 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); return encryptAEADBlock(null, data, key, n);
} }

View File

@ -176,8 +176,7 @@ public class MuxedSKM extends SessionKeyManager {
public SessionKey consumeTag(SessionTag tag) { public SessionKey consumeTag(SessionTag tag) {
SessionKey rv = _elg.consumeTag(tag); SessionKey rv = _elg.consumeTag(tag);
if (rv == null) { if (rv == null) {
byte[] stag = new byte[8]; long stag = RatchetPayload.fromLong8(tag.getData(), 0);
System.arraycopy(tag.getData(), 0, stag, 0, 8);
RatchetSessionTag rstag = new RatchetSessionTag(stag); RatchetSessionTag rstag = new RatchetSessionTag(stag);
rv = _ec.consumeTag(rstag); rv = _ec.consumeTag(rstag);
} }

View File

@ -178,9 +178,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
boolean rv = addSession(sess); boolean rv = addSession(sess);
if (_log.shouldInfo()) { if (_log.shouldInfo()) {
if (rv) 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 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; return rv;
} else { } else {
@ -191,14 +191,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (pending != null) { if (pending != null) {
pending.add(sess); pending.add(sess);
if (_log.shouldInfo()) 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)); ". Bob: " + toString(target));
} else { } else {
pending = new ArrayList<OutboundSession>(4); pending = new ArrayList<OutboundSession>(4);
pending.add(sess); pending.add(sess);
_pendingOutboundSessions.put(target, pending); _pendingOutboundSessions.put(target, pending);
if (_log.shouldInfo()) 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; return true;
@ -220,23 +220,25 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
boolean isInbound = state.getRole() == HandshakeState.RESPONDER; boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
if (isInbound) { if (isInbound) {
// we are Bob, NSR sent // we are Bob, NSR sent
if (_log.shouldInfo())
_log.info("Session " + state.hashCode() + " update as Bob. Alice: " + toString(target));
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) { if (sess == null) {
if (_log.shouldDebug()) 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? // TODO can we recover?
return false; return false;
} }
sess.updateSession(state); sess.updateSession(state);
if (_log.shouldInfo())
_log.info("Session update as Bob. Alice: " + toString(target));
} else { } else {
// we are Alice, NSR received // we are Alice, NSR received
if (_log.shouldInfo())
_log.info("Session " + oldState.hashCode() + " to " + state.hashCode() + " update as Alice. Bob: " + toString(target));
synchronized (_pendingOutboundSessions) { synchronized (_pendingOutboundSessions) {
List<OutboundSession> pending = _pendingOutboundSessions.get(target); List<OutboundSession> pending = _pendingOutboundSessions.get(target);
if (pending == null) { if (pending == null) {
if (_log.shouldDebug()) 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? // TODO can we recover?
return false; return false;
} }
@ -250,7 +252,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
boolean ok = addSession(sess); boolean ok = addSession(sess);
if (_log.shouldDebug()) { if (_log.shouldDebug()) {
if (ok) if (ok)
_log.debug("Update session from NSR to ES for " + target); _log.debug("Update Alice session from NSR to ES for " + target);
else else
_log.debug("Session already updated from NSR to ES for " + target); _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()) 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? // TODO can we recover?
return false; return false;
} }
} }
if (_log.shouldInfo())
_log.info("Session update as Alice. Bob: " + toString(target));
} }
return true; return true;
} }
@ -520,8 +521,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
SessionKeyAndNonce key; SessionKeyAndNonce key;
tagSet = _inboundTagSets.remove(tag); tagSet = _inboundTagSets.remove(tag);
if (tagSet == null) { if (tagSet == null) {
if (_log.shouldDebug()) //if (_log.shouldDebug())
_log.debug("IB tag not found: " + tag.toBase64()); // _log.debug("IB tag not found: " + tag.toBase64());
return null; return null;
} }
boolean firstInbound; boolean firstInbound;
@ -535,14 +536,22 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
HandshakeState state = tagSet.getHandshakeState(); HandshakeState state = tagSet.getHandshakeState();
if (firstInbound) { if (firstInbound) {
if (state == null) { 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 (_log.shouldDebug()) {
if (state != null) if (state != null)
_log.debug("IB NSR Tag consumed: " + tag + " from: " + tagSet); _log.debug("IB NSR Tag consumed: " + tag.toBase64() + " from: " + tagSet);
else else
_log.debug("IB ES Tag consumed: " + tag + " from: " + tagSet); _log.debug("IB ES Tag consumed: " + tag.toBase64() + " from: " + tagSet);
} }
} else { } else {
if (_log.shouldWarn()) if (_log.shouldWarn())
@ -838,12 +847,21 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state, RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state,
rk, tk, rk, tk,
_established, _rcvTagSetID.getAndIncrement(), 5, 5); _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); _unackedTagSets.add(tagset);
if (_log.shouldDebug()) 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: " + 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) { void updateSession(HandshakeState state) {
byte[] ck = state.getChainingKey(); byte[] ck = state.getChainingKey();
byte[] k_ab = new byte[32]; byte[] k_ab = new byte[32];
@ -855,7 +873,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (isInbound) { if (isInbound) {
// 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, rk, new SessionKey(k_ab), RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ab),
now, _rcvTagSetID.getAndIncrement(), 5, 5); now, _rcvTagSetID.getAndIncrement(), 5, 5);
// 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),
@ -873,7 +891,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
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, _sentTagSetID.getAndIncrement());
// and an inbound one // 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); now, _rcvTagSetID.getAndIncrement(), 5, 5);
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);
@ -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(). * This is used only by renderStatusHTML().
* It includes both acked and unacked RatchetTagSets. * It includes both acked and unacked RatchetTagSets.
* @return list of RatchetTagSet objects
*/ */
List<RatchetTagSet> getTagSets() { List<RatchetTagSet> getTagSets() {
List<RatchetTagSet> rv; List<RatchetTagSet> rv;

View File

@ -15,6 +15,10 @@ public class RatchetSessionTag {
private final long _data; private final long _data;
public RatchetSessionTag(long val) {
_data = val;
}
public RatchetSessionTag(byte val[]) { public RatchetSessionTag(byte val[]) {
if (val.length != LENGTH) if (val.length != LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();

View File

@ -7,13 +7,16 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import com.southernstorm.noise.protocol.DHState;
import com.southernstorm.noise.protocol.HandshakeState; import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType;
import net.i2p.crypto.HKDF; import net.i2p.crypto.HKDF;
import net.i2p.crypto.TagSetHandle; import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.util.Log; import net.i2p.util.Log;
@ -32,6 +35,7 @@ import net.i2p.util.Log;
*/ */
class RatchetTagSet implements TagSetHandle { class RatchetTagSet implements TagSetHandle {
private final SessionTagListener _lsnr; private final SessionTagListener _lsnr;
private final PublicKey _remoteKey;
private final SessionKey _key; private final SessionKey _key;
private final HandshakeState _state; private final HandshakeState _state;
// We use object for tags because we must do indexOfValueByValue() // 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, public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id) { 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, public RatchetTagSet(HKDF hkdf, SessionKey rootKey, SessionKey data,
long date, int id) { 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, public RatchetTagSet(HKDF hkdf, SessionTagListener lsnr, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id, int minSize, int maxSize) { 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 * @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) { 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 * @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) { long date, int id, boolean isInbound, int minSize, int maxSize) {
_lsnr = lsnr; _lsnr = lsnr;
_state = state; _state = state;
_remoteKey = remoteKey;
_key = rootKey; _key = rootKey;
_created = date; _created = date;
_date = 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. * Not used for cryptographic operations after setup.
*/ */
public SessionKey getAssociatedKey() { public SessionKey getAssociatedKey() {
@ -386,21 +410,28 @@ class RatchetTagSet implements TagSetHandle {
@Override @Override
public String toString() { public String toString() {
StringBuilder buf = new StringBuilder(256); StringBuilder buf = new StringBuilder(256);
if (_sessionTags != null)
buf.append("Inbound ");
else
buf.append("Outbound ");
if (_state != null) if (_state != null)
buf.append("NSR "); buf.append("NSR ").append(_state.hashCode()).append(' ');
else else
buf.append("ES "); buf.append("ES ");
buf.append("TagSet #").append(_id) buf.append("TagSet #").append(_id)
.append(" created: ").append(DataHelper.formatTime(_created)) .append("\nCreated: ").append(DataHelper.formatTime(_created))
.append(" last use: ").append(DataHelper.formatTime(_date)); .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(); int sz = size();
buf.append(" Size: ").append(sz) buf.append("\nSize: ").append(sz)
.append(" Orig: ").append(_originalSize) .append(" Orig: ").append(_originalSize)
.append(" Max: ").append(_maxSize) .append(" Max: ").append(_maxSize)
.append(" Remaining: ").append(remaining()); .append(" Remaining: ").append(remaining());
buf.append(" Acked? ").append(_acked); buf.append(" Acked? ").append(_acked);
if (_sessionTags != null) { if (_sessionTags != null) {
buf.append(" Inbound");
for (int i = 0; i < sz; i++) { for (int i = 0; i < sz; i++) {
int n = _sessionTags.keyAt(i); int n = _sessionTags.keyAt(i);
RatchetSessionTag tag = _sessionTags.valueAt(i); RatchetSessionTag tag = _sessionTags.valueAt(i);
@ -413,8 +444,6 @@ class RatchetTagSet implements TagSetHandle {
buf.append("\tdeferred"); buf.append("\tdeferred");
} }
} }
} else {
buf.append(" Outbound");
} }
return buf.toString(); return buf.toString();
} }