Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package net.i2p.data.i2np;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Properties;
import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType;
import net.i2p.crypto.HKDF;
import net.i2p.crypto.KeyFactory;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
/**
* As of 0.9.48, supports two formats.
* As of 0.9.51, supports three formats.
* The original 222-byte ElGamal format, the new 464-byte ECIES format,
* and the newest 154-byte ECIES format.
* See proposal 152 and 157 for details on the new formats.
*
* None of the readXXX() calls are cached. For efficiency,
* they should only be called once.
*
* Original ElGamal format:
*
* Holds the unencrypted 222-byte tunnel request record,
* with a constructor for ElGamal decryption and a method for ElGamal encryption.
* Iterative AES encryption/decryption is done elsewhere.
*
* Cleartext:
*
* bytes 0-3: tunnel ID to receive messages as
* bytes 4-35: local router identity hash (Unused and no accessor here)
* 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-192: next message ID
* bytes 193-221: uninterpreted / random padding
*
*
* Encrypted:
*
* bytes 0-15: First 16 bytes of router hash
* bytes 16-527: ElGamal encrypted block (discarding zero bytes at elg[0] and elg[257])
*
*
* 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.
* Iterative AES encryption/decryption is done elsewhere.
*
* Cleartext:
*
* bytes 0-3: tunnel ID to receive messages as, nonzero
* bytes 4-7: next tunnel ID, nonzero
* bytes 8-39: next router identity hash
* bytes 40-71: AES-256 tunnel layer key
* bytes 72-103: AES-256 tunnel IV key
* bytes 104-135: AES-256 reply key
* bytes 136-151: AES-256 reply IV
* byte 152: flags
* bytes 153-155: more flags, unused, set to 0 for compatibility
* bytes 156-159: request time (in minutes since the epoch, rounded down)
* bytes 160-163: request expiration (in seconds since creation)
* bytes 164-167: next message ID
* bytes 168-x: tunnel build options (Mapping)
* bytes x-x: other data as implied by flags or options
* bytes x-463: random padding
*
*
* ECIES short record format, ref: proposal 157:
*
* Holds the unencrypted 154-byte tunnel request record,
* with a constructor for ECIES decryption and a method for ECIES encryption.
* Iterative AES encryption/decryption is done elsewhere.
*
* Cleartext:
*
* 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-153: random padding
*
*
*/
public class BuildRequestRecord {
private final byte[] _data;
private final boolean _isEC;
private SessionKey _chachaReplyKey;
private byte[] _chachaReplyAD;
// derived keys for short records
private SessionKey _derivedLayerKey;
private SessionKey _derivedIVKey;
private OneTimeSession _derivedGarlicKeys;
/**
* 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;
private static final int DEFAULT_EXPIRATION_SECONDS = 10*60;
private static final int EC_LEN = EncType.ECIES_X25519.getPubkeyLen();
private static final byte[] NULL_KEY = new byte[EC_LEN];
/**
* @return 222 (ElG) or 464 (ECIES) bytes, non-null
*/
public byte[] getData() { return _data; }
// Original ElGamal format
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;
public 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;
private static final int OFF_SEND_MSG_ID = OFF_REQ_TIME + 4;
private static final int PADDING_SIZE = 29;
// 222
private static final int LENGTH = OFF_SEND_MSG_ID + 4 + PADDING_SIZE;
// 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;
private static final int OFF_IV_KEY_EC = OFF_LAYER_KEY_EC + SessionKey.KEYSIZE_BYTES;
public static final int OFF_REPLY_KEY_EC = OFF_IV_KEY_EC + SessionKey.KEYSIZE_BYTES;
private static final int OFF_REPLY_IV_EC = OFF_REPLY_KEY_EC + SessionKey.KEYSIZE_BYTES;
private static final int OFF_FLAG_EC = OFF_REPLY_IV_EC + IV_SIZE;
private static final int OFF_REQ_TIME_EC = OFF_FLAG_EC + 4;
private static final int OFF_EXPIRATION = OFF_REQ_TIME_EC + 4;
private static final int OFF_SEND_MSG_ID_EC = OFF_EXPIRATION + 4;
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_SHORT + 4;
private static final int OFF_SEND_MSG_ID_EC_SHORT = OFF_EXPIRATION_SHORT + 4;
private static final int OFF_OPTIONS_SHORT = OFF_SEND_MSG_ID_EC_SHORT + 4;
// 16 byte trunc. hash, 32 byte eph. key, 16 byte MAC
private static final int LENGTH_EC_SHORT = ShortTunnelBuildMessage.SHORT_RECORD_SIZE - (16 + 32 + 16);
private static final int MAX_OPTIONS_LENGTH_SHORT = LENGTH_EC_SHORT - OFF_OPTIONS_SHORT; // includes options length
// short record HKDF
private static final byte[] ZEROLEN = new byte[0];
private static final String INFO_1 = "SMTunnelReplyKey";
private static final String INFO_2 = "SMTunnelLayerKey";
private static final String INFO_3 = "TunnelLayerIVKey";
private static final String INFO_4 = "RGarlicKeyAndTag";
private static final boolean TEST = false;
private static KeyFactory TESTKF;
/** what tunnel ID should this receive messages on */
public long readReceiveTunnelId() {
return DataHelper.fromLong(_data, OFF_RECV_TUNNEL, 4);
}
/**
* 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() {
int off = _isEC ? OFF_SEND_TUNNEL_EC : OFF_SEND_TUNNEL;
return DataHelper.fromLong(_data, off, 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() {
int off = _isEC ? OFF_SEND_IDENT_EC : OFF_SEND_IDENT;
return Hash.create(_data, off);
}
/**
* Tunnel layer encryption key that the current hop should use
*/
public SessionKey readLayerKey() {
if (_data.length == LENGTH_EC_SHORT)
return _derivedLayerKey;
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
int off = _isEC ? OFF_LAYER_KEY_EC : OFF_LAYER_KEY;
System.arraycopy(_data, off, key, 0, SessionKey.KEYSIZE_BYTES);
return new SessionKey(key);
}
/**
* Tunnel IV encryption key that the current hop should use
*/
public SessionKey readIVKey() {
if (_data.length == LENGTH_EC_SHORT)
return _derivedIVKey;
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
int off = _isEC ? OFF_IV_KEY_EC : OFF_IV_KEY;
System.arraycopy(_data, off, key, 0, SessionKey.KEYSIZE_BYTES);
return new SessionKey(key);
}
/**
* AES Session key that should be used to encrypt the reply.
* Not to be used for short ECIES records; use the ChaChaReplyKey instead.
*/
public SessionKey readReplyKey() {
if (_isEC && _data.length == LENGTH_EC_SHORT)
throw new IllegalStateException();
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
int off = _isEC ? OFF_REPLY_KEY_EC : OFF_REPLY_KEY;
System.arraycopy(_data, off, key, 0, SessionKey.KEYSIZE_BYTES);
return new SessionKey(key);
}
/**
* AES IV that should be used to encrypt the reply.
* Not to be used for short ECIES records.
* @return 16 bytes
*/
public byte[] readReplyIV() {
if (_isEC && _data.length == LENGTH_EC_SHORT)
throw new IllegalStateException();
byte iv[] = new byte[IV_SIZE];
int off = _isEC ? OFF_REPLY_IV_EC : OFF_REPLY_IV;
System.arraycopy(_data, off, 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() {
int off = _isEC ? (_data.length == LENGTH_EC_SHORT ? OFF_FLAG_EC_SHORT : OFF_FLAG_EC)
: OFF_FLAG;
return (_data[off] & 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() {
int off = _isEC ? (_data.length == LENGTH_EC_SHORT ? OFF_FLAG_EC_SHORT : OFF_FLAG_EC)
: OFF_FLAG;
return (_data[off] & FLAG_OUTBOUND_ENDPOINT) != 0;
}
/**
* For ElGamal, time that the request was sent (ms), truncated to the nearest hour.
* For ECIES, time that the request was sent (ms), truncated to the nearest minute.
* This ignores leap seconds.
*/
public long readRequestTime() {
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);
}
/**
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
* this specifies the message ID with which the reply should be sent.
*/
public long readReplyMessageId() {
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);
}
/**
* The expiration in milliseconds from now.
* @since 0.9.48
*/
public long readExpiration() {
if (!_isEC)
return DEFAULT_EXPIRATION_SECONDS * 1000L;
int off = _data.length == LENGTH_EC_SHORT ? OFF_EXPIRATION_SHORT : OFF_EXPIRATION;
return DataHelper.fromLong(_data, off, 4) * 1000L;
}
/**
* ECIES only.
* @return null for ElGamal or on error
* @since 0.9.48
*/
public Properties readOptions() {
if (!_isEC)
return null;
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) {
return null;
} catch (IOException ioe) {
return null;
}
}
/**
* 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;
}
/**
* ECIES short OBEP record only.
* @return null for ElGamal or ECIES long record or non-OBEP
* @since 0.9.51
*/
public OneTimeSession readGarlicKeys() {
return _derivedGarlicKeys;
}
/**
* Encrypt the record to the specified peer. The result is formatted as:
* bytes 0-15: truncated SHA-256 of the current hop's identity (the toPeer parameter)
* bytes 15-527: ElGamal-2048 encrypted block
*
*
* ElGamal only
*
* @return non-null
*/
public EncryptedBuildRecord encryptRecord(I2PAppContext ctx, PublicKey toKey, Hash toPeer) {
EncType type = toKey.getType();
if (type != EncType.ELGAMAL_2048)
throw new IllegalArgumentException();
byte[] out = new byte[EncryptedBuildRecord.LENGTH];
System.arraycopy(toPeer.getData(), 0, out, 0, PEER_SIZE);
byte encrypted[] = ctx.elGamalEngine().encrypt(_data, 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, PEER_SIZE, 256);
System.arraycopy(encrypted, 258, out, 256 + PEER_SIZE, 256);
return new EncryptedBuildRecord(out);
}
/**
* Encrypt the record to the specified peer. ECIES only.
* The ChaCha reply key and IV will be available via the getters
* after this call.
* For short records, derived keys will be available via
* readLayerKey(), readIVKey(), and readGarlicKeys() after this call.
* See class javadocs for format.
* See proposals 152 and 157.
*
* @return non-null
* @since 0.9.48
*/
public EncryptedBuildRecord encryptECIESRecord(RouterContext ctx, PublicKey toKey, Hash toPeer) {
EncType type = toKey.getType();
if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException();
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 {
KeyFactory kf = TEST ? TESTKF : ctx.commSystem().getXDHFactory();
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, _data.length);
EncryptedBuildRecord rv = isShort ? new ShortEncryptedBuildRecord(out) : new EncryptedBuildRecord(out);
_chachaReplyAD = new byte[32];
System.arraycopy(state.getHandshakeHash(), 0, _chachaReplyAD, 0, 32);
byte[] ck = state.getChainingKey();
if (isShort) {
byte[] crk = new byte[32];
byte[] newck = new byte[32];
HKDF hkdf = new HKDF(ctx);
hkdf.calculate(ck, ZEROLEN, INFO_1, newck, crk, 0);
_chachaReplyKey = new SessionKey(crk);
System.arraycopy(newck, 0, ck, 0, 32);
byte[] dlk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_2, newck, dlk, 0);
_derivedLayerKey = new SessionKey(dlk);
boolean isOBEP = readIsOutboundEndpoint();
if (isOBEP) {
System.arraycopy(newck, 0, ck, 0, 32);
byte[] divk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_3, newck, divk, 0);
_derivedIVKey = new SessionKey(divk);
System.arraycopy(newck, 0, ck, 0, 32);
byte[] dgk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_4, newck, dgk, 0);
SessionKey sdgk = new SessionKey(dgk);
RatchetSessionTag rst = new RatchetSessionTag(newck);
_derivedGarlicKeys = new OneTimeSession(sdgk, rst);
} else {
_derivedIVKey = new SessionKey(newck);
}
} else {
_chachaReplyKey = new SessionKey(ck);
}
return rv;
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("failed", gse);
} finally {
if (state != null)
state.destroy();
}
}
/**
* Valid after calling encryptECIESRecord() or after the decrypting constructor
* with an ECIES private key.
* See proposal 152.
*
* @return null if no ECIES encrypt/decrypt operation was performed
* @since 0.9.48
*/
public SessionKey getChaChaReplyKey() { return _chachaReplyKey; }
/**
* Valid after calling encryptECIESRecord() or after the decrypting constructor
* with an ECIES private key.
* See proposal 152.
*
* @return null if no ECIES encrypt/decrypt operation was performed
* @since 0.9.48
*/
public byte[] getChaChaReplyAD() { return _chachaReplyAD; }
/**
* Decrypt the data from the specified record, writing the decrypted record into this instance's
* data buffer
*
* Caller MUST check that first 16 bytes of our hash matches first 16 bytes of encryptedRecord
* before calling this. Not checked here.
*
* The ChaCha reply key and IV will be available via the getters
* after this call if ourKey is ECIES.
*
* @throws DataFormatException on decrypt fail
* @since 0.9.48
*/
public BuildRequestRecord(RouterContext ctx, PrivateKey ourKey,
EncryptedBuildRecord encryptedRecord) throws DataFormatException {
byte[] encrypted = encryptedRecord.getData();
byte decrypted[];
EncType type = ourKey.getType();
if (type == EncType.ELGAMAL_2048) {
byte preDecrypt[] = new byte[514];
System.arraycopy(encrypted, PEER_SIZE, preDecrypt, 1, 256);
System.arraycopy(encrypted, PEER_SIZE + 256, preDecrypt, 258, 256);
decrypted = ctx.elGamalEngine().decrypt(preDecrypt, ourKey);
_isEC = false;
} else if (type == EncType.ECIES_X25519) {
// There's several reasons to get bogus-encrypted requests:
// very old Java and i2pd routers that don't check the type at all and send ElG,
// i2pd treating type 4 like type 1,
// and very old i2pd routers like 0.9.32 that have all sorts of bugs.
// The following 3 checks weed out about 85% before we get to the DH.
// fast MSB check for key < 2^255
if ((encrypted[PEER_SIZE + EC_LEN - 1] & 0x80) != 0)
throw new DataFormatException("Bad PK decrypt fail");
// i2pd 0.9.46/47 bug, treating us like type 1
if (DataHelper.eq(ourKey.toPublic().getData(), 0, encrypted, PEER_SIZE, EC_LEN))
throw new DataFormatException("Our PK decrypt fail");
// very old i2pd bug?
if (DataHelper.eq(NULL_KEY, 0, encrypted, PEER_SIZE, EC_LEN))
throw new DataFormatException("Null PK decrypt fail");
HandshakeState state = null;
try {
KeyFactory kf = TEST ? TESTKF : ctx.commSystem().getXDHFactory();
state = new HandshakeState(HandshakeState.PATTERN_ID_N, HandshakeState.RESPONDER, kf);
state.getLocalKeyPair().setKeys(ourKey.getData(), 0,
ourKey.toPublic().getData(), 0);
state.start();
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);
_chachaReplyAD = new byte[32];
System.arraycopy(state.getHandshakeHash(), 0, _chachaReplyAD, 0, 32);
byte[] ck = state.getChainingKey();
if (isShort) {
byte[] crk = new byte[32];
byte[] newck = new byte[32];
HKDF hkdf = new HKDF(ctx);
hkdf.calculate(ck, ZEROLEN, INFO_1, newck, crk, 0);
_chachaReplyKey = new SessionKey(crk);
System.arraycopy(newck, 0, ck, 0, 32);
byte[] dlk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_2, newck, dlk, 0);
_derivedLayerKey = new SessionKey(dlk);
boolean isOBEP = (decrypted[OFF_FLAG_EC_SHORT] & FLAG_OUTBOUND_ENDPOINT) != 0;
if (isOBEP) {
System.arraycopy(newck, 0, ck, 0, 32);
byte[] divk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_3, newck, divk, 0);
_derivedIVKey = new SessionKey(divk);
System.arraycopy(newck, 0, ck, 0, 32);
byte[] dgk = new byte[32];
hkdf.calculate(ck, ZEROLEN, INFO_4, newck, dgk, 0);
SessionKey sdgk = new SessionKey(dgk);
RatchetSessionTag rst = new RatchetSessionTag(newck);
_derivedGarlicKeys = new OneTimeSession(sdgk, rst);
} else {
_derivedIVKey = new SessionKey(newck);
}
} else {
_chachaReplyKey = new SessionKey(ck);
}
} catch (GeneralSecurityException gse) {
if (state != null) {
Log log = ctx.logManager().getLog(BuildRequestRecord.class);
if (log.shouldInfo())
log.info("ECIES BRR decrypt failure, state at failure:\n" + state);
}
throw new DataFormatException("ChaCha decrypt fail", gse);
} finally {
if (state != null)
state.destroy();
}
_isEC = true;
} else {
throw new DataFormatException("Unsupported EncType " + type);
}
if (decrypted != null) {
_data = decrypted;
} else {
throw new DataFormatException("decrypt fail");
}
}
/**
* Populate this instance with data. A new buffer is created to contain the data, with the
* necessary randomized padding.
*
* ElGamal only. ECIES constructor below.
*
* @param receiveTunnelId tunnel the current hop will receive messages on
* @param peer current hop's identity, unused, no read() method
* @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 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?
* @since 0.9.18, was createRecord()
*/
public BuildRequestRecord(I2PAppContext ctx, long receiveTunnelId, Hash peer, long nextTunnelId, Hash nextHop, long nextMsgId,
SessionKey layerKey, SessionKey ivKey, SessionKey replyKey, byte iv[], boolean isInGateway,
boolean isOutEndpoint) {
byte buf[] = new byte[LENGTH];
_data = buf;
_isEC = false;
/* 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-192: next message ID
* bytes 193-221: 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();
// prevent hop identification at top of the hour
truncatedHour -= ctx.random().nextInt(90*1000);
// this ignores leap seconds
truncatedHour /= (60l*60l*1000l);
DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour);
DataHelper.toLong(buf, OFF_SEND_MSG_ID, 4, nextMsgId);
ctx.random().nextBytes(buf, OFF_SEND_MSG_ID+4, PADDING_SIZE);
}
/**
* Populate this instance with data. A new buffer is created to contain the data, with the
* necessary randomized padding.
*
* 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)
* @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 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?
* @param options 296 bytes max when serialized
* @since 0.9.48
* @throws IllegalArgumentException if options too long
*/
public BuildRequestRecord(I2PAppContext ctx, long receiveTunnelId, long nextTunnelId, Hash nextHop, long nextMsgId,
SessionKey layerKey, SessionKey ivKey, SessionKey replyKey, byte iv[], boolean isInGateway,
boolean isOutEndpoint, Properties options) {
byte buf[] = new byte[LENGTH_EC];
_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);
System.arraycopy(layerKey.getData(), 0, buf, OFF_LAYER_KEY_EC, SessionKey.KEYSIZE_BYTES);
System.arraycopy(ivKey.getData(), 0, buf, OFF_IV_KEY_EC, SessionKey.KEYSIZE_BYTES);
System.arraycopy(replyKey.getData(), 0, buf, OFF_REPLY_KEY_EC, SessionKey.KEYSIZE_BYTES);
System.arraycopy(iv, 0, buf, OFF_REPLY_IV_EC, IV_SIZE);
if (isInGateway)
buf[OFF_FLAG_EC] |= FLAG_UNRESTRICTED_PREV;
else if (isOutEndpoint)
buf[OFF_FLAG_EC] |= FLAG_OUTBOUND_ENDPOINT;
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, 4, truncatedMinute);
DataHelper.toLong(buf, OFF_EXPIRATION, 4, DEFAULT_EXPIRATION_SECONDS);
DataHelper.toLong(buf, OFF_SEND_MSG_ID_EC, 4, nextMsgId);
try {
int off = DataHelper.toProperties(buf, OFF_OPTIONS, options);
int sz = LENGTH_EC - off;
if (sz > 0)
ctx.random().nextBytes(buf, off, sz);
} catch (Exception e) {
throw new IllegalArgumentException("options", e);
}
}
/**
* 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 98 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
*/
@Override
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();
if (isIBGW) {
buf.append("IBGW in: ").append(readReceiveTunnelId())
.append(" out ").append(readNextTunnelId());
} else if (isOBEP) {
buf.append("OBEP in: ").append(readReceiveTunnelId());
} else {
buf.append("part. in: ").append(readReceiveTunnelId())
.append(" out: ").append(readNextTunnelId());
}
buf.append(" to: ").append(readNextIdentity());
buf.append(" layer key: ").append(readLayerKey())
.append(" IV key: ").append(readIVKey());
if (_data.length != LENGTH_EC_SHORT) {
buf.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) {
buf.append(" options: ").append(readOptions());
if (_chachaReplyKey != null) {
buf.append(" chacha reply key: ").append(_chachaReplyKey)
.append(" chacha reply IV: ").append(Base64.encode(_chachaReplyAD));
}
if (_derivedGarlicKeys != null) {
buf.append(" garlic reply key: ").append(_derivedGarlicKeys.key)
.append(" garlic reply tag: ").append(_derivedGarlicKeys.rtag);
}
}
// to chase i2pd bug
//buf.append('\n').append(net.i2p.util.HexDump.dump(readReplyKey().getData()));
return buf.toString();
}
/****
public static void main(String[] args) throws Exception {
System.out.println("OFF_OPTIONS is " + OFF_OPTIONS);
RouterContext ctx = new RouterContext(null);
TESTKF = new net.i2p.router.transport.crypto.X25519KeyFactory(ctx);
byte[] h = new byte[32];
ctx.random().nextBytes(h);
Hash bh = new Hash(h);
SessionKey k1 = ctx.keyGenerator().generateSessionKey();
SessionKey k2 = ctx.keyGenerator().generateSessionKey();
SessionKey k3 = ctx.keyGenerator().generateSessionKey();
byte[] iv = new byte[16];
ctx.random().nextBytes(iv);
Properties props = new Properties();
props.setProperty("foo", "bar");
BuildRequestRecord brr = new BuildRequestRecord(ctx, 1, 2, bh, 3, k1, k2, k3, iv, false, false, props);
System.out.println(brr.toString());
System.out.println("\nplaintext request:\n" + net.i2p.util.HexDump.dump(brr.getData()));
net.i2p.crypto.KeyPair kp = ctx.keyGenerator().generatePKIKeys(net.i2p.crypto.EncType.ECIES_X25519);
PublicKey bpub = kp.getPublic();
PrivateKey bpriv = kp.getPrivate();
EncryptedBuildRecord record = brr.encryptECIESRecord(ctx, bpub, bh);
System.out.println("\nencrypted request:\n" + net.i2p.util.HexDump.dump(record.getData()));
System.out.println("reply key: " + brr.getChaChaReplyKey());
System.out.println("reply IV: " + net.i2p.data.Base64.encode(brr.getChaChaReplyAD()));
BuildRequestRecord brr2 = new BuildRequestRecord(ctx, bpriv, record);
System.out.println(brr2.toString());
System.out.println("\nreply key: " + brr2.getChaChaReplyKey());
System.out.println("reply IV: " + net.i2p.data.Base64.encode(brr2.getChaChaReplyAD()));
props.setProperty("yes", "no");
EncryptedBuildRecord ebr = BuildResponseRecord.create(ctx, 1, brr.getChaChaReplyKey(), brr.getChaChaReplyAD(), props);
System.out.println("\nencrypted reply:\n" + net.i2p.util.HexDump.dump(ebr.getData()));
BuildResponseRecord.decrypt(ebr, brr2.getChaChaReplyKey(), brr2.getChaChaReplyAD());
System.out.println("\nplaintext reply:\n" + net.i2p.util.HexDump.dump(ebr.getData()));
Properties p2 = new net.i2p.util.OrderedProperties();
DataHelper.fromProperties(ebr.getData(), 0, p2);
System.out.println("reply props: " + p2);
System.out.println("reply status: " + (ebr.getData()[511] & 0xff));
}
****/
}