diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java index 805c8cc4d..fb719ec2e 100644 --- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -2,6 +2,7 @@ package net.i2p.data.i2np; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; +import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.PrivateKey; @@ -9,7 +10,8 @@ import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; /** - * Hold the tunnel request record, managing its ElGamal encryption and decryption. + * Holds the unencrypted 222-byte tunnel request record, + * with a constructor for ElGamal decryption and a method for ElGamal encryption. * Iterative AES encryption/decryption is done elsewhere. * * Cleartext: @@ -36,7 +38,7 @@ import net.i2p.data.SessionKey; * */ public class BuildRequestRecord { - private ByteArray _data; + private final byte[] _data; /** * If set in the flag byte, any peer may send a message into this tunnel, but if @@ -55,11 +57,10 @@ public class BuildRequestRecord { /** we show 16 bytes of the peer hash outside the elGamal block */ public static final int PEER_SIZE = 16; - public BuildRequestRecord(ByteArray data) { _data = data; } - public BuildRequestRecord() { } - - public ByteArray getData() { return _data; } - public void setData(ByteArray data) { _data = data; } + /** + * @return 222 bytes, non-null + */ + public byte[] getData() { return _data; } private static final int OFF_RECV_TUNNEL = 0; private static final int OFF_OUR_IDENT = OFF_RECV_TUNNEL + 4; @@ -72,91 +73,101 @@ public class BuildRequestRecord { private static final int OFF_FLAG = OFF_REPLY_IV + IV_SIZE; private static final int OFF_REQ_TIME = OFF_FLAG + 1; private static final int OFF_SEND_MSG_ID = OFF_REQ_TIME + 4; + private static final int PADDING_SIZE = 29; + // 222 + private static final int LENGTH = OFF_SEND_MSG_ID + 4 + PADDING_SIZE; + /** what tunnel ID should this receive messages on */ public long readReceiveTunnelId() { - return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_RECV_TUNNEL, 4); - } - /** true if the identity they expect us to be is who we are */ - public boolean readOurIdentityMatches(Hash ourIdentity) { - return DataHelper.eq(ourIdentity.getData(), 0, _data.getData(), _data.getOffset() + OFF_OUR_IDENT, Hash.HASH_LENGTH); + return DataHelper.fromLong(_data, OFF_RECV_TUNNEL, 4); } + /** * What tunnel ID the next hop receives messages on. If this is the outbound tunnel endpoint, * this specifies the tunnel ID to which the reply should be sent. */ public long readNextTunnelId() { - return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_SEND_TUNNEL, 4); + return DataHelper.fromLong(_data, OFF_SEND_TUNNEL, 4); } + /** * Read the next hop from the record. If this is the outbound tunnel endpoint, this specifies * the gateway to which the reply should be sent. */ public Hash readNextIdentity() { //byte rv[] = new byte[Hash.HASH_LENGTH]; - //System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH); + //System.arraycopy(_data, OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH); //return new Hash(rv); - return Hash.create(_data.getData(), _data.getOffset() + OFF_SEND_IDENT); + return Hash.create(_data, OFF_SEND_IDENT); } + /** * Tunnel layer encryption key that the current hop should use */ public SessionKey readLayerKey() { byte key[] = new byte[SessionKey.KEYSIZE_BYTES]; - System.arraycopy(_data.getData(), _data.getOffset() + OFF_LAYER_KEY, key, 0, SessionKey.KEYSIZE_BYTES); + System.arraycopy(_data, OFF_LAYER_KEY, key, 0, SessionKey.KEYSIZE_BYTES); return new SessionKey(key); } + /** * Tunnel IV encryption key that the current hop should use */ public SessionKey readIVKey() { byte key[] = new byte[SessionKey.KEYSIZE_BYTES]; - System.arraycopy(_data.getData(), _data.getOffset() + OFF_IV_KEY, key, 0, SessionKey.KEYSIZE_BYTES); + System.arraycopy(_data, OFF_IV_KEY, key, 0, SessionKey.KEYSIZE_BYTES); return new SessionKey(key); } + /** * Session key that should be used to encrypt the reply */ public SessionKey readReplyKey() { byte key[] = new byte[SessionKey.KEYSIZE_BYTES]; - System.arraycopy(_data.getData(), _data.getOffset() + OFF_REPLY_KEY, key, 0, SessionKey.KEYSIZE_BYTES); + System.arraycopy(_data, OFF_REPLY_KEY, key, 0, SessionKey.KEYSIZE_BYTES); return new SessionKey(key); } + /** * IV that should be used to encrypt the reply */ public byte[] readReplyIV() { byte iv[] = new byte[IV_SIZE]; - System.arraycopy(_data.getData(), _data.getOffset() + OFF_REPLY_IV, iv, 0, IV_SIZE); + System.arraycopy(_data, OFF_REPLY_IV, iv, 0, IV_SIZE); return iv; } + /** * The current hop is the inbound gateway. If this is true, it means anyone can send messages to * this tunnel, but if it is false, only the current predecessor can. * */ public boolean readIsInboundGateway() { - return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_UNRESTRICTED_PREV) != 0; + return (_data[OFF_FLAG] & FLAG_UNRESTRICTED_PREV) != 0; } + /** * The current hop is the outbound endpoint. If this is true, the next identity and next tunnel * fields refer to where the reply should be sent. */ public boolean readIsOutboundEndpoint() { - return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0; + return (_data[OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0; } + /** * Time that the request was sent (ms), truncated to the nearest hour */ public long readRequestTime() { - return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L); + return DataHelper.fromLong(_data, OFF_REQ_TIME, 4) * (60 * 60 * 1000L); } + /** * What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint, * this specifies the message ID with which the reply should be sent. */ public long readReplyMessageId() { - return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_SEND_MSG_ID, 4); + return DataHelper.fromLong(_data, OFF_SEND_MSG_ID, 4); } /** @@ -164,42 +175,43 @@ public class BuildRequestRecord { * bytes 0-15: truncated SHA-256 of the current hop's identity (the toPeer parameter) * bytes 15-527: ElGamal-2048 encrypted block * + * + * @return non-null */ - public void encryptRecord(I2PAppContext ctx, PublicKey toKey, Hash toPeer, byte out[], int outOffset) { - System.arraycopy(toPeer.getData(), 0, out, outOffset, PEER_SIZE); - byte preEncr[] = new byte[OFF_SEND_MSG_ID + 4 + PADDING_SIZE]; - System.arraycopy(_data.getData(), _data.getOffset(), preEncr, 0, preEncr.length); - byte encrypted[] = ctx.elGamalEngine().encrypt(preEncr, toKey); + public EncryptedBuildRecord encryptRecord(I2PAppContext ctx, PublicKey toKey, Hash toPeer) { + byte[] out = new byte[EncryptedBuildRecord.LENGTH]; + System.arraycopy(toPeer.getData(), 0, out, 0, PEER_SIZE); + byte encrypted[] = ctx.elGamalEngine().encrypt(_data, toKey); // the elg engine formats it kind of weird, giving 257 bytes for each part rather than 256, so // we want to strip out that excess byte and store it in the record - System.arraycopy(encrypted, 1, out, outOffset + PEER_SIZE, 256); - System.arraycopy(encrypted, 258, out, outOffset + 256 + PEER_SIZE, 256); + System.arraycopy(encrypted, 1, out, PEER_SIZE, 256); + System.arraycopy(encrypted, 258, out, 256 + PEER_SIZE, 256); + return new EncryptedBuildRecord(out); } /** * Decrypt the data from the specified record, writing the decrypted record into this instance's - * buffer (but not overwriting the array contained within the old buffer) + * data buffer + * + * Caller MUST check that first 16 bytes of our hash matches first 16 bytes of encryptedRecord + * before calling this. Not checked here. + * + * @throws DataFormatException on decrypt fail + * @since 0.9.18, was decryptRecord() */ - public boolean decryptRecord(I2PAppContext ctx, PrivateKey ourKey, Hash ourIdent, ByteArray encryptedRecord) { - if (DataHelper.eq(ourIdent.getData(), 0, encryptedRecord.getData(), encryptedRecord.getOffset(), PEER_SIZE)) { + public BuildRequestRecord(I2PAppContext ctx, PrivateKey ourKey, + EncryptedBuildRecord encryptedRecord) throws DataFormatException { byte preDecrypt[] = new byte[514]; - System.arraycopy(encryptedRecord.getData(), encryptedRecord.getOffset() + PEER_SIZE, preDecrypt, 1, 256); - System.arraycopy(encryptedRecord.getData(), encryptedRecord.getOffset() + PEER_SIZE + 256, preDecrypt, 258, 256); + System.arraycopy(encryptedRecord.getData(), PEER_SIZE, preDecrypt, 1, 256); + System.arraycopy(encryptedRecord.getData(), PEER_SIZE + 256, preDecrypt, 258, 256); byte decrypted[] = ctx.elGamalEngine().decrypt(preDecrypt, ourKey); if (decrypted != null) { - _data = new ByteArray(decrypted); - _data.setOffset(0); - return true; + _data = decrypted; } else { - return false; + throw new DataFormatException("decrypt fail"); } - } else { - return false; - } } - private static final int PADDING_SIZE = 29; - /** * Populate this instance with data. A new buffer is created to contain the data, with the * necessary randomized padding. @@ -215,14 +227,13 @@ public class BuildRequestRecord { * @param iv iv to be used when encrypting the reply to this build request * @param isInGateway are we the gateway of an inbound tunnel? * @param isOutEndpoint are we the endpoint of an outbound tunnel? + * @since 0.9.18, was createRecord() */ - public void createRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, long nextMsgId, + public BuildRequestRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, long nextMsgId, SessionKey layerKey, SessionKey ivKey, SessionKey replyKey, byte iv[], boolean isInGateway, boolean isOutEndpoint) { - if ( (_data == null) || (_data.getData() != null) ) - _data = new ByteArray(); - byte buf[] = new byte[OFF_SEND_MSG_ID+4+PADDING_SIZE]; - _data.setData(buf); + byte buf[] = new byte[LENGTH]; + _data = buf; /* bytes 0-3: tunnel ID to receive messages as * bytes 4-35: local router identity hash diff --git a/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java index 5b2ee944a..6087ef343 100644 --- a/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java @@ -7,12 +7,17 @@ import net.i2p.data.SessionKey; //import net.i2p.util.Log; /** - * Read and write the reply to a tunnel build message record. + * Class that creates an encrypted tunnel build message record. * * The reply record is the same size as the request record (528 bytes). + * + * When decrypted: + * + *
* Bytes 0-31 contain the hash of bytes 32-527 * Bytes 32-526 contain random data. * Byte 527 contains the reply. + **/ public class BuildResponseRecord { @@ -20,10 +25,12 @@ public class BuildResponseRecord { * Create a new encrypted response * * @param status the response 0-255 + * @param replyIV 16 bytes * @param responseMessageId unused except for debugging * @return a 528-byte response record */ - public static byte[] create(I2PAppContext ctx, int status, SessionKey replyKey, byte replyIV[], long responseMessageId) { + public static EncryptedBuildRecord create(I2PAppContext ctx, int status, SessionKey replyKey, + byte replyIV[], long responseMessageId) { //Log log = ctx.logManager().getLog(BuildResponseRecord.class); byte rv[] = new byte[TunnelBuildReplyMessage.RECORD_SIZE]; ctx.random().nextBytes(rv, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE - Hash.HASH_LENGTH - 1); @@ -35,6 +42,6 @@ public class BuildResponseRecord { ctx.aes().encrypt(rv, 0, rv, 0, replyKey, replyIV, rv.length); //if (log.shouldLog(Log.DEBUG)) // log.debug(responseMessageId + ": after encrypt: " + Base64.encode(rv, 0, 128)); - return rv; + return new EncryptedBuildRecord(rv); } } diff --git a/router/java/src/net/i2p/data/i2np/EncryptedBuildRecord.java b/router/java/src/net/i2p/data/i2np/EncryptedBuildRecord.java new file mode 100644 index 000000000..995f30d90 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/EncryptedBuildRecord.java @@ -0,0 +1,32 @@ +package net.i2p.data.i2np; + +/* + * free (adj.): unencumbered; not under the control of others + * No warranty of any kind, either expressed or implied. + */ + +import net.i2p.data.SimpleDataStructure; + +/** + * ElGamal-encrypted request or response. + * 528 bytes. Previously stored in a ByteArray. + * May or may not be AES layer-encrypted. + * + * Note that these are layer-encrypted and layer-decrypted in-place. + * Do not cache. + * + * @since 0.9.18 + */ +public class EncryptedBuildRecord extends SimpleDataStructure { + + public final static int LENGTH = TunnelBuildMessageBase.RECORD_SIZE; + + /** @throws IllegalArgumentException if data is not correct length (null is ok) */ + public EncryptedBuildRecord(byte data[]) { + super(data); + } + + public int length() { + return LENGTH; + } +} diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java b/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java index bb3f579a3..81e349636 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildMessageBase.java @@ -1,7 +1,6 @@ package net.i2p.data.i2np; import net.i2p.I2PAppContext; -import net.i2p.data.ByteArray; /** * Base for TBM, TBRM, VTBM, VTBRM @@ -18,7 +17,7 @@ import net.i2p.data.ByteArray; * @since 0.8.8 */ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl { - protected ByteArray _records[]; + protected EncryptedBuildRecord _records[]; protected int RECORD_COUNT; public static final int MAX_RECORD_COUNT = 8; @@ -31,14 +30,14 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl { super(context); if (records > 0) { RECORD_COUNT = records; - _records = new ByteArray[records]; + _records = new EncryptedBuildRecord[records]; } // else will be initialized by readMessage() } - public void setRecord(int index, ByteArray record) { _records[index] = record; } + public void setRecord(int index, EncryptedBuildRecord record) { _records[index] = record; } - public ByteArray getRecord(int index) { return _records[index]; } + public EncryptedBuildRecord getRecord(int index) { return _records[index]; } /** @since 0.7.12 */ public int getRecordCount() { return RECORD_COUNT; } @@ -57,7 +56,7 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl { int off = offset + (i * RECORD_SIZE); byte rec[] = new byte[RECORD_SIZE]; System.arraycopy(data, off, rec, 0, RECORD_SIZE); - setRecord(i, new ByteArray(rec)); + setRecord(i, new EncryptedBuildRecord(rec)); } } @@ -66,7 +65,7 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl { if (remaining < 0) throw new I2NPMessageException("Not large enough (too short by " + remaining + ")"); for (int i = 0; i < RECORD_COUNT; i++) { - System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE); + System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE); curIndex += RECORD_SIZE; } return curIndex; diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java index 9f7dea7a7..b33cba761 100644 --- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java +++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java @@ -1,7 +1,6 @@ package net.i2p.data.i2np; import net.i2p.I2PAppContext; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; /** @@ -36,7 +35,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage { RECORD_COUNT = r; if (dataSize != calculateWrittenLength()) throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")"); - _records = new ByteArray[RECORD_COUNT]; + _records = new EncryptedBuildRecord[RECORD_COUNT]; super.readMessage(data, offset + 1, dataSize, type); } @@ -51,7 +50,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage { // can't call super, written length check will fail //return super.writeMessageBody(out, curIndex + 1); for (int i = 0; i < RECORD_COUNT; i++) { - System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE); + System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE); curIndex += RECORD_SIZE; } return curIndex; diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java index fb7bc5b6a..368104a2a 100644 --- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java @@ -1,7 +1,6 @@ package net.i2p.data.i2np; import net.i2p.I2PAppContext; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; /** @@ -38,7 +37,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage { RECORD_COUNT = r; if (dataSize != calculateWrittenLength()) throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")"); - _records = new ByteArray[RECORD_COUNT]; + _records = new EncryptedBuildRecord[RECORD_COUNT]; super.readMessage(data, offset + 1, dataSize, type); } @@ -53,7 +52,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage { // can't call super, written length check will fail //return super.writeMessageBody(out, curIndex + 1); for (int i = 0; i < RECORD_COUNT; i++) { - System.arraycopy(_records[i].getData(), _records[i].getOffset(), out, curIndex, RECORD_SIZE); + System.arraycopy(_records[i].getData(), 0, out, curIndex, RECORD_SIZE); curIndex += RECORD_SIZE; } return curIndex; diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java b/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java index e2044305c..c6ddbb1b0 100644 --- a/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java @@ -3,11 +3,11 @@ package net.i2p.router.tunnel; import java.util.List; import net.i2p.I2PAppContext; -import net.i2p.data.ByteArray; import net.i2p.data.Hash; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; import net.i2p.data.i2np.BuildRequestRecord; +import net.i2p.data.i2np.EncryptedBuildRecord; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.TunnelBuildMessage; @@ -54,33 +54,41 @@ public abstract class BuildMessageGenerator { * containing the hop's configuration (as well as the reply info, if it is an outbound endpoint) * * @param msg out parameter + * @throws IllegalArgumentException if hop bigger than config */ public static void createRecord(int recordNum, int hop, TunnelBuildMessage msg, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel, I2PAppContext ctx, PublicKey peerKey) { - byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE]; //Log log = ctx.logManager().getLog(BuildMessageGenerator.class); + EncryptedBuildRecord erec; if (peerKey != null) { BuildRequestRecord req = null; if ( (!cfg.isInbound()) && (hop + 1 == cfg.getLength()) ) //outbound endpoint req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel); else req = createUnencryptedRecord(ctx, cfg, hop, null, -1); + if (req == null) + throw new IllegalArgumentException("hop bigger than config"); Hash peer = cfg.getPeer(hop); //if (log.shouldLog(Log.DEBUG)) // log.debug("Record " + recordNum + "/" + hop + "/" + peer.toBase64() // + ": unencrypted = " + Base64.encode(req.getData().getData())); - req.encryptRecord(ctx, peerKey, peer, encrypted, 0); + erec = req.encryptRecord(ctx, peerKey, peer); //if (log.shouldLog(Log.DEBUG)) // log.debug("Record " + recordNum + "/" + hop + ": encrypted = " + Base64.encode(encrypted)); } else { //if (log.shouldLog(Log.DEBUG)) // log.debug("Record " + recordNum + "/" + hop + "/ is blank/random"); + byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE]; ctx.random().nextBytes(encrypted); + erec = new EncryptedBuildRecord(encrypted); } - msg.setRecord(recordNum, new ByteArray(encrypted)); + msg.setRecord(recordNum, erec); } + /** + * Returns null if hop >= cfg.length + */ private static BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop, Hash replyRouter, long replyTunnel) { //Log log = ctx.logManager().getLog(BuildMessageGenerator.class); @@ -111,11 +119,11 @@ public abstract class BuildMessageGenerator { SessionKey layerKey = hopConfig.getLayerKey(); SessionKey ivKey = hopConfig.getIVKey(); SessionKey replyKey = hopConfig.getReplyKey(); - byte iv[] = hopConfig.getReplyIV().getData(); - if ( (iv == null) || (iv.length != BuildRequestRecord.IV_SIZE) ) { + byte iv[] = hopConfig.getReplyIV(); + if (iv == null) { iv = new byte[BuildRequestRecord.IV_SIZE]; ctx.random().nextBytes(iv); - hopConfig.getReplyIV().setData(iv); + hopConfig.setReplyIV(iv); } boolean isInGW = (cfg.isInbound() && (hop == 0)); boolean isOutEnd = (!cfg.isInbound() && (hop + 1 >= cfg.getLength())); @@ -132,9 +140,9 @@ public abstract class BuildMessageGenerator { // log.debug("Hop " + hop + " has the next message ID of " + nextMsgId + " for " + cfg // + " with replyKey " + replyKey.toBase64() + " and replyIV " + Base64.encode(iv)); - BuildRequestRecord rec= new BuildRequestRecord(); - rec.createRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer, nextMsgId, layerKey, ivKey, replyKey, - iv, isInGW, isOutEnd); + BuildRequestRecord rec= new BuildRequestRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer, + nextMsgId, layerKey, ivKey, replyKey, + iv, isInGW, isOutEnd); return rec; } else { @@ -143,7 +151,11 @@ public abstract class BuildMessageGenerator { } /** - * Encrypt the records so their hop ident is visible at the appropriate times + * Encrypt the records so their hop ident is visible at the appropriate times. + * + * Note that this layer-encrypts the build records for the message in-place. + * Only call this onece for a given message. + * * @param order list of hop #s as Integers. For instance, if (order.get(1) is 4), it is peer cfg.getPeer(4) */ public static void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg, @@ -151,7 +163,7 @@ public abstract class BuildMessageGenerator { //Log log = ctx.logManager().getLog(BuildMessageGenerator.class); // encrypt the records so that the right elements will be visible at the right time for (int i = 0; i < msg.getRecordCount(); i++) { - ByteArray rec = msg.getRecord(i); + EncryptedBuildRecord rec = msg.getRecord(i); Integer hopNum = order.get(i); int hop = hopNum.intValue(); if ( (isBlank(cfg, hop)) || (!cfg.isInbound() && hop == 1) ) { @@ -166,12 +178,12 @@ public abstract class BuildMessageGenerator { for (int j = hop-1; j >= stop; j--) { HopConfig hopConfig = cfg.getConfig(j); SessionKey key = hopConfig.getReplyKey(); - byte iv[] = hopConfig.getReplyIV().getData(); - int off = rec.getOffset(); + byte iv[] = hopConfig.getReplyIV(); //if (log.shouldLog(Log.DEBUG)) // log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg // + " with " + key.toBase64() + "/" + Base64.encode(iv)); - ctx.aes().decrypt(rec.getData(), off, rec.getData(), off, key, iv, TunnelBuildMessage.RECORD_SIZE); + // corrupts the SDS + ctx.aes().decrypt(rec.getData(), 0, rec.getData(), 0, key, iv, TunnelBuildMessage.RECORD_SIZE); } } //if (log.shouldLog(Log.DEBUG)) diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java index 1a2c6a5b8..7b649e73c 100644 --- a/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java @@ -2,12 +2,13 @@ package net.i2p.router.tunnel; import net.i2p.I2PAppContext; import net.i2p.data.Base64; -import net.i2p.data.ByteArray; +import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; 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.TunnelBuildMessage; import net.i2p.router.util.DecayingBloomFilter; import net.i2p.router.util.DecayingHashSet; @@ -32,7 +33,10 @@ public class BuildMessageProcessor { * message (so that the reply can be placed in that position after going through the decrypted * request record). * - * @return the current hop's decrypted record + * Note that this layer-decrypts the build records in-place. + * Do not call this more than once for a given message. + * + * @return the current hop's decrypted record or null on failure */ public BuildRequestRecord decrypt(I2PAppContext ctx, TunnelBuildMessage msg, Hash ourHash, PrivateKey privKey) { Log log = ctx.logManager().getLog(getClass()); @@ -44,35 +48,33 @@ public class BuildMessageProcessor { long totalDup = 0; long beforeLoop = System.currentTimeMillis(); for (int i = 0; i < msg.getRecordCount(); i++) { - ByteArray rec = msg.getRecord(i); - int off = rec.getOffset(); + EncryptedBuildRecord rec = msg.getRecord(i); int len = BuildRequestRecord.PEER_SIZE; long beforeEq = System.currentTimeMillis(); - boolean eq = DataHelper.eq(ourHash.getData(), 0, rec.getData(), off, len); + boolean eq = DataHelper.eq(ourHash.getData(), 0, rec.getData(), 0, len); totalEq += System.currentTimeMillis()-beforeEq; if (eq) { long beforeIsDup = System.currentTimeMillis(); - boolean isDup = _filter.add(rec.getData(), off + len, 32); + boolean isDup = _filter.add(rec.getData(), len, 32); totalDup += System.currentTimeMillis()-beforeIsDup; if (isDup) { if (log.shouldLog(Log.WARN)) log.debug(msg.getUniqueId() + ": A record matching our hash was found, but it seems to be a duplicate"); - ctx.statManager().addRateData("tunnel.buildRequestDup", 1, 0); + ctx.statManager().addRateData("tunnel.buildRequestDup", 1); return null; } - BuildRequestRecord req = new BuildRequestRecord(); beforeActualDecrypt = System.currentTimeMillis(); - boolean ok = req.decryptRecord(ctx, privKey, ourHash, rec); - afterActualDecrypt = System.currentTimeMillis(); - if (ok) { + try { + BuildRequestRecord req = new BuildRequestRecord(ctx, privKey, rec); if (log.shouldLog(Log.DEBUG)) log.debug(msg.getUniqueId() + ": A record matching our hash was found and decrypted"); rv = req; - } else { + } catch (DataFormatException dfe) { if (log.shouldLog(Log.DEBUG)) log.debug(msg.getUniqueId() + ": A record matching our hash was found, but could not be decrypted"); return null; // our hop is invalid? b0rkage } + afterActualDecrypt = System.currentTimeMillis(); ourHop = i; } } @@ -89,11 +91,12 @@ public class BuildMessageProcessor { int ivOff = 0; for (int i = 0; i < msg.getRecordCount(); i++) { if (i != ourHop) { - ByteArray data = msg.getRecord(i); + EncryptedBuildRecord data = msg.getRecord(i); if (log.shouldLog(Log.DEBUG)) - log.debug("Encrypting record " + i + "/?/" + data.getOffset() + "/" + data.getValid() + " with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv, ivOff, 16)); - ctx.aes().encrypt(data.getData(), data.getOffset(), data.getData(), data.getOffset(), replyKey, - iv, ivOff, data.getValid()); + log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv, ivOff, 16)); + // corrupts SDS + ctx.aes().encrypt(data.getData(), 0, data.getData(), 0, replyKey, + iv, ivOff, data.length()); } } long afterEncrypt = System.currentTimeMillis(); diff --git a/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java index b2c7c84b3..2196a851e 100644 --- a/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java +++ b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java @@ -4,10 +4,10 @@ import java.util.List; import net.i2p.I2PAppContext; import net.i2p.data.Base64; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.SessionKey; +import net.i2p.data.i2np.EncryptedBuildRecord; import net.i2p.data.i2np.TunnelBuildReplyMessage; import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; @@ -34,6 +34,9 @@ public class BuildReplyHandler { * Decrypt the tunnel build reply records. This overwrites the contents of the reply. * Thread safe (no state). * + * Note that this layer-decrypts the build records in-place. + * Do not call this more than once for a given message. + * * @return status for the records (in record order), or null if the replies were not valid. Fake records * always have 0 as their value */ @@ -70,7 +73,10 @@ public class BuildReplyHandler { /** * Decrypt the record (removing the layers of reply encyption) and read out the status * - * @return -1 on decrypt failure + * Note that this layer-decrypts the build records in-place. + * Do not call this more than once for a given message. + * + * @return the status 0-255, or -1 on decrypt failure */ private int decryptRecord(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) { if (BuildMessageGenerator.isBlank(cfg, hop)) { @@ -78,9 +84,8 @@ public class BuildReplyHandler { log.debug(reply.getUniqueId() + ": Record " + recordNum + "/" + hop + " is fake, so consider it valid..."); return 0; } - ByteArray rec = reply.getRecord(recordNum); + EncryptedBuildRecord rec = reply.getRecord(recordNum); byte[] data = rec.getData(); - int off = rec.getOffset(); int start = cfg.getLength() - 1; if (cfg.isInbound()) start--; // the last hop in an inbound tunnel response doesn't actually encrypt @@ -88,35 +93,34 @@ public class BuildReplyHandler { for (int j = start; j >= hop; j--) { HopConfig hopConfig = cfg.getConfig(j); SessionKey replyKey = hopConfig.getReplyKey(); - byte replyIV[] = hopConfig.getReplyIV().getData(); - int replyIVOff = hopConfig.getReplyIV().getOffset(); + byte replyIV[] = hopConfig.getReplyIV(); if (log.shouldLog(Log.DEBUG)) { log.debug(reply.getUniqueId() + ": Decrypting record " + recordNum + "/" + hop + "/" + j + " with replyKey " - + replyKey.toBase64() + "/" + Base64.encode(replyIV, replyIVOff, 16) + ": " + cfg); - log.debug(reply.getUniqueId() + ": before decrypt("+ off + "-"+(off+rec.getValid())+"): " + Base64.encode(data, off, rec.getValid())); - log.debug(reply.getUniqueId() + ": Full reply rec: offset=" + off + ", sz=" + data.length + "/" + rec.getValid() + ", data=" + Base64.encode(data, off, TunnelBuildReplyMessage.RECORD_SIZE)); + + 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)); } - ctx.aes().decrypt(data, off, data, off, replyKey, replyIV, replyIVOff, TunnelBuildReplyMessage.RECORD_SIZE); + 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, off, rec.getValid())); + 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) // don't cache the result //Hash h = ctx.sha().calculateHash(data, off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH); byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH); - ctx.sha().calculateHash(data, off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH, h, 0); - boolean ok = DataHelper.eq(h, 0, data, off, Hash.HASH_LENGTH); + ctx.sha().calculateHash(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH, h, 0); + boolean ok = DataHelper.eq(h, 0, data, 0, Hash.HASH_LENGTH); if (!ok) { if (log.shouldLog(Log.DEBUG)) log.debug(reply.getUniqueId() + ": Failed verification on " + recordNum + "/" + hop + ": " + Base64.encode(h) + " calculated, " + - Base64.encode(data, off, Hash.HASH_LENGTH) + " expected\n" + - "Record: " + Base64.encode(data, off+Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH)); + Base64.encode(data, 0, Hash.HASH_LENGTH) + " expected\n" + + "Record: " + Base64.encode(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH)); SimpleByteCache.release(h); return -1; } else { SimpleByteCache.release(h); - int rv = (int)DataHelper.fromLong(data, off + TunnelBuildReplyMessage.RECORD_SIZE - 1, 1); + int rv = (int)DataHelper.fromLong(data, TunnelBuildReplyMessage.RECORD_SIZE - 1, 1); if (log.shouldLog(Log.DEBUG)) log.debug(reply.getUniqueId() + ": Verified: " + rv + " for record " + recordNum + "/" + hop); return rv; diff --git a/router/java/src/net/i2p/router/tunnel/HopConfig.java b/router/java/src/net/i2p/router/tunnel/HopConfig.java index 4c40a1727..447f6a083 100644 --- a/router/java/src/net/i2p/router/tunnel/HopConfig.java +++ b/router/java/src/net/i2p/router/tunnel/HopConfig.java @@ -1,6 +1,5 @@ package net.i2p.router.tunnel; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.SessionKey; @@ -20,7 +19,7 @@ public class HopConfig { private SessionKey _layerKey; private SessionKey _ivKey; private SessionKey _replyKey; - private ByteArray _replyIV; + private byte[] _replyIV; private long _creation; private long _expiration; //private Map _options; @@ -87,9 +86,23 @@ public class HopConfig { public SessionKey getReplyKey() { return _replyKey; } public void setReplyKey(SessionKey key) { _replyKey = key; } - /** iv used to encrypt the reply sent for the new tunnel creation crypto */ - public ByteArray getReplyIV() { return _replyIV; } - public void setReplyIV(ByteArray iv) { _replyIV = iv; } + /** + * IV used to encrypt the reply sent for the new tunnel creation crypto + * + * @return 16 bytes + */ + public byte[] getReplyIV() { return _replyIV; } + + /** + * IV used to encrypt the reply sent for the new tunnel creation crypto + * + * @throws IllegalArgumentException if not 16 bytes + */ + public void setReplyIV(byte[] iv) { + if (iv.length != REPLY_IV_LENGTH) + throw new IllegalArgumentException(); + _replyIV = iv; + } /** when does this tunnel expire (in ms since the epoch)? */ public long getExpiration() { return _expiration; } 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 0de09ff24..cf3cb1053 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java @@ -6,7 +6,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.data.Base64; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.router.RouterIdentity; @@ -14,6 +13,7 @@ import net.i2p.data.router.RouterInfo; 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.I2NPMessage; import net.i2p.data.i2np.TunnelBuildMessage; import net.i2p.data.i2np.TunnelBuildReplyMessage; @@ -782,13 +782,13 @@ class BuildHandler implements Runnable { return; } - byte reply[] = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId()); + EncryptedBuildRecord reply = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId()); int records = state.msg.getRecordCount(); int ourSlot = -1; for (int j = 0; j < records; j++) { if (state.msg.getRecord(j) == null) { ourSlot = j; - state.msg.setRecord(j, new ByteArray(reply)); + state.msg.setRecord(j, reply); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Full reply record for slot " + ourSlot + "/" + ourId + "/" + nextId + "/" + req.readReplyMessageId() // + ": " + Base64.encode(reply)); diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java index 31aaa8665..76ccd0561 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.PublicKey; @@ -90,7 +89,7 @@ abstract class BuildRequestor { cfg.getConfig(i-1).setSendTunnelId(cfg.getConfig(i).getReceiveTunnelId()); byte iv[] = new byte[16]; ctx.random().nextBytes(iv); - cfg.getConfig(i).setReplyIV(new ByteArray(iv)); + cfg.getConfig(i).setReplyIV(iv); cfg.getConfig(i).setReplyKey(ctx.keyGenerator().generateSessionKey()); } // This is in BuildExecutor.buildTunnel() now