All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.i2p.data.i2np.BuildRequestRecord Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
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
 * 
* * Encrypted: *
 *   bytes    0-15: Hop's truncated identity hash
 *   bytes   16-47: Sender's ephemeral X25519 public key
 *   bytes  48-511: ChaCha20 encrypted BuildRequestRecord
 *   bytes 512-527: Poly1305 MAC
 * 
* * 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
 * 
* * Encrypted: *
 *   bytes    0-15: Hop's truncated identity hash
 *   bytes   16-47: Sender's ephemeral X25519 public key
 *   bytes  48-201: ChaCha20 encrypted BuildRequestRecord
 *   bytes 202-217: Poly1305 MAC
 * 
* */ 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)); } ****/ }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy