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:
zzz
2021-06-13 10:31:02 -04:00
parent 3fbfb689af
commit 010d1a9953
9 changed files with 264 additions and 82 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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); }

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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();
}