diff --git a/router/java/src/net/i2p/router/tunnel/GatewayMessage.java b/router/java/src/net/i2p/router/tunnel/GatewayMessage.java deleted file mode 100644 index 8f9ffbfde5a1bc9660c47baff06d72cd71817967..0000000000000000000000000000000000000000 --- a/router/java/src/net/i2p/router/tunnel/GatewayMessage.java +++ /dev/null @@ -1,369 +0,0 @@ -package net.i2p.router.tunnel; - -import java.util.ArrayList; -import java.util.Collections; - -import net.i2p.I2PAppContext; -import net.i2p.data.Base64; -import net.i2p.data.DataHelper; -import net.i2p.data.Hash; -import net.i2p.data.SessionKey; -import net.i2p.util.Log; - -/** - * <p>Manage the actual encryption necessary to turn a message into what the - * gateway needs to know so that it can send out a tunnel message. See the doc - * "tunnel.html" for more details on the algorithm and motivation.</p> - * - */ -public class GatewayMessage { - private I2PAppContext _context; - private Log _log; - /** _iv[i] is the IV used to encrypt the entire message at peer i */ - private byte _iv[][]; - /** payload, overwritten with the encrypted data at each peer */ - private byte _payload[]; - /** _eIV[i] is the IV used to encrypt the checksum block at peer i */ - private byte _eIV[][]; - /** _H[i] is the SHA256 of the decrypted payload as seen at peer i */ - private byte _H[][]; - /** _order[i] is the column of the checksum block in which _H[i] will be placed */ - private int _order[]; - /** - * _eH[column][row] contains the (encrypted) hash of _H[_order[column]] as seen in the - * column at peer 'row' BEFORE decryption. when _order[column] == row+1, the hash is - * in the cleartext. - */ - private byte _eH[][][]; - /** _preV is _eH[*][8] */ - private byte _preV[]; - /** - * _V is SHA256 of _preV, overwritten with its own encrypted value at each - * layer, using the last IV_SIZE bytes of _eH[7][*] as the IV - */ - private byte _V[]; - /** if true, the data has been encrypted */ - private boolean _encrypted; - /** if true, someone gave us a payload */ - private boolean _payloadSet; - /** includes the gateway and endpoint */ - static final int HOPS = 8; - /** aes256 */ - static final int IV_SIZE = 16; - private static final byte EMPTY[] = new byte[0]; - private static final int COLUMNS = HOPS; - private static final int HASH_ROWS = HOPS; - /** # bytes of the hash to maintain in each column */ - static final int COLUMN_WIDTH = IV_SIZE; - /** # bytes of the verification hash to maintain */ - static final int VERIFICATION_WIDTH = IV_SIZE; - - /** used to munge the IV during per-hop translations */ - static final byte IV_WHITENER[] = new byte[] { (byte)0x31, (byte)0xd6, (byte)0x74, (byte)0x17, - (byte)0xa0, (byte)0xb6, (byte)0x28, (byte)0xed, - (byte)0xdf, (byte)0xee, (byte)0x5b, (byte)0x86, - (byte)0x74, (byte)0x61, (byte)0x50, (byte)0x7d }; - - public GatewayMessage(I2PAppContext ctx) { - _context = ctx; - _log = ctx.logManager().getLog(GatewayMessage.class); - initialize(); - } - - private void initialize() { - _iv = new byte[HOPS-1][IV_SIZE]; - _eIV = new byte[HOPS-1][IV_SIZE]; - _H = new byte[HOPS][Hash.HASH_LENGTH]; - _eH = new byte[COLUMNS][HASH_ROWS][COLUMN_WIDTH]; - _preV = new byte[HOPS*COLUMN_WIDTH]; - _V = new byte[VERIFICATION_WIDTH]; - _order = new int[HOPS]; - _encrypted = false; - _payloadSet = false; - } - - /** - * Provide the data to be encrypted through the tunnel. This data will - * be available only to the tunnel endpoint as it is entered. Its size - * must be a multiple of 16 bytes (0 is a valid size). The parameter is - * overwritten during encryption. - * - */ - public void setPayload(byte payload[]) { - if ( (payload != null) && (payload.length % 16 != 0) ) - throw new IllegalArgumentException("Payload size must be a multiple of 16"); - _payload = payload; - _payloadSet = true; - } - - /** - * Actually encrypt the payload, producing the tunnel checksum that is verified - * at each hop as well as the verification block that verifies the checksum at - * the end of the tunnel. After encrypting, the data can be retrieved by - * exporting it to a byte array. - * - */ - public void encrypt(GatewayTunnelConfig config) { - if (!_payloadSet) throw new IllegalStateException("You must set the payload before encrypting"); - buildOrder(); - encryptIV(config); - encryptPayload(config); - encryptChecksumBlocks(config); - encryptVerificationHash(config); - _encrypted = true; - } - - /** - * We want the hashes to be placed randomly throughout the checksum block so - * that sometimes the first peer finds their hash in column 3, sometimes in - * column 1, etc (and hence, doesn't know whether they are the first peer) - * - */ - private final void buildOrder() { - ArrayList order = new ArrayList(HOPS); - for (int i = 0; i < HOPS; i++) - order.add(new Integer(i)); - Collections.shuffle(order, _context.random()); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("_order = " + order); - - for (int i = 0; i < HOPS; i++) { - _order[i] = ((Integer)order.get(i)).intValue(); - } - } - - /** - * The IV is a random value, encrypted backwards and hashed along the way to - * destroy any keying material from colluding attackers. - * - */ - private final void encryptIV(GatewayTunnelConfig cfg) { - _context.random().nextBytes(_iv[0]); - - for (int i = 1; i < HOPS - 1; i++) { - SessionKey key = cfg.getSessionKey(i); - - // decrypt, since we're simulating what the participants do - _context.aes().decryptBlock(_iv[i-1], 0, key, _iv[i], 0); - DataHelper.xor(_iv[i], 0, IV_WHITENER, 0, _iv[i], 0, IV_SIZE); - Hash h = _context.sha().calculateHash(_iv[i]); - System.arraycopy(h.getData(), 0, _iv[i], 0, IV_SIZE); - } - - if (_log.shouldLog(Log.DEBUG)) { - for (int i = 0; i < HOPS-1; i++) - _log.debug("_iv[" + i + "] = " + Base64.encode(_iv[i])); - } - } - - /** - * Encrypt the payload and IV blocks, overwriting the _payload, - * populating _H[] with the SHA256(payload) along the way and placing - * the last 16 bytes of the encrypted payload into eIV[] at each step. - * - */ - private final void encryptPayload(GatewayTunnelConfig cfg) { - int numBlocks = (_payload != null ? _payload.length / 16 : 0); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("# payload blocks: " + numBlocks); - - for (int i = HOPS - 1; i > 0; i--) { - SessionKey key = cfg.getSessionKey(i); - - if ( (_payload != null) && (_payload.length > 0) ) { - // set _H[i] = SHA256 of the payload seen at the i'th peer after decryption - Hash h = _context.sha().calculateHash(_payload); - System.arraycopy(h.getData(), 0, _H[i], 0, Hash.HASH_LENGTH); - - // first block, use the IV - DataHelper.xor(_iv[i-1], 0, _payload, 0, _payload, 0, IV_SIZE); - _context.aes().encryptBlock(_payload, 0, key, _payload, 0); - - for (int j = 1; j < numBlocks; j++) { - // subsequent blocks, use the prev block as IV (aka CTR mode) - DataHelper.xor(_payload, (j-1)*IV_SIZE, _payload, j*IV_SIZE, _payload, j*IV_SIZE, IV_SIZE); - _context.aes().encryptBlock(_payload, j*IV_SIZE, key, _payload, j*IV_SIZE); - } - - System.arraycopy(_payload, _payload.length - IV_SIZE, _eIV[i-1], 0, IV_SIZE); - } else { - Hash h = _context.sha().calculateHash(EMPTY); - System.arraycopy(h.getData(), 0, _H[i], 0, Hash.HASH_LENGTH); - - // nothing to encrypt... pass on the IV to the checksum blocks - System.arraycopy(_iv, 0, _eIV[i-1], 0, IV_SIZE); - } - } - - // we need to know what the gateway would "decrypt" to, even though they - // aren't actually doing any decrypting (so the first peer can't look - // back and say "hey, the previous peer had no match, they must be the - // gateway") - Hash h0 = null; - if ( (_payload != null) && (_payload.length > 0) ) - h0 = _context.sha().calculateHash(_payload); - else - h0 = _context.sha().calculateHash(EMPTY); - System.arraycopy(h0.getData(), 0, _H[0], 0, Hash.HASH_LENGTH); - - if (_log.shouldLog(Log.DEBUG)) { - for (int i = 0; i < HOPS-1; i++) - _log.debug("_eIV["+ i + "] = " + Base64.encode(_eIV[i])); - for (int i = 0; i < HOPS; i++) - _log.debug("_H["+ i + "] = " + Base64.encode(_H[i])); - } - } - - /** - * Fill in the _eH[column][step] matrix with the encrypted _H values so - * that at each step, exactly one column will contain the _H value for - * that step in the clear. _eH[column][0] will contain what is sent from - * the gateway (peer0) to the first hop (peer1). The encryption uses the _eIV for each - * step so that a plain AES/CTR decrypt of the entire message will expose - * the layer. The columns are ordered according to _order - */ - private final void encryptChecksumBlocks(GatewayTunnelConfig cfg) { - for (int column = 0; column < COLUMNS; column++) { - // which _H[hash] value are we rendering in this column? - int hash = _order[column]; - // fill in the cleartext version for this column - System.arraycopy(_H[hash], 0, _eH[column][hash], 0, COLUMN_WIDTH); - - // now fill in the "earlier" _eH[column][row-1] values for earlier hops - // by encrypting _eH[column][row] with the peer's key, using the - // previous column (or _eIV[row-1]) as the IV - for (int row = hash; row > 0; row--) { - SessionKey key = cfg.getSessionKey(row); - if (column == 0) { - DataHelper.xor(_eIV[row-1], 0, _eH[column][row], 0, _eH[column][row-1], 0, IV_SIZE); - } else { - DataHelper.xor(_eH[column-1][row-1], 0, _eH[column][row], 0, _eH[column][row-1], 0, IV_SIZE); - } - _context.aes().encryptBlock(_eH[column][row-1], 0, key, _eH[column][row-1], 0); - } - - // fill in the "later" rows by encrypting the previous rows with the - // appropriate key, using the end of the previous column (or _eIV[row]) - // as the IV - for (int row = hash + 1; row < HASH_ROWS; row++) { - // row is the one we are *writing* to - SessionKey key = cfg.getSessionKey(row); - - _context.aes().decryptBlock(_eH[column][row-1], 0, key, _eH[column][row], 0); - if (column == 0) - DataHelper.xor(_eIV[row-1], 0, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE); - else - DataHelper.xor(_eH[column-1][row-1], 0, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE); - } - } - - if (_log.shouldLog(Log.DEBUG)) { - _log.debug("_eH[column][peer]"); - for (int peer = 0; peer < HASH_ROWS; peer++) { - for (int column = 0; column < COLUMNS; column++) { - try { - _log.debug("_eH[" + column + "][" + peer + "] = " + Base64.encode(_eH[column][peer]) - + (peer == 0 ? "" : DataHelper.eq(_H[peer-1], 0, _eH[column][peer], 0, COLUMN_WIDTH) ? " CLEARTEXT" : "")); - } catch (Exception e) { - e.printStackTrace(); - System.out.println("column="+column + " peer=" + peer); - } - } - } - } - } - - /** - * Build the _V hash as the SHA256 of _eH[*][7], then encrypt it on top of - * itself using the last 16 bytes of _eH[7][*] as the IV. - * - */ - private final void encryptVerificationHash(GatewayTunnelConfig cfg) { - for (int i = 0; i < COLUMNS; i++) - System.arraycopy(_eH[i][HASH_ROWS-1], 0, _preV, i * COLUMN_WIDTH, COLUMN_WIDTH); - Hash v = _context.sha().calculateHash(_preV); - System.arraycopy(v.getData(), 0, _V, 0, VERIFICATION_WIDTH); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("_V final = " + Base64.encode(_V)); - - for (int i = HOPS - 1; i > 0; i--) { - SessionKey key = cfg.getSessionKey(i); - // xor the last block of the encrypted payload with the first block of _V to - // continue the CTR operation - DataHelper.xor(_V, 0, _eH[COLUMNS-1][i-1], 0, _V, 0, IV_SIZE); - _context.aes().encryptBlock(_V, 0, key, _V, 0); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("_V at peer " + i + " = " + Base64.encode(_V)); - } - - // _V now contains the hash of what the checksum blocks look like on the - // endpoint, encrypted for delivery to the first hop - } - - /** - * Calculate the total size of the encrypted tunnel message that needs to be - * sent to the first hop. This includes the IV and checksum/verification hashes. - * - */ - public final int getExportedSize() { - return IV_SIZE + - _payload.length + - COLUMNS * COLUMN_WIDTH + - VERIFICATION_WIDTH; // verification hash - } - - /** - * Write out the fully encrypted tunnel message to the target - * (starting with what peer1 should see) - * - * @param target array to write to, which must be large enough - * @param offset offset into the array to start writing - * @return offset into the array after writing - * @throws IllegalStateException if it is not yet encrypted - * @throws NullPointerException if the target is null - * @throws IllegalArgumentException if the target is too small - */ - public final int export(byte target[], int offset) { - if (!_encrypted) throw new IllegalStateException("Not yet encrypted - please call encrypt"); - if (target == null) throw new NullPointerException("Target is null"); - if (target.length - offset < getExportedSize()) throw new IllegalArgumentException("target is too small"); - - int cur = offset; - System.arraycopy(_iv[0], 0, target, cur, IV_SIZE); - cur += IV_SIZE; - System.arraycopy(_payload, 0, target, cur, _payload.length); - cur += _payload.length; - for (int column = 0; column < COLUMNS; column++) { - System.arraycopy(_eH[column][0], 0, target, cur, COLUMN_WIDTH); - cur += COLUMN_WIDTH; - } - System.arraycopy(_V, 0, target, cur, VERIFICATION_WIDTH); - cur += VERIFICATION_WIDTH; - return cur; - } - - /** - * Verify that the checksum block is as it should be (per _eH[*][peer+1]) - * when presented with what the peer has decrypted. Useful for debugging - * only. - */ - public final boolean compareChecksumBlock(I2PAppContext ctx, byte message[], int peer) { - Log log = ctx.logManager().getLog(GatewayMessage.class); - boolean match = true; - - int off = message.length - (COLUMNS + 1) * COLUMN_WIDTH; - for (int column = 0; column < COLUMNS; column++) { - boolean ok = DataHelper.eq(_eH[column][peer], 0, message, off, COLUMN_WIDTH); - if (log.shouldLog(Log.DEBUG)) - log.debug("checksum[" + column + "][" + (peer) + "] matches? " + ok); - - off += COLUMN_WIDTH; - match = match && ok; - } - - return match; - } -} diff --git a/router/java/src/net/i2p/router/tunnel/GatewayTunnelConfig.java b/router/java/src/net/i2p/router/tunnel/GatewayTunnelConfig.java deleted file mode 100644 index b9b671844d5891face553ebebdc5b564d55349e7..0000000000000000000000000000000000000000 --- a/router/java/src/net/i2p/router/tunnel/GatewayTunnelConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.i2p.router.tunnel; - -import net.i2p.data.SessionKey; - -/** - * Coordinate the data that the gateway to a tunnel needs to know - * - */ -public class GatewayTunnelConfig { - /** the key for the first hop after the gateway is in _keys[0] */ - private SessionKey _keys[]; - - /** Creates a new instance of TunnelConfig */ - public GatewayTunnelConfig() { - _keys = new SessionKey[GatewayMessage.HOPS]; - } - - /** What is the session key for the given hop? */ - public SessionKey getSessionKey(int layer) { return _keys[layer]; } - public void setSessionKey(int layer, SessionKey key) { _keys[layer] = key; } -} diff --git a/router/java/src/net/i2p/router/tunnel/HopConfig.java b/router/java/src/net/i2p/router/tunnel/HopConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..8ad2e48ce401d5789383158fd1bba02f7615b617 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/HopConfig.java @@ -0,0 +1,70 @@ +package net.i2p.router.tunnel; + +import java.util.Map; + +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; + +/** + * Defines the general configuration for a hop in a tunnel. + * + */ +public class HopConfig { + private byte _receiveTunnelId[]; + private Hash _receiveFrom; + private byte _sendTunnelId[]; + private Hash _sendTo; + private SessionKey _layerKey; + private SessionKey _ivKey; + private long _expiration; + private Map _options; + + public HopConfig() { + _receiveTunnelId = null; + _receiveFrom = null; + _sendTunnelId = null; + _sendTo = null; + _layerKey = null; + _ivKey = null; + _expiration = -1; + _options = null; + } + + /** what tunnel ID are we receiving on? */ + public byte[] getReceiveTunnelId() { return _receiveTunnelId; } + public void setReceiveTunnelId(byte id[]) { _receiveTunnelId = id; } + + /** what is the previous peer in the tunnel (if any)? */ + public Hash getReceiveFrom() { return _receiveFrom; } + public void setReceiveFrom(Hash from) { _receiveFrom = from; } + + /** what is the next tunnel ID we are sending to? */ + public byte[] getSendTunnelId() { return _sendTunnelId; } + public void setSendTunnelId(byte id[]) { _sendTunnelId = id; } + + /** what is the next peer in the tunnel (if any)? */ + public Hash getSendTo() { return _sendTo; } + public void setSendTo(Hash to) { _sendTo = to; } + + /** what key should we use to encrypt the layer before passing it on? */ + public SessionKey getLayerKey() { return _layerKey; } + public void setLayerKey(SessionKey key) { _layerKey = key; } + + /** what key should we use to encrypt the preIV before passing it on? */ + public SessionKey getIVKey() { return _ivKey; } + public void setIVKey(SessionKey key) { _ivKey = key; } + + /** when does this tunnel expire (in ms since the epoch)? */ + public long getExpiration() { return _expiration; } + public void setExpiration(long when) { _expiration = when; } + + /** + * what are the configuration options for this tunnel (if any)? keys to + * this map should be strings and values should be Objects of an + * option-specific type (e.g. "maxMessages" would be an Integer, "shouldPad" + * would be a Boolean, etc). + * + */ + public Map getOptions() { return _options; } + public void setOptions(Map options) { _options = options; } +} diff --git a/router/java/src/net/i2p/router/tunnel/HopProcessor.java b/router/java/src/net/i2p/router/tunnel/HopProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..35140e20f8ca737e8c35413fb0db121774d5c42e --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/HopProcessor.java @@ -0,0 +1,93 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.util.Log; + +/** + * Take a received tunnel message, verify that it isn't a + * duplicate, and translate it into what the next hop will + * want. The hop processor works the same on all peers - + * inbound and outbound participants, outbound endpoints, + * and inbound gateways (with a small modification per + * InbuondGatewayProcessor). + * + */ +public class HopProcessor { + protected I2PAppContext _context; + private Log _log; + protected HopConfig _config; + private IVValidator _validator; + + static final int IV_LENGTH = 16; + + public HopProcessor(I2PAppContext ctx, HopConfig config) { + _context = ctx; + _log = ctx.logManager().getLog(HopProcessor.class); + _config = config; + _validator = createValidator(); + } + + protected IVValidator createValidator() { + return new HashSetIVValidator(); + } + + /** + * Process the data for the current hop, overwriting the original data with + * what should be sent to the next peer. This also validates the previous + * peer and the IV, making sure its not a repeat and not a loop. + * + * @param orig IV+data of the message + * @param offset index into orig where the IV begins + * @param length how long after the offset does the message go for? + * @param prev previous hop in the tunnel, or null if we are the gateway + * @return true if the message was updated and valid, false if it was not. + */ + public boolean process(byte orig[], int offset, int length, Hash prev) { + // prev is null on gateways + if (prev != null) { + if (_config.getReceiveFrom() == null) + _config.setReceiveFrom(prev); + if (!_config.getReceiveFrom().equals(prev)) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Invalid previous peer - attempted hostile loop? from " + prev + + ", expected " + _config.getReceiveFrom()); + return false; + } + } + + byte iv[] = new byte[IV_LENGTH]; + System.arraycopy(orig, offset, iv, 0, IV_LENGTH); + boolean okIV = _validator.receiveIV(iv); + if (!okIV) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid IV received on tunnel " + _config.getReceiveTunnelId()); + return false; + } + + if (_log.shouldLog(Log.DEBUG)) { + //_log.debug("IV received: " + Base64.encode(iv)); + //_log.debug("Before:" + Base64.encode(orig, IV_LENGTH, orig.length - IV_LENGTH)); + } + encrypt(orig, offset, length); + updateIV(orig, offset); + if (_log.shouldLog(Log.DEBUG)) { + //_log.debug("Data after processing: " + Base64.encode(orig, IV_LENGTH, orig.length - IV_LENGTH)); + //_log.debug("IV sent: " + Base64.encode(orig, 0, IV_LENGTH)); + } + return true; + } + + private final void encrypt(byte data[], int offset, int length) { + for (int off = offset + IV_LENGTH; off < length; off += IV_LENGTH) { + DataHelper.xor(data, off - IV_LENGTH, data, off, data, off, IV_LENGTH); + _context.aes().encryptBlock(data, off, _config.getLayerKey(), data, off); + } + } + + private final void updateIV(byte orig[], int offset) { + _context.aes().encryptBlock(orig, offset, _config.getIVKey(), orig, offset); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/IVValidator.java b/router/java/src/net/i2p/router/tunnel/IVValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..ebf3a54179bb877a8cc58bf28d5ea948b95c6032 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/IVValidator.java @@ -0,0 +1,45 @@ +package net.i2p.router.tunnel; + +import java.util.HashSet; +import net.i2p.data.ByteArray; + +/** + * Provide a generic interface for IV validation which may be implemented + * through something as simple as a hashtable or more a complicated + * bloom filter. + * + */ +public interface IVValidator { + /** + * receive the IV for the tunnel, returning true if it is valid, + * or false if it has already been used (or is otherwise invalid). + * + */ + public boolean receiveIV(byte iv[]); +} + +/** accept everything */ +class DummyValidator implements IVValidator { + private static final DummyValidator _instance = new DummyValidator(); + public static DummyValidator getInstance() { return _instance; } + private DummyValidator() {} + + public boolean receiveIV(byte[] iv) { return true; } +} + +/** waste lots of RAM */ +class HashSetIVValidator implements IVValidator { + private HashSet _received; + + public HashSetIVValidator() { + _received = new HashSet(); + } + public boolean receiveIV(byte[] iv) { + ByteArray ba = new ByteArray(iv); + boolean isNew = false; + synchronized (_received) { + isNew = _received.add(ba); + } + return isNew; + } +} \ No newline at end of file diff --git a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..cb3dcbf3bb3b674392b362ee1444277398a9ea82 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java @@ -0,0 +1,58 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.util.Log; + +/** + * Receive the inbound tunnel message, removing all of the layers + * added by earlier hops to recover the preprocessed data sent + * by the gateway. This delegates the crypto to the + * OutboundGatewayProcessor, since the tunnel creator does the + * same thing in both instances. + * + */ +public class InboundEndpointProcessor { + private I2PAppContext _context; + private Log _log; + private TunnelCreatorConfig _config; + private IVValidator _validator; + + public InboundEndpointProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg) { + _context = ctx; + _log = ctx.logManager().getLog(InboundEndpointProcessor.class); + _config = cfg; + _validator = DummyValidator.getInstance(); + } + + /** + * Undo all of the encryption done by the peers in the tunnel, recovering the + * preprocessed data sent by the gateway. + * + * @return true if the data was recovered (and written in place to orig), false + * if it was a duplicate or from the wrong peer. + */ + public boolean retrievePreprocessedData(byte orig[], int offset, int length, Hash prev) { + Hash last = _config.getPeer(_config.getLength()-1); + if (!last.equals(prev)) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Invalid previous peer - attempted hostile loop? from " + prev + + ", expected " + last); + return false; + } + + byte iv[] = new byte[HopProcessor.IV_LENGTH]; + System.arraycopy(orig, offset, iv, 0, iv.length); + boolean ok = _validator.receiveIV(iv); + if (!ok) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid IV received"); + return false; + } + + // inbound endpoints and outbound gateways have to undo the crypto in the same way + OutboundGatewayProcessor.decrypt(_context, _config, iv, orig, offset, length); + return true; + } +} diff --git a/router/java/src/net/i2p/router/tunnel/InboundGatewayProcessor.java b/router/java/src/net/i2p/router/tunnel/InboundGatewayProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..cba42f2229216a91bc278404c345c58531525497 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/InboundGatewayProcessor.java @@ -0,0 +1,33 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.Hash; +import net.i2p.util.Log; + +/** + * Override the hop processor to seed the message with a random + * IV. + */ +public class InboundGatewayProcessor extends HopProcessor { + public InboundGatewayProcessor(I2PAppContext ctx, HopConfig config) { + super(ctx, config); + } + + /** we are the gateway, no need to validate the IV */ + protected IVValidator createValidator() { + return DummyValidator.getInstance(); + } + + /** + * Since we are the inbound gateway, pick a random IV, ignore the 'prev' + * hop, and encrypt the message like every other participant. + * + */ + public boolean process(byte orig[], int offset, int length, Hash prev) { + byte iv[] = new byte[IV_LENGTH]; + _context.random().nextBytes(iv); + System.arraycopy(iv, 0, orig, offset, IV_LENGTH); + + return super.process(orig, offset, length, null); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/InboundTest.java b/router/java/src/net/i2p/router/tunnel/InboundTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fe69b7408e5fab73320a3726263511eb85af392b --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/InboundTest.java @@ -0,0 +1,99 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; +import net.i2p.util.Log; + +/** + * Quick unit test for base functionality of inbound tunnel + * operation + */ +public class InboundTest { + private I2PAppContext _context; + private Log _log; + + public InboundTest() { + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(InboundTest.class); + } + + public void runTest() { + int numHops = 8; + TunnelCreatorConfig config = prepareConfig(numHops); + long start = _context.clock().now(); + for (int i = 0; i < 1000; i++) + runTest(numHops, config); + long time = _context.clock().now() - start; + _log.debug("Time for 1000 messages: " + time); + } + + private void runTest(int numHops, TunnelCreatorConfig config) { + byte orig[] = new byte[1024]; + byte message[] = new byte[1024]; + _context.random().nextBytes(orig); // might as well fill the IV + System.arraycopy(orig, 0, message, 0, message.length); + + InboundGatewayProcessor p = new InboundGatewayProcessor(_context, config.getConfig(0)); + p.process(message, 0, message.length, null); + + for (int i = 1; i < numHops; i++) { + HopProcessor hop = new HopProcessor(_context, config.getConfig(i)); + Hash prev = config.getConfig(i).getReceiveFrom(); + boolean ok = hop.process(message, 0, message.length, prev); + if (!ok) + _log.error("Error processing at hop " + i); + //else + // _log.info("Processing OK at hop " + i); + } + + InboundEndpointProcessor end = new InboundEndpointProcessor(_context, config); + boolean ok = end.retrievePreprocessedData(message, 0, message.length, config.getPeer(numHops-1)); + if (!ok) + _log.error("Error retrieving cleartext at the endpoint"); + + //_log.debug("After: " + Base64.encode(message, 16, orig.length-16)); + boolean eq = DataHelper.eq(orig, 16, message, 16, orig.length - 16); + _log.info("equal? " + eq); + } + + private TunnelCreatorConfig prepareConfig(int numHops) { + Hash peers[] = new Hash[numHops]; + byte tunnelIds[][] = new byte[numHops][4]; + for (int i = 0; i < numHops; i++) { + peers[i] = new Hash(); + peers[i].setData(new byte[Hash.HASH_LENGTH]); + _context.random().nextBytes(peers[i].getData()); + _context.random().nextBytes(tunnelIds[i]); + } + + TunnelCreatorConfig config = new TunnelCreatorConfig(numHops, false); + for (int i = 0; i < numHops; i++) { + config.setPeer(i, peers[i]); + HopConfig cfg = config.getConfig(i); + cfg.setExpiration(_context.clock().now() + 60000); + cfg.setIVKey(_context.keyGenerator().generateSessionKey()); + cfg.setLayerKey(_context.keyGenerator().generateSessionKey()); + if (i > 0) + cfg.setReceiveFrom(peers[i-1]); + else + cfg.setReceiveFrom(null); + cfg.setReceiveTunnelId(tunnelIds[i]); + if (i < numHops - 1) { + cfg.setSendTo(peers[i+1]); + cfg.setSendTunnelId(tunnelIds[i+1]); + } else { + cfg.setSendTo(null); + cfg.setSendTunnelId(null); + } + } + return config; + } + + public static void main(String args[]) { + InboundTest test = new InboundTest(); + test.runTest(); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..d756321536dd91e73a0e1147940109b372c96ab3 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundGatewayProcessor.java @@ -0,0 +1,87 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.util.Log; + +/** + * Turn the preprocessed tunnel data into something that can be delivered to the + * first hop in the tunnel. The crypto used in this class is also used by the + * InboundEndpointProcessor, as its the same 'undo' function of the tunnel crypto. + * + */ +public class OutboundGatewayProcessor { + private I2PAppContext _context; + private Log _log; + private TunnelCreatorConfig _config; + + public OutboundGatewayProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg) { + _context = ctx; + _log = ctx.logManager().getLog(OutboundGatewayProcessor.class); + _config = cfg; + } + + /** + * Since we are the outbound gateway, pick a random IV and wrap the preprocessed + * data so that it will be exposed at the endpoint. + * + * @param orig original data with an extra 16 bytes prepended. + * @param offset index into the array where the extra 16 bytes (IV) begins + * @param length how much of orig can we write to (must be a multiple of 16). + */ + public void process(byte orig[], int offset, int length) { + byte iv[] = new byte[HopProcessor.IV_LENGTH]; + _context.random().nextBytes(iv); + System.arraycopy(iv, 0, orig, offset, HopProcessor.IV_LENGTH); + + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Original random IV: " + Base64.encode(iv)); + _log.debug("data: " + Base64.encode(orig, iv.length, length - iv.length)); + } + decrypt(_context, _config, iv, orig, offset, length); + } + + /** + * Undo the crypto that the various layers in the tunnel added. This is used + * by both the outbound gateway (preemptively undoing the crypto peers will add) + * and by the inbound endpoint. + * + */ + static void decrypt(I2PAppContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) { + Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class); + byte cur[] = new byte[HopProcessor.IV_LENGTH]; // so we dont malloc + for (int i = cfg.getLength()-1; i >= 0; i--) { + decrypt(ctx, iv, orig, offset, length, cur, cfg.getConfig(i)); + if (log.shouldLog(Log.DEBUG)) { + //_log.debug("IV at hop " + i + ": " + Base64.encode(orig, offset, iv.length)); + //log.debug("hop " + i + ": " + Base64.encode(orig, offset + iv.length, length - iv.length)); + } + } + } + + private static void decrypt(I2PAppContext ctx, byte iv[], byte orig[], int offset, int length, byte cur[], HopConfig config) { + // update the IV for the previous (next?) hop + ctx.aes().decryptBlock(orig, offset, config.getIVKey(), orig, offset); + + int numBlocks = (length - HopProcessor.IV_LENGTH) / HopProcessor.IV_LENGTH; + + // prev == previous encrypted block (or IV for the first block) + byte prev[] = iv; + System.arraycopy(orig, offset, prev, 0, HopProcessor.IV_LENGTH); + //_log.debug("IV at curHop: " + Base64.encode(iv)); + + //decrypt the whole row + for (int i = 0; i < numBlocks; i++) { + int off = (i + 1) * HopProcessor.IV_LENGTH + offset; + + System.arraycopy(orig, off, cur, 0, HopProcessor.IV_LENGTH); + ctx.aes().decryptBlock(orig, off, config.getLayerKey(), orig, off); + DataHelper.xor(prev, 0, orig, off, orig, off, HopProcessor.IV_LENGTH); + byte xf[] = prev; + prev = cur; + cur = xf; + } + } +} diff --git a/router/java/src/net/i2p/router/tunnel/OutboundTest.java b/router/java/src/net/i2p/router/tunnel/OutboundTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bed83a9ca82107935707908f479165ae1db50993 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/OutboundTest.java @@ -0,0 +1,87 @@ +package net.i2p.router.tunnel; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; +import net.i2p.util.Log; + +/** + * Quick unit test for base functionality of outbound tunnel + * operation + * + */ +public class OutboundTest { + private I2PAppContext _context; + private Log _log; + + public OutboundTest() { + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(OutboundTest.class); + } + + public void runTest() { + int numHops = 8; + TunnelCreatorConfig config = prepareConfig(numHops); + + byte orig[] = new byte[1024]; + byte message[] = new byte[1024]; + _context.random().nextBytes(orig); // might as well fill the IV + System.arraycopy(orig, 0, message, 0, message.length); + + OutboundGatewayProcessor p = new OutboundGatewayProcessor(_context, config); + p.process(message, 0, message.length); + + for (int i = 0; i < numHops; i++) { + HopProcessor hop = new HopProcessor(_context, config.getConfig(i)); + Hash prev = config.getConfig(i).getReceiveFrom(); + boolean ok = hop.process(message, 0, message.length, prev); + if (!ok) + _log.error("Error processing at hop " + i); + //else + // _log.info("Processing OK at hop " + i); + } + + _log.debug("After: " + Base64.encode(message, 16, orig.length-16)); + boolean eq = DataHelper.eq(orig, 16, message, 16, orig.length - 16); + _log.info("equal? " + eq); + } + + private TunnelCreatorConfig prepareConfig(int numHops) { + Hash peers[] = new Hash[numHops]; + byte tunnelIds[][] = new byte[numHops][4]; + for (int i = 0; i < numHops; i++) { + peers[i] = new Hash(); + peers[i].setData(new byte[Hash.HASH_LENGTH]); + _context.random().nextBytes(peers[i].getData()); + _context.random().nextBytes(tunnelIds[i]); + } + + TunnelCreatorConfig config = new TunnelCreatorConfig(numHops, false); + for (int i = 0; i < numHops; i++) { + HopConfig cfg = config.getConfig(i); + cfg.setExpiration(_context.clock().now() + 60000); + cfg.setIVKey(_context.keyGenerator().generateSessionKey()); + cfg.setLayerKey(_context.keyGenerator().generateSessionKey()); + if (i > 0) + cfg.setReceiveFrom(peers[i-1]); + else + cfg.setReceiveFrom(null); + cfg.setReceiveTunnelId(tunnelIds[i]); + if (i < numHops - 1) { + cfg.setSendTo(peers[i+1]); + cfg.setSendTunnelId(tunnelIds[i+1]); + } else { + cfg.setSendTo(null); + cfg.setSendTunnelId(null); + } + } + return config; + } + + public static void main(String args[]) { + OutboundTest test = new OutboundTest(); + test.runTest(); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..23f4b2de0aaa2752470ac381eb7b5b6f7ff5fd70 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java @@ -0,0 +1,51 @@ +package net.i2p.router.tunnel; + +import net.i2p.data.Destination; +import net.i2p.data.Hash; + +/** + * Coordinate the info that the tunnel creator keeps track of, including what + * peers are in the tunnel and what their configuration is + * + */ +public class TunnelCreatorConfig { + /** only necessary for client tunnels */ + private Destination _destination; + /** gateway first */ + private HopConfig _config[]; + /** gateway first */ + private Hash _peers[]; + private boolean _isInbound; + + public TunnelCreatorConfig(int length, boolean isInbound) { + this(length, isInbound, null); + } + public TunnelCreatorConfig(int length, boolean isInbound, Destination destination) { + _config = new HopConfig[length]; + _peers = new Hash[length]; + for (int i = 0; i < length; i++) { + _config[i] = new HopConfig(); + } + _isInbound = isInbound; + _destination = destination; + } + + /** how many hops are there in the tunnel? */ + public int getLength() { return _config.length; } + + /** + * retrieve the config for the given hop. the gateway is + * hop 0. + */ + public HopConfig getConfig(int hop) { return _config[hop]; } + + /** retrieve the peer at the given hop. the gateway is hop 0 */ + public Hash getPeer(int hop) { return _peers[hop]; } + public void setPeer(int hop, Hash peer) { _peers[hop] = peer; } + + /** is this an inbound tunnel? */ + public boolean isInbound() { return _isInbound; } + + /** if this is a client tunnel, what destination is it for? */ + public Destination getDestination() { return _destination; } +} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelMessageProcessor.java b/router/java/src/net/i2p/router/tunnel/TunnelMessageProcessor.java deleted file mode 100644 index 1c17f66c44274d78e578ef5629f587c313874201..0000000000000000000000000000000000000000 --- a/router/java/src/net/i2p/router/tunnel/TunnelMessageProcessor.java +++ /dev/null @@ -1,151 +0,0 @@ -package net.i2p.router.tunnel; - -import net.i2p.I2PAppContext; -import net.i2p.data.DataHelper; -import net.i2p.data.Base64; -import net.i2p.data.Hash; -import net.i2p.data.SessionKey; -import net.i2p.util.Log; - -/** - * Decrypt a step in the tunnel, verifying the message in the process. - * - */ -public class TunnelMessageProcessor { - private static final int IV_SIZE = GatewayMessage.IV_SIZE; - private static final int HOPS = GatewayMessage.HOPS; - private static final int COLUMN_WIDTH = GatewayMessage.COLUMN_WIDTH; - private static final int VERIFICATION_WIDTH = GatewayMessage.VERIFICATION_WIDTH; - - /** - * Unwrap the tunnel message, overwriting it with the decrypted version. - * - * @param data full message received, written to while decrypted - * @param send decrypted tunnel message, ready to send - * @param layerKey session key to be used at the current layer - * @return true if the message was valid, false if it was not. - */ - public boolean unwrapMessage(I2PAppContext ctx, byte data[], SessionKey layerKey) { - Log log = getLog(ctx); - - int payloadLength = data.length - - IV_SIZE // IV - - HOPS * COLUMN_WIDTH // checksum blocks - - VERIFICATION_WIDTH; // verification of the checksum blocks - - Hash recvPayloadHash = ctx.sha().calculateHash(data, IV_SIZE, payloadLength); - if (log.shouldLog(Log.DEBUG)) - log.debug("H(recvPayload) = " + recvPayloadHash.toBase64()); - - decryptMessage(ctx, data, layerKey); - - Hash payloadHash = ctx.sha().calculateHash(data, IV_SIZE, payloadLength); - if (log.shouldLog(Log.DEBUG)) - log.debug("H(payload) = " + payloadHash.toBase64()); - - boolean ok = verifyMessage(ctx, data, payloadHash); - - if (ok) { - return true; - } else { - // no hashes were found that match the seen hash - if (log.shouldLog(Log.DEBUG)) - log.debug("No hashes match"); - return false; - } - } - - private void decryptMessage(I2PAppContext ctx, byte data[], SessionKey layerKey) { - Log log = getLog(ctx); - if (log.shouldLog(Log.DEBUG)) - log.debug("IV[recv] = " + Base64.encode(data, 0, IV_SIZE)); - - int numBlocks = (data.length - IV_SIZE) / IV_SIZE; - // for debugging, so we can compare eIV - int numPayloadBlocks = (data.length - IV_SIZE - COLUMN_WIDTH * HOPS - VERIFICATION_WIDTH) / IV_SIZE; - - // prev == previous encrypted block (or IV for the first block) - byte prev[] = new byte[IV_SIZE]; - // cur == current encrypted block (so we can overwrite the data in place) - byte cur[] = new byte[IV_SIZE]; - System.arraycopy(data, 0, prev, 0, IV_SIZE); - - //decrypt the whole row - for (int i = 0; i < numBlocks; i++) { - int off = (i + 1) * IV_SIZE; - - if (i == numPayloadBlocks) { - // should match the eIV - if (log.shouldLog(Log.DEBUG)) - log.debug("block[" + i + "].prev=" + Base64.encode(prev)); - } - - System.arraycopy(data, off, cur, 0, IV_SIZE); - ctx.aes().decryptBlock(data, off, layerKey, data, off); - DataHelper.xor(prev, 0, data, off, data, off, IV_SIZE); - byte xf[] = prev; - prev = cur; - cur = xf; - } - - // update the IV for the next layer - - ctx.aes().decryptBlock(data, 0, layerKey, data, 0); - DataHelper.xor(data, 0, GatewayMessage.IV_WHITENER, 0, data, 0, IV_SIZE); - Hash h = ctx.sha().calculateHash(data, 0, IV_SIZE); - System.arraycopy(h.getData(), 0, data, 0, IV_SIZE); - - if (log.shouldLog(Log.DEBUG)) { - log.debug("IV[send] = " + Base64.encode(data, 0, IV_SIZE)); - log.debug("key = " + layerKey.toBase64()); - } - } - - private boolean verifyMessage(I2PAppContext ctx, byte data[], Hash payloadHash) { - Log log = getLog(ctx); - int matchFound = -1; - - int off = data.length - HOPS * COLUMN_WIDTH - VERIFICATION_WIDTH; - for (int i = 0; i < HOPS; i++) { - if (DataHelper.eq(payloadHash.getData(), 0, data, off, COLUMN_WIDTH)) { - matchFound = i; - break; - } - - off += COLUMN_WIDTH; - } - - if (log.shouldLog(Log.DEBUG)) { - off = data.length - HOPS * COLUMN_WIDTH - VERIFICATION_WIDTH; - for (int i = 0; i < HOPS; i++) - log.debug("checksum[" + i + "] = " + Base64.encode(data, off + i*COLUMN_WIDTH, COLUMN_WIDTH) - + (i == matchFound ? " * MATCH" : "")); - - log.debug("verification = " + Base64.encode(data, data.length - VERIFICATION_WIDTH, VERIFICATION_WIDTH)); - } - - return matchFound != -1; - } - - /** - * Determine whether the checksum block has been modified by comparing the final - * verification hash to the hash of the block. - * - * @return true if the checksum is valid, false if it has been modified - */ - public boolean verifyChecksum(I2PAppContext ctx, byte message[]) { - int checksumSize = HOPS * COLUMN_WIDTH; - int offset = message.length - (checksumSize + VERIFICATION_WIDTH); - Hash checksumHash = ctx.sha().calculateHash(message, offset, checksumSize); - getLog(ctx).debug("Measured checksum: " + checksumHash.toBase64()); - byte expected[] = new byte[VERIFICATION_WIDTH]; - System.arraycopy(message, message.length-VERIFICATION_WIDTH, expected, 0, VERIFICATION_WIDTH); - getLog(ctx).debug("Expected checksum: " + Base64.encode(expected)); - - return DataHelper.eq(checksumHash.getData(), 0, message, message.length-VERIFICATION_WIDTH, VERIFICATION_WIDTH); - } - - private static final Log getLog(I2PAppContext ctx) { - return ctx.logManager().getLog(TunnelMessageProcessor.class); - } -} diff --git a/router/java/src/net/i2p/router/tunnel/TunnelProcessingTest.java b/router/java/src/net/i2p/router/tunnel/TunnelProcessingTest.java deleted file mode 100644 index cf9dd157d3608a2f5f209b92830171d8e6f91949..0000000000000000000000000000000000000000 --- a/router/java/src/net/i2p/router/tunnel/TunnelProcessingTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.i2p.router.tunnel; - -import net.i2p.I2PAppContext; -import net.i2p.data.DataHelper; -import net.i2p.data.SessionKey; -import net.i2p.util.Log; - -/** - * Test the tunnel encryption - build a message as a gateway, pass it through - * the sequence of participants (verifying the message along the way), and - * make sure it comes out the other side correctly. - * - */ -public class TunnelProcessingTest { - public void testTunnel() { - I2PAppContext ctx = I2PAppContext.getGlobalContext(); - Log log = ctx.logManager().getLog(TunnelProcessingTest.class); - if (true) { - byte orig[] = new byte[16*1024]; - ctx.random().nextBytes(orig); - GatewayTunnelConfig cfg = new GatewayTunnelConfig(); - for (int i = 0; i < GatewayMessage.HOPS; i++) { - cfg.setSessionKey(i, ctx.keyGenerator().generateSessionKey()); - log.debug("key[" + i + "] = " + cfg.getSessionKey(i).toBase64()); - } - testTunnel(ctx, orig, cfg); - } - if (false) { - GatewayTunnelConfig cfg = new GatewayTunnelConfig(); - for (int i = 0; i < GatewayMessage.HOPS; i++) { - SessionKey key = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]); - cfg.setSessionKey(i, key); - log.debug("key[" + i + "] = " + key.toBase64()); - } - - testTunnel(ctx, new byte[0], cfg); - } - } - - public void testTunnel(I2PAppContext ctx, byte orig[], GatewayTunnelConfig cfg) { - Log log = ctx.logManager().getLog(TunnelProcessingTest.class); - - log.debug("H[orig] = " + ctx.sha().calculateHash(orig).toBase64()); - - log.debug("\n\nEncrypting the payload"); - - byte cur[] = new byte[orig.length]; - System.arraycopy(orig, 0, cur, 0, cur.length); - GatewayMessage msg = new GatewayMessage(ctx); - msg.setPayload(cur); - msg.encrypt(cfg); - int size = msg.getExportedSize(); - byte message[] = new byte[size]; - int exp = msg.export(message, 0); - if (exp != size) throw new RuntimeException("Foo!"); - - TunnelMessageProcessor proc = new TunnelMessageProcessor(); - for (int i = 1; i < GatewayMessage.HOPS; i++) { - log.debug("\n\nUnwrapping step " + i); - boolean ok = proc.unwrapMessage(ctx, message, cfg.getSessionKey(i)); - if (!ok) - log.error("Unwrap failed at step " + i); - else - log.info("** Unwrap succeeded at step " + i); - boolean match = msg.compareChecksumBlock(ctx, message, i); - } - - log.debug("\n\nVerifying the tunnel processing"); - - for (int i = 0; i < orig.length; i++) { - if (orig[i] != message[16 + i]) { - log.error("Finished payload does not match at byte " + i + - ctx.sha().calculateHash(message, 16, orig.length).toBase64()); - break; - } - } - - boolean ok = proc.verifyChecksum(ctx, message); - if (!ok) - log.error("Checksum could not be verified"); - else - log.error("** Checksum verified"); - } - - public static void main(String args[]) { - TunnelProcessingTest t = new TunnelProcessingTest(); - t.testTunnel(); - } -}