use of net.i2p.data.i2np.EncryptedBuildRecord in project i2p.i2p by i2p.
the class BuildHandler method handleReq.
/**
* If we are dropping lots of requests before even trying to handle them,
* I suppose you could call us "overloaded"
*/
/**
** unused, see handleReq() below
* private final static int MAX_PROACTIVE_DROPS = 240;
*
* private int countProactiveDrops() {
* int dropped = 0;
* dropped += countEvents("tunnel.dropLoadProactive", 60*1000);
* dropped += countEvents("tunnel.dropLoad", 60*1000);
* dropped += countEvents("tunnel.dropLoadBacklog", 60*1000);
* dropped += countEvents("tunnel.dropLoadDelay", 60*1000);
* return dropped;
* }
*
* private int countEvents(String stat, long period) {
* RateStat rs = _context.statManager().getRate(stat);
* if (rs != null) {
* Rate r = rs.getRate(period);
* if (r != null)
* return (int)r.getCurrentEventCount();
* }
* return 0;
* }
***
*/
/**
* Actually process the request and send the reply.
*
* Todo: Replies are not subject to RED for bandwidth reasons,
* and the bandwidth is not credited to any tunnel.
* If we did credit the reply to the tunnel, it would
* prevent the classification of the tunnel as 'inactive' on tunnels.jsp.
*/
private void handleReq(RouterInfo nextPeerInfo, BuildMessageState state, BuildRequestRecord req, Hash nextPeer) {
long ourId = req.readReceiveTunnelId();
long nextId = req.readNextTunnelId();
boolean isInGW = req.readIsInboundGateway();
boolean isOutEnd = req.readIsOutboundEndpoint();
Hash from = state.fromHash;
if (from == null && state.from != null)
from = state.from.calculateHash();
if (isInGW && isOutEnd) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
_log.error("Dropping build request, IBGW+OBEP: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (ourId <= 0 || ourId > TunnelId.MAX_ID_VALUE || nextId <= 0 || nextId > TunnelId.MAX_ID_VALUE) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping build request, bad tunnel ID: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
// Loop checks
if ((!isOutEnd) && _context.routerHash().equals(nextPeer)) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
// old i2pd
if (_log.shouldWarn())
_log.warn("Dropping build request, we are the next hop: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (!isInGW) {
// but if not, something is seriously wrong here.
if (from == null || _context.routerHash().equals(from)) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping build request, we are the previous hop: " + req);
return;
}
}
if ((!isOutEnd) && (!isInGW)) {
// A-B-C-A is not preventable
if (nextPeer.equals(from)) {
// i2pd does this
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request with the same previous and next hop: " + req);
_context.commSystem().mayDisconnect(from);
return;
}
}
// time is in hours, rounded down.
// tunnel-alt-creation.html specifies that this is enforced +/- 1 hour but it was not.
// As of 0.9.16, allow + 5 minutes to - 65 minutes.
long time = req.readRequestTime();
long now = (_context.clock().now() / (60l * 60l * 1000l)) * (60 * 60 * 1000);
long timeDiff = now - time;
if (timeDiff > MAX_REQUEST_AGE) {
_context.statManager().addRateData("tunnel.rejectTooOld", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request too old... replay attack? " + DataHelper.formatDuration(timeDiff) + ": " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (timeDiff < 0 - MAX_REQUEST_FUTURE) {
_context.statManager().addRateData("tunnel.rejectFuture", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request too far in future " + DataHelper.formatDuration(0 - timeDiff) + ": " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
int response;
if (_context.router().isHidden()) {
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Hidden mode"));
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
response = _context.throttle().acceptTunnelRequest();
}
// This only checked OUR tunnels, so the log message was wrong.
// Now checked by TunnelDispatcher.joinXXX()
// and returned as success value, checked below.
// if (_context.tunnelManager().getTunnelInfo(new TunnelId(ourId)) != null) {
// if (_log.shouldLog(Log.ERROR))
// _log.error("Already participating in a tunnel with the given Id (" + ourId + "), so gotta reject");
// if (response == 0)
// response = TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
// }
// if ( (response == 0) && (_context.random().nextInt(50) <= 1) )
// response = TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
long recvDelay = _context.clock().now() - state.recvTime;
if (response == 0) {
// unused
// int proactiveDrops = countProactiveDrops();
float pDrop = ((float) recvDelay) / (float) (BuildRequestor.REQUEST_TIMEOUT * 3);
pDrop = (float) Math.pow(pDrop, 16);
if (_context.random().nextFloat() < pDrop) {
// || (proactiveDrops > MAX_PROACTIVE_DROPS) ) ) {
_context.statManager().addRateData("tunnel.rejectOverloaded", recvDelay);
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Request overload"));
// if (true || (proactiveDrops < MAX_PROACTIVE_DROPS*2))
response = TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
// else
// response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
_context.statManager().addRateData("tunnel.acceptLoad", recvDelay);
}
}
/*
* Being a IBGW or OBEP generally leads to more connections, so if we are
* approaching our connection limit (i.e. !haveCapacity()),
* reject this request.
*
* Don't do this for class N or O, under the assumption that they are already talking
* to most of the routers, so there's no reason to reject. This may drive them
* to their conn. limits, but it's hopefully a temporary solution to the
* tunnel build congestion. As the net grows this will have to be revisited.
*/
RouterInfo ri = _context.router().getRouterInfo();
if (response == 0) {
if (ri == null) {
// ?? We should always have a RI
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
char bw = ri.getBandwidthTier().charAt(0);
if (bw != 'O' && bw != 'N' && bw != 'P' && bw != 'X' && ((isInGW && !_context.commSystem().haveInboundCapacity(87)) || (isOutEnd && !_context.commSystem().haveOutboundCapacity(87)))) {
_context.statManager().addRateData("tunnel.rejectConnLimits", 1);
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Connection limit"));
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
}
}
// We may need another counter above for requests.
if (response == 0 && !isInGW) {
if (from != null && _throttler.shouldThrottle(from)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting tunnel (hop throttle), previous hop: " + from + ": " + req);
// no setTunnelStatus() indication
_context.statManager().addRateData("tunnel.rejectHopThrottle", 1);
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
}
if (response == 0 && (!isOutEnd) && _throttler.shouldThrottle(nextPeer)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting tunnel (hop throttle), next hop: " + req);
_context.statManager().addRateData("tunnel.rejectHopThrottle", 1);
// no setTunnelStatus() indication
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
HopConfig cfg = null;
if (response == 0) {
cfg = new HopConfig();
cfg.setCreation(_context.clock().now());
cfg.setExpiration(_context.clock().now() + 10 * 60 * 1000);
cfg.setIVKey(req.readIVKey());
cfg.setLayerKey(req.readLayerKey());
if (isInGW) {
// default
// cfg.setReceiveFrom(null);
} else {
if (from != null) {
cfg.setReceiveFrom(from);
} else {
// b0rk
return;
}
}
cfg.setReceiveTunnelId(DataHelper.toLong(4, ourId));
if (isOutEnd) {
// default
// cfg.setSendTo(null);
// cfg.setSendTunnelId(null);
} else {
cfg.setSendTo(nextPeer);
cfg.setSendTunnelId(DataHelper.toLong(4, nextId));
}
// now "actually" join
boolean success;
if (isOutEnd)
success = _context.tunnelDispatcher().joinOutboundEndpoint(cfg);
else if (isInGW)
success = _context.tunnelDispatcher().joinInboundGateway(cfg);
else
success = _context.tunnelDispatcher().joinParticipant(cfg);
if (success) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Joining: " + req);
} else {
// Dup Tunnel ID. This can definitely happen (birthday paradox).
// Probability in 11 minutes (per hop type):
// 0.1% for 2900 tunnels; 1% for 9300 tunnels
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
_context.statManager().addRateData("tunnel.rejectDupID", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("DUP ID failure: " + req);
}
}
if (response != 0) {
_context.statManager().addRateData("tunnel.reject." + response, 1);
_context.messageHistory().tunnelRejected(from, new TunnelId(ourId), nextPeer, // (isOutEnd ? "outbound endpoint" : isInGW ? "inbound gw" : "participant"));
Integer.toString(response));
if (from != null)
_context.commSystem().mayDisconnect(from);
// 81% = between 75% control measures in Transports and 87% rejection above
if ((!_context.routerHash().equals(nextPeer)) && (!_context.commSystem().haveOutboundCapacity(81)) && (!_context.commSystem().isEstablished(nextPeer))) {
_context.statManager().addRateData("tunnel.dropConnLimits", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Not sending rejection due to conn limits: " + req);
return;
}
} else if (isInGW && from != null) {
// we're the start of the tunnel, no use staying connected
_context.commSystem().mayDisconnect(from);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Responding to " + state.msg.getUniqueId() + " after " + recvDelay + " with " + response + " from " + (from != null ? from : "tunnel") + ": " + req);
EncryptedBuildRecord reply = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
int records = state.msg.getRecordCount();
int ourSlot = -1;
for (int j = 0; j < records; j++) {
if (state.msg.getRecord(j) == null) {
ourSlot = j;
state.msg.setRecord(j, reply);
// + ": " + Base64.encode(reply));
break;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read slot " + ourSlot + " containing: " + req + " accepted? " + response + " recvDelay " + recvDelay + " replyMessage " + req.readReplyMessageId());
// now actually send the response
long expires = _context.clock().now() + NEXT_HOP_SEND_TIMEOUT;
if (!isOutEnd) {
state.msg.setUniqueId(req.readReplyMessageId());
state.msg.setMessageExpiration(expires);
OutNetMessage msg = new OutNetMessage(_context, state.msg, expires, PRIORITY, nextPeerInfo);
if (response == 0)
msg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg));
_context.outNetMessagePool().add(msg);
} else {
// We are the OBEP.
// send it to the reply tunnel on the reply peer within a new TunnelBuildReplyMessage
// (enough layers jrandom?)
TunnelBuildReplyMessage replyMsg;
if (records == TunnelBuildMessage.MAX_RECORD_COUNT)
replyMsg = new TunnelBuildReplyMessage(_context);
else
replyMsg = new VariableTunnelBuildReplyMessage(_context, records);
for (int i = 0; i < records; i++) replyMsg.setRecord(i, state.msg.getRecord(i));
replyMsg.setUniqueId(req.readReplyMessageId());
replyMsg.setMessageExpiration(expires);
TunnelGatewayMessage m = new TunnelGatewayMessage(_context);
m.setMessage(replyMsg);
m.setMessageExpiration(expires);
m.setTunnelId(new TunnelId(nextId));
if (_context.routerHash().equals(nextPeer)) {
// ok, we are the gateway, so inject it
if (_log.shouldLog(Log.DEBUG))
_log.debug("We are the reply gateway for " + nextId + " when replying to replyMessage " + req);
_context.tunnelDispatcher().dispatch(m);
} else {
// ok, the gateway is some other peer, shove 'er across
OutNetMessage outMsg = new OutNetMessage(_context, m, expires, PRIORITY, nextPeerInfo);
if (response == 0)
outMsg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg));
_context.outNetMessagePool().add(outMsg);
}
}
}
use of net.i2p.data.i2np.EncryptedBuildRecord in project i2p.i2p by i2p.
the class BuildMessageGenerator method createRecord.
/**
* return null if it is unable to find a router's public key (etc)
*/
/**
**
* public TunnelBuildMessage createInbound(RouterContext ctx, TunnelCreatorConfig cfg) {
* return create(ctx, cfg, null, -1);
* }
***
*/
/**
* return null if it is unable to find a router's public key (etc)
*/
/**
**
* public TunnelBuildMessage createOutbound(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) {
* return create(ctx, cfg, replyRouter, replyTunnel);
* }
***
*/
/**
**
* private TunnelBuildMessage create(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) {
* TunnelBuildMessage msg = new TunnelBuildMessage(ctx);
* List order = new ArrayList(ORDER.length);
* for (int i = 0; i < ORDER.length; i++) order.add(ORDER[i]);
* Collections.shuffle(order, ctx.random());
* for (int i = 0; i < ORDER.length; i++) {
* int hop = ((Integer)order.get(i)).intValue();
* Hash peer = cfg.getPeer(hop);
* RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(peer);
* if (ri == null)
* return null;
* createRecord(i, hop, msg, cfg, replyRouter, replyTunnel, ctx, ri.getIdentity().getPublicKey());
* }
* layeredEncrypt(ctx, msg, cfg, order);
* return msg;
* }
***
*/
/**
* Place the asymmetrically encrypted record in the specified record slot,
* containing the hop's configuration (as well as the reply info, if it is an outbound endpoint)
*
* @param msg out parameter
* @param peerKey Encrypt using this key.
* If null, replyRouter and replyTunnel are ignored,
* and the entire record is filled with random data
* @throws IllegalArgumentException if hop bigger than config
*/
public static void createRecord(int recordNum, int hop, TunnelBuildMessage msg, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel, I2PAppContext ctx, PublicKey peerKey) {
// Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
EncryptedBuildRecord erec;
if (peerKey != null) {
BuildRequestRecord req = null;
if (// outbound endpoint
(!cfg.isInbound()) && (hop + 1 == cfg.getLength()))
req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel);
else
req = createUnencryptedRecord(ctx, cfg, hop, null, -1);
if (req == null)
throw new IllegalArgumentException("hop bigger than config");
Hash peer = cfg.getPeer(hop);
// if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + "/" + peer.toBase64()
// + ": unencrypted = " + Base64.encode(req.getData().getData()));
erec = req.encryptRecord(ctx, peerKey, peer);
// if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + ": encrypted = " + Base64.encode(encrypted));
} else {
// if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + "/ is blank/random");
byte[] encrypted = new byte[TunnelBuildMessage.RECORD_SIZE];
ctx.random().nextBytes(encrypted);
erec = new EncryptedBuildRecord(encrypted);
}
msg.setRecord(recordNum, erec);
}
use of net.i2p.data.i2np.EncryptedBuildRecord in project i2p.i2p by i2p.
the class BuildMessageGenerator method layeredEncrypt.
/**
* Encrypt the records so their hop ident is visible at the appropriate times.
*
* Note that this layer-encrypts the build records for the message in-place.
* Only call this once for a given message.
*
* @param order list of hop #s as Integers. For instance, if (order.get(1) is 4), it is peer cfg.getPeer(4)
*/
public static void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg, TunnelCreatorConfig cfg, List<Integer> order) {
// encrypt the records so that the right elements will be visible at the right time
for (int i = 0; i < msg.getRecordCount(); i++) {
EncryptedBuildRecord rec = msg.getRecord(i);
Integer hopNum = order.get(i);
int hop = hopNum.intValue();
if ((isBlank(cfg, hop)) || (!cfg.isInbound() && hop == 1)) {
// log.debug(msg.getUniqueId() + ": not pre-decrypting record " + i + "/" + hop + " for " + cfg);
continue;
}
// if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg);
// ok, now decrypt the record with all of the reply keys from cfg.getConfig(0) through hop-1
int stop = (cfg.isInbound() ? 0 : 1);
for (int j = hop - 1; j >= stop; j--) {
HopConfig hopConfig = cfg.getConfig(j);
SessionKey key = hopConfig.getReplyKey();
byte[] iv = hopConfig.getReplyIV();
// if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg
// + " with " + key.toBase64() + "/" + Base64.encode(iv));
// corrupts the SDS
ctx.aes().decrypt(rec.getData(), 0, rec.getData(), 0, key, iv, TunnelBuildMessage.RECORD_SIZE);
}
}
// if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": done pre-decrypting all records for " + cfg);
}
use of net.i2p.data.i2np.EncryptedBuildRecord in project i2p.i2p by i2p.
the class BuildMessageProcessor method decrypt.
/**
* Decrypt the record targetting us, encrypting all of the other records with the included
* reply key and IV. The original, encrypted record targetting us is removed from the request
* message (so that the reply can be placed in that position after going through the decrypted
* request record).
*
* Note that this layer-decrypts the build records in-place.
* Do not call this more than once for a given message.
*
* @return the current hop's decrypted record or null on failure
*/
public BuildRequestRecord decrypt(TunnelBuildMessage msg, Hash ourHash, PrivateKey privKey) {
BuildRequestRecord rv = null;
int ourHop = -1;
long beforeActualDecrypt = 0;
long afterActualDecrypt = 0;
byte[] ourHashData = ourHash.getData();
long beforeLoop = System.currentTimeMillis();
for (int i = 0; i < msg.getRecordCount(); i++) {
EncryptedBuildRecord rec = msg.getRecord(i);
int len = BuildRequestRecord.PEER_SIZE;
boolean eq = DataHelper.eq(ourHashData, 0, rec.getData(), 0, len);
if (eq) {
beforeActualDecrypt = System.currentTimeMillis();
try {
rv = new BuildRequestRecord(ctx, privKey, rec);
afterActualDecrypt = System.currentTimeMillis();
// i2pd bug
boolean isBad = SessionKey.INVALID_KEY.equals(rv.readReplyKey());
if (isBad) {
if (log.shouldLog(Log.WARN))
log.warn(msg.getUniqueId() + ": Bad reply key: " + rv);
ctx.statManager().addRateData("tunnel.buildRequestBadReplyKey", 1);
return null;
}
// The spec says to feed the 32-byte AES-256 reply key into the Bloom filter.
// But we were using the first 32 bytes of the encrypted reply.
// Fixed in 0.9.24
boolean isDup = _filter.add(rv.getData(), BuildRequestRecord.OFF_REPLY_KEY, 32);
if (isDup) {
if (log.shouldLog(Log.WARN))
log.warn(msg.getUniqueId() + ": Dup record: " + rv);
ctx.statManager().addRateData("tunnel.buildRequestDup", 1);
return null;
}
if (log.shouldLog(Log.DEBUG))
log.debug(msg.getUniqueId() + ": Matching record: " + rv);
ourHop = i;
// TODO should we keep looking for a second match and fail if found?
break;
} catch (DataFormatException dfe) {
if (log.shouldLog(Log.WARN))
log.warn(msg.getUniqueId() + ": Matching record decrypt failure", dfe);
// out there with the same first 16 bytes, go around again
continue;
}
}
}
if (rv == null) {
// none of the records matched, b0rk
if (log.shouldLog(Log.WARN))
log.warn(msg.getUniqueId() + ": No matching record");
return null;
}
long beforeEncrypt = System.currentTimeMillis();
SessionKey replyKey = rv.readReplyKey();
byte[] iv = rv.readReplyIV();
for (int i = 0; i < msg.getRecordCount(); i++) {
if (i != ourHop) {
EncryptedBuildRecord data = msg.getRecord(i);
// if (log.shouldLog(Log.DEBUG))
// log.debug("Encrypting record " + i + "/? with replyKey " + replyKey.toBase64() + "/" + Base64.encode(iv));
// encrypt in-place, corrupts SDS
byte[] bytes = data.getData();
ctx.aes().encrypt(bytes, 0, bytes, 0, replyKey, iv, 0, EncryptedBuildRecord.LENGTH);
}
}
long afterEncrypt = System.currentTimeMillis();
msg.setRecord(ourHop, null);
if (afterEncrypt - beforeLoop > 1000) {
if (log.shouldLog(Log.WARN))
log.warn("Slow decryption, total=" + (afterEncrypt - beforeLoop) + " looping=" + (beforeEncrypt - beforeLoop) + " decrypt=" + (afterActualDecrypt - beforeActualDecrypt) + " encrypt=" + (afterEncrypt - beforeEncrypt));
}
return rv;
}
use of net.i2p.data.i2np.EncryptedBuildRecord in project i2p.i2p by i2p.
the class BuildReplyHandler method decryptRecord.
/**
* 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())
// the last hop in an inbound tunnel response doesn't actually encrypt
start--;
// 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;
}
}
Aggregations