Ratchet: Next Key WIP

This commit is contained in:
zzz
2020-04-01 18:44:06 +00:00
parent 471b53698a
commit 6b05acff8d
5 changed files with 175 additions and 27 deletions

View File

@ -510,6 +510,11 @@ public final class ECIESAEADEngine {
if (_log.shouldWarn())
_log.warn("No garlic block in ES payload");
}
if (pc.nextKeys != null) {
for (NextSessionKey nextKey : pc.nextKeys) {
keyManager.nextKeyReceived(remote, nextKey);
}
}
if (pc.ackRequested) {
keyManager.ackRequested(remote, key.getID(), nonce);
}
@ -667,7 +672,7 @@ public final class ECIESAEADEngine {
if (_log.shouldDebug())
_log.debug("State before encrypt new session: " + state);
byte[] payload = createPayload(cloves, cloves.getExpiration(), false, null, null);
byte[] payload = createPayload(cloves, cloves.getExpiration());
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
try {
@ -726,7 +731,7 @@ public final class ECIESAEADEngine {
if (_log.shouldDebug())
_log.debug("State after mixhash tag before encrypt new session reply: " + state);
byte[] payload = createPayload(cloves, 0, false, null, null);
byte[] payload = createPayload(cloves, 0);
// part 1 - tag and empty payload
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
@ -793,7 +798,7 @@ public final class ECIESAEADEngine {
RatchetSKM keyManager) {
boolean ackreq = callback != null || ACKREQ_IN_ES;
byte rawTag[] = re.tag.getData();
byte[] payload = createPayload(cloves, 0, ackreq, re.nextKey, re.acksToSend);
byte[] payload = createPayload(cloves, 0, ackreq, re.nextForwardKey, re.nextReverseKey, re.acksToSend);
SessionKeyAndNonce key = re.key;
int nonce = key.getNonce();
byte encr[] = encryptAEADBlock(rawTag, payload, key, nonce);
@ -823,7 +828,7 @@ public final class ECIESAEADEngine {
*/
public byte[] encrypt(CloveSet cloves, SessionKey key, RatchetSessionTag tag) {
byte rawTag[] = tag.getData();
byte[] payload = createPayload(cloves, 0, false, null, null);
byte[] payload = createPayload(cloves, 0);
byte encr[] = encryptAEADBlock(rawTag, payload, key, 0);
System.arraycopy(rawTag, 0, encr, 0, TAGLEN);
return encr;
@ -857,7 +862,7 @@ public final class ECIESAEADEngine {
return enc;
}
private static final PrivateKey doDH(PrivateKey privkey, PublicKey pubkey) {
static final PrivateKey doDH(PrivateKey privkey, PublicKey pubkey) {
byte[] dh = new byte[KEYLEN];
Curve25519.eval(dh, 0, privkey.getData(), pubkey.getData());
return new PrivateKey(EncType.ECIES_X25519, dh);
@ -868,11 +873,13 @@ public final class ECIESAEADEngine {
/////////////////////////////////////////////////////////
private class PLCallback implements RatchetPayload.PayloadCallback {
/** non null, may be empty */
public final List<GarlicClove> cloveSet = new ArrayList<GarlicClove>(3);
private final RatchetSKM skm;
private final PublicKey remote;
public long datetime;
public NextSessionKey nextKey;
/** null or non-empty */
public List<NextSessionKey> nextKeys;
public boolean ackRequested;
/**
@ -920,7 +927,11 @@ public final class ECIESAEADEngine {
public void gotNextKey(NextSessionKey next) {
if (_log.shouldDebug())
_log.debug("Got NEXTKEY block: " + next);
nextKey = next;
// could have both a forward and reverse.
// shouldn't have two forwards or two reverses
if (nextKeys == null)
nextKeys = new ArrayList<NextSessionKey>(2);
nextKeys.add(next);
}
public void gotAck(int id, int n) {
@ -954,21 +965,33 @@ public final class ECIESAEADEngine {
}
}
/**
* @param expiration if greater than zero, add a DateTime block
* @since 0.9.46
*/
private byte[] createPayload(CloveSet cloves, long expiration) {
return createPayload(cloves, expiration, false, null, null, null);
}
/**
* @param expiration if greater than zero, add a DateTime block
* @param ackreq to request an ack, must be false for NS/NSR
* @param nextKey1 may be null
* @param nextKey2 may be null
* @param acksTOSend may be null
*/
private byte[] createPayload(CloveSet cloves, long expiration,
boolean ackreq, NextSessionKey nextKey,
List<Integer> acksToSend) {
boolean ackreq, NextSessionKey nextKey1,
NextSessionKey nextKey2, List<Integer> acksToSend) {
int count = cloves.getCloveCount();
int numblocks = count + 1;
if (expiration > 0)
numblocks++;
if (ackreq)
numblocks++;
if (nextKey != null)
if (nextKey1 != null)
numblocks++;
if (nextKey2 != null)
numblocks++;
if (acksToSend != null)
numblocks++;
@ -979,8 +1002,13 @@ public final class ECIESAEADEngine {
blocks.add(block);
len += block.getTotalLength();
}
if (nextKey != null) {
Block block = new NextKeyBlock(nextKey);
if (nextKey1 != null) {
Block block = new NextKeyBlock(nextKey1);
blocks.add(block);
len += block.getTotalLength();
}
if (nextKey2 != null) {
Block block = new NextKeyBlock(nextKey2);
blocks.add(block);
len += block.getTotalLength();
}

View File

@ -40,7 +40,7 @@ class NextSessionKey extends PublicKey {
public String toString() {
StringBuilder buf = new StringBuilder(64);
buf.append("[NextSessionKey: ");
buf.append(toBase64());
buf.append(super.toString());
buf.append(" ID: ").append(_id);
buf.append(" reverse? ").append(_isReverse);
buf.append(" request? ").append(_isRequest);

View File

@ -6,7 +6,7 @@ import net.i2p.data.SessionKey;
/**
* Simple object with outbound tag, key, and nonce,
* and an optional next key.
* and an optional next keys.
* The object returned from SKM.consumeNextAvailableTag() to the engine encrypt.
*
* @since 0.9.44
@ -16,21 +16,23 @@ class RatchetEntry {
public final SessionKeyAndNonce key;
public final int keyID;
public final int pn;
public final NextSessionKey nextKey;
public final NextSessionKey nextForwardKey;
public final NextSessionKey nextReverseKey;
public final List<Integer> acksToSend;
/** outbound - calculated key */
public RatchetEntry(RatchetSessionTag tag, SessionKeyAndNonce key, int keyID, int pn) {
this(tag, key, keyID, pn, null, null);
this(tag, key, keyID, pn, null, null, null);
}
public RatchetEntry(RatchetSessionTag tag, SessionKeyAndNonce key, int keyID, int pn,
NextSessionKey nextKey, List<Integer> acksToSend) {
NextSessionKey nextFwdKey, NextSessionKey nextRevKey, List<Integer> acksToSend) {
this.tag = tag;
this.key = key;
this.keyID = keyID;
this.pn = pn;
this.nextKey = nextKey;
this.nextForwardKey = nextFwdKey;
this.nextReverseKey = nextRevKey;
this.acksToSend = acksToSend;
}

View File

@ -22,10 +22,12 @@ import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType;
import net.i2p.crypto.HKDF;
import net.i2p.crypto.KeyPair;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
@ -241,8 +243,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
_log.info("Session " + state.hashCode() + " update as Bob. Alice: " + toString(target));
OutboundSession sess = getSession(target);
if (sess == null) {
if (_log.shouldDebug())
_log.debug("Update Bob session but no session found for " + target);
if (_log.shouldWarn())
_log.warn("Update Bob session but no session found for " + target);
// TODO can we recover?
return false;
}
@ -304,6 +306,19 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
return true;
}
/**
* @since 0.9.46
*/
public void nextKeyReceived(PublicKey target, NextSessionKey key) {
OutboundSession sess = getSession(target);
if (sess == null) {
if (_log.shouldWarn())
_log.warn("Got NextKey but no session found for " + target);
return;
}
sess.nextKeyReceived(key);
}
/**
* @throws UnsupportedOperationException always
*/
@ -793,7 +808,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
for (RatchetTagSet ts : sets) {
int size = ts.size();
total += size;
buf.append("<li><b>ID: ").append(ts.getID());
buf.append("<li><b>ID: ").append(ts.getID())
.append(" / ").append(ts.getDebugID());
buf.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
.append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
@ -835,6 +851,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
for (RatchetTagSet ts : sets) {
int size = ts.remaining();
buf.append("<li><b>ID: ").append(ts.getID())
.append(" / ").append(ts.getDebugID())
.append(" created:</b> ").append(DataHelper.formatTime(ts.getCreated()))
.append(" <b>last use:</b> ").append(DataHelper.formatTime(ts.getDate()));
long expires = ts.getExpiration() - now;
@ -914,6 +931,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
*/
private int _consecutiveFailures;
// 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 static final int MAX_FAILS = 2;
private static final int MAX_SEND_ACKS = 8;
private static final int DEBUG_OB_NSR = 0x10001;
@ -1024,6 +1049,76 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
//state.destroy();
}
/**
* @since 0.9.46
*/
public void nextKeyReceived(NextSessionKey key) {
boolean isReverse = key.isReverse();
boolean isRequest = key.isRequest();
boolean hasKey = key.getData() != null;
int id = key.getID();
synchronized (_tagSets) {
if (isReverse) {
// 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 (_log.shouldWarn())
_log.warn("Got bad new key id OB? " + id);
}
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 {
// TODO get it from above
if (_log.shouldWarn())
_log.warn("Got nextkey w/o key but we don't have it " + id);
}
} else {
if (_log.shouldWarn())
_log.warn("Got dup new key id for OB " + id);
}
if (isRequest) {
if (_log.shouldWarn())
_log.warn("invalid req+rev in nextkey");
// ignore
}
} 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 (_log.shouldWarn())
_log.warn("Got bad new key id IB? " + id);
}
if (!hasKey) {
if (_log.shouldWarn())
_log.warn("Got nextkey w/o key but we don't have it " + id);
}
// find current OB TS, tell him to send ack
// create new IB TS
} else {
if (_log.shouldWarn())
_log.warn("Got dup new key 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
}
}
}
}
/**
* First tag was received for this inbound (ES) tagset.
* Find the corresponding outbound (ES) tagset in _unackedTagSets,
@ -1190,7 +1285,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
set.setDate(now);
SessionKeyAndNonce skn = set.consumeNextKey();
// TODO PN
return new RatchetEntry(tag, skn, set.getID(), 0, set.getNextKey(), getAcksToSend());
// TODO reverse next key
return new RatchetEntry(tag, skn, set.getID(), 0, set.getNextKey(), null, getAcksToSend());
} else if (_log.shouldInfo()) {
_log.info("Removing empty " + set);
}

View File

@ -263,6 +263,7 @@ class RatchetTagSet implements TagSetHandle {
* @since 0.9.46
*/
public synchronized long getExpiration() {
// TODO return shorter if not acked?
return _date + _timeout;
}
@ -299,8 +300,9 @@ class RatchetTagSet implements TagSetHandle {
}
/**
* Next Key if applicable
* null if remaining is sufficient
* Next Forward Key if applicable (we're running low).
* Null if remaining is sufficient.
* Once non-null, will be constant for the remaining life of the tagset.
*
* @return key or null
* @since 0.9.46
@ -308,8 +310,6 @@ class RatchetTagSet implements TagSetHandle {
public NextSessionKey getNextKey() {
if (remaining() > LOW)
return null;
if (_nextKeyAcked) // maybe not needed, keep sending until unused
return null;
if (_nextKeys == null) {
_nextKeys = I2PAppContext.getGlobalContext().keyGenerator().generatePKIKeys(EncType.ECIES_X25519);
boolean isIB = _sessionTags != null;
@ -319,6 +319,18 @@ class RatchetTagSet implements TagSetHandle {
return _nextKey;
}
/**
* Next Forward KeyPair if applicable (we're running low).
* Null if remaining is sufficient.
* Once non-null, will be constant for the remaining life of the tagset.
*
* @return keys or null
* @since 0.9.46
*/
public KeyPair getNextKeys() {
return _nextKeys;
}
/**
* tags still available
* inbound only
@ -502,11 +514,21 @@ class RatchetTagSet implements TagSetHandle {
*/
public boolean getAcked() { return _acked; }
/** the Key ID */
/**
* The TagSet ID, starting at 0.
* After that = 1 + my key id + his key id
*/
public int getID() {
return _id;
}
/**
* A unique ID for debugging only
*/
public int getDebugID() {
return _tagSetID;
}
@Override
public synchronized String toString() {
StringBuilder buf = new StringBuilder(256);