forked from I2P_Developers/i2p.i2p
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:
238
router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
Normal file
238
router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
Normal 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");
|
||||
}
|
||||
}
|
||||
23
router/java/src/net/i2p/data/i2np/BuildResponseRecord.java
Normal file
23
router/java/src/net/i2p/data/i2np/BuildResponseRecord.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
51
router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java
Normal file
51
router/java/src/net/i2p/data/i2np/TunnelBuildMessage.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user