forked from I2P_Developers/i2p.i2p
Ratchet: Updates
- Modify NextKey, start of support (WIP) - Don't expect DSM reply to ECIES destinations - Debug setting to always sent ack request
This commit is contained in:
@ -62,6 +62,8 @@ public final class ECIESAEADEngine {
|
|||||||
private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE;
|
private static final int MIN_ENCRYPTED_SIZE = MIN_ES_SIZE;
|
||||||
private static final byte[] NULLPK = new byte[KEYLEN];
|
private static final byte[] NULLPK = new byte[KEYLEN];
|
||||||
private static final int MAXPAD = 16;
|
private static final int MAXPAD = 16;
|
||||||
|
// debug, send ACKREQ in every ES
|
||||||
|
private static final boolean ACKREQ_IN_ES = false;
|
||||||
|
|
||||||
private static final String INFO_0 = "SessionReplyTags";
|
private static final String INFO_0 = "SessionReplyTags";
|
||||||
private static final String INFO_6 = "AttachPayloadKDF";
|
private static final String INFO_6 = "AttachPayloadKDF";
|
||||||
@ -603,7 +605,7 @@ public final class ECIESAEADEngine {
|
|||||||
}
|
}
|
||||||
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.key, re.tag, replyDI);
|
byte rv[] = encryptExistingSession(cloves, target, re, replyDI);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,7 +642,7 @@ public final class ECIESAEADEngine {
|
|||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("State before encrypt new session: " + state);
|
_log.debug("State before encrypt new session: " + state);
|
||||||
|
|
||||||
byte[] payload = createPayload(cloves, cloves.getExpiration(), replyDI);
|
byte[] payload = createPayload(cloves, cloves.getExpiration(), replyDI, null);
|
||||||
|
|
||||||
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
||||||
try {
|
try {
|
||||||
@ -699,7 +701,7 @@ public final class ECIESAEADEngine {
|
|||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("State after mixhash tag before encrypt new session reply: " + state);
|
_log.debug("State after mixhash tag before encrypt new session reply: " + state);
|
||||||
|
|
||||||
byte[] payload = createPayload(cloves, 0, replyDI);
|
byte[] payload = createPayload(cloves, 0, replyDI, null);
|
||||||
|
|
||||||
// part 1 - tag and empty payload
|
// part 1 - tag and empty payload
|
||||||
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
||||||
@ -762,11 +764,14 @@ public final class ECIESAEADEngine {
|
|||||||
* @param replyDI non-null to request an ack, or null
|
* @param replyDI non-null to request an ack, or null
|
||||||
* @return encrypted data or null on failure
|
* @return encrypted data or null on failure
|
||||||
*/
|
*/
|
||||||
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, SessionKeyAndNonce key,
|
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re,
|
||||||
RatchetSessionTag currentTag,
|
|
||||||
DeliveryInstructions replyDI) {
|
DeliveryInstructions replyDI) {
|
||||||
byte rawTag[] = currentTag.getData();
|
//
|
||||||
byte[] payload = createPayload(cloves, 0, replyDI);
|
if (ACKREQ_IN_ES && replyDI == null)
|
||||||
|
replyDI = new DeliveryInstructions();
|
||||||
|
byte rawTag[] = re.tag.getData();
|
||||||
|
byte[] payload = createPayload(cloves, 0, replyDI, re.nextKey);
|
||||||
|
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);
|
||||||
return encr;
|
return encr;
|
||||||
@ -843,12 +848,12 @@ public final class ECIESAEADEngine {
|
|||||||
|
|
||||||
public void gotAck(int id, int n) {
|
public void gotAck(int id, int n) {
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("Got ACK block: " + n);
|
_log.debug("Got ACK block: " + id + " / " + n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void gotAckRequest(int id, DeliveryInstructions di) {
|
public void gotAckRequest(int id, DeliveryInstructions di) {
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("Got ACK REQUEST block: " + di);
|
_log.debug("Got ACK REQUEST block: " + id + " / " + di);
|
||||||
ackRequested = true;
|
ackRequested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -872,13 +877,16 @@ public final class ECIESAEADEngine {
|
|||||||
* @param expiration if greater than zero, add a DateTime block
|
* @param expiration if greater than zero, add a DateTime block
|
||||||
* @param replyDI non-null to request an ack, or null
|
* @param replyDI non-null to request an ack, or null
|
||||||
*/
|
*/
|
||||||
private byte[] createPayload(CloveSet cloves, long expiration, DeliveryInstructions replyDI) {
|
private byte[] createPayload(CloveSet cloves, long expiration,
|
||||||
|
DeliveryInstructions replyDI, NextSessionKey nextKey) {
|
||||||
int count = cloves.getCloveCount();
|
int count = cloves.getCloveCount();
|
||||||
int numblocks = count + 1;
|
int numblocks = count + 1;
|
||||||
if (expiration > 0)
|
if (expiration > 0)
|
||||||
numblocks++;
|
numblocks++;
|
||||||
if (replyDI != null)
|
if (replyDI != null)
|
||||||
numblocks++;
|
numblocks++;
|
||||||
|
if (nextKey != null)
|
||||||
|
numblocks++;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
List<Block> blocks = new ArrayList<Block>(numblocks);
|
List<Block> blocks = new ArrayList<Block>(numblocks);
|
||||||
if (expiration > 0) {
|
if (expiration > 0) {
|
||||||
@ -886,6 +894,11 @@ public final class ECIESAEADEngine {
|
|||||||
blocks.add(block);
|
blocks.add(block);
|
||||||
len += block.getTotalLength();
|
len += block.getTotalLength();
|
||||||
}
|
}
|
||||||
|
if (nextKey != null) {
|
||||||
|
Block block = new NextKeyBlock(nextKey);
|
||||||
|
blocks.add(block);
|
||||||
|
len += block.getTotalLength();
|
||||||
|
}
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
GarlicClove clove = cloves.getClove(i);
|
GarlicClove clove = cloves.getClove(i);
|
||||||
Block block = new GarlicBlock(clove);
|
Block block = new GarlicBlock(clove);
|
||||||
|
@ -1,30 +1,46 @@
|
|||||||
package net.i2p.router.crypto.ratchet;
|
package net.i2p.router.crypto.ratchet;
|
||||||
|
|
||||||
import net.i2p.data.SessionKey;
|
import net.i2p.crypto.EncType;
|
||||||
|
import net.i2p.data.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A session key and key ID.
|
* A X25519 key and key ID.
|
||||||
*
|
*
|
||||||
* @since 0.9.44
|
* @since 0.9.44
|
||||||
*/
|
*/
|
||||||
class NextSessionKey extends SessionKey {
|
class NextSessionKey extends PublicKey {
|
||||||
private final int _id;
|
private final int _id;
|
||||||
|
private final boolean _isReverse, _isRequest;
|
||||||
|
|
||||||
public NextSessionKey(byte[] data, int id) {
|
public NextSessionKey(byte[] data, int id, boolean isReverse, boolean isRequest) {
|
||||||
super(data);
|
super(EncType.ECIES_X25519, data);
|
||||||
_id = id;
|
_id = id;
|
||||||
|
_isReverse = isReverse;
|
||||||
|
_isRequest = isRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getID() {
|
public int getID() {
|
||||||
return _id;
|
return _id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.46 */
|
||||||
|
public boolean isReverse() {
|
||||||
|
return _isReverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.9.46 */
|
||||||
|
public boolean isRequest() {
|
||||||
|
return _isRequest;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder buf = new StringBuilder(64);
|
StringBuilder buf = new StringBuilder(64);
|
||||||
buf.append("[NextSessionKey: ");
|
buf.append("[NextSessionKey: ");
|
||||||
buf.append(toBase64());
|
buf.append(toBase64());
|
||||||
buf.append(" ID: ").append(_id);
|
buf.append(" ID: ").append(_id);
|
||||||
|
buf.append(" reverse? ").append(_isReverse);
|
||||||
|
buf.append(" request? ").append(_isRequest);
|
||||||
buf.append(']');
|
buf.append(']');
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
@ -136,12 +136,14 @@ class RatchetPayload {
|
|||||||
|
|
||||||
case BLOCK_NEXTKEY:
|
case BLOCK_NEXTKEY:
|
||||||
{
|
{
|
||||||
if (len != 34)
|
if (len != 35)
|
||||||
throw new IOException("Bad length for NEXTKEY: " + len);
|
throw new IOException("Bad length for NEXTKEY: " + len);
|
||||||
int id = (int) DataHelper.fromLong(payload, i, 2);
|
boolean isReverse = (payload[i] & 0x01) != 0;
|
||||||
|
boolean isRequest = (payload[i] & 0x02) != 0;
|
||||||
|
int id = (int) DataHelper.fromLong(payload, i + 1, 2);
|
||||||
byte[] data = new byte[32];
|
byte[] data = new byte[32];
|
||||||
System.arraycopy(payload, i + 2, data, 0, 32);
|
System.arraycopy(payload, i + 3, data, 0, 32);
|
||||||
NextSessionKey nsk = new NextSessionKey(data, id);
|
NextSessionKey nsk = new NextSessionKey(data, id, isReverse, isRequest);
|
||||||
cb.gotNextKey(nsk);
|
cb.gotNextKey(nsk);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -350,13 +352,17 @@ class RatchetPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getDataLength() {
|
public int getDataLength() {
|
||||||
return 34;
|
return 35;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int writeData(byte[] tgt, int off) {
|
public int writeData(byte[] tgt, int off) {
|
||||||
DataHelper.toLong(tgt, off, 2, next.getID());
|
if (next.isReverse())
|
||||||
System.arraycopy(next.getData(), 0, tgt, off + 2, 32);
|
tgt[off] = 0x01;
|
||||||
return off + 34;
|
if (next.isRequest())
|
||||||
|
tgt[off] |= 0x02;
|
||||||
|
DataHelper.toLong(tgt, off + 1, 2, next.getID());
|
||||||
|
System.arraycopy(next.getData(), 0, tgt, off + 3, 32);
|
||||||
|
return off + 35;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,6 +1100,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
set.setDate(now);
|
set.setDate(now);
|
||||||
SessionKeyAndNonce skn = set.consumeNextKey();
|
SessionKeyAndNonce skn = set.consumeNextKey();
|
||||||
// TODO key ID and PN
|
// TODO key ID and PN
|
||||||
|
// TODO next key
|
||||||
return new RatchetEntry(tag, skn, 0, 0);
|
return new RatchetEntry(tag, skn, 0, 0);
|
||||||
} else if (_log.shouldInfo()) {
|
} else if (_log.shouldInfo()) {
|
||||||
_log.info("Removing empty " + set);
|
_log.info("Removing empty " + set);
|
||||||
|
@ -13,6 +13,7 @@ import com.southernstorm.noise.protocol.HandshakeState;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.crypto.EncType;
|
import net.i2p.crypto.EncType;
|
||||||
import net.i2p.crypto.HKDF;
|
import net.i2p.crypto.HKDF;
|
||||||
|
import net.i2p.crypto.KeyPair;
|
||||||
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;
|
||||||
@ -56,6 +57,9 @@ class RatchetTagSet implements TagSetHandle {
|
|||||||
private final byte[] _symmkey_constant;
|
private final byte[] _symmkey_constant;
|
||||||
private int _lastTag = -1;
|
private int _lastTag = -1;
|
||||||
private int _lastKey = -1;
|
private int _lastKey = -1;
|
||||||
|
private KeyPair _nextKeys;
|
||||||
|
private NextSessionKey _nextKey;
|
||||||
|
private boolean _nextKeyAcked;
|
||||||
|
|
||||||
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";
|
||||||
@ -65,6 +69,7 @@ class RatchetTagSet implements TagSetHandle {
|
|||||||
private static final byte[] ZEROLEN = new byte[0];
|
private static final byte[] ZEROLEN = new byte[0];
|
||||||
private static final int TAGLEN = RatchetSessionTag.LENGTH;
|
private static final int TAGLEN = RatchetSessionTag.LENGTH;
|
||||||
private static final int MAX = 65535;
|
private static final int MAX = 65535;
|
||||||
|
private static final int LOW = 50;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outbound NSR Tagset
|
* Outbound NSR Tagset
|
||||||
@ -241,6 +246,27 @@ class RatchetTagSet implements TagSetHandle {
|
|||||||
return MAX - nextKey;
|
return MAX - nextKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next Key if applicable
|
||||||
|
* null if remaining is sufficient
|
||||||
|
*
|
||||||
|
* @return key or null
|
||||||
|
* @since 0.9.46
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
// TODO request only needed every other time
|
||||||
|
_nextKey = new NextSessionKey(_nextKeys.getPublic().getData(), 0, isIB, !isIB);
|
||||||
|
}
|
||||||
|
return _nextKey;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tags still available
|
* tags still available
|
||||||
* inbound only
|
* inbound only
|
||||||
|
@ -678,7 +678,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
|||||||
SendSuccessJob onReply = null;
|
SendSuccessJob onReply = null;
|
||||||
SendTimeoutJob onFail = null;
|
SendTimeoutJob onFail = null;
|
||||||
ReplySelector selector = null;
|
ReplySelector selector = null;
|
||||||
if (wantACK) {
|
|
||||||
|
if (wantACK && _encryptionKey.getType() == EncType.ELGAMAL_2048) {
|
||||||
TagSetHandle tsh = null;
|
TagSetHandle tsh = null;
|
||||||
if (!tags.isEmpty()) {
|
if (!tags.isEmpty()) {
|
||||||
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
|
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
|
||||||
|
Reference in New Issue
Block a user