forked from I2P_Developers/i2p.i2p
Ratchet: Next Key WIP
This commit is contained in:
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user