forked from I2P_Developers/i2p.i2p
Tunnels: Continue work on new build messages (proposal #157)
WIP, still disabled, proposal not complete - Use ChaCha20 to encrypt/decrypt records - Add OTBRM methods for plaintext record - Add OTBRM checks for correct plaintext slot number - Add BRR checks to prevent use of nonexistent AES key/IV - Set plaintext reply at OBEP in BuildHandler - Allow OTBRM in InboundMessageDistributor - Remove timing measurements in BuildMessageProcessor.decrypt() - Add test to BuildMessageTestStandalone for outbound build - Add check for all replies to BuildMessageTestStandalone - Log tweaks
This commit is contained in:
@@ -246,6 +246,8 @@ public class BuildRequestRecord {
|
||||
* Not to be used for short ECIES records; use the ChaChaReplyKey instead.
|
||||
*/
|
||||
public SessionKey readReplyKey() {
|
||||
if (_isEC && _data.length == LENGTH_EC_SHORT)
|
||||
throw new IllegalStateException();
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
int off = _isEC ? OFF_REPLY_KEY_EC : OFF_REPLY_KEY;
|
||||
System.arraycopy(_data, off, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
@@ -258,6 +260,8 @@ public class BuildRequestRecord {
|
||||
* @return 16 bytes
|
||||
*/
|
||||
public byte[] readReplyIV() {
|
||||
if (_isEC && _data.length == LENGTH_EC_SHORT)
|
||||
throw new IllegalStateException();
|
||||
byte iv[] = new byte[IV_SIZE];
|
||||
int off = _isEC ? OFF_REPLY_IV_EC : OFF_REPLY_IV;
|
||||
System.arraycopy(_data, off, iv, 0, IV_SIZE);
|
||||
|
||||
@@ -86,7 +86,7 @@ public class BuildResponseRecord {
|
||||
* @param options 116 bytes max when serialized
|
||||
* @return a 236-byte response record
|
||||
* @throws IllegalArgumentException if options too big or on encryption failure
|
||||
* @since 0.9.451
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public static ShortEncryptedBuildRecord createShort(I2PAppContext ctx, int status, SessionKey replyKey,
|
||||
byte replyAD[], Properties options) {
|
||||
|
||||
@@ -18,7 +18,7 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
|
||||
public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
|
||||
public static final int MAX_PLAINTEXT_RECORD_SIZE = OutboundTunnelBuildReplyMessage.MAX_PLAINTEXT_RECORD_SIZE;
|
||||
|
||||
private int _plaintextSlot;
|
||||
private int _plaintextSlot = -1;
|
||||
private byte[] _plaintextRecord;
|
||||
|
||||
/** zero record count, will be set with readMessage() */
|
||||
@@ -32,10 +32,11 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
|
||||
|
||||
/**
|
||||
* @param record must be ShortEncryptedBuildRecord or null
|
||||
* @throws IllegalArgumentException on bad slot or record length.
|
||||
*/
|
||||
@Override
|
||||
public void setRecord(int index, EncryptedBuildRecord record) {
|
||||
if (record != null && record.length() != SHORT_RECORD_SIZE)
|
||||
if (record != null && (record.length() != SHORT_RECORD_SIZE || index == _plaintextSlot))
|
||||
throw new IllegalArgumentException();
|
||||
super.setRecord(index, record);
|
||||
}
|
||||
@@ -45,7 +46,8 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
|
||||
* @throws IllegalArgumentException on bad slot or data length.
|
||||
*/
|
||||
public void setPlaintextRecord(int slot, byte[] data) {
|
||||
if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE)
|
||||
if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE ||
|
||||
(_records != null && _records[slot] != null))
|
||||
throw new IllegalArgumentException();
|
||||
_plaintextSlot = slot;
|
||||
_plaintextRecord = data;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package net.i2p.data.i2np;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
@@ -18,7 +23,7 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
|
||||
public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
|
||||
public static final int MAX_PLAINTEXT_RECORD_SIZE = 172;
|
||||
|
||||
private int _plaintextSlot;
|
||||
private int _plaintextSlot = -1;
|
||||
private byte[] _plaintextRecord;
|
||||
|
||||
/** zero record count, will be set with readMessage() */
|
||||
@@ -32,20 +37,58 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
|
||||
|
||||
/**
|
||||
* @param record must be ShortEncryptedBuildRecord or null
|
||||
* @throws IllegalArgumentException on bad slot or record length.
|
||||
*/
|
||||
@Override
|
||||
public void setRecord(int index, EncryptedBuildRecord record) {
|
||||
if (record != null && record.length() != SHORT_RECORD_SIZE)
|
||||
if (record != null && (record.length() != SHORT_RECORD_SIZE || index == _plaintextSlot))
|
||||
throw new IllegalArgumentException();
|
||||
super.setRecord(index, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the slot and data for the plaintext record.
|
||||
* Empty properties will be used.
|
||||
*
|
||||
* @param reply 0-255
|
||||
* @throws IllegalArgumentException on bad slot or data length.
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public void setPlaintextRecord(int slot, int reply) {
|
||||
// 00 00 reply
|
||||
byte[] data = new byte[3];
|
||||
data[2] = (byte) reply;
|
||||
setPlaintextRecord(slot, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the slot and data for the plaintext record.
|
||||
*
|
||||
* @param reply 0-255
|
||||
* @param props may be null
|
||||
* @throws IllegalArgumentException on bad slot or data length.
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public void setPlaintextRecord(int slot, int reply, Properties props) throws DataFormatException {
|
||||
if (props == null || props.isEmpty()) {
|
||||
setPlaintextRecord(slot, reply);
|
||||
return;
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
DataHelper.writeProperties(baos, props);
|
||||
} catch (IOException ioe) {}
|
||||
baos.write((byte) reply);
|
||||
setPlaintextRecord(slot, baos.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the slot and data for the plaintext record.
|
||||
* @throws IllegalArgumentException on bad slot or data length.
|
||||
*/
|
||||
public void setPlaintextRecord(int slot, byte[] data) {
|
||||
if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE)
|
||||
if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE ||
|
||||
(_records != null && _records[slot] != null))
|
||||
throw new IllegalArgumentException();
|
||||
_plaintextSlot = slot;
|
||||
_plaintextRecord = data;
|
||||
@@ -66,6 +109,24 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
|
||||
return _plaintextRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the plaintext record.
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public int getPlaintextReply() {
|
||||
return _plaintextRecord[_plaintextRecord.length - 1] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the plaintext record.
|
||||
* @since 0.9.51
|
||||
*/
|
||||
public Properties getPlaintextOptions() throws DataFormatException {
|
||||
Properties props = new Properties();
|
||||
DataHelper.fromProperties(_plaintextRecord, 0, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); }
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
||||
import net.i2p.data.i2np.VariableTunnelBuildReplyMessage;
|
||||
import net.i2p.router.ClientMessage;
|
||||
@@ -131,6 +132,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
|
||||
|
||||
case DeliveryStatusMessage.MESSAGE_TYPE:
|
||||
case GarlicMessage.MESSAGE_TYPE:
|
||||
case OutboundTunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
case TunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
case VariableTunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
// these are safe, handled below
|
||||
@@ -161,6 +163,7 @@ class InboundMessageDistributor implements GarlicMessageReceiver.CloveReceiver {
|
||||
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
|
||||
case DeliveryStatusMessage.MESSAGE_TYPE:
|
||||
case GarlicMessage.MESSAGE_TYPE:
|
||||
case OutboundTunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
case TunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
case VariableTunnelBuildReplyMessage.MESSAGE_TYPE:
|
||||
// these are safe, handled below
|
||||
|
||||
@@ -1049,7 +1049,7 @@ class BuildHandler implements Runnable {
|
||||
TunnelBuildReplyMessage replyMsg;
|
||||
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) {
|
||||
OutboundTunnelBuildReplyMessage otbrm = new OutboundTunnelBuildReplyMessage(_context, records);
|
||||
otbrm.setPlaintextRecord(ourSlot, null); // TODO
|
||||
otbrm.setPlaintextRecord(ourSlot, response);
|
||||
replyMsg = otbrm;
|
||||
} else if (records == TunnelBuildMessage.MAX_RECORD_COUNT) {
|
||||
replyMsg = new TunnelBuildReplyMessage(_context);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.i2p.router.tunnel.pool;
|
||||
|
||||
import net.i2p.crypto.ChaCha20;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
@@ -9,6 +10,8 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.BuildRequestRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.ShortEncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.ShortTunnelBuildMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterThrottleImpl;
|
||||
@@ -92,39 +95,39 @@ class BuildMessageProcessor {
|
||||
public BuildRequestRecord decrypt(TunnelBuildMessage msg, Hash ourHash, PrivateKey privKey) {
|
||||
BuildRequestRecord rv = null;
|
||||
int ourHop = -1;
|
||||
long beforeActualDecrypt = 0;
|
||||
long afterActualDecrypt = 0;
|
||||
byte[] ourHashData = ourHash.getData();
|
||||
long beforeLoop = System.currentTimeMillis();
|
||||
boolean isShort = msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE;
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
EncryptedBuildRecord rec = msg.getRecord(i);
|
||||
boolean eq = DataHelper.eq(ourHashData, 0, rec.getData(), 0, BuildRequestRecord.PEER_SIZE);
|
||||
if (eq) {
|
||||
beforeActualDecrypt = System.currentTimeMillis();
|
||||
try {
|
||||
rv = new BuildRequestRecord(ctx, privKey, rec);
|
||||
afterActualDecrypt = System.currentTimeMillis();
|
||||
|
||||
// i2pd bug
|
||||
boolean isBad = SessionKey.INVALID_KEY.equals(rv.readReplyKey());
|
||||
if (isBad) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn(msg.getUniqueId() + ": Bad reply key: " + rv);
|
||||
ctx.statManager().addRateData("tunnel.buildRequestBadReplyKey", 1);
|
||||
return null;
|
||||
}
|
||||
if (isShort) {
|
||||
// Bloom filter TBD
|
||||
} else {
|
||||
// i2pd bug
|
||||
boolean isBad = SessionKey.INVALID_KEY.equals(rv.readReplyKey());
|
||||
if (isBad) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn(msg.getUniqueId() + ": Bad reply key: " + rv);
|
||||
ctx.statManager().addRateData("tunnel.buildRequestBadReplyKey", 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
// The spec says to feed the 32-byte AES-256 reply key into the Bloom filter.
|
||||
// But we were using the first 32 bytes of the encrypted reply.
|
||||
// Fixed in 0.9.24
|
||||
boolean isEC = ctx.keyManager().getPrivateKey().getType() == EncType.ECIES_X25519;
|
||||
int off = isEC ? BuildRequestRecord.OFF_REPLY_KEY_EC : BuildRequestRecord.OFF_REPLY_KEY;
|
||||
boolean isDup = _filter.add(rv.getData(), off, 32);
|
||||
if (isDup) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn(msg.getUniqueId() + ": Dup record: " + rv);
|
||||
ctx.statManager().addRateData("tunnel.buildRequestDup", 1);
|
||||
return null;
|
||||
// The spec says to feed the 32-byte AES-256 reply key into the Bloom filter.
|
||||
// But we were using the first 32 bytes of the encrypted reply.
|
||||
// Fixed in 0.9.24
|
||||
boolean isEC = ctx.keyManager().getPrivateKey().getType() == EncType.ECIES_X25519;
|
||||
int off = isEC ? BuildRequestRecord.OFF_REPLY_KEY_EC : BuildRequestRecord.OFF_REPLY_KEY;
|
||||
boolean isDup = _filter.add(rv.getData(), off, 32);
|
||||
if (isDup) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn(msg.getUniqueId() + ": Dup record: " + rv);
|
||||
ctx.statManager().addRateData("tunnel.buildRequestDup", 1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
@@ -150,28 +153,36 @@ class BuildMessageProcessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
long beforeEncrypt = System.currentTimeMillis();
|
||||
SessionKey replyKey = rv.readReplyKey();
|
||||
byte iv[] = rv.readReplyIV();
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
if (i != ourHop) {
|
||||
EncryptedBuildRecord data = msg.getRecord(i);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv));
|
||||
// encrypt in-place, corrupts SDS
|
||||
byte[] bytes = data.getData();
|
||||
ctx.aes().encrypt(bytes, 0, bytes, 0, replyKey, iv, 0, EncryptedBuildRecord.LENGTH);
|
||||
if (isShort) {
|
||||
byte[] replyKey = rv.getChaChaReplyKey().getData();
|
||||
byte iv[] = new byte[12];
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
if (i != ourHop) {
|
||||
EncryptedBuildRecord data = msg.getRecord(i);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv));
|
||||
// encrypt in-place, corrupts SDS
|
||||
byte[] bytes = data.getData();
|
||||
// slot number, little endian
|
||||
iv[0] = (byte) i;
|
||||
ChaCha20.encrypt(replyKey, iv, bytes, 0, bytes, 0, ShortEncryptedBuildRecord.LENGTH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SessionKey replyKey = rv.readReplyKey();
|
||||
byte iv[] = rv.readReplyIV();
|
||||
for (int i = 0; i < msg.getRecordCount(); i++) {
|
||||
if (i != ourHop) {
|
||||
EncryptedBuildRecord data = msg.getRecord(i);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv));
|
||||
// encrypt in-place, corrupts SDS
|
||||
byte[] bytes = data.getData();
|
||||
ctx.aes().encrypt(bytes, 0, bytes, 0, replyKey, iv, 0, EncryptedBuildRecord.LENGTH);
|
||||
}
|
||||
}
|
||||
}
|
||||
long afterEncrypt = System.currentTimeMillis();
|
||||
msg.setRecord(ourHop, null);
|
||||
if (afterEncrypt-beforeLoop > 1000) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Slow decryption, total=" + (afterEncrypt-beforeLoop)
|
||||
+ " looping=" + (beforeEncrypt-beforeLoop)
|
||||
+ " decrypt=" + (afterActualDecrypt-beforeActualDecrypt)
|
||||
+ " encrypt=" + (afterEncrypt-beforeEncrypt));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.i2p.router.tunnel.pool;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.ChaCha20;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
@@ -10,6 +11,7 @@ import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.BuildResponseRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
|
||||
import net.i2p.data.i2np.ShortEncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
||||
import net.i2p.router.tunnel.TunnelCreatorConfig;
|
||||
import net.i2p.util.Log;
|
||||
@@ -50,6 +52,7 @@ class BuildReplyHandler {
|
||||
log.error("Corrupted build reply, expected " + recordOrder.size() + " records, got " + reply.getRecordCount());
|
||||
return null;
|
||||
}
|
||||
boolean isShort = reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE;
|
||||
int rv[] = new int[reply.getRecordCount()];
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
int hop = recordOrder.get(i).intValue();
|
||||
@@ -86,6 +89,10 @@ class BuildReplyHandler {
|
||||
rv[i] = ok;
|
||||
}
|
||||
}
|
||||
if (isShort) {
|
||||
OutboundTunnelBuildReplyMessage otbrm = (OutboundTunnelBuildReplyMessage) reply;
|
||||
rv[otbrm.getPlaintextSlot()] = otbrm.getPlaintextReply();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -100,6 +107,24 @@ class BuildReplyHandler {
|
||||
*/
|
||||
private int decryptRecord(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) {
|
||||
EncryptedBuildRecord rec = reply.getRecord(recordNum);
|
||||
boolean isShort = reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE;
|
||||
if (rec == null) {
|
||||
if (!isShort) {
|
||||
if (log.shouldWarn())
|
||||
log.warn("Missing record " + recordNum);
|
||||
return -1;
|
||||
}
|
||||
OutboundTunnelBuildReplyMessage otbrm = (OutboundTunnelBuildReplyMessage) reply;
|
||||
if (otbrm.getPlaintextSlot() != recordNum) {
|
||||
if (log.shouldWarn())
|
||||
log.warn("Plaintext slot mismatch expected " + recordNum + " got " + otbrm.getPlaintextSlot());
|
||||
return -1;
|
||||
}
|
||||
int rv = otbrm.getPlaintextReply();
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(reply.getUniqueId() + ": Received: " + rv + " for plaintext record " + recordNum + "/" + hop);
|
||||
return rv;
|
||||
}
|
||||
byte[] data = rec.getData();
|
||||
int start = cfg.getLength() - 1;
|
||||
if (cfg.isInbound())
|
||||
@@ -110,18 +135,32 @@ class BuildReplyHandler {
|
||||
if (isEC)
|
||||
end++;
|
||||
// do we need to adjust this for the endpoint?
|
||||
for (int j = start; j >= end; j--) {
|
||||
SessionKey replyKey = cfg.getAESReplyKey(j);
|
||||
byte replyIV[] = cfg.getAESReplyIV(j);
|
||||
if (log.shouldLog(Log.DEBUG)) {
|
||||
log.debug(reply.getUniqueId() + ": Decrypting record " + recordNum + "/" + hop + "/" + j + " with replyKey "
|
||||
+ replyKey.toBase64() + "/" + Base64.encode(replyIV) + ": " + cfg);
|
||||
log.debug(reply.getUniqueId() + ": before decrypt: " + Base64.encode(data));
|
||||
log.debug(reply.getUniqueId() + ": Full reply rec: sz=" + data.length + " data=" + Base64.encode(data, 0, TunnelBuildReplyMessage.RECORD_SIZE));
|
||||
if (isShort) {
|
||||
byte iv[] = new byte[12];
|
||||
for (int j = start; j >= end; j--) {
|
||||
byte[] replyKey = cfg.getChaChaReplyKey(j).getData();
|
||||
if (log.shouldDebug()) {
|
||||
log.debug(reply.getUniqueId() + ": Decrypting ChaCha record " + recordNum + "/" + hop + "/" + j + " with replyKey "
|
||||
+ Base64.encode(replyKey) + " : " + cfg);
|
||||
}
|
||||
// slot number, little endian
|
||||
iv[0] = (byte) recordNum;
|
||||
ChaCha20.encrypt(replyKey, iv, data, 0, data, 0, ShortEncryptedBuildRecord.LENGTH);
|
||||
}
|
||||
} else {
|
||||
for (int j = start; j >= end; j--) {
|
||||
SessionKey replyKey = cfg.getAESReplyKey(j);
|
||||
byte replyIV[] = cfg.getAESReplyIV(j);
|
||||
if (log.shouldDebug()) {
|
||||
log.debug(reply.getUniqueId() + ": Decrypting AES record " + recordNum + "/" + hop + "/" + j + " with replyKey "
|
||||
+ replyKey.toBase64() + "/" + Base64.encode(replyIV) + ": " + cfg);
|
||||
//log.debug(reply.getUniqueId() + ": before decrypt: " + Base64.encode(data));
|
||||
//log.debug(reply.getUniqueId() + ": Full reply rec: sz=" + data.length + " data=" + Base64.encode(data));
|
||||
}
|
||||
ctx.aes().decrypt(data, 0, data, 0, replyKey, replyIV, 0, data.length);
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug(reply.getUniqueId() + ": after decrypt: " + Base64.encode(data));
|
||||
}
|
||||
ctx.aes().decrypt(data, 0, data, 0, replyKey, replyIV, 0, TunnelBuildReplyMessage.RECORD_SIZE);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug(reply.getUniqueId() + ": after decrypt: " + Base64.encode(data));
|
||||
}
|
||||
// ok, all of the layered encryption is stripped, so lets verify it
|
||||
// (formatted per BuildResponseRecord.create)
|
||||
|
||||
@@ -21,6 +21,8 @@ import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.BuildRequestRecord;
|
||||
import net.i2p.data.i2np.BuildResponseRecord;
|
||||
import net.i2p.data.i2np.EncryptedBuildRecord;
|
||||
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
|
||||
import net.i2p.data.i2np.ShortTunnelBuildMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildMessage;
|
||||
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
||||
import net.i2p.router.Router;
|
||||
@@ -50,10 +52,11 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
RouterContext ctx = new RouterContext(null);
|
||||
x_testBuildMessage(ctx, 1);
|
||||
x_testBuildMessage(ctx, 2);
|
||||
x_testBuildMessage(ctx, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param testType 1=ElG; 2=ECIES; 3=ECIES short
|
||||
* @param testType 1=ElG; 2=ECIES; 3=ECIES short outbound; 4=ECIES short inbound
|
||||
*/
|
||||
private void x_testBuildMessage(RouterContext ctx, int testType) {
|
||||
Log log = ctx.logManager().getLog(getClass());
|
||||
@@ -75,7 +78,11 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
_replyTunnel = 42;
|
||||
|
||||
// populate and encrypt the message
|
||||
TunnelBuildMessage msg = new TunnelBuildMessage(ctx);
|
||||
TunnelBuildMessage msg;
|
||||
if (testType == 3)
|
||||
msg = new ShortTunnelBuildMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT);
|
||||
else
|
||||
msg = new TunnelBuildMessage(ctx);
|
||||
for (int i = 0; i < order.size(); i++) {
|
||||
int hop = order.get(i).intValue();
|
||||
PublicKey key = null;
|
||||
@@ -102,7 +109,6 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
// If false, no records matched the _peers[i], or the decryption failed
|
||||
assertTrue("foo @ " + i, req != null);
|
||||
long ourId = req.readReceiveTunnelId();
|
||||
byte replyIV[] = req.readReplyIV();
|
||||
long nextId = req.readNextTunnelId();
|
||||
Hash nextPeer = req.readNextIdentity();
|
||||
boolean isInGW = req.readIsInboundGateway();
|
||||
@@ -112,23 +118,54 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
int ourSlot = -1;
|
||||
|
||||
EncryptedBuildRecord reply;
|
||||
if (testType == 1)
|
||||
reply = BuildResponseRecord.create(ctx, 0, req.readReplyKey(), replyIV, i);
|
||||
else
|
||||
if (testType == 1) {
|
||||
reply = BuildResponseRecord.create(ctx, 0, req.readReplyKey(), req.readReplyIV(), i);
|
||||
} else if (testType == 2) {
|
||||
reply = BuildResponseRecord.create(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE);
|
||||
for (int j = 0; j < TunnelBuildMessage.MAX_RECORD_COUNT; j++) {
|
||||
if (msg.getRecord(j) == null) {
|
||||
ourSlot = j;
|
||||
msg.setRecord(j, reply);
|
||||
break;
|
||||
} else {
|
||||
reply = BuildResponseRecord.createShort(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE);
|
||||
}
|
||||
if (testType != 3 || i != cfg.getLength() - 1) {
|
||||
for (int j = 0; j < TunnelBuildMessage.MAX_RECORD_COUNT; j++) {
|
||||
if (msg.getRecord(j) == null) {
|
||||
ourSlot = j;
|
||||
msg.setRecord(j, reply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < TunnelBuildMessage.MAX_RECORD_COUNT; j++) {
|
||||
if (msg.getRecord(j) == null) {
|
||||
ourSlot = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64()
|
||||
+ " receives on " + ourId
|
||||
+ " w/ replyIV " + Base64.encode(replyIV) + " sending to " + nextId
|
||||
+ " on " + nextPeer.toBase64()
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time));
|
||||
|
||||
if (testType == 1) {
|
||||
log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64()
|
||||
+ " receives on " + ourId + " sending to " + nextId
|
||||
+ " replyKey " + Base64.encode(req.readReplyKey().getData())
|
||||
+ " replyIV " + Base64.encode(req.readReplyIV())
|
||||
+ " on " + nextPeer.toBase64()
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time));
|
||||
} else if (testType == 2) {
|
||||
log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64()
|
||||
+ " receives on " + ourId + " sending to " + nextId
|
||||
+ " replyKey " + Base64.encode(req.readReplyKey().getData())
|
||||
+ " replyIV " + Base64.encode(req.readReplyIV())
|
||||
+ " chachaKey " + Base64.encode(req.getChaChaReplyKey().getData())
|
||||
+ " chachaAD " + Base64.encode(req.getChaChaReplyAD())
|
||||
+ " on " + nextPeer.toBase64()
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time));
|
||||
} else {
|
||||
log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64()
|
||||
+ " receives on " + ourId + " sending to " + nextId
|
||||
+ " chachaKey " + Base64.encode(req.getChaChaReplyKey().getData())
|
||||
+ " chachaAD " + Base64.encode(req.getChaChaReplyAD())
|
||||
+ " on " + nextPeer.toBase64()
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,9 +174,32 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
"\n================================================================");
|
||||
|
||||
// now all of the replies are populated, toss 'em into a reply message and handle it
|
||||
TunnelBuildReplyMessage reply = new TunnelBuildReplyMessage(ctx);
|
||||
for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++)
|
||||
reply.setRecord(i, msg.getRecord(i));
|
||||
TunnelBuildReplyMessage reply;
|
||||
if (testType == 3) {
|
||||
OutboundTunnelBuildReplyMessage otbrm = new OutboundTunnelBuildReplyMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT);
|
||||
int ibep = _peers.length - 1;
|
||||
int ibepSlot = -1;
|
||||
for (int i = 0; i < order.size(); i++) {
|
||||
int slot = order.get(i).intValue();
|
||||
if (slot == ibep) {
|
||||
ibepSlot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.debug("OTBRM plaintext slot is " + ibepSlot);
|
||||
for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++) {
|
||||
if (i == ibepSlot)
|
||||
otbrm.setPlaintextRecord(i, 0);
|
||||
else
|
||||
otbrm.setRecord(i, msg.getRecord(i));
|
||||
}
|
||||
reply = otbrm;
|
||||
} else {
|
||||
reply = new TunnelBuildReplyMessage(ctx);
|
||||
for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++) {
|
||||
reply.setRecord(i, msg.getRecord(i));
|
||||
}
|
||||
}
|
||||
|
||||
int statuses[] = (new BuildReplyHandler(ctx)).decrypt(reply, cfg, order);
|
||||
if (statuses == null) throw new RuntimeException("bar");
|
||||
@@ -154,6 +214,7 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
log.debug("\n================================================================" +
|
||||
"\nAll peers agree? " + allAgree +
|
||||
"\n================================================================");
|
||||
assertTrue("All peers agree", allAgree);
|
||||
}
|
||||
|
||||
private static final List<Integer> pickOrder() {
|
||||
@@ -224,6 +285,7 @@ public class BuildMessageTestStandalone extends TestCase {
|
||||
try {
|
||||
test.x_testBuildMessage(ctx, 1);
|
||||
test.x_testBuildMessage(ctx, 2);
|
||||
test.x_testBuildMessage(ctx, 3);
|
||||
} finally {
|
||||
ctx.logManager().flush();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user