Tunnels: Continue work on prop. 157

- Add new internal-only ShortTunnelBuildReplyMessage,
  for processing of STBM as a reply.
- Add support for inbound tunnel tests to TunnelBuildMessageStandalone.
  The ITBM test is WIP.
- Add checks for unset plaintext record in ITBM and OTBRM
This commit is contained in:
zzz
2021-06-13 15:28:48 -04:00
parent aa0e0b3a62
commit f9e8fa8150
6 changed files with 147 additions and 36 deletions

View File

@@ -69,7 +69,11 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
}
@Override
protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); }
protected int calculateWrittenLength() {
if (_plaintextRecord == null)
throw new IllegalStateException("Plaintext record not set");
return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE);
}
@Override
public int getType() { return MESSAGE_TYPE; }
@@ -108,6 +112,8 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
@Override
protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
if (_plaintextRecord == null)
throw new I2NPMessageException("Plaintext record not set");
int remaining = out.length - (curIndex + calculateWrittenLength());
if (remaining < 0)
throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");

View File

@@ -128,7 +128,11 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
}
@Override
protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); }
protected int calculateWrittenLength() {
if (_plaintextRecord == null)
throw new IllegalStateException("Plaintext record not set");
return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE);
}
@Override
public int getType() { return MESSAGE_TYPE; }
@@ -167,6 +171,8 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
@Override
protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
if (_plaintextRecord == null)
throw new I2NPMessageException("Plaintext record not set");
int remaining = out.length - (curIndex + calculateWrittenLength());
if (remaining < 0)
throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");

View File

@@ -0,0 +1,64 @@
package net.i2p.data.i2np;
import net.i2p.I2PAppContext;
/**
* Internal use only, to convert an inbound STBM to a reply.
* Never serialized/deserialized/sent/received.
* See BuildHandler and BuildReplyHandler.
*
* @since 0.9.51
*/
public class ShortTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
/**
* Impossible value, more than 1 byte
*/
public static final int MESSAGE_TYPE = 999;
public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
public ShortTunnelBuildReplyMessage(I2PAppContext context, int records) {
super(context, records);
}
/**
* @param record must be ShortEncryptedBuildRecord or null
*/
@Override
public void setRecord(int index, EncryptedBuildRecord record) {
if (record != null && record.length() != SHORT_RECORD_SIZE)
throw new IllegalArgumentException();
super.setRecord(index, record);
}
@Override
protected int calculateWrittenLength() { return 0; }
@Override
public int getType() { return MESSAGE_TYPE; }
/**
* @throws UnsupportedOperationException always
*/
@Override
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(64);
buf.append("[ShortTunnelBuildReplyMessage: " +
"\n\tID: ").append(getUniqueId())
.append("\n\tRecords: ").append(getRecordCount())
.append(']');
return buf.toString();
}
}

View File

@@ -22,6 +22,7 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.ShortTunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
@@ -566,12 +567,15 @@ class BuildHandler implements Runnable {
private void handleRequestAsInboundEndpoint(BuildEndMessageState state) {
int records = state.msg.getRecordCount();
TunnelBuildReplyMessage msg;
if (records == TunnelBuildMessage.MAX_RECORD_COUNT)
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE)
msg = new ShortTunnelBuildReplyMessage(_context, records);
else if (records == TunnelBuildMessage.MAX_RECORD_COUNT)
msg = new TunnelBuildReplyMessage(_context);
else
msg = new VariableTunnelBuildReplyMessage(_context, records);
for (int i = 0; i < records; i++)
for (int i = 0; i < records; i++) {
msg.setRecord(i, state.msg.getRecord(i));
}
msg.setUniqueId(state.msg.getUniqueId());
handleReply(msg, state.cfg, System.currentTimeMillis() - state.recvTime);
}

View File

@@ -12,6 +12,7 @@ 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.ShortTunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.router.tunnel.TunnelCreatorConfig;
import net.i2p.util.Log;
@@ -52,7 +53,6 @@ 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();
@@ -89,7 +89,7 @@ class BuildReplyHandler {
rv[i] = ok;
}
}
if (isShort) {
if (reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE) {
OutboundTunnelBuildReplyMessage otbrm = (OutboundTunnelBuildReplyMessage) reply;
rv[otbrm.getPlaintextSlot()] = otbrm.getPlaintextReply();
}
@@ -107,9 +107,10 @@ 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;
int type = reply.getType();
boolean isOTBRM = type == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE;
if (rec == null) {
if (!isShort) {
if (!isOTBRM) {
if (log.shouldWarn())
log.warn("Missing record " + recordNum);
return -1;
@@ -135,6 +136,7 @@ class BuildReplyHandler {
if (isEC)
end++;
// do we need to adjust this for the endpoint?
boolean isShort = isOTBRM || type == ShortTunnelBuildReplyMessage.MESSAGE_TYPE;
if (isShort) {
byte iv[] = new byte[12];
for (int j = start; j >= end; j--) {

View File

@@ -24,8 +24,10 @@ import net.i2p.data.i2np.EncryptedBuildRecord;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.I2NPMessageException;
import net.i2p.data.i2np.I2NPMessageHandler;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.ShortTunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.router.Router;
@@ -53,18 +55,18 @@ public class BuildMessageTestStandalone extends TestCase {
public void testBuildMessage() {
RouterContext ctx = new RouterContext(null);
x_testBuildMessage(ctx, 1);
x_testBuildMessage(ctx, 2);
x_testBuildMessage(ctx, 3);
for (int i = 1; i <= 6; i++) {
x_testBuildMessage(ctx, i);
}
}
/**
* @param testType 1=ElG; 2=ECIES; 3=ECIES short outbound; 4=ECIES short inbound
* @param testType outbound: 1=ElG; 2=ECIES; 3=ECIES short; inbound: 4-6
*/
private void x_testBuildMessage(RouterContext ctx, int testType) {
Log log = ctx.logManager().getLog(getClass());
// set our keys to avoid NPE
KeyPair kpr = ctx.keyGenerator().generatePKIKeys(testType == 1 ? EncType.ELGAMAL_2048 : EncType.ECIES_X25519);
KeyPair kpr = ctx.keyGenerator().generatePKIKeys((testType == 1 || testType == 4) ? EncType.ELGAMAL_2048 : EncType.ECIES_X25519);
PublicKey k1 = kpr.getPublic();
PrivateKey k2 = kpr.getPrivate();
Object[] kp = ctx.keyGenerator().generateSigningKeypair();
@@ -82,16 +84,34 @@ public class BuildMessageTestStandalone extends TestCase {
// populate and encrypt the message
TunnelBuildMessage msg;
if (testType == 3)
if (testType == 3) {
msg = new ShortTunnelBuildMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT);
else
} else if (testType == 6) {
InboundTunnelBuildMessage itbm = new InboundTunnelBuildMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT);
// set plaintext record for ibgw
for (int i = 0; i < order.size(); i++) {
int hop = order.get(i).intValue();
if (hop == 0) {
// TODO
itbm.setPlaintextRecord(i, new byte[100]);
break;
}
}
msg = itbm;
} else {
msg = new TunnelBuildMessage(ctx);
}
int end = cfg.getLength();
if (testType > 3)
end--;
for (int i = 0; i < order.size(); i++) {
int hop = order.get(i).intValue();
PublicKey key = null;
if (hop < _pubKeys.length)
if (hop < end)
key = _pubKeys[hop];
BuildMessageGenerator.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key);
// don't do this for ibgw in itbm
if (testType != 6 || hop != 0)
BuildMessageGenerator.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key);
}
BuildMessageGenerator.layeredEncrypt(ctx, msg, cfg, order);
@@ -100,14 +120,14 @@ public class BuildMessageTestStandalone extends TestCase {
"\n" + cfg +
"\n================================================================");
if (testType == 3) {
// test read/write
if (testType == 3 || testType == 6) {
// test read/write for new messages
byte[] data = msg.toByteArray();
try {
I2NPMessage msg2 = (new I2NPMessageHandler(ctx)).readMessage(data);
msg = (ShortTunnelBuildMessage) msg2;
msg = (TunnelBuildMessage) msg2;
} catch (Exception e) {
log.error("STBM out/in fail", e);
log.error("TBM out/in fail", e);
assertTrue(e.toString(), false);
}
}
@@ -115,8 +135,10 @@ public class BuildMessageTestStandalone extends TestCase {
// as necessary
BuildMessageProcessor proc = new BuildMessageProcessor(ctx);
// skip cfg(0) which is the gateway (us)
for (int i = 1; i < cfg.getLength(); i++) {
// skip cfg(0) which is the gateway (us) for outbound
// skip cfg(end) which is the endpoint (us) for inbound
int start = testType > 3 ? 0 : 1;
for (int i = start; i < end; i++) {
// this not only decrypts the current hop's record, but encrypts the other records
// with the reply key
BuildRequestRecord req = proc.decrypt(msg, _peers[i], _privKeys[i]);
@@ -132,9 +154,9 @@ public class BuildMessageTestStandalone extends TestCase {
int ourSlot = -1;
EncryptedBuildRecord reply;
if (testType == 1) {
if (testType == 1 || testType == 4) {
reply = BuildResponseRecord.create(ctx, 0, req.readReplyKey(), req.readReplyIV(), i);
} else if (testType == 2) {
} else if (testType == 2 || testType == 5) {
reply = BuildResponseRecord.create(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE);
} else {
reply = BuildResponseRecord.createShort(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE);
@@ -156,14 +178,14 @@ public class BuildMessageTestStandalone extends TestCase {
}
}
if (testType == 1) {
if (testType == 1 || testType == 4) {
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) {
} else if (testType == 2 || testType == 5) {
log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64()
+ " receives on " + ourId + " sending to " + nextId
+ " replyKey " + Base64.encode(req.readReplyKey().getData())
@@ -218,7 +240,10 @@ public class BuildMessageTestStandalone extends TestCase {
assertTrue(e.toString(), false);
}
} else {
reply = new TunnelBuildReplyMessage(ctx);
if (testType == 6)
reply = new ShortTunnelBuildReplyMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT);
else
reply = new TunnelBuildReplyMessage(ctx);
for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++) {
reply.setRecord(i, msg.getRecord(i));
}
@@ -257,15 +282,19 @@ public class BuildMessageTestStandalone extends TestCase {
}
private TunnelCreatorConfig createConfig(I2PAppContext ctx, int testType) {
return configOutbound(ctx, testType);
boolean isInbound = testType > 3;
if (isInbound)
testType -= 3;
return createConfig(ctx, testType, isInbound);
}
/**
* This creates a 3-hop (4 entries in the config) outbound tunnel.
* The first entry in the config is the gateway (us),
* This creates a 3-hop (4 entries in the config) tunnel.
* The first entry in the outbound config is the gateway (us),
* and is mostly ignored.
* Ditto last entry in inbound config.
*/
private TunnelCreatorConfig configOutbound(I2PAppContext ctx, int testType) {
private TunnelCreatorConfig createConfig(I2PAppContext ctx, int testType, boolean isInbound) {
_peers = new Hash[4];
_pubKeys = new PublicKey[_peers.length];
_privKeys = new PrivateKey[_peers.length];
@@ -279,7 +308,7 @@ public class BuildMessageTestStandalone extends TestCase {
_privKeys[i] = kp.getPrivate();
}
TunnelCreatorConfig cfg = new TCConfig(null, _peers.length, false);
TunnelCreatorConfig cfg = new TCConfig(null, _peers.length, isInbound);
long now = ctx.clock().now();
// peers[] is ordered gateway first (unlike in production code)
for (int i = 0; i < _peers.length; i++) {
@@ -306,9 +335,9 @@ public class BuildMessageTestStandalone extends TestCase {
RouterContext ctx = r.getContext();
ctx.initAll();
try {
test.x_testBuildMessage(ctx, 1);
test.x_testBuildMessage(ctx, 2);
test.x_testBuildMessage(ctx, 3);
for (int i = 1; i <= 6; i++) {
test.x_testBuildMessage(ctx, i);
}
} finally {
ctx.logManager().flush();
}