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.
This commit is contained in:
jrandom
2006-01-17 22:56:15 +00:00
committed by zzz
parent eb3442823e
commit a12ede096a
15 changed files with 1024 additions and 42 deletions

View File

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

View File

@ -0,0 +1,161 @@
<code>$Id: tunnel-alt.html,v 1.9 2005/07/27 14:04:07 jrandom Exp $</code>
<pre>
1) <a href="#tunnelCreate.overview">Tunnel creation</a>
1.1) <a href="#tunnelCreate.requestRecord">Tunnel creation request record</a>
1.2) <a href="#tunnelCreate.hopProcessing">Hop processing</a>
1.3) <a href="#tunnelCreate.replyRecord">Tunnel creation reply record</a>
1.4) <a href="#tunnelCreate.requestPreparation">Request preparation</a>
1.5) <a href="#tunnelCreate.requestDelivery">Request delivery</a>
1.6) <a href="#tunnelCreate.endpointHandling">Endpoint handling</a>
1.7) <a href="#tunnelCreate.replyProcessing">Reply processing</a>
2) <a href="#tunnelCreate.notes">Notes</a>
</pre>
<h2 id="tunnelCreate.overview">1) Tunnel creation encryption:</h2>
<p>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.</p>
<h3 id="tunnelCreate.requestRecord">1.1) Tunnel creation request record</h3>
<p>Cleartext of the record, visible only to the hop being asked:</p><pre>
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</pre>
<p>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.</p>
<p>The flags field currently has two bits defined:</p><pre>
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</pre>
<p>That cleartext record is ElGamal 2048 encrypted with the hop's
public encryption key and formatted into a 528 byte record:</p><pre>
bytes 0-15: SHA-256-128 of the current hop's router identity
bytes 16-527: ElGamal-2048 encrypted request record</pre>
<p>Since the cleartext uses the full field, there is no need for
additional padding beyond <code>SHA256(cleartext) + cleartext</code>.</p>
<h3 id="tunnelCreate.hopProcessing">1.2) Hop processing</h3>
<p>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.</p>
<p>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).</p>
<h3 id="tunnelCreate.replyRecord">1.3) Tunnel creation reply record</h3>
<p>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:</p><pre>
AES-256-CBC(SHA-256(padding+status) + padding + status, key, IV)</pre>
<h3 id="tunnelCreate.requestPreparation">1.4) Request preparation</h3>
<p>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.</p>
<p>The excess records not needed for individual requests are simply
filled with random data by the creator.</p>
<h3 id="tunnelCreate.requestDelivery">1.5) Request delivery</h3>
<p>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).</p>
<h3 id="tunnelCreate.endpointHandling">1.6) Endpoint handling</h3>
<p>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.</p>
<p>When the request reaches the inbound endpoint (also known as the
tunnel creator), the router processes each of the replies, as below.</p>
<h3 id="tunnelCreate.replyProcessing">1.7) Reply processing</h3>
<p>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.</p>
<h2 id="tunnelCreate.notes">2) Notes</h2>
<ul>
<li>This does not prevent two hostile peers within a tunnel from
tagging one or more request or reply records to detect that they are
within the same tunnel, but doing so can be detected by the tunnel
creator when reading the reply, causing the tunnel to be marked as
invalid.</li>
<li>This does not include a proof of work on the asymmetrically
encrypted section, though the 16 byte identity hash could be cut in
half with the later replaced by a hashcash function of up to 2^64
cost. This will not immediately be pursued, however.</li>
<li>This alone does not prevent two hostile peers within a tunnel from
using timing information to determine whether they are in the same
tunnel. The use of batched and synchronized request delivery
could help (batching up requests and sending them off on the
(ntp-synchronized) minute). However, doing so lets peers 'tag' the
requests by delaying them and detecting the delay later in the
tunnel, though perhaps dropping requests not delivered in a small
window would work (though doing that would require a high degree of
clock synchronization). Alternately, perhaps individual hops could
inject a random delay before forwarding on the request?</li>
<li>Are there any nonfatal methods of tagging the request?</li>
<li>This strategy came about during a discussion on the I2P mailing list
between Michael Rogers, Matthew Toseland (toad), and jrandom regarding
the predecessor attack. See: <ul>
<li><a href="http://dev.i2p.net/pipermail/i2p/2005-October/001073.html">Summary</a></li>
<li><a href="http://dev.i2p.net/pipermail/i2p/2005-October/001064.html">Reasoning</a></li>
</ul></li>
</ul>

View File

@ -1,4 +1,4 @@
<code>$Id: tunnel-alt.html,v 1.8 2005/07/07 16:16:57 jrandom Exp $</code>
<code>$Id: tunnel-alt.html,v 1.9 2005/07/27 14:04:07 jrandom Exp $</code>
<pre>
1) <a href="#tunnel.overview">Tunnel overview</a>
2) <a href="#tunnel.operation">Tunnel operation</a>
@ -329,11 +329,11 @@ tunnel to be scrapped and the profiles for peers within it to be adjusted.</p>
<h2>3) <a name="tunnel.building">Tunnel building</a></h2>
<p>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.</p>
@ -376,30 +376,12 @@ as controls for the user to select which strategy to use for individual clients.
<h3>3.2) <a name="tunnel.request">Request delivery</a></h3>
<p>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.</p>
<p>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.</p>
<p>A new tunnel request preparation, delivery, and response method has been
<a href="tunnel-alt-creation.html">devised</a>, 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 <a
href="#tunnel.building.exploratory">alternative</a>.</p>
<p>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 <a href="http://tor.eff.org/">TOR</a>. 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 <a href="#tunnel.reroute">2.7.2</a>) for a random
number of messages before continuing on to build the next hop.</p>
on in the tunnel by monitoring the timing or <a
href="http://dev.i2p.net/pipermail/2005-October/001057.html">packet count</a> as
the tunnel is built.</p>
<h4>3.4.2) <a name="tunnel.building.nonexploratory">Non-exploratory tunnels for management</a></h4>
@ -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.</p>
<h4>3.4.3) <a name="tunnel.building.exploratory">Exploratory request delivery</a></h4>
<p>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.</p>
<h2>4) <a name="tunnel.throttling">Tunnel throttling</a></h2>
<p>Even though the tunnels within I2P bear a resemblance to a circuit switched

View File

@ -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:
* <pre>
* 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
* </pre>
*
*/
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: <pre>
* bytes 0-15: SHA-256-128 of the current hop's identity (the toPeer parameter)
* bytes 15-527: ElGamal-2048 encrypted block
* </pre>
*/
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");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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