diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java index 680766116d791848c1fb0d3f1bf2f7c800edb769..d70d92e3a4f44555830b8b238bc9d8d1d0382c59 100644 --- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -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); diff --git a/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java index a3128ede2250fb5bffc0437068045e740f50eb5d..eb44a903df80653d92a0768d0cbd232b62a514ba 100644 --- a/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java @@ -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) { diff --git a/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java index d25443d2f659f4583b49d3e0fba5a4d4522b9d34..6ae986d9e48008d8638d4f552511208348ad254d 100644 --- a/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java +++ b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java @@ -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; diff --git a/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java index 59537b718ef7481b9c1fb77389b574371a6e29b0..da00dc495070e15b6798143508bc16d8032e2e92 100644 --- a/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java @@ -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); } diff --git a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java index ff13ce889a178ea8ea7df898a4ab5f17b92ec6e1..e393105d230295172faf26aec49eda5492e84ad0 100644 --- a/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java +++ b/router/java/src/net/i2p/router/tunnel/InboundMessageDistributor.java @@ -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 diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java index da1ecb4b8bb3772f66835dba8014f17340d37204..1c44dc6d00bf19ed2abcbbda6fdf820e433ea940 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java @@ -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); diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildMessageProcessor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildMessageProcessor.java index 9d0aade215b7e511b40ee12d0f2c95cac8452bee..028de88710d3b0f22afd947f92b05848878819ae 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildMessageProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildMessageProcessor.java @@ -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; } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java index ba13ad318504c7f7ad5b4ee5f65f400977801487..6371ddcd257e0624306cf066b888b4808ca84efa 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java @@ -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) diff --git a/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java b/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java index dde9559c20b348bcd2ffdb098d806cf450a0f432..29177039e07b907c418d4503ed803d8f992325d7 100644 --- a/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java +++ b/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java @@ -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(); }