mirror of
https://github.com/i2p/i2p.i2p.git
synced 2026-05-31 09:14:38 +00:00
130 lines
5.9 KiB
Java
130 lines
5.9 KiB
Java
package net.i2p.router.tunnel;
|
|
|
|
import java.util.List;
|
|
|
|
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.data.i2np.EncryptedBuildRecord;
|
|
import net.i2p.data.i2np.TunnelBuildReplyMessage;
|
|
import net.i2p.util.Log;
|
|
import net.i2p.util.SimpleByteCache;
|
|
|
|
/**
|
|
* Decrypt the layers of a tunnel build reply message, determining whether the individual
|
|
* hops agreed to participate in the tunnel, or if not, why not.
|
|
*
|
|
*/
|
|
public class BuildReplyHandler {
|
|
|
|
private final I2PAppContext ctx;
|
|
private final Log log;
|
|
|
|
/**
|
|
* @since 0.9.8 (methods were static before)
|
|
*/
|
|
public BuildReplyHandler(I2PAppContext context) {
|
|
ctx = context;
|
|
log = ctx.logManager().getLog(BuildReplyHandler.class);
|
|
}
|
|
|
|
/**
|
|
* Decrypt the tunnel build reply records. This overwrites the contents of the reply.
|
|
* Thread safe (no state).
|
|
*
|
|
* Note that this layer-decrypts the build records in-place.
|
|
* Do not call this more than once for a given message.
|
|
*
|
|
* @return status for the records (in record order), or null if the replies were not valid. Fake records
|
|
* always have 0 as their value
|
|
*/
|
|
public int[] decrypt(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, List<Integer> recordOrder) {
|
|
if (reply.getRecordCount() != recordOrder.size()) {
|
|
// somebody messed with us
|
|
log.error("Corrupted build reply, expected " + recordOrder.size() + " records, got " + reply.getRecordCount());
|
|
return null;
|
|
}
|
|
int rv[] = new int[reply.getRecordCount()];
|
|
for (int i = 0; i < rv.length; i++) {
|
|
int hop = recordOrder.get(i).intValue();
|
|
if (BuildMessageGenerator.isBlank(cfg, hop)) {
|
|
// self...
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": no need to decrypt record " + i + "/" + hop + ", as its out of range: " + cfg);
|
|
rv[i] = 0;
|
|
} else {
|
|
int ok = decryptRecord(reply, cfg, i, hop);
|
|
if (ok == -1) {
|
|
if (log.shouldLog(Log.WARN))
|
|
log.warn(reply.getUniqueId() + ": decrypt record " + i + "/" + hop + " was not ok: " + cfg);
|
|
return null;
|
|
} else {
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": decrypt record " + i + "/" + hop + " was ok: " + ok + " for " + cfg);
|
|
}
|
|
rv[i] = ok;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Decrypt the record (removing the layers of reply encyption) and read out the status
|
|
*
|
|
* Note that this layer-decrypts the build records in-place.
|
|
* Do not call this more than once for a given message.
|
|
*
|
|
* @return the status 0-255, or -1 on decrypt failure
|
|
*/
|
|
private int decryptRecord(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) {
|
|
if (BuildMessageGenerator.isBlank(cfg, hop)) {
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": Record " + recordNum + "/" + hop + " is fake, so consider it valid...");
|
|
return 0;
|
|
}
|
|
EncryptedBuildRecord rec = reply.getRecord(recordNum);
|
|
byte[] data = rec.getData();
|
|
int start = cfg.getLength() - 1;
|
|
if (cfg.isInbound())
|
|
start--; // the last hop in an inbound tunnel response doesn't actually encrypt
|
|
// do we need to adjust this for the endpoint?
|
|
for (int j = start; j >= hop; j--) {
|
|
HopConfig hopConfig = cfg.getConfig(j);
|
|
SessionKey replyKey = hopConfig.getReplyKey();
|
|
byte replyIV[] = hopConfig.getReplyIV();
|
|
if (log.shouldLog(Log.DEBUG)) {
|
|
log.debug(reply.getUniqueId() + ": Decrypting record " + recordNum + "/" + hop + "/" + j + " with replyKey "
|
|
+ replyKey.toBase64() + "/" + Base64.encode(replyIV) + ": " + cfg);
|
|
log.debug(reply.getUniqueId() + ": before decrypt: " + Base64.encode(data));
|
|
log.debug(reply.getUniqueId() + ": Full reply rec: sz=" + data.length + " data=" + Base64.encode(data, 0, TunnelBuildReplyMessage.RECORD_SIZE));
|
|
}
|
|
ctx.aes().decrypt(data, 0, data, 0, replyKey, replyIV, 0, TunnelBuildReplyMessage.RECORD_SIZE);
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": after decrypt: " + Base64.encode(data));
|
|
}
|
|
// ok, all of the layered encryption is stripped, so lets verify it
|
|
// (formatted per BuildResponseRecord.create)
|
|
// don't cache the result
|
|
//Hash h = ctx.sha().calculateHash(data, off + Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH);
|
|
byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH);
|
|
ctx.sha().calculateHash(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH, h, 0);
|
|
boolean ok = DataHelper.eq(h, 0, data, 0, Hash.HASH_LENGTH);
|
|
if (!ok) {
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": Failed verification on " + recordNum + "/" + hop + ": " + Base64.encode(h) + " calculated, " +
|
|
Base64.encode(data, 0, Hash.HASH_LENGTH) + " expected\n" +
|
|
"Record: " + Base64.encode(data, Hash.HASH_LENGTH, TunnelBuildReplyMessage.RECORD_SIZE-Hash.HASH_LENGTH));
|
|
SimpleByteCache.release(h);
|
|
return -1;
|
|
} else {
|
|
SimpleByteCache.release(h);
|
|
int rv = data[TunnelBuildReplyMessage.RECORD_SIZE - 1] & 0xff;
|
|
if (log.shouldLog(Log.DEBUG))
|
|
log.debug(reply.getUniqueId() + ": Verified: " + rv + " for record " + recordNum + "/" + hop);
|
|
return rv;
|
|
}
|
|
}
|
|
}
|