diff --git a/history.txt b/history.txt
index 70bfc09cca..da76874bc7 100644
--- a/history.txt
+++ b/history.txt
@@ -1,13 +1,20 @@
-$Id: history.txt,v 1.389 2006/01/14 15:14:47 cervantes Exp $
+$Id: history.txt,v 1.390 2006/01/16 17:00:04 cervantes Exp $
+
+2006-01-17 jrandom
+ * First pass of the new tunnel creation crypto, specified in the new
+ router/doc/tunnel-alt-creation.html (referenced in the current
+ router/doc/tunnel-alt.html). It isn't actually used anywhere yet, other
+ than in the test code, but the code verifies the technical viability, so
+ further scrutiny would be warranted.
2006-01-16 cervantes
- * Dragged I2P kicking and screaming into 2006 (Oops)
+ * Dragged I2P kicking and screaming into 2006 (Oops)
2006-01-14 cervantes
- * Removed entirely misleading memory status from the console summary.
+ * Removed entirely misleading memory status from the console summary.
2006-01-13 cervantes
- * Further Syndie layout hardening and typeface balancing.
+ * Further Syndie layout hardening and typeface balancing.
* 2006-01-12 0.6.1.9 released
diff --git a/router/doc/tunnel-alt-creation.html b/router/doc/tunnel-alt-creation.html
new file mode 100644
index 0000000000..15f1bec625
--- /dev/null
+++ b/router/doc/tunnel-alt-creation.html
@@ -0,0 +1,161 @@
+$Id: tunnel-alt.html,v 1.9 2005/07/27 14:04:07 jrandom Exp $
+
+1) Tunnel creation +1.1) Tunnel creation request record +1.2) Hop processing +1.3) Tunnel creation reply record +1.4) Request preparation +1.5) Request delivery +1.6) Endpoint handling +1.7) Reply processing +2) Notes ++ +
The tunnel creation is accomplished by a single message passed along +the path of peers in the tunnel, rewritten in place, and transmitted +back to the tunnel creator. This single tunnel message is made up +of a fixed number of records (8) - one for each potential peer in +the tunnel. Individual records are asymmetrically encrypted to be +read only by a specific peer along the path, while an additional +symmetric layer of encryption is added at each hop so as to expose +the asymmetrically encrypted record only at the appropriate time.
+ +Cleartext of the record, visible only to the hop being asked:
+ bytes 0-3: tunnel ID to receive messages as + bytes 4-35: local router identity hash + bytes 36-39: next tunnel ID + bytes 40-71: next router identity hash + bytes 72-103: AES-256 tunnel layer key + bytes 104-135: AES-256 tunnel IV key + bytes 136-167: AES-256 reply key + bytes 168-183: reply IV + byte 184: flags + bytes 185-188: request time (in hours since the epoch) + bytes 189-222: uninterpreted / random padding+ +
The next tunnel ID and next router identity hash fields are used to +specify the next hop in the tunnel, though for an outbound tunnel +endpoint, they specify where the rewritten tunnel creation reply +message should be sent.
+ +The flags field currently has two bits defined:
+ bit 0: if set, allow messages from anyone + bit 1: if set, allow messages to anyone, and send the reply to the + specified next hop in a tunnel message+ +
That cleartext record is ElGamal 2048 encrypted with the hop's +public encryption key and formatted into a 528 byte record:
+ bytes 0-15: SHA-256-128 of the current hop's router identity + bytes 16-527: ElGamal-2048 encrypted request record+ +
Since the cleartext uses the full field, there is no need for
+additional padding beyond SHA256(cleartext) + cleartext
.
When a hop receives a TunnelBuildMessage, it looks through the 8 +records contained within it for one starting with their own identity +hash (trimmed to 8 bytes). It then decryptes the ElGamal block from +that record and retrieves the protected cleartext. At that point, +they make sure the tunnel request is not a duplicate by feeding the +AES-256 reply key into a bloom filter and making sure the request +time is within an hour of current. Duplicates or invalid requests +are dropped.
+ +After deciding whether they will agree to participate in the tunnel +or not, they replace the record that had contained the request with +an encrypted reply block. All other records are AES-256/CBC +encrypted with the included reply key and IV (though each is +encrypted separately, rather than chained across records).
+ +After the current hop reads their record, they replace it with a +reply record stating whether or not they agree to participate in the +tunnel, and if they do not, they classify their reason for +rejection. This is simply a 1 byte value, with 0x0 meaning they +agree to participate in the tunnel, and higher values meaning higher +levels of rejection. The reply is encrypted with the AES session +key delivered to it in the encrypted block, padded with random data +until it reaches the full record size:
+ AES-256-CBC(SHA-256(padding+status) + padding + status, key, IV)+ +
When building a new request, all of the records must first be +built and asymmetrically encrypted. Each record should then be +decrypted with the reply keys and IVs of the hops earlier in the +path. That decryption should be run in reverse order so that the +asymmetrically encrypted data will show up in the clear at the +right hop after their predecessor encrypts it.
+ +The excess records not needed for individual requests are simply +filled with random data by the creator.
+ +For outbound tunnels, the delivery is done directly from the tunnel +creator to the first hop, packaging up the TunnelBuildMessage as if +the creator was just another hop in the tunnel. For inbound +tunnels, the delivery is done through an existing outbound tunnel +(and during startup, when no outbound tunnel exists yet, a fake 0 +hop outbound tunnel is used).
+ +When the request reaches an outbound endpoint (as determined by the +'allow messages to anyone' flag), the hop is processed as usual, +encrypting a reply in place of the record and encrypting all of the +other records, but since there is no 'next hop' to forward the +TunnelBuildMessage on to, it instead places the encrypted reply +records into a TunnelBuildReplyMessage and delivers it to the +reply tunnel specified within the request record. That reply tunnel +forwards the reply records down to the tunnel creator for +processing, as below.
+ +When the request reaches the inbound endpoint (also known as the +tunnel creator), the router processes each of the replies, as below.
+ +To process the reply records, the creator simply has to AES decrypt +each record individually, using the reply key and IV of each hop in +the tunnel after the peer (in reverse order). This then exposes the +reply specifying whether they agree to participate in the tunnel or +why they refuse. If they all agree, the tunnel is considered +created and may be used immediately, but if anyone refuses, the +tunnel is discarded.
+ +$Id: tunnel-alt.html,v 1.8 2005/07/07 16:16:57 jrandom Exp $
+$Id: tunnel-alt.html,v 1.9 2005/07/27 14:04:07 jrandom Exp $
1) Tunnel overview 2) Tunnel operation @@ -329,11 +329,11 @@ tunnel to be scrapped and the profiles for peers within it to be adjusted.3) Tunnel building
When building a tunnel, the creator must send a request with the necessary -configuration data to each of the hops in turn, starting with the endpoint, -waiting for their reply, then moving on to the next earlier hop. These tunnel request -messages and their replies are garlic wrapped so that only the router who knows -the key can decrypt it, and the path taken in both directions is tunnel routed -as well. There are three important dimensions to keep in mind when producing +configuration data to each of the hops and wait for all of them to agree before +enabling the tunnel. The requests are encrypted so that only the peers who need +to know a piece of information (such as the tunnel layer or IV key) has that +data. In addition, only the tunnel creator will have access to the peer's +reply. There are three important dimensions to keep in mind when producing the tunnels: what peers are used (and where), how the requests are sent (and replies received), and how they are maintained.
@@ -376,30 +376,12 @@ as controls for the user to select which strategy to use for individual clients.3.2) Request delivery
-As mentioned above, once the tunnel creator knows what peers should go into -a tunnel and in what order, the creator builds a series of tunnel request -messages, each containing the necessary information for that peer. For instance, -participating tunnels will be given the 4 byte nonce with which to reply with, -the 4 byte tunnel ID on which they are to send out the messages, -the 32 byte hash of the next hop's identity, the 32 byte layer key used to -remove a layer from the tunnel, and a 32 byte IV key used to encrypt the IV. -Of course, outbound tunnel endpoints are not -given any "next hop" or "next tunnel ID" information. To allow -replies, the request contains a random session tag and a random session key with -which the peer should garlic encrypt their decision, as well as the tunnel to which -that garlic should be sent. In addition to the above information, various client -specific options may be included, such as what throttling to place on the tunnel, -what padding or batch strategies to use, etc.
- -After building all of the request messages, they are garlic wrapped for the -target router and sent out an exploratory tunnel. Upon receipt, that peer -determines whether they can or will participate, and if it will, it selects the -tunnel ID on which it will receive messages. It then garlic wraps and tunnel -routes that agreement, tunnel ID, and the nonce provided in the request using the -supplied information (session tag, garlic session key, tunnel ID to reply to, and -router on which that tunnel listens). Upon receipt of the reply at the tunnel -creator, the tunnel is considered valid on that hop (if accepted). Once all -peers have accepted, the tunnel is active.
+A new tunnel request preparation, delivery, and response method has been +devised, which reduces the number of +predecessors exposed, cuts the number of messages transmitted, verifies proper +connectivity, and avoids the message counting attack of traditional telescopic +tunnel creation. The old technique is listed below as an alternative.
Peers may reject tunnel creation requests for a variety of reasons, though a series of four increasingly severe rejections are known: probabalistic rejection @@ -438,10 +420,9 @@ including the tunnel creator in that set), another alternative is to use the tunnel pathways themselves to pass along the request and response, as is done in TOR. This, however, may lead to leaks during tunnel creation, allowing peers to discover how many hops there are later -on in the tunnel by monitoring the timing or packet count as the tunnel is -built. Techniques could be used to minimize this issue, such as using each of -the hops as endpoints (per 2.7.2) for a random -number of messages before continuing on to build the next hop.
+on in the tunnel by monitoring the timing or packet count as +the tunnel is built.3.4.2) Non-exploratory tunnels for management
@@ -452,6 +433,14 @@ of the network, this should not be necessary, but if the router was partitioned in some way, using non-exploratory pools for tunnel management would reduce the leakage of information about what peers are in the router's partition. +3.4.3) Exploratory request delivery
+ +A third alternative, used until I2P 0.6.2, garlic encrypts individual tunnel +request messages and delivers them to the hops individually, transmitting them +through exploratory tunnels with their reply coming back in a separate +exploratory tunnel. This strategy has been dropped in favor of the one outlined +above.
+4) Tunnel throttling
Even though the tunnels within I2P bear a resemblance to a circuit switched diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java new file mode 100644 index 0000000000..21b9aa79c4 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -0,0 +1,238 @@ +package net.i2p.data.i2np; + +import net.i2p.I2PAppContext; +import net.i2p.data.*; + +/** + * Hold the tunnel request record, managing its encryption and decryption. + * Cleartext: + *
+ * bytes 0-3: tunnel ID to receive messages as + * bytes 4-35: local router identity hash + * bytes 36-39: next tunnel ID + * bytes 40-71: next router identity hash + * bytes 72-103: AES-256 tunnel layer key + * bytes 104-135: AES-256 tunnel IV key + * bytes 136-167: AES-256 reply key + * bytes 168-183: reply IV + * byte 184: flags + * bytes 185-188: request time (in hours since the epoch) + * bytes 189-222: uninterpreted / random padding + *+ * + */ +public class BuildRequestRecord { + private ByteArray _data; + + /** + * If set in the flag byte, any peer may send a message into this tunnel, but if + * not set, only the current predecessor may send messages in. This is only set on + * an inbound tunnel gateway. + */ + private static final int FLAG_UNRESTRICTED_PREV = (1 << 7); + /** + * If set in the flag byte, this is an outbound tunnel endpoint, which means that + * there is no 'next hop' and that the next hop fields contain the tunnel to which the + * reply message should be sent. + */ + private static final int FLAG_OUTBOUND_ENDPOINT = (1 << 6); + + public static final int IV_SIZE = 16; + /** 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; } + + private static final int OFF_RECV_TUNNEL = 0; + private static final int OFF_OUR_IDENT = OFF_RECV_TUNNEL + 4; + private static final int OFF_SEND_TUNNEL = OFF_OUR_IDENT + Hash.HASH_LENGTH; + private static final int OFF_SEND_IDENT = OFF_SEND_TUNNEL + 4; + private static final int OFF_LAYER_KEY = OFF_SEND_IDENT + Hash.HASH_LENGTH; + private static final int OFF_IV_KEY = OFF_LAYER_KEY + SessionKey.KEYSIZE_BYTES; + private static final int OFF_REPLY_KEY = OFF_IV_KEY + SessionKey.KEYSIZE_BYTES; + private static final int OFF_REPLY_IV = OFF_REPLY_KEY + SessionKey.KEYSIZE_BYTES; + private static final int OFF_FLAG = OFF_REPLY_IV + IV_SIZE; + private static final int OFF_REQ_TIME = OFF_FLAG + 1; + + /** 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); + } + /** + * 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); + } + /** + * 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); + return new Hash(rv); + } + /** + * 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); + 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); + 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); + 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); + 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; + } + /** + * 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; + } + /** + * Time that the request was sent, truncated to the nearest hour + */ + public long readRequestTime() { + return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * 60l * 60l * 1000l; + } + + /** + * Encrypt the record to the specified peer. The result is formatted as:+ * bytes 0-15: SHA-256-128 of the current hop's identity (the toPeer parameter) + * bytes 15-527: ElGamal-2048 encrypted block + *+ */ + 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_REQ_TIME + 4 + PADDING_SIZE]; + System.arraycopy(_data.getData(), _data.getOffset(), preEncr, 0, preEncr.length); + byte encrypted[] = ctx.elGamalEngine().encrypt(preEncr, 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); + } + + /** + * 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) + */ + public boolean decryptRecord(I2PAppContext ctx, PrivateKey ourKey, Hash ourIdent, ByteArray encryptedRecord) { + if (DataHelper.eq(ourIdent.getData(), 0, encryptedRecord.getData(), encryptedRecord.getOffset(), PEER_SIZE)) { + 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); + byte decrypted[] = ctx.elGamalEngine().decrypt(preDecrypt, ourKey); + if (decrypted != null) { + _data = new ByteArray(decrypted); + _data.setOffset(0); + return true; + } else { + return false; + } + } else { + return false; + } + } + + private static final int PADDING_SIZE = 33; + + /** + * Populate this instance with data. A new buffer is created to contain the data, with the + * necessary randomized padding. + * + * @param receiveTunnelId tunnel the current hop will receive messages on + * @param peer current hop's identity + * @param nextTunnelId id for the next hop, or where we send the reply (if we are the outbound endpoint) + * @param nextHop next hop's identity, or where we send the reply (if we are the outbound endpoint) + * @param layerKey tunnel layer key to be used by the peer + * @param ivKey tunnel IV key to be used by the peer + * @param replyKey key to be used when encrypting the reply to this build request + * @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? + */ + public void createRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, + 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_REQ_TIME+4+PADDING_SIZE]; + _data.setData(buf); + + /* bytes 0-3: tunnel ID to receive messages as + * bytes 4-35: local router identity hash + * bytes 36-39: next tunnel ID + * bytes 40-71: next router identity hash + * bytes 72-103: AES-256 tunnel layer key + * bytes 104-135: AES-256 tunnel IV key + * bytes 136-167: AES-256 reply key + * bytes 168-183: reply IV + * byte 184: flags + * bytes 185-188: request time (in hours since the epoch) + * bytes 189-222: uninterpreted / random padding + */ + DataHelper.toLong(buf, OFF_RECV_TUNNEL, 4, receiveTunnelId); + System.arraycopy(peer.getData(), 0, buf, OFF_OUR_IDENT, Hash.HASH_LENGTH); + DataHelper.toLong(buf, OFF_SEND_TUNNEL, 4, nextTunnelId); + System.arraycopy(nextHop.getData(), 0, buf, OFF_SEND_IDENT, Hash.HASH_LENGTH); + System.arraycopy(layerKey.getData(), 0, buf, OFF_LAYER_KEY, SessionKey.KEYSIZE_BYTES); + System.arraycopy(ivKey.getData(), 0, buf, OFF_IV_KEY, SessionKey.KEYSIZE_BYTES); + System.arraycopy(replyKey.getData(), 0, buf, OFF_REPLY_KEY, SessionKey.KEYSIZE_BYTES); + System.arraycopy(iv, 0, buf, OFF_REPLY_IV, IV_SIZE); + if (isInGateway) + buf[OFF_FLAG] |= FLAG_UNRESTRICTED_PREV; + else if (isOutEndpoint) + buf[OFF_FLAG] |= FLAG_OUTBOUND_ENDPOINT; + long truncatedHour = ctx.clock().now(); + truncatedHour /= (60l*60l*1000l); + DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour); + byte rnd[] = new byte[PADDING_SIZE]; + ctx.random().nextBytes(rnd); + System.arraycopy(rnd, 0, buf, OFF_REQ_TIME+4, rnd.length); + + byte wroteIV[] = readReplyIV(); + if (!DataHelper.eq(iv, wroteIV)) + throw new RuntimeException("foo"); + } +} diff --git a/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java new file mode 100644 index 0000000000..1e3f7959d5 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/BuildResponseRecord.java @@ -0,0 +1,23 @@ +package net.i2p.data.i2np; + +import net.i2p.I2PAppContext; +import net.i2p.data.*; + +/** + * Read and write the reply to a tunnel build message record. + * + */ +public class BuildResponseRecord { + /** + * Create a new encrypted response + */ + public byte[] create(I2PAppContext ctx, int status, SessionKey replyKey, byte replyIV[]) { + byte rv[] = new byte[TunnelBuildReplyMessage.RECORD_SIZE]; + ctx.random().nextBytes(rv); + DataHelper.toLong(rv, TunnelBuildMessage.RECORD_SIZE-1, 1, status); + // rv = AES(SHA256(padding+status) + padding + status, replyKey, replyIV) + ctx.sha().calculateHash(rv, Hash.HASH_LENGTH, rv.length - Hash.HASH_LENGTH, rv, 0); + ctx.aes().encrypt(rv, 0, rv, 0, replyKey, replyIV, rv.length); + return rv; + } +} diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java index b5f97c2316..5495004811 100644 --- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java +++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java @@ -345,6 +345,10 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM return new TunnelCreateMessage(context); case TunnelCreateStatusMessage.MESSAGE_TYPE: return new TunnelCreateStatusMessage(context); + case TunnelBuildMessage.MESSAGE_TYPE: + return new TunnelBuildMessage(context); + case TunnelBuildReplyMessage.MESSAGE_TYPE: + return new TunnelBuildReplyMessage(context); default: Builder builder = (Builder)_builders.get(new Integer(type)); if (builder == null) diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java new file mode 100644 index 0000000000..d3015304e2 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java @@ -0,0 +1,51 @@ +package net.i2p.data.i2np; + +import java.io.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; + +/** + * + */ +public class TunnelBuildMessage extends I2NPMessageImpl { + private ByteArray _records[]; + + public static final int MESSAGE_TYPE = 21; + public static final int RECORD_COUNT = 8; + + public TunnelBuildMessage(I2PAppContext context) { + super(context); + _records = new ByteArray[RECORD_COUNT]; + } + + public void setRecord(int index, ByteArray record) { _records[index] = record; } + public ByteArray getRecord(int index) { return _records[index]; } + + public static final int RECORD_SIZE = 512+16; + + protected int calculateWrittenLength() { return RECORD_SIZE * RECORD_COUNT; } + public int getType() { return MESSAGE_TYPE; } + public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException { + if (type != MESSAGE_TYPE) + throw new I2NPMessageException("Message type is incorrect for this message"); + if (dataSize != calculateWrittenLength()) + throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")"); + + for (int i = 0; i < RECORD_COUNT; i++) { + int off = offset + (i * RECORD_SIZE); + int len = RECORD_SIZE; + setRecord(i, new ByteArray(data, off, len)); + } + } + + protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + int remaining = out.length - (curIndex + calculateWrittenLength()); + 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); + curIndex += RECORD_SIZE; + } + return curIndex; + } +} diff --git a/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java new file mode 100644 index 0000000000..3549f3e159 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/TunnelBuildReplyMessage.java @@ -0,0 +1,52 @@ +package net.i2p.data.i2np; + +import java.io.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; + +/** + * Transmitted from the new outbound endpoint to the creator through a + * reply tunnel + */ +public class TunnelBuildReplyMessage extends I2NPMessageImpl { + private ByteArray _records[]; + + public static final int MESSAGE_TYPE = 22; + public static final int RECORD_COUNT = TunnelBuildMessage.RECORD_COUNT; + + public TunnelBuildReplyMessage(I2PAppContext context) { + super(context); + _records = new ByteArray[RECORD_COUNT]; + } + + public void setRecord(int index, ByteArray record) { _records[index] = record; } + public ByteArray getRecord(int index) { return _records[index]; } + + public static final int RECORD_SIZE = TunnelBuildMessage.RECORD_SIZE; + + protected int calculateWrittenLength() { return RECORD_SIZE * RECORD_COUNT; } + public int getType() { return MESSAGE_TYPE; } + public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException { + if (type != MESSAGE_TYPE) + throw new I2NPMessageException("Message type is incorrect for this message"); + if (dataSize != calculateWrittenLength()) + throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")"); + + for (int i = 0; i < RECORD_COUNT; i++) { + int off = offset + (i * RECORD_SIZE); + int len = RECORD_SIZE; + setRecord(i, new ByteArray(data, off, len)); + } + } + + protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + int remaining = out.length - (curIndex + calculateWrittenLength()); + 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); + curIndex += RECORD_SIZE; + } + return curIndex; + } +} diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 57589a2e9f..cd2cf01d4a 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.333 $ $Date: 2006/01/13 01:27:44 $"; + public final static String ID = "$Revision: 1.334 $ $Date: 2006/01/14 15:14:47 $"; public final static String VERSION = "0.6.1.9"; - public final static long BUILD = 2; + public final static long BUILD = 3; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java b/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java new file mode 100644 index 0000000000..a4c3c48eb6 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageGenerator.java @@ -0,0 +1,136 @@ +package net.i2p.router.tunnel; + +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.data.i2np.*; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * + */ +public class BuildMessageGenerator { + // cached, rather than creating lots of temporary Integer objects whenever we build a tunnel + static final Integer ORDER[] = new Integer[TunnelBuildMessage.RECORD_COUNT]; + static { for (int i = 0; i < ORDER.length; i++) ORDER[i] = new Integer(i); } + + /** return null if it is unable to find a router's public key (etc) */ + public TunnelBuildMessage createInbound(RouterContext ctx, TunnelCreatorConfig cfg) { + return create(ctx, cfg, null, -1); + } + /** return null if it is unable to find a router's public key (etc) */ + public TunnelBuildMessage createOutbound(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) { + return create(ctx, cfg, replyRouter, replyTunnel); + } + + private TunnelBuildMessage create(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) { + TunnelBuildMessage msg = new TunnelBuildMessage(ctx); + List order = new ArrayList(ORDER.length); + for (int i = 0; i < ORDER.length; i++) order.add(ORDER[i]); + Collections.shuffle(order, ctx.random()); + for (int i = 0; i < ORDER.length; i++) { + int hop = ((Integer)order.get(i)).intValue(); + Hash peer = cfg.getPeer(hop); + RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(peer); + if (ri == null) + return null; + createRecord(i, hop, msg, cfg, replyRouter, replyTunnel, ctx, ri.getIdentity().getPublicKey()); + } + layeredEncrypt(ctx, msg, cfg, order); + return msg; + } + + /** + * Place the asymmetrically encrypted record in the specified record slot, + * containing the hop's configuration (as well as the reply info, if it is an outbound endpoint) + */ + public void createRecord(int recordNum, int hop, TunnelBuildMessage msg, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel, I2PAppContext ctx, PublicKey peerKey) { + Log log = ctx.logManager().getLog(getClass()); + 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); + byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE]; + if (hop < cfg.getLength()) { + Hash peer = cfg.getPeer(hop); + if (peerKey == null) + throw new RuntimeException("hop = " + hop + " recordNum = " + recordNum + " len = " + cfg.getLength()); + //if (log.shouldLog(Log.DEBUG)) + // log.debug("Record " + recordNum + "/" + hop + ": unencrypted = " + Base64.encode(req.getData().getData())); + req.encryptRecord(ctx, peerKey, peer, encrypted, 0); + //if (log.shouldLog(Log.DEBUG)) + // log.debug("Record " + recordNum + "/" + hop + ": encrypted = " + Base64.encode(encrypted)); + } else { + ctx.random().nextBytes(encrypted); + } + msg.setRecord(recordNum, new ByteArray(encrypted)); + } + + private BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop, Hash replyRouter, long replyTunnel) { + if (hop < cfg.getLength()) { + // ok, now lets fill in some data + HopConfig hopConfig = cfg.getConfig(hop); + Hash peer = cfg.getPeer(hop); + long recvTunnelId = hopConfig.getReceiveTunnel().getTunnelId(); + long nextTunnelId = -1; + Hash nextPeer = null; + if (hop + 1 < cfg.getLength()) { + nextTunnelId = cfg.getConfig(hop+1).getReceiveTunnel().getTunnelId(); + nextPeer = cfg.getPeer(hop+1); + } else { + if ( (replyTunnel >= 0) && (replyRouter != null) ) { + nextTunnelId = replyTunnel; + nextPeer = replyRouter; + } else { + // inbound endpoint (aka creator) + nextTunnelId = 0; + nextPeer = peer; // self + } + } + 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) ) { + iv = new byte[BuildRequestRecord.IV_SIZE]; + ctx.random().nextBytes(iv); + hopConfig.getReplyIV().setData(iv); + } + boolean isInGW = (cfg.isInbound() && (hop == 0)); + boolean isOutEnd = (!cfg.isInbound() && (hop + 1 >= cfg.getLength())); + + BuildRequestRecord rec= new BuildRequestRecord(); + rec.createRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer, layerKey, ivKey, replyKey, + iv, isInGW, isOutEnd); + + return rec; + } else { + return null; + } + } + + /** + * Encrypt the records so their hop ident is visible at the appropriate times + * @param order list of hop #s as Integers. For instance, if (order.get(1) is 4), it is peer cfg.getPeer(4) + */ + public void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg, TunnelCreatorConfig cfg, List order) { + // encrypt the records so that the right elements will be visible at the right time + for (int i = 0; i < TunnelBuildMessage.RECORD_COUNT; i++) { + ByteArray rec = msg.getRecord(i); + Integer hopNum = (Integer)order.get(i); + int hop = hopNum.intValue(); + if (hop >= cfg.getLength()) + continue; // no need to encrypt it, as its random + // ok, now decrypt the record with all of the reply keys from cfg.getConfig(0) through hop-1 + for (int j = hop-1; j >= 0; j--) { + HopConfig hopConfig = cfg.getConfig(j); + SessionKey key = hopConfig.getReplyKey(); + byte iv[] = hopConfig.getReplyIV().getData(); + int off = rec.getOffset(); + ctx.aes().decrypt(rec.getData(), off, rec.getData(), off, key, iv, TunnelBuildMessage.RECORD_SIZE); + } + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java new file mode 100644 index 0000000000..984f34e6d6 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageProcessor.java @@ -0,0 +1,61 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.data.i2np.*; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; + +/** + * Receive the build message at a certain hop, decrypt its encrypted record, + * read the enclosed tunnel request, decide how to reply, write the reply, + * encrypt the reply record, and return a TunnelBuildMessage to forward on to + * the next hop + */ +class BuildMessageProcessor { + /** + * Decrypt the record targetting us, encrypting all of the other records with the included + * reply key and IV. The original, encrypted record targetting us is removed from the request + * 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 + */ + public BuildRequestRecord decrypt(I2PAppContext ctx, TunnelBuildMessage msg, Hash ourHash, PrivateKey privKey) { + Log log = ctx.logManager().getLog(getClass()); + BuildRequestRecord rv = null; + int ourHop = -1; + for (int i = 0; i < TunnelBuildMessage.RECORD_COUNT; i++) { + ByteArray rec = msg.getRecord(i); + int off = rec.getOffset(); + int len = BuildRequestRecord.PEER_SIZE; + if (DataHelper.eq(ourHash.getData(), 0, rec.getData(), off, len)) { + BuildRequestRecord req = new BuildRequestRecord(); + boolean ok = req.decryptRecord(ctx, privKey, ourHash, rec); + if (ok) + rv = req; + else + return null; // our hop is invalid? b0rkage + ourHop = i; + } + } + if (rv == null) { + // none of the records matched, b0rk + return null; + } + SessionKey replyKey = rv.readReplyKey(); + byte iv[] = rv.readReplyIV(); + int ivOff = 0; + for (int i = 0; i < TunnelBuildMessage.RECORD_COUNT; i++) { + if (i != ourHop) { + if (log.shouldLog(Log.DEBUG)) + log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv, ivOff, 16)); + ByteArray data = msg.getRecord(i); + ctx.aes().encrypt(data.getData(), data.getOffset(), data.getData(), data.getOffset(), replyKey, + iv, ivOff, data.getValid()); + } + } + msg.setRecord(ourHop, null); + return rv; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/BuildMessageTest.java b/router/java/src/net/i2p/router/tunnel/BuildMessageTest.java new file mode 100644 index 0000000000..60f26757aa --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/BuildMessageTest.java @@ -0,0 +1,172 @@ +package net.i2p.router.tunnel; + +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.data.i2np.*; +import net.i2p.util.Log; + +/** + * Simple test to create an encrypted TunnelBuildMessage, decrypt its layers (as it would be + * during transmission), inject replies, then handle the TunnelBuildReplyMessage (unwrapping + * the reply encryption and reading the replies). + */ +public class BuildMessageTest { + private Hash _peers[]; + private PrivateKey _privKeys[]; + private PublicKey _pubKeys[]; + private Hash _replyRouter; + private long _replyTunnel; + + public static void main(String args[]) { + BuildMessageTest test = new BuildMessageTest(); + try { + test.runTest(); + } catch (Exception e) { e.printStackTrace(); } + } + + private void runTest() { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + Log log = ctx.logManager().getLog(getClass()); + + List order = pickOrder(ctx); + + TunnelCreatorConfig cfg = createConfig(ctx); + _replyRouter = new Hash(); + byte h[] = new byte[Hash.HASH_LENGTH]; + Arrays.fill(h, (byte)0xFF); + _replyRouter.setData(h); + _replyTunnel = 42; + + // populate and encrypt the message + BuildMessageGenerator gen = new BuildMessageGenerator(); + TunnelBuildMessage msg = new TunnelBuildMessage(ctx); + for (int i = 0; i < BuildMessageGenerator.ORDER.length; i++) { + int hop = ((Integer)order.get(i)).intValue(); + PublicKey key = null; + if (hop < _pubKeys.length) + key = _pubKeys[hop]; + gen.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key); + } + gen.layeredEncrypt(ctx, msg, cfg, order); + + log.debug("\n================================================================" + + "\nMessage fully encrypted" + + "\n================================================================"); + + // now msg is fully encrypted, so lets go through the hops, decrypting and replying + // as necessary + + for (int i = 0; i < cfg.getLength(); i++) { + BuildMessageProcessor proc = new BuildMessageProcessor(); + // this not only decrypts the current hop's record, but encrypts the other records + // with the reply key + BuildRequestRecord req = proc.decrypt(ctx, msg, _peers[i], _privKeys[i]); + if (req == null) { + // no records matched the _peers[i], or the decryption failed + throw new RuntimeException("foo @ " + i); + } + long ourId = req.readReceiveTunnelId(); + byte replyIV[] = req.readReplyIV(); + long nextId = req.readNextTunnelId(); + Hash nextPeer = req.readNextIdentity(); + boolean isInGW = req.readIsInboundGateway(); + boolean isOutEnd = req.readIsOutboundEndpoint(); + long time = req.readRequestTime(); + long now = (ctx.clock().now() / (60l*60l*1000l)) * (60*60*1000); + int ourSlot = -1; + + BuildResponseRecord resp = new BuildResponseRecord(); + byte reply[] = resp.create(ctx, 0, req.readReplyKey(), req.readReplyIV()); + for (int j = 0; j < TunnelBuildMessage.RECORD_COUNT; j++) { + if (msg.getRecord(j) == null) { + ourSlot = j; + msg.setRecord(j, new ByteArray(reply)); + 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)); + } + + + log.debug("\n================================================================" + + "\nAll hops traversed and replies gathered" + + "\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.RECORD_COUNT; i++) + reply.setRecord(i, msg.getRecord(i)); + + BuildReplyHandler handler = new BuildReplyHandler(); + int statuses[] = handler.decrypt(ctx, reply, cfg, order); + if (statuses == null) throw new RuntimeException("bar"); + boolean allAgree = true; + for (int i = 0; i < cfg.getLength(); i++) { + Hash peer = cfg.getPeer(i); + int record = ((Integer)order.get(i)).intValue(); + if (statuses[record] != 0) + allAgree = false; + //else + // penalize peer according to the rejection cause + } + + log.debug("\n================================================================" + + "\nAll peers agree? " + allAgree + + "\n================================================================"); + } + + private static final List pickOrder(I2PAppContext ctx) { + // pseudorandom, yet consistent (so we can be repeatable) + List rv = new ArrayList(8); + rv.add(new Integer(2)); + rv.add(new Integer(4)); + rv.add(new Integer(6)); + rv.add(new Integer(0)); + rv.add(new Integer(1)); + rv.add(new Integer(3)); + rv.add(new Integer(5)); + rv.add(new Integer(7)); + return rv; + } + + private TunnelCreatorConfig createConfig(I2PAppContext ctx) { + return configOutbound(ctx); + } + private TunnelCreatorConfig configOutbound(I2PAppContext ctx) { + _peers = new Hash[4]; + _pubKeys = new PublicKey[_peers.length]; + _privKeys = new PrivateKey[_peers.length]; + for (int i = 0; i < _peers.length; i++) { + byte buf[] = new byte[Hash.HASH_LENGTH]; + Arrays.fill(buf, (byte)i); // consistent for repeatability + Hash h = new Hash(buf); + _peers[i] = h; + Object kp[] = ctx.keyGenerator().generatePKIKeypair(); + _pubKeys[i] = (PublicKey)kp[0]; + _privKeys[i] = (PrivateKey)kp[1]; + } + + TunnelCreatorConfig cfg = new TunnelCreatorConfig(null, _peers.length, false); + long now = ctx.clock().now(); + // peers[] is ordered endpoint first, but cfg.getPeer() is ordered gateway first + for (int i = 0; i < _peers.length; i++) { + cfg.setPeer(i, _peers[i]); + HopConfig hop = cfg.getConfig(i); + hop.setExpiration(now+10*60*1000); + hop.setIVKey(ctx.keyGenerator().generateSessionKey()); + hop.setLayerKey(ctx.keyGenerator().generateSessionKey()); + hop.setReplyKey(ctx.keyGenerator().generateSessionKey()); + byte iv[] = new byte[BuildRequestRecord.IV_SIZE]; + Arrays.fill(iv, (byte)i); // consistent for repeatability + hop.setReplyIV(new ByteArray(iv)); + hop.setReceiveTunnelId(new TunnelId(i+1)); + } + return cfg; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java new file mode 100644 index 0000000000..0e6c7bc4be --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java @@ -0,0 +1,73 @@ +package net.i2p.router.tunnel; + +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.data.i2np.*; +import net.i2p.util.Log; + +/** + * Decrypt the layers of a tunnel build reply message, determining whether the individual + * hops agreed to participate in the tunnel, or if not, why not. + * + */ +public class BuildReplyHandler { + public BuildReplyHandler() {} + + /** + * Decrypt the tunnel build reply records. This overwrites the contents of the reply + * + * @return status for the records (in record order), or null if the replies were not valid. Fake records + * always have 0 as their value + */ + public int[] decrypt(I2PAppContext ctx, TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, List recordOrder) { + int rv[] = new int[TunnelBuildReplyMessage.RECORD_COUNT]; + for (int i = 0; i < rv.length; i++) { + int hop = ((Integer)recordOrder.get(i)).intValue(); + int ok = decryptRecord(ctx, reply, cfg, i, hop); + if (ok == -1) return null; + rv[i] = ok; + } + return rv; + } + + /** + * Decrypt the record (removing the layers of reply encyption) and read out the status + * + * @return -1 on decrypt failure + */ + private int decryptRecord(I2PAppContext ctx, TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) { + Log log = ctx.logManager().getLog(getClass()); + if (hop >= cfg.getLength()) { + if (log.shouldLog(Log.DEBUG)) + log.debug("Record " + recordNum + "/" + hop + " is fake, so consider it valid..."); + return 0; + } + ByteArray rec = reply.getRecord(recordNum); + int off = rec.getOffset(); + for (int j = cfg.getLength() - 1; j >= hop; j--) { + HopConfig hopConfig = cfg.getConfig(j); + SessionKey replyKey = hopConfig.getReplyKey(); + byte replyIV[] = hopConfig.getReplyIV().getData(); + int replyIVOff = hopConfig.getReplyIV().getOffset(); + if (log.shouldLog(Log.DEBUG)) + log.debug("Decrypting record " + recordNum + "/" + hop + " with replyKey " + replyKey.toBase64() + "/" + Base64.encode(replyIV, replyIVOff, 16)); + ctx.aes().decrypt(rec.getData(), off, rec.getData(), off, replyKey, replyIV, replyIVOff, TunnelBuildReplyMessage.RECORD_SIZE); + } + // ok, all of the layered encryption is stripped, so lets verify it + // (formatted per BuildResponseRecord.create) + Hash h = ctx.sha().calculateHash(rec.getData(), off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH); + if (!DataHelper.eq(h.getData(), 0, rec.getData(), off, Hash.HASH_LENGTH)) { + if (log.shouldLog(Log.DEBUG)) + log.debug("Failed verification on " + recordNum + "/" + hop + ": " + h.toBase64() + " calculated, " + + Base64.encode(rec.getData(), off, Hash.HASH_LENGTH) + " expected\n" + + "Record: " + Base64.encode(rec.getData())); + return -1; + } else { + int rv = (int)DataHelper.fromLong(rec.getData(), off + TunnelBuildReplyMessage.RECORD_SIZE - 1, 1); + if (log.shouldLog(Log.DEBUG)) + log.debug("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 cfd1f72717..0c190dc0e4 100644 --- a/router/java/src/net/i2p/router/tunnel/HopConfig.java +++ b/router/java/src/net/i2p/router/tunnel/HopConfig.java @@ -2,6 +2,7 @@ package net.i2p.router.tunnel; import java.util.Map; +import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.SessionKey; @@ -20,10 +21,15 @@ public class HopConfig { private Hash _sendTo; private SessionKey _layerKey; private SessionKey _ivKey; + private SessionKey _replyKey; + private ByteArray _replyIV; private long _expiration; private Map _options; private long _messagesProcessed; + /** IV length for {@link #getReplyIV} */ + public static final int REPLY_IV_LENGTH = 16; + public HopConfig() { _receiveTunnelId = null; _receiveFrom = null; @@ -77,6 +83,14 @@ public class HopConfig { public SessionKey getIVKey() { return _ivKey; } public void setIVKey(SessionKey key) { _ivKey = key; } + /** key to encrypt the reply sent for the new tunnel creation crypto */ + 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; } + /** when does this tunnel expire (in ms since the epoch)? */ public long getExpiration() { return _expiration; } public void setExpiration(long when) { _expiration = when; } diff --git a/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java index d5ff4bbca4..715653fb62 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java @@ -99,8 +99,9 @@ public class TunnelCreatorConfig implements TunnelInfo { double normalized = (double)tot * 60d*1000d / (double)timeSince; _peakThroughputLastCoallesce = now; _peakThroughputCurrentTotal = 0; - for (int i = 0; i < _peers.length; i++) - _context.profileManager().tunnelDataPushed1m(_peers[i], (int)normalized); + if (_context != null) + for (int i = 0; i < _peers.length; i++) + _context.profileManager().tunnelDataPushed1m(_peers[i], (int)normalized); } } public long getVerifiedBytesTransferred() { return _verifiedBytesTransferred; }