Compare commits

...

3 Commits

Author SHA1 Message Date
zzz
6e1c419ec7 short build records 2021-05-22 15:40:59 -04:00
zzz
2693cf97c4 short build records 2021-05-22 15:33:05 -04:00
zzz
aa2da6da45 Hook in new prop. 157 messages
WIP, incomplete, untested
2021-05-06 09:38:19 -04:00
12 changed files with 387 additions and 88 deletions

View File

@@ -57,7 +57,7 @@ import net.i2p.router.RouterContext;
* bytes 16-527: ElGamal encrypted block (discarding zero bytes at elg[0] and elg[257])
* </pre>
*
* New ECIES format, ref: proposal 152:
* ECIES long record format, ref: proposal 152:
*
* Holds the unencrypted 464-byte tunnel request record,
* with a constructor for ECIES decryption and a method for ECIES encryption.
@@ -90,6 +90,36 @@ import net.i2p.router.RouterContext;
* bytes 512-527: Poly1305 MAC
* </pre>
*
* ECIES short record format, ref: proposal 157:
*
* Holds the unencrypted 172-byte tunnel request record,
* with a constructor for ECIES decryption and a method for ECIES encryption.
* Iterative AES encryption/decryption is done elsewhere.
*
* Cleartext:
* <pre>
* bytes 0-3: tunnel ID to receive messages as, nonzero
* bytes 4-7: next tunnel ID, nonzero
* bytes 8-39: next router identity hash
* byte 40: flags
* bytes 41-42: more flags, unused, set to 0 for compatibility
* byte 43: layer enc. type
* bytes 44-47: request time (in minutes since the epoch, rounded down)
* bytes 48-51: request expiration (in seconds since creation)
* bytes 52-55: next message ID
* bytes 56-x: tunnel build options (Mapping)
* bytes x-x: other data as implied by flags or options
* bytes x-171: random padding
* </pre>
*
* Encrypted:
* <pre>
* bytes 0-15: Hop's truncated identity hash
* bytes 16-47: Sender's ephemeral X25519 public key
* bytes 48-219: ChaCha20 encrypted BuildRequestRecord
* bytes 220-235: Poly1305 MAC
* </pre>
*
*/
public class BuildRequestRecord {
private final byte[] _data;
@@ -138,7 +168,7 @@ public class BuildRequestRecord {
// 222
private static final int LENGTH = OFF_SEND_MSG_ID + 4 + PADDING_SIZE;
// New ECIES format
// ECIES long record format
private static final int OFF_SEND_TUNNEL_EC = OFF_OUR_IDENT;
private static final int OFF_SEND_IDENT_EC = OFF_SEND_TUNNEL_EC + 4;
private static final int OFF_LAYER_KEY_EC = OFF_SEND_IDENT_EC + Hash.HASH_LENGTH;
@@ -152,6 +182,16 @@ public class BuildRequestRecord {
private static final int OFF_OPTIONS = OFF_SEND_MSG_ID_EC + 4;
private static final int LENGTH_EC = 464;
private static final int MAX_OPTIONS_LENGTH = LENGTH_EC - OFF_OPTIONS; // includes options length
// ECIES short record format
private static final int OFF_FLAG_EC_SHORT = OFF_SEND_IDENT_EC + Hash.HASH_LENGTH;
private static final int OFF_LAYER_ENC_TYPE = OFF_FLAG_EC_SHORT + 3;
private static final int OFF_REQ_TIME_EC_SHORT = OFF_LAYER_ENC_TYPE + 1;
private static final int OFF_EXPIRATION_SHORT = OFF_REQ_TIME_EC + 4;
private static final int OFF_SEND_MSG_ID_EC_SHORT = OFF_EXPIRATION + 4;
private static final int OFF_OPTIONS_SHORT = OFF_SEND_MSG_ID_EC_SHORT + 4;
private static final int LENGTH_EC_SHORT = 172;
private static final int MAX_OPTIONS_LENGTH_SHORT = LENGTH_EC_SHORT - OFF_OPTIONS_SHORT; // includes options length
private static final boolean TEST = false;
private static KeyFactory TESTKF;
@@ -225,7 +265,8 @@ public class BuildRequestRecord {
*
*/
public boolean readIsInboundGateway() {
int off = _isEC ? OFF_FLAG_EC : OFF_FLAG;
int off = _isEC ? ( _data.length == LENGTH_EC_SHORT ? OFF_FLAG_EC_SHORT : OFF_FLAG_EC)
: OFF_FLAG;
return (_data[off] & FLAG_UNRESTRICTED_PREV) != 0;
}
@@ -234,7 +275,8 @@ public class BuildRequestRecord {
* fields refer to where the reply should be sent.
*/
public boolean readIsOutboundEndpoint() {
int off = _isEC ? OFF_FLAG_EC : OFF_FLAG;
int off = _isEC ? ( _data.length == LENGTH_EC_SHORT ? OFF_FLAG_EC_SHORT : OFF_FLAG_EC)
: OFF_FLAG;
return (_data[off] & FLAG_OUTBOUND_ENDPOINT) != 0;
}
@@ -244,8 +286,10 @@ public class BuildRequestRecord {
* This ignores leap seconds.
*/
public long readRequestTime() {
if (_isEC)
return DataHelper.fromLong(_data, OFF_REQ_TIME_EC, 4) * (60 * 1000L);
if (_isEC) {
int off = _data.length == LENGTH_EC_SHORT ? OFF_REQ_TIME_EC_SHORT : OFF_REQ_TIME_EC;
return DataHelper.fromLong(_data, off, 4) * (60 * 1000L);
}
return DataHelper.fromLong(_data, OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
}
@@ -254,7 +298,8 @@ public class BuildRequestRecord {
* this specifies the message ID with which the reply should be sent.
*/
public long readReplyMessageId() {
int off = _isEC ? OFF_SEND_MSG_ID_EC : OFF_SEND_MSG_ID;
int off = _isEC ? ( _data.length == LENGTH_EC_SHORT ? OFF_SEND_MSG_ID_EC_SHORT : OFF_SEND_MSG_ID_EC)
: OFF_SEND_MSG_ID;
return DataHelper.fromLong(_data, off, 4);
}
@@ -265,7 +310,8 @@ public class BuildRequestRecord {
public long readExpiration() {
if (!_isEC)
return DEFAULT_EXPIRATION_SECONDS * 1000L;
return DataHelper.fromLong(_data, OFF_EXPIRATION, 4) * 1000L;
int off = _data.length == LENGTH_EC_SHORT ? OFF_EXPIRATION_SHORT : OFF_EXPIRATION;
return DataHelper.fromLong(_data, off, 4) * 1000L;
}
/**
@@ -276,7 +322,11 @@ public class BuildRequestRecord {
public Properties readOptions() {
if (!_isEC)
return null;
ByteArrayInputStream in = new ByteArrayInputStream(_data, OFF_OPTIONS, MAX_OPTIONS_LENGTH);
ByteArrayInputStream in;
if (_data.length == LENGTH_EC_SHORT)
in = new ByteArrayInputStream(_data, OFF_OPTIONS_SHORT, MAX_OPTIONS_LENGTH_SHORT);
else
in = new ByteArrayInputStream(_data, OFF_OPTIONS, MAX_OPTIONS_LENGTH);
try {
return DataHelper.readProperties(in, null);
} catch (DataFormatException dfe) {
@@ -286,6 +336,17 @@ public class BuildRequestRecord {
}
}
/**
* ECIES short record only.
* @return 0 for ElGamal or ECIES long record
* @since 0.9.51
*/
public int readLayerEncryptionType() {
if (_data.length == LENGTH_EC_SHORT)
return _data[OFF_LAYER_ENC_TYPE] & 0xff;
return 0;
}
/**
* Encrypt the record to the specified peer. The result is formatted as: <pre>
* bytes 0-15: truncated SHA-256 of the current hop's identity (the toPeer parameter)
@@ -324,7 +385,8 @@ public class BuildRequestRecord {
EncType type = toKey.getType();
if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException();
byte[] out = new byte[EncryptedBuildRecord.LENGTH];
boolean isShort = _data.length == LENGTH_EC_SHORT;
byte[] out = new byte[isShort ? ShortEncryptedBuildRecord.LENGTH : EncryptedBuildRecord.LENGTH];
System.arraycopy(toPeer.getData(), 0, out, 0, PEER_SIZE);
HandshakeState state = null;
try {
@@ -332,8 +394,8 @@ public class BuildRequestRecord {
state = new HandshakeState(HandshakeState.PATTERN_ID_N, HandshakeState.INITIATOR, kf);
state.getRemotePublicKey().setPublicKey(toKey.getData(), 0);
state.start();
state.writeMessage(out, PEER_SIZE, _data, 0, LENGTH_EC);
EncryptedBuildRecord rv = new EncryptedBuildRecord(out);
state.writeMessage(out, PEER_SIZE, _data, 0, _data.length);
EncryptedBuildRecord rv = isShort ? new ShortEncryptedBuildRecord(out) : new EncryptedBuildRecord(out);
_chachaReplyKey = new SessionKey(state.getChainingKey());
_chachaReplyAD = new byte[32];
System.arraycopy(state.getHandshakeHash(), 0, _chachaReplyAD, 0, 32);
@@ -414,8 +476,10 @@ public class BuildRequestRecord {
state.getLocalKeyPair().setKeys(ourKey.getData(), 0,
ourKey.toPublic().getData(), 0);
state.start();
decrypted = new byte[LENGTH_EC];
state.readMessage(encrypted, PEER_SIZE, EncryptedBuildRecord.LENGTH - PEER_SIZE,
int len = encryptedRecord.length();
boolean isShort = len == ShortEncryptedBuildRecord.LENGTH;
decrypted = new byte[isShort ? LENGTH_EC_SHORT : LENGTH_EC];
state.readMessage(encrypted, PEER_SIZE, len - PEER_SIZE,
decrypted, 0);
_chachaReplyKey = new SessionKey(state.getChainingKey());
_chachaReplyAD = new byte[32];
@@ -507,7 +571,7 @@ public class BuildRequestRecord {
* Populate this instance with data. A new buffer is created to contain the data, with the
* necessary randomized padding.
*
* ECIES only. ElGamal constructor above.
* ECIES long record only. ElGamal constructor above.
*
* @param receiveTunnelId tunnel the current hop will receive messages on
* @param nextTunnelId id for the next hop, or where we send the reply (if we are the outbound endpoint)
@@ -559,6 +623,55 @@ public class BuildRequestRecord {
}
}
/**
* Populate this instance with data. A new buffer is created to contain the data, with the
* necessary randomized padding.
*
* ECIES short record only. ElGamal constructor above.
*
* @param receiveTunnelId tunnel the current hop will receive messages on
* @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 nextMsgId message ID to use when sending on to the next hop (or for the reply)
* @param isInGateway are we the gateway of an inbound tunnel?
* @param isOutEndpoint are we the endpoint of an outbound tunnel?
* @param options 116 bytes max when serialized
* @since 0.9.51
* @throws IllegalArgumentException if options too long
*/
public BuildRequestRecord(I2PAppContext ctx, long receiveTunnelId, long nextTunnelId, Hash nextHop, long nextMsgId,
boolean isInGateway, boolean isOutEndpoint, Properties options) {
byte buf[] = new byte[LENGTH_EC_SHORT];
_data = buf;
_isEC = true;
DataHelper.toLong(buf, OFF_RECV_TUNNEL, 4, receiveTunnelId);
DataHelper.toLong(buf, OFF_SEND_TUNNEL_EC, 4, nextTunnelId);
System.arraycopy(nextHop.getData(), 0, buf, OFF_SEND_IDENT_EC, Hash.HASH_LENGTH);
if (isInGateway)
buf[OFF_FLAG_EC_SHORT] |= FLAG_UNRESTRICTED_PREV;
else if (isOutEndpoint)
buf[OFF_FLAG_EC_SHORT] |= FLAG_OUTBOUND_ENDPOINT;
// 2 bytes unused flags = 0
// 1 byte layer enc. type = 0
long truncatedMinute = ctx.clock().now();
// prevent hop identification at top of the minute
truncatedMinute -= ctx.random().nextInt(2048);
// this ignores leap seconds
truncatedMinute /= (60*1000L);
DataHelper.toLong(buf, OFF_REQ_TIME_EC_SHORT, 4, truncatedMinute);
DataHelper.toLong(buf, OFF_EXPIRATION_SHORT, 4, DEFAULT_EXPIRATION_SECONDS);
DataHelper.toLong(buf, OFF_SEND_MSG_ID_EC_SHORT, 4, nextMsgId);
try {
int off = DataHelper.toProperties(buf, OFF_OPTIONS_SHORT, options);
int sz = LENGTH_EC_SHORT - off;
if (sz > 0)
ctx.random().nextBytes(buf, off, sz);
} catch (Exception e) {
throw new IllegalArgumentException("options", e);
}
}
/**
* @since 0.9.24
*/
@@ -566,6 +679,8 @@ public class BuildRequestRecord {
public String toString() {
StringBuilder buf = new StringBuilder(256);
buf.append(_isEC ? "ECIES" : "ElGamal");
if (_data.length == LENGTH_EC_SHORT)
buf.append(" short ");
buf.append(" BRR ");
boolean isIBGW = readIsInboundGateway();
boolean isOBEP = readIsOutboundEndpoint();
@@ -578,12 +693,14 @@ public class BuildRequestRecord {
buf.append("part. in: ").append(readReceiveTunnelId())
.append(" out: ").append(readNextTunnelId());
}
buf.append(" to: ").append(readNextIdentity())
.append(" layer key: ").append(readLayerKey())
.append(" IV key: ").append(readIVKey())
.append(" reply key: ").append(readReplyKey())
.append(" reply IV: ").append(Base64.encode(readReplyIV()))
.append(" time: ").append(DataHelper.formatTime(readRequestTime()))
buf.append(" to: ").append(readNextIdentity());
if (_data.length != LENGTH_EC_SHORT) {
buf.append(" layer key: ").append(readLayerKey())
.append(" IV key: ").append(readIVKey())
.append(" reply key: ").append(readReplyKey())
.append(" reply IV: ").append(Base64.encode(readReplyIV()));
}
buf.append(" time: ").append(DataHelper.formatTime(readRequestTime()))
.append(" reply msg id: ").append(readReplyMessageId())
.append(" expires in: ").append(DataHelper.formatDuration(readExpiration()));
if (_isEC) {

View File

@@ -46,7 +46,7 @@ public class BuildResponseRecord {
}
/**
* Create a new encrypted response.
* Create a new encrypted response (long record).
* ChaCha/Poly only for ECIES routers.
*
* @param status the response 0-255
@@ -77,6 +77,38 @@ public class BuildResponseRecord {
return new EncryptedBuildRecord(rv);
}
/**
* Create a new encrypted response (short record).
* ChaCha/Poly only for ECIES routers.
*
* @param status the response 0-255
* @param replyAD 32 bytes
* @param options 116 bytes max when serialized
* @return a 236-byte response record
* @throws IllegalArgumentException if options too big or on encryption failure
* @since 0.9.451
*/
public static ShortEncryptedBuildRecord createShort(I2PAppContext ctx, int status, SessionKey replyKey,
byte replyAD[], Properties options) {
byte rv[] = new byte[ShortTunnelBuildMessage.SHORT_RECORD_SIZE];
int off;
try {
off = DataHelper.toProperties(rv, 0, options);
} catch (Exception e) {
throw new IllegalArgumentException("options", e);
}
int sz = ShortTunnelBuildMessage.SHORT_RECORD_SIZE - off - 1;
if (sz > 0)
ctx.random().nextBytes(rv, off, sz);
else if (sz < 0)
throw new IllegalArgumentException("options");
rv[ShortTunnelBuildMessage.SHORT_RECORD_SIZE - 17] = (byte) status;
boolean ok = encryptAEADBlock(replyAD, rv, replyKey);
if (!ok)
throw new IllegalArgumentException("encrypt fail");
return new ShortEncryptedBuildRecord(rv);
}
/**
* Encrypts in place
* @param ad non-null

View File

@@ -438,6 +438,15 @@ public abstract class I2NPMessageImpl implements I2NPMessage {
// since 0.7.10
case VariableTunnelBuildReplyMessage.MESSAGE_TYPE:
return new VariableTunnelBuildReplyMessage(context);
// since 0.9.51
case InboundTunnelBuildMessage.MESSAGE_TYPE:
return new InboundTunnelBuildMessage(context);
// since 0.9.51
case OutboundTunnelBuildReplyMessage.MESSAGE_TYPE:
return new OutboundTunnelBuildReplyMessage(context);
// since 0.9.51
case ShortTunnelBuildMessage.MESSAGE_TYPE:
return new ShortTunnelBuildMessage(context);
default:
// unused
Builder builder = _builders.get(Integer.valueOf(type));

View File

@@ -30,6 +30,16 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
super(context, records);
}
/**
* @param record must be ShortEncryptedBuildRecord or null
*/
@Override
public void setRecord(int index, EncryptedBuildRecord record) {
if (record != null && record.length() != SHORT_RECORD_SIZE)
throw new IllegalArgumentException();
super.setRecord(index, record);
}
/**
* Set the slot and data for the plaintext record.
* @throws IllegalArgumentException on bad slot or data length.
@@ -71,7 +81,7 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage {
throw new I2NPMessageException("Bad record count " + r);
RECORD_COUNT = r;
int _plaintextSlot = data[offset++] & 0xff;
if (_plaintextSlot < 0 || _plaintextSlot >= r)
if (_plaintextSlot >= r)
throw new I2NPMessageException("Bad slot " + _plaintextSlot);
int size = (int) DataHelper.fromLong(data, offset, 2);
if (size <= 0 || size > MAX_PLAINTEXT_RECORD_SIZE)

View File

@@ -30,6 +30,16 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
super(context, records);
}
/**
* @param record must be ShortEncryptedBuildRecord or null
*/
@Override
public void setRecord(int index, EncryptedBuildRecord record) {
if (record != null && record.length() != SHORT_RECORD_SIZE)
throw new IllegalArgumentException();
super.setRecord(index, record);
}
/**
* Set the slot and data for the plaintext record.
* @throws IllegalArgumentException on bad slot or data length.

View File

@@ -21,6 +21,16 @@ public class ShortTunnelBuildMessage extends TunnelBuildMessage {
super(context, records);
}
/**
* @param record must be ShortEncryptedBuildRecord or null
*/
@Override
public void setRecord(int index, EncryptedBuildRecord record) {
if (record != null && record.length() != SHORT_RECORD_SIZE)
throw new IllegalArgumentException();
super.setRecord(index, record);
}
@Override
protected int calculateWrittenLength() { return 1 + (RECORD_COUNT * SHORT_RECORD_SIZE); }

View File

@@ -35,8 +35,14 @@ public abstract class TunnelBuildMessageBase extends I2NPMessageImpl {
// else will be initialized by readMessage()
}
/**
* @param record may be null
*/
public void setRecord(int index, EncryptedBuildRecord record) { _records[index] = record; }
/**
* @return may be null
*/
public EncryptedBuildRecord getRecord(int index) { return _records[index]; }
/** @since 0.7.12 */

View File

@@ -11,6 +11,9 @@ import net.i2p.data.SessionKey;
import net.i2p.data.i2np.BuildRequestRecord;
import net.i2p.data.i2np.EncryptedBuildRecord;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.ShortEncryptedBuildRecord;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.router.RouterContext;
@@ -32,14 +35,17 @@ public abstract class BuildMessageGenerator {
public static void createRecord(int recordNum, int hop, TunnelBuildMessage msg,
TunnelCreatorConfig cfg, Hash replyRouter,
long replyTunnel, RouterContext ctx, PublicKey peerKey) {
int mtype = msg.getType();
boolean isShort = mtype == InboundTunnelBuildMessage.MESSAGE_TYPE || mtype == ShortTunnelBuildMessage.MESSAGE_TYPE;
EncryptedBuildRecord erec;
if (peerKey != null) {
boolean isEC = peerKey.getType() == EncType.ECIES_X25519;
BuildRequestRecord req;
if ( (!cfg.isInbound()) && (hop + 1 == cfg.getLength()) ) //outbound endpoint
req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel, isEC);
//// TODO if we're the OBEP
req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel, isEC, isShort);
else
req = createUnencryptedRecord(ctx, cfg, hop, null, -1, isEC);
req = createUnencryptedRecord(ctx, cfg, hop, null, -1, isEC, isShort);
if (req == null)
throw new IllegalArgumentException("hop bigger than config");
Hash peer = cfg.getPeer(hop);
@@ -50,26 +56,31 @@ public abstract class BuildMessageGenerator {
erec = req.encryptRecord(ctx, peerKey, peer);
}
} else {
byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE];
int len = isShort ? ShortTunnelBuildMessage.SHORT_RECORD_SIZE : TunnelBuildMessage.RECORD_SIZE;
byte encrypted[] = new byte[len];
if (cfg.isInbound() && hop + 1 == cfg.getLength()) { // IBEP
System.arraycopy(cfg.getPeer(hop).getData(), 0, encrypted, 0, BuildRequestRecord.PEER_SIZE);
ctx.random().nextBytes(encrypted, BuildRequestRecord.PEER_SIZE, TunnelBuildMessage.RECORD_SIZE - BuildRequestRecord.PEER_SIZE);
ctx.random().nextBytes(encrypted, BuildRequestRecord.PEER_SIZE, len - BuildRequestRecord.PEER_SIZE);
byte[] h = new byte[Hash.HASH_LENGTH];
ctx.sha().calculateHash(encrypted, 0, TunnelBuildMessage.RECORD_SIZE, h, 0);
ctx.sha().calculateHash(encrypted, 0, len, h, 0);
cfg.setBlankHash(new Hash(h));
} else {
ctx.random().nextBytes(encrypted);
}
erec = new EncryptedBuildRecord(encrypted);
erec = isShort ? new ShortEncryptedBuildRecord(encrypted) : new EncryptedBuildRecord(encrypted);
}
msg.setRecord(recordNum, erec);
}
/**
* Returns null if hop >= cfg.length
*
* @param isEC must be true if isShort is true
* @param isShort short EC record
*/
private static BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop,
Hash replyRouter, long replyTunnel, boolean isEC) {
Hash replyRouter, long replyTunnel, boolean isEC,
boolean isShort) {
if (hop < cfg.getLength()) {
// ok, now lets fill in some data
HopConfig hopConfig = cfg.getConfig(hop);
@@ -114,9 +125,15 @@ public abstract class BuildMessageGenerator {
BuildRequestRecord rec;
if (isEC) {
// TODO pass properties from cfg
rec = new BuildRequestRecord(ctx, recvTunnelId, nextTunnelId, nextPeer,
nextMsgId, layerKey, ivKey, replyKey,
iv, isInGW, isOutEnd, EmptyProperties.INSTANCE);
if (isShort) {
rec = new BuildRequestRecord(ctx, recvTunnelId, nextTunnelId, nextPeer,
nextMsgId, layerKey, ivKey, replyKey,
iv, isInGW, isOutEnd, EmptyProperties.INSTANCE);
} else {
rec = new BuildRequestRecord(ctx, recvTunnelId, nextTunnelId, nextPeer,
nextMsgId,
isInGW, isOutEnd, EmptyProperties.INSTANCE);
}
} else {
rec = new BuildRequestRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer,
nextMsgId, layerKey, ivKey, replyKey,
@@ -138,6 +155,9 @@ public abstract class BuildMessageGenerator {
*/
public static void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg,
TunnelCreatorConfig cfg, List<Integer> order) {
////// TODO check msg type for short (STBM or ITBM)
int mtype = msg.getType();
boolean isShort = mtype == InboundTunnelBuildMessage.MESSAGE_TYPE || mtype == ShortTunnelBuildMessage.MESSAGE_TYPE;
// encrypt the records so that the right elements will be visible at the right time
for (int i = 0; i < msg.getRecordCount(); i++) {
EncryptedBuildRecord rec = msg.getRecord(i);

View File

@@ -59,6 +59,7 @@ class OutboundGatewayMessage extends PendingGatewayMessage implements CDPQEntry
return 1000;
// building new IB tunnel
case ShortTunnelBuildMessage.MESSAGE_TYPE:
case TunnelBuildMessage.MESSAGE_TYPE:
case VariableTunnelBuildMessage.MESSAGE_TYPE:
return 500;
@@ -78,6 +79,8 @@ class OutboundGatewayMessage extends PendingGatewayMessage implements CDPQEntry
// these shouldn't go into a OBGW
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
case DataMessage.MESSAGE_TYPE:
case InboundTunnelBuildMessage.MESSAGE_TYPE:
case OutboundTunnelBuildReplyMessage.MESSAGE_TYPE:
case TunnelBuildReplyMessage.MESSAGE_TYPE:
case TunnelDataMessage.MESSAGE_TYPE:
case TunnelGatewayMessage.MESSAGE_TYPE:

View File

@@ -11,6 +11,9 @@ import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelDataMessage;
@@ -774,13 +777,15 @@ public class TunnelDispatcher implements Service {
float factor;
if (loc == Location.OBEP) {
// we don't need to check for VTBRM/TBRM as that happens at tunnel creation
if (type == VariableTunnelBuildMessage.MESSAGE_TYPE || type == TunnelBuildMessage.MESSAGE_TYPE)
if (type == VariableTunnelBuildMessage.MESSAGE_TYPE || type == TunnelBuildMessage.MESSAGE_TYPE ||
type == ShortTunnelBuildMessage.MESSAGE_TYPE)
factor = 1 / 1.5f;
else
factor = 1.5f;
} else if (loc == Location.IBGW) {
// we don't need to check for VTBM/TBM as that happens at tunnel creation
if (type == VariableTunnelBuildReplyMessage.MESSAGE_TYPE || type == TunnelBuildReplyMessage.MESSAGE_TYPE)
if (type == VariableTunnelBuildReplyMessage.MESSAGE_TYPE || type == TunnelBuildReplyMessage.MESSAGE_TYPE ||
type == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE || type == InboundTunnelBuildMessage.MESSAGE_TYPE)
factor = 1 / (1.5f * 1.5f * 1.5f);
else
factor = 1 / 1.5f;

View File

@@ -13,11 +13,15 @@ import net.i2p.data.EmptyProperties;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.BuildRequestRecord;
import net.i2p.data.i2np.BuildResponseRecord;
import net.i2p.data.i2np.EncryptedBuildRecord;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildReplyMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
@@ -28,6 +32,8 @@ import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
import net.i2p.router.networkdb.kademlia.MessageWrapper;
import net.i2p.router.peermanager.TunnelHistory;
import net.i2p.router.tunnel.BuildMessageProcessor;
import net.i2p.router.tunnel.BuildReplyHandler;
@@ -166,6 +172,9 @@ class BuildHandler implements Runnable {
ctx.inNetMessagePool().registerHandlerJobBuilder(TunnelBuildReplyMessage.MESSAGE_TYPE, tbrmhjb);
ctx.inNetMessagePool().registerHandlerJobBuilder(VariableTunnelBuildMessage.MESSAGE_TYPE, tbmhjb);
ctx.inNetMessagePool().registerHandlerJobBuilder(VariableTunnelBuildReplyMessage.MESSAGE_TYPE, tbrmhjb);
ctx.inNetMessagePool().registerHandlerJobBuilder(InboundTunnelBuildMessage.MESSAGE_TYPE, tbmhjb);
ctx.inNetMessagePool().registerHandlerJobBuilder(ShortTunnelBuildMessage.MESSAGE_TYPE, tbmhjb);
ctx.inNetMessagePool().registerHandlerJobBuilder(OutboundTunnelBuildReplyMessage.MESSAGE_TYPE, tbrmhjb);
}
/**
@@ -722,6 +731,21 @@ class BuildHandler implements Runnable {
_log.warn("Dropping build request, we are the previous hop: " + req);
return;
}
if (state.msg.getType() == InboundTunnelBuildMessage.MESSAGE_TYPE) {
// can only be at IBGW
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping ITBM, we are not IBGW: " + req);
return;
}
} else {
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) {
// cannot be at IBGW
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping STBM, we are IBGW: " + req);
return;
}
}
if ((!isOutEnd) && (!isInGW)) {
// Previous and next hop the same? Don't help somebody be evil. Drop it without a reply.
@@ -948,20 +972,44 @@ class BuildHandler implements Runnable {
if (isEC) {
// TODO options
Properties props = EmptyProperties.INSTANCE;
reply = BuildResponseRecord.create(_context, response, req.getChaChaReplyKey(), req.getChaChaReplyAD(), props);
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) {
if (isOutEnd) {
// reply will be sent in plaintext in a OTBRM, see below
reply = null;
} else {
// short build record
reply = BuildResponseRecord.createShort(_context, response, req.getChaChaReplyKey(), req.getChaChaReplyAD(), props);
}
} else {
reply = BuildResponseRecord.create(_context, response, req.getChaChaReplyKey(), req.getChaChaReplyAD(), props);
}
} else {
reply = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
}
int records = state.msg.getRecordCount();
int ourSlot = -1;
for (int j = 0; j < records; j++) {
if (state.msg.getRecord(j) == null) {
ourSlot = j;
state.msg.setRecord(j, reply);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Full reply record for slot " + ourSlot + "/" + ourId + "/" + nextId + "/" + req.readReplyMessageId()
// + ": " + Base64.encode(reply));
break;
ShortTunnelBuildMessage stbm = null;
if (state.msg.getType() == InboundTunnelBuildMessage.MESSAGE_TYPE) {
// IBGW only (enforced above)
// Create a ShortTunnelBuildMessage and populate it for sending
InboundTunnelBuildMessage itbm = (InboundTunnelBuildMessage) state.msg;
ourSlot = itbm.getPlaintextSlot();
stbm = new ShortTunnelBuildMessage(_context, records);
for (int j = 0; j < records; j++) {
if (j == ourSlot)
stbm.setRecord(j, reply);
else
stbm.setRecord(j, itbm.getRecord(j));
}
} else {
for (int j = 0; j < records; j++) {
if (state.msg.getRecord(j) == null) {
ourSlot = j;
if (!(isOutEnd && state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE))
state.msg.setRecord(j, reply);
// else reply will be sent in plaintext
break;
}
}
}
@@ -973,9 +1021,14 @@ class BuildHandler implements Runnable {
// now actually send the response
long expires = now + NEXT_HOP_SEND_TIMEOUT;
if (!isOutEnd) {
state.msg.setUniqueId(req.readReplyMessageId());
state.msg.setMessageExpiration(expires);
OutNetMessage msg = new OutNetMessage(_context, state.msg, expires, PRIORITY, nextPeerInfo);
TunnelBuildMessage nextMessage;
if (stbm != null)
nextMessage = stbm;
else
nextMessage = state.msg;
nextMessage.setUniqueId(req.readReplyMessageId());
nextMessage.setMessageExpiration(expires);
OutNetMessage msg = new OutNetMessage(_context, nextMessage, expires, PRIORITY, nextPeerInfo);
if (response == 0)
msg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg));
_context.outNetMessagePool().add(msg);
@@ -984,16 +1037,36 @@ class BuildHandler implements Runnable {
// send it to the reply tunnel on the reply peer within a new TunnelBuildReplyMessage
// (enough layers jrandom?)
TunnelBuildReplyMessage replyMsg;
if (records == TunnelBuildMessage.MAX_RECORD_COUNT)
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) {
OutboundTunnelBuildReplyMessage otbrm = new OutboundTunnelBuildReplyMessage(_context, records);
otbrm.setPlaintextRecord(ourSlot, null); // TODO
replyMsg = otbrm;
} else if (records == TunnelBuildMessage.MAX_RECORD_COUNT) {
replyMsg = new TunnelBuildReplyMessage(_context);
else
} else {
replyMsg = new VariableTunnelBuildReplyMessage(_context, records);
for (int i = 0; i < records; i++)
}
for (int i = 0; i < records; i++) {
replyMsg.setRecord(i, state.msg.getRecord(i));
}
replyMsg.setUniqueId(req.readReplyMessageId());
replyMsg.setMessageExpiration(expires);
I2NPMessage outMessage;
if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) {
// garlic encrypt
SessionKey sk = null; // TODO
RatchetSessionTag st = null; // TODO
outMessage = MessageWrapper.wrap(_context, replyMsg, sk, st);
if (outMessage == null) {
if (_log.shouldWarn())
_log.warn("OTBRM encrypt fail");
return;
}
} else {
outMessage = replyMsg;
}
TunnelGatewayMessage m = new TunnelGatewayMessage(_context);
m.setMessage(replyMsg);
m.setMessage(outMessage);
m.setMessageExpiration(expires);
m.setTunnelId(new TunnelId(nextId));
if (_context.routerHash().equals(nextPeer)) {

View File

@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.i2p.crypto.EncType;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
@@ -11,6 +12,7 @@ import net.i2p.data.router.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.InboundTunnelBuildMessage;
import net.i2p.data.i2np.ShortTunnelBuildMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
import net.i2p.data.i2np.VariableTunnelBuildMessage;
import net.i2p.router.JobImpl;
@@ -31,8 +33,9 @@ import net.i2p.util.VersionComparator;
*/
abstract class BuildRequestor {
private static final List<Integer> ORDER = new ArrayList<Integer>(TunnelBuildMessage.MAX_RECORD_COUNT);
//private static final String MIN_VARIABLE_VERSION = "0.7.12";
private static final String MIN_NEWTBM_VERSION = "0.9.51";
private static final boolean SEND_VARIABLE = true;
private static final boolean SEND_SHORT = false;
private static final int SHORT_RECORDS = 4;
private static final List<Integer> SHORT_ORDER = new ArrayList<Integer>(SHORT_RECORDS);
/** 5 (~2600 bytes) fits nicely in 3 tunnel messages */
@@ -268,18 +271,18 @@ abstract class BuildRequestor {
return true;
}
/** @since 0.7.12 */
/****
we can assume everybody supports variable now...
keep this here for the next time we change the build protocol
private static boolean supportsVariable(RouterContext ctx, Hash h) {
/**
* @since 0.9.51
*/
private static boolean supportsShortTBM(RouterContext ctx, Hash h) {
RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(h);
if (ri == null)
return false;
if (ri.getIdentity().getPublicKey().getType() != EncType.ECIES_X25519)
return false;
String v = ri.getVersion();
return VersionComparator.comp(v, MIN_VARIABLE_VERSION) >= 0;
return VersionComparator.comp(v, MIN_NEWTBM_VERSION) >= 0;
}
****/
/**
* If the tunnel is short enough, and everybody in the tunnel, and the
@@ -294,53 +297,54 @@ keep this here for the next time we change the build protocol
long replyTunnel = 0;
Hash replyRouter;
boolean useVariable = SEND_VARIABLE && cfg.getLength() <= MEDIUM_RECORDS;
boolean useShortTBM = SEND_SHORT && ctx.keyManager().getPublicKey().getType() == EncType.ECIES_X25519;
if (cfg.isInbound()) {
//replyTunnel = 0; // as above
replyRouter = ctx.routerHash();
/****
we can assume everybody supports variable now...
keep this here for the next time we change the build protocol
if (useVariable) {
// check the reply OBEP and all the tunnel peers except ourselves
if (!supportsVariable(ctx, pairedTunnel.getPeer(pairedTunnel.getLength() - 1))) {
useVariable = false;
} else {
for (int i = 0; i < cfg.getLength() - 1; i++) {
if (!supportsVariable(ctx, cfg.getPeer(i))) {
useVariable = false;
break;
}
if (useShortTBM) {
// check all the tunnel peers except ourselves
for (int i = 0; i < cfg.getLength() - 1; i++) {
if (!supportsShortTBM(ctx, cfg.getPeer(i))) {
useShortTBM = false;
break;
}
}
}
****/
} else {
replyTunnel = pairedTunnel.getReceiveTunnelId(0).getTunnelId();
replyRouter = pairedTunnel.getPeer(0);
/****
we can assume everybody supports variable now
keep this here for the next time we change the build protocol
if (useVariable) {
// check the reply IBGW and all the tunnel peers except ourselves
if (!supportsVariable(ctx, replyRouter)) {
useVariable = false;
} else {
for (int i = 1; i < cfg.getLength() - 1; i++) {
if (!supportsVariable(ctx, cfg.getPeer(i))) {
useVariable = false;
break;
}
if (useShortTBM) {
// check all the tunnel peers except ourselves
for (int i = 1; i < cfg.getLength() - 1; i++) {
if (!supportsShortTBM(ctx, cfg.getPeer(i))) {
useShortTBM = false;
break;
}
}
}
****/
}
// populate and encrypt the message
TunnelBuildMessage msg;
List<Integer> order;
if (useVariable) {
if (useShortTBM) {
int len;
if (cfg.getLength() <= SHORT_RECORDS) {
len = SHORT_RECORDS;
order = new ArrayList<Integer>(SHORT_ORDER);
} else if (cfg.getLength() <= MEDIUM_RECORDS) {
len = MEDIUM_RECORDS;
order = new ArrayList<Integer>(MEDIUM_ORDER);
} else {
len = TunnelBuildMessage.MAX_RECORD_COUNT;
order = new ArrayList<Integer>(ORDER);
}
if (cfg.isInbound())
msg = new InboundTunnelBuildMessage(ctx, len);
else
msg = new ShortTunnelBuildMessage(ctx, len);
} else if (useVariable) {
if (cfg.getLength() <= SHORT_RECORDS) {
msg = new VariableTunnelBuildMessage(ctx, SHORT_RECORDS);
order = new ArrayList<Integer>(SHORT_ORDER);