I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit cf84f453 authored by jrandom's avatar jrandom Committed by zzz
Browse files

Initial implementation of the new tunnel encryption code. Still much more work to be

done (e.g. *what* gets encrypted, modifying the tunnelCreate messages, the tunnel
building process, and the new tunnel pooling).  I seem to have lost much of the typed
up docs describing this too, so I'll be hitting that next.
parent daf32a24
No related branches found
No related tags found
No related merge requests found
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>Turn some raw data into something we can pass down the tunnel, decrypting
* and verifying along the way. The encryption used is such that decryption
* merely requires running over the data with AES in CTR mode, calculating the
* SHA256 of a certain fixed portion of the message (bytes 16 through $size-288),
* and searching for that hash in the checksum block. There is a fixed number
* of hops defined (8 peers after the gateway) so that we can verify the message
* without either leaking the position in the tunnel or having the message
* continually "shrink" as layers are peeled off. For tunnels shorter than 9
* hops, the tunnel creator will take the place of the excess hops, decrypting
* with their keys (for outbound tunnels, this is done at the beginning, and for
* inbound tunnels, the end).</p>
*
* <p>The hard part in the encryption is building that entangled checksum block,
* which requires essentially finding out what the hash of the payload will look
* like at each step, randomly ordering those hashes, then building a matrix of
* what each of those randomly ordered hashes will look like at each step.
* To visualize this a bit:</p>
*
* <table border="1">
* <tr><td colspan="2"></td>
* <td><b>IV</b></td><td><b>Payload</b></td>
* <td><b>eH[0]</b></td><td><b>eH[1]</b></td>
* <td><b>eH[2]</b></td><td><b>eH[3]</b></td>
* <td><b>eH[4]</b></td><td><b>eH[5]</b></td>
* <td><b>eH[6]</b></td><td><b>eH[7]</b></td>
* <td><b>V</b></td>
* <td><b>Key</b></td>
* </tr>
* <tr><td rowspan="2"><b>peer0</b></td><td><b>recv</b></td>
* <td>IV[0]</td><td>P[0]</td>
* <td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td>
* <td>V[0]</td>
* <td rowspan="2">K[0]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[1]</td><td rowspan="2">P[1]</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2">H(P[1])</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2">V[1]</td>
* </tr>
* <tr><td rowspan="2"><b>peer1</b></td><td><b>recv</b></td>
* <td rowspan="2">K[1]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[2]</td><td rowspan="2">P[2]</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2">H(P[2])</td><td rowspan="2"></td>
* <td rowspan="2">V[2]</td>
* </tr>
* <tr><td rowspan="2"><b>peer2</b></td><td><b>recv</b></td>
* <td rowspan="2">K[2]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[3]</td><td rowspan="2">P[3]</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2">H(P[3])</td>
* <td rowspan="2">V[3]</td>
* </tr>
* <tr><td rowspan="2"><b>peer3</b></td><td><b>recv</b></td>
* <td rowspan="2">K[3]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[4]</td><td rowspan="2">P[4]</td>
* <td rowspan="2">H(P[4])</td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2">V[4]</td>
* </tr>
* <tr><td rowspan="2"><b>peer4</b></td><td><b>recv</b></td>
* <td rowspan="2">K[4]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[5]</td><td rowspan="2">P[5]</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2">H(P[5])</td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2">V[5]</td>
* </tr>
* <tr><td rowspan="2"><b>peer5</b></td><td><b>recv</b></td>
* <td rowspan="2">K[5]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[6]</td><td rowspan="2">P[6]</td>
* <td rowspan="2"></td><td rowspan="2">H(P[6])</td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2">V[6]</td>
* </tr>
* <tr><td rowspan="2"><b>peer6</b></td><td><b>recv</b></td>
* <td rowspan="2">K[6]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td rowspan="2">IV[7]</td><td rowspan="2">P[7]</td>
* <td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2"></td><td rowspan="2">H(P[7])</td><td rowspan="2"></td><td rowspan="2"></td>
* <td rowspan="2">V[7]</td>
* </tr>
* <tr><td rowspan="2"><b>peer7</b></td><td><b>recv</b></td>
* <td rowspan="2">K[7]</td>
* </tr>
* <tr><td><b>send</b></td>
* <td>IV[8]</td><td>P[8]</td>
* <td></td><td></td><td></td><td></td><td>H(P[8])</td><td></td><td></td><td></td>
* <td>V[8]</td>
* </tr>
* </table>
*
* <p>In the above, P[8] is the same as the original data being passed through the
* tunnel, and V[8] is the SHA256 of eH[0-8] as seen on peer7 after decryption. For
* cells in the matrix "higher up" than the hash, their value is derived by encrypting
* the cell below it with the key for the peer below it, using the end of the column
* to the left of it as the IV. For cells in the matrix "lower down" than the hash,
* they're equal to the cell above them, decrypted by the current peer's key, using
* the end of the previous encrypted block on that row.</p>
*
* <p>With this randomized matrix of checksum blocks, each peer will be able to find
* the hash of the payload, or if it is not there, know that the message is corrupt.
* The entanglement by using CTR mode increases the difficulty in tagging the
* checksum blocks themselves, but it is still possible for that tagging to go
* briefly undetected if the columns after the tagged data have already been used
* to check the payload at a peer. In any case, the tunnel endpoint (peer 7) knows
* for certain whether any of the checksum blocks have been tagged, as that would
* corrupt the verification block (V[8]).</p>
*
* <p>The IV[0] is a random 16 byte value, and IV[i] is the first 16 bytes of
* H(D(IV[i-1], K[i-1])). We don't use the same IV along the path, as that would
* allow trivial collusion, and we use the hash of the decrypted value to propogate
* the IV so as to hamper key leakage.</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;
static final int HOPS = 8;
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 + 1;
public GatewayMessage(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(GatewayMessage.class);
initialize();
}
private void initialize() {
_iv = new byte[HOPS][IV_SIZE];
_eIV = new byte[HOPS][IV_SIZE];
_H = new byte[HOPS][Hash.HASH_LENGTH];
_eH = new byte[COLUMNS][HASH_ROWS][Hash.HASH_LENGTH];
_preV = new byte[HOPS*Hash.HASH_LENGTH];
_V = new byte[Hash.HASH_LENGTH];
_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 = 0; i < HOPS - 1; i++) {
SessionKey key = cfg.getSessionKey(i);
// decrypt, since we're simulating what the participants do
_context.aes().decryptBlock(_iv[i], 0, key, _iv[i+1], 0);
Hash h = _context.sha().calculateHash(_iv[i+1]);
System.arraycopy(h.getData(), 0, _iv[i+1], 0, IV_SIZE);
}
if (_log.shouldLog(Log.DEBUG)) {
for (int i = 0; i < HOPS; i++)
_log.debug("_iv[" + i + "] = " + Base64.encode(_iv[i]));
}
}
/**
* Encrypt the payload and IV blocks, overwriting _iv and _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], 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], 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], 0, IV_SIZE);
}
}
if (_log.shouldLog(Log.DEBUG)) {
for (int i = 0; i < HOPS; 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+1] 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 to the first hop. The encryption uses the _eIV for each
* step so that a plain AES/CTR decrypt of the entire message will expose
* the layer. _eH[column][_order[i]+1] == _H[_order[i]]
*/
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+1], 0, Hash.HASH_LENGTH);
// now fill in the "earlier" _eH[column][row] values for earlier hops
// by encrypting _eH[column][row+1] with the peer's key, using the end
// of the previous column (or _eIV[row]) as the IV
for (int row = hash; row >= 0; row--) {
SessionKey key = cfg.getSessionKey(row);
// first half
if (column == 0) {
DataHelper.xor(_eIV[row], 0, _eH[column][row+1], 0, _eH[column][row], 0, IV_SIZE);
} else {
DataHelper.xor(_eH[column-1][row], IV_SIZE, _eH[column][row+1], 0, _eH[column][row], 0, IV_SIZE);
}
_context.aes().encryptBlock(_eH[column][row], 0, key, _eH[column][row], 0);
// second half
DataHelper.xor(_eH[column][row], 0, _eH[column][row+1], IV_SIZE, _eH[column][row], IV_SIZE, IV_SIZE);
_context.aes().encryptBlock(_eH[column][row], IV_SIZE, key, _eH[column][row], IV_SIZE);
}
// 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-1);
_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], IV_SIZE, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE);
_context.aes().decryptBlock(_eH[column][row-1], IV_SIZE, key, _eH[column][row], IV_SIZE);
DataHelper.xor(_eH[column][row-1], 0, _eH[column][row], IV_SIZE, _eH[column][row], IV_SIZE, 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], _eH[column][peer]) ? " CLEARTEXT" : ""));
} catch (Exception e) {
e.printStackTrace();
System.out.println("column="+column + " peer=" + peer);
}
}
}
}
}
/**
* Build the _V hash as the SHA256 of _eH[*][8], 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 * Hash.HASH_LENGTH, Hash.HASH_LENGTH);
Hash v = _context.sha().calculateHash(_preV);
System.arraycopy(v.getData(), 0, _V, 0, Hash.HASH_LENGTH);
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[HOPS-1][i], IV_SIZE, _V, 0, IV_SIZE);
_context.aes().encryptBlock(_V, 0, key, _V, 0);
DataHelper.xor(_V, 0, _V, IV_SIZE, _V, IV_SIZE, IV_SIZE);
_context.aes().encryptBlock(_V, IV_SIZE, key, _V, IV_SIZE);
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 * Hash.HASH_LENGTH +
Hash.HASH_LENGTH; // verification hash
}
/**
* Write out the fully encrypted tunnel message to the target
*
* @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, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
}
System.arraycopy(_V, 0, target, cur, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
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) * Hash.HASH_LENGTH;
for (int column = 0; column < COLUMNS; column++) {
boolean ok = DataHelper.eq(_eH[column][peer+1], 0, message, off, Hash.HASH_LENGTH);
if (log.shouldLog(Log.DEBUG))
log.debug("checksum[" + column + "][" + (peer+1) + "] matches? " + ok);
off += Hash.HASH_LENGTH;
match = match && ok;
}
return match;
}
}
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; }
}
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;
/**
* 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 * Hash.HASH_LENGTH // checksum blocks
- Hash.HASH_LENGTH; // 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 - 2 * IV_SIZE * (GatewayMessage.HOPS + 1)) / 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);
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 - (GatewayMessage.HOPS + 1) * Hash.HASH_LENGTH;
for (int i = 0; i < GatewayMessage.HOPS; i++) {
if (DataHelper.eq(payloadHash.getData(), 0, data, off, Hash.HASH_LENGTH)) {
matchFound = i;
break;
}
off += Hash.HASH_LENGTH;
}
if (log.shouldLog(Log.DEBUG)) {
off = data.length - (GatewayMessage.HOPS + 1) * Hash.HASH_LENGTH;
for (int i = 0; i < HOPS; i++)
log.debug("checksum[" + i + "] = " + Base64.encode(data, off + i*Hash.HASH_LENGTH, Hash.HASH_LENGTH)
+ (i == matchFound ? " * MATCH" : ""));
log.debug("verification = " + Base64.encode(data, data.length - Hash.HASH_LENGTH, Hash.HASH_LENGTH));
}
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 = GatewayMessage.HOPS * Hash.HASH_LENGTH;
int offset = message.length - (checksumSize + Hash.HASH_LENGTH);
Hash checksumHash = ctx.sha().calculateHash(message, offset, checksumSize);
getLog(ctx).debug("Measured checksum: " + checksumHash.toBase64());
byte expected[] = new byte[Hash.HASH_LENGTH];
System.arraycopy(message, message.length-Hash.HASH_LENGTH, expected, 0, Hash.HASH_LENGTH);
getLog(ctx).debug("Expected checksum: " + Base64.encode(expected));
return DataHelper.eq(checksumHash.getData(), 0, message, message.length-Hash.HASH_LENGTH, Hash.HASH_LENGTH);
}
private static final Log getLog(I2PAppContext ctx) {
return ctx.logManager().getLog(TunnelMessageProcessor.class);
}
}
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 = 0; 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();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment