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

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