package com.subgraph.orchid.circuits; import java.util.logging.Logger; import com.subgraph.orchid.Cell; import com.subgraph.orchid.CircuitNode; import com.subgraph.orchid.RelayCell; import com.subgraph.orchid.Router; import com.subgraph.orchid.TorException; import com.subgraph.orchid.circuits.cells.CellImpl; import com.subgraph.orchid.circuits.cells.RelayCellImpl; import com.subgraph.orchid.crypto.TorCreateFastKeyAgreement; import com.subgraph.orchid.crypto.TorKeyAgreement; import com.subgraph.orchid.crypto.TorMessageDigest; import com.subgraph.orchid.crypto.TorStreamCipher; public class CircuitExtender { private final static Logger logger = Logger.getLogger(CircuitExtender.class.getName()); private final static int DH_BYTES = 1024 / 8; private final static int PKCS1_OAEP_PADDING_OVERHEAD = 42; private final static int CIPHER_KEY_LEN = TorStreamCipher.KEY_LEN; final static int TAP_ONIONSKIN_LEN = PKCS1_OAEP_PADDING_OVERHEAD + CIPHER_KEY_LEN + DH_BYTES; final static int TAP_ONIONSKIN_REPLY_LEN = DH_BYTES + TorMessageDigest.TOR_DIGEST_SIZE; private final CircuitImpl circuit; private final boolean ntorEnabled; CircuitExtender(CircuitImpl circuit, boolean ntorEnabled) { this.circuit = circuit; this.ntorEnabled = ntorEnabled; } CircuitNode createFastTo(Router targetRouter) { logger.fine("Creating 'fast' to "+ targetRouter); final TorCreateFastKeyAgreement kex = new TorCreateFastKeyAgreement(); sendCreateFastCell(kex); return receiveAndProcessCreateFastResponse(targetRouter, kex); } private void sendCreateFastCell(TorCreateFastKeyAgreement kex) { final Cell cell = CellImpl.createCell(circuit.getCircuitId(), Cell.CREATE_FAST); cell.putByteArray(kex.createOnionSkin()); circuit.sendCell(cell); } private CircuitNode receiveAndProcessCreateFastResponse(Router targetRouter, TorKeyAgreement kex) { final Cell cell = circuit.receiveControlCellResponse(); if(cell == null) { throw new TorException("Timeout building circuit waiting for CREATE_FAST response from "+ targetRouter); } return processCreatedFastCell(targetRouter, cell, kex); } private CircuitNode processCreatedFastCell(Router targetRouter, Cell cell, TorKeyAgreement kex) { final byte[] payload = new byte[TorMessageDigest.TOR_DIGEST_SIZE * 2]; final byte[] keyMaterial = new byte[CircuitNodeCryptoState.KEY_MATERIAL_SIZE]; final byte[] verifyHash = new byte[TorMessageDigest.TOR_DIGEST_SIZE]; cell.getByteArray(payload); if(!kex.deriveKeysFromHandshakeResponse(payload, keyMaterial, verifyHash)) { // XXX return null; } final CircuitNode node = CircuitNodeImpl.createFirstHop(targetRouter, keyMaterial, verifyHash); circuit.appendNode(node); return node; } CircuitNode extendTo(Router targetRouter) { if(circuit.getCircuitLength() == 0) { throw new TorException("Cannot EXTEND an empty circuit"); } if(useNtor(targetRouter)) { final NTorCircuitExtender nce = new NTorCircuitExtender(this, targetRouter); return nce.extendTo(); } else { final TapCircuitExtender tce = new TapCircuitExtender(this, targetRouter); return tce.extendTo(); } } private boolean useNtor(Router targetRouter) { return ntorEnabled && targetRouter.getNTorOnionKey() != null; } private void logProtocolViolation(String sourceName, Router targetRouter) { final String version = (targetRouter == null) ? "(none)" : targetRouter.getVersion(); final String targetName = (targetRouter == null) ? "(none)" : targetRouter.getNickname(); logger.warning("Protocol error extending circuit from ("+ sourceName +") to ("+ targetName +") [version: "+ version +"]"); } private String nodeToName(CircuitNode node) { if(node == null || node.getRouter() == null) { return "(null)"; } final Router router = node.getRouter(); return router.getNickname(); } public void sendRelayCell(RelayCell cell) { circuit.sendRelayCell(cell); } public RelayCell receiveRelayResponse(int expectedCommand, Router extendTarget) { final RelayCell cell = circuit.receiveRelayCell(); if(cell == null) { throw new TorException("Timeout building circuit"); } final int command = cell.getRelayCommand(); if(command == RelayCell.RELAY_TRUNCATED) { final int code = cell.getByte() & 0xFF; final String msg = CellImpl.errorToDescription(code); final String source = nodeToName(cell.getCircuitNode()); if(code == Cell.ERROR_PROTOCOL) { logProtocolViolation(source, extendTarget); } throw new TorException("Error from ("+ source +") while extending to ("+ extendTarget.getNickname() + "): "+ msg); } else if(command != expectedCommand) { final String expected = RelayCellImpl.commandToDescription(expectedCommand); final String received = RelayCellImpl.commandToDescription(command); throw new TorException("Received incorrect extend response, expecting "+ expected + " but received "+ received); } else { return cell; } } public CircuitNode createNewNode(Router r, byte[] keyMaterial, byte[] verifyDigest) { final CircuitNode node = CircuitNodeImpl.createNode(r, circuit.getFinalCircuitNode(), keyMaterial, verifyDigest); logger.fine("Adding new circuit node for "+ r.getNickname()); circuit.appendNode(node); return node; } public RelayCell createRelayCell(int command) { return new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), 0, command, true); } Router getFinalRouter() { final CircuitNode node = circuit.getFinalCircuitNode(); if(node != null) { return node.getRouter(); } else { return null; } } }