package net.i2p.router.tunnel; import java.util.List; import net.i2p.I2PAppContext; import net.i2p.data.Hash; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; import net.i2p.data.i2np.BuildRequestRecord; import net.i2p.data.i2np.EncryptedBuildRecord; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.TunnelBuildMessage; /** * Fill in the encrypted BuildRequestRecords in a TunnelBuildMessage */ public abstract class BuildMessageGenerator { /** 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 ( (!cfg.isInbound()) && (hop + 1 == cfg.getLength()) ) //outbound endpoint 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); } /** * Returns null if hop >= cfg.length */ private static BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop, Hash replyRouter, long replyTunnel) { //Log log = ctx.logManager().getLog(BuildMessageGenerator.class); if (hop < cfg.getLength()) { // ok, now lets fill in some data HopConfig hopConfig = cfg.getConfig(hop); Hash peer = cfg.getPeer(hop); long recvTunnelId = -1; if (cfg.isInbound() || (hop > 0)) recvTunnelId = hopConfig.getReceiveTunnel().getTunnelId(); else recvTunnelId = 0; long nextTunnelId = -1; Hash nextPeer = null; if (hop + 1 < cfg.getLength()) { nextTunnelId = cfg.getConfig(hop+1).getReceiveTunnel().getTunnelId(); nextPeer = cfg.getPeer(hop+1); } else { if ( (replyTunnel >= 0) && (replyRouter != null) ) { nextTunnelId = replyTunnel; nextPeer = replyRouter; } else { // inbound endpoint (aka creator) nextTunnelId = 0; nextPeer = peer; // self } } SessionKey layerKey = hopConfig.getLayerKey(); SessionKey ivKey = hopConfig.getIVKey(); SessionKey replyKey = hopConfig.getReplyKey(); byte iv[] = hopConfig.getReplyIV(); if (iv == null) { iv = new byte[BuildRequestRecord.IV_SIZE]; ctx.random().nextBytes(iv); hopConfig.setReplyIV(iv); } boolean isInGW = (cfg.isInbound() && (hop == 0)); boolean isOutEnd = (!cfg.isInbound() && (hop + 1 >= cfg.getLength())); long nextMsgId = -1; if (isOutEnd || (cfg.isInbound() && (hop + 2 >= cfg.getLength())) ) { nextMsgId = cfg.getReplyMessageId(); } else { // dont care about these intermediary hops nextMsgId = ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE); } //if (log.shouldLog(Log.DEBUG)) // log.debug("Hop " + hop + " has the next message ID of " + nextMsgId + " for " + cfg // + " with replyKey " + replyKey.toBase64() + " and replyIV " + Base64.encode(iv)); BuildRequestRecord rec= new BuildRequestRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer, nextMsgId, layerKey, ivKey, replyKey, iv, isInGW, isOutEnd); return rec; } else { return null; } } /** * 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) { //Log log = ctx.logManager().getLog(BuildMessageGenerator.class); // 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) ) { //if (log.shouldLog(Log.DEBUG)) // 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); } public static boolean isBlank(TunnelCreatorConfig cfg, int hop) { if (cfg.isInbound()) { if (hop + 1 >= cfg.getLength()) return true; else return false; } else { if (hop == 0) return true; else if (hop >= cfg.getLength()) return true; else return false; } } }