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 byte[] NULLPK = new byte[KEYLEN];
|
||||
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_6 = "AttachPayloadKDF";
|
||||
@ -603,7 +605,7 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_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;
|
||||
}
|
||||
|
||||
@ -640,7 +642,7 @@ public final class ECIESAEADEngine {
|
||||
if (_log.shouldDebug())
|
||||
_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];
|
||||
try {
|
||||
@ -699,7 +701,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, replyDI);
|
||||
byte[] payload = createPayload(cloves, 0, replyDI, null);
|
||||
|
||||
// part 1 - tag and empty payload
|
||||
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
|
||||
* @return encrypted data or null on failure
|
||||
*/
|
||||
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, SessionKeyAndNonce key,
|
||||
RatchetSessionTag currentTag,
|
||||
private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re,
|
||||
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());
|
||||
System.arraycopy(rawTag, 0, encr, 0, TAGLEN);
|
||||
return encr;
|
||||
@ -843,12 +848,12 @@ public final class ECIESAEADEngine {
|
||||
|
||||
public void gotAck(int id, int n) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got ACK block: " + n);
|
||||
_log.debug("Got ACK block: " + id + " / " + n);
|
||||
}
|
||||
|
||||
public void gotAckRequest(int id, DeliveryInstructions di) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got ACK REQUEST block: " + di);
|
||||
_log.debug("Got ACK REQUEST block: " + id + " / " + di);
|
||||
ackRequested = true;
|
||||
}
|
||||
|
||||
@ -872,13 +877,16 @@ public final class ECIESAEADEngine {
|
||||
* @param expiration if greater than zero, add a DateTime block
|
||||
* @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 numblocks = count + 1;
|
||||
if (expiration > 0)
|
||||
numblocks++;
|
||||
if (replyDI != null)
|
||||
numblocks++;
|
||||
if (nextKey != null)
|
||||
numblocks++;
|
||||
int len = 0;
|
||||
List<Block> blocks = new ArrayList<Block>(numblocks);
|
||||
if (expiration > 0) {
|
||||
@ -886,6 +894,11 @@ public final class ECIESAEADEngine {
|
||||
blocks.add(block);
|
||||
len += block.getTotalLength();
|
||||
}
|
||||
if (nextKey != null) {
|
||||
Block block = new NextKeyBlock(nextKey);
|
||||
blocks.add(block);
|
||||
len += block.getTotalLength();
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
GarlicClove clove = cloves.getClove(i);
|
||||
Block block = new GarlicBlock(clove);
|
||||
|
@ -1,30 +1,46 @@
|
||||
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
|
||||
*/
|
||||
class NextSessionKey extends SessionKey {
|
||||
class NextSessionKey extends PublicKey {
|
||||
private final int _id;
|
||||
private final boolean _isReverse, _isRequest;
|
||||
|
||||
public NextSessionKey(byte[] data, int id) {
|
||||
super(data);
|
||||
public NextSessionKey(byte[] data, int id, boolean isReverse, boolean isRequest) {
|
||||
super(EncType.ECIES_X25519, data);
|
||||
_id = id;
|
||||
_isReverse = isReverse;
|
||||
_isRequest = isRequest;
|
||||
}
|
||||
|
||||
public int getID() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
/** @since 0.9.46 */
|
||||
public boolean isReverse() {
|
||||
return _isReverse;
|
||||
}
|
||||
|
||||
/** @since 0.9.46 */
|
||||
public boolean isRequest() {
|
||||
return _isRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append("[NextSessionKey: ");
|
||||
buf.append(toBase64());
|
||||
buf.append(" ID: ").append(_id);
|
||||
buf.append(" reverse? ").append(_isReverse);
|
||||
buf.append(" request? ").append(_isRequest);
|
||||
buf.append(']');
|
||||
return buf.toString();
|
||||
}
|
||||
|
@ -136,12 +136,14 @@ class RatchetPayload {
|
||||
|
||||
case BLOCK_NEXTKEY:
|
||||
{
|
||||
if (len != 34)
|
||||
if (len != 35)
|
||||
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];
|
||||
System.arraycopy(payload, i + 2, data, 0, 32);
|
||||
NextSessionKey nsk = new NextSessionKey(data, id);
|
||||
System.arraycopy(payload, i + 3, data, 0, 32);
|
||||
NextSessionKey nsk = new NextSessionKey(data, id, isReverse, isRequest);
|
||||
cb.gotNextKey(nsk);
|
||||
}
|
||||
break;
|
||||
@ -350,13 +352,17 @@ class RatchetPayload {
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return 34;
|
||||
return 35;
|
||||
}
|
||||
|
||||
public int writeData(byte[] tgt, int off) {
|
||||
DataHelper.toLong(tgt, off, 2, next.getID());
|
||||
System.arraycopy(next.getData(), 0, tgt, off + 2, 32);
|
||||
return off + 34;
|
||||
if (next.isReverse())
|
||||
tgt[off] = 0x01;
|
||||
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);
|
||||
SessionKeyAndNonce skn = set.consumeNextKey();
|
||||
// TODO key ID and PN
|
||||
// TODO next key
|
||||
return new RatchetEntry(tag, skn, 0, 0);
|
||||
} else if (_log.shouldInfo()) {
|
||||
_log.info("Removing empty " + set);
|
||||
|
@ -13,6 +13,7 @@ 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.TagSetHandle;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -56,6 +57,9 @@ class RatchetTagSet implements TagSetHandle {
|
||||
private final byte[] _symmkey_constant;
|
||||
private int _lastTag = -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_2 = "TagAndKeyGenKeys";
|
||||
@ -65,6 +69,7 @@ class RatchetTagSet implements TagSetHandle {
|
||||
private static final byte[] ZEROLEN = new byte[0];
|
||||
private static final int TAGLEN = RatchetSessionTag.LENGTH;
|
||||
private static final int MAX = 65535;
|
||||
private static final int LOW = 50;
|
||||
|
||||
/**
|
||||
* Outbound NSR Tagset
|
||||
@ -241,6 +246,27 @@ class RatchetTagSet implements TagSetHandle {
|
||||
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
|
||||
* inbound only
|
||||
|
@ -678,7 +678,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
SendSuccessJob onReply = null;
|
||||
SendTimeoutJob onFail = null;
|
||||
ReplySelector selector = null;
|
||||
if (wantACK) {
|
||||
|
||||
if (wantACK && _encryptionKey.getType() == EncType.ELGAMAL_2048) {
|
||||
TagSetHandle tsh = null;
|
||||
if (!tags.isEmpty()) {
|
||||
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
|
||||
|
Reference in New Issue
Block a user