package org.torproject.jtor.circuits.impl; import java.io.IOException; import java.math.BigInteger; import java.util.Date; import java.util.List; import org.torproject.jtor.TorException; import org.torproject.jtor.circuits.CircuitBuildHandler; import org.torproject.jtor.circuits.CircuitNode; import org.torproject.jtor.circuits.ConnectionConnectException; import org.torproject.jtor.circuits.cells.Cell; import org.torproject.jtor.circuits.cells.RelayCell; import org.torproject.jtor.crypto.TorKeyAgreement; import org.torproject.jtor.crypto.TorMessageDigest; import org.torproject.jtor.data.HexDigest; import org.torproject.jtor.directory.Router; import org.torproject.jtor.logging.Logger; /* * Utility class used by CircuitImpl that manages setting up a circuit * through a specified path of router nodes. */ class CircuitBuilder { private final CircuitImpl circuit; private final ConnectionManagerImpl connectionManager; private final Logger logger; CircuitBuilder(CircuitImpl circuit, ConnectionManagerImpl connectionManager, Logger logger) { this.circuit = circuit; this.connectionManager = connectionManager; this.logger = logger; } boolean openCircuit(List<Router> circuitPath, CircuitBuildHandler handler) { if(circuitPath.isEmpty()) throw new IllegalArgumentException("Path must contain at least one router to create a circuit."); final Router entryRouter = circuitPath.get(0); return openEntryNodeConnection(entryRouter, handler) && buildCircuit(circuitPath, handler); } private boolean openEntryNodeConnection(Router entryRouter, CircuitBuildHandler handler) { final ConnectionImpl entryConnection = createEntryConnection(entryRouter); if(!entryConnection.isConnected() && !connectEntryNodeConnection(entryConnection, handler)) return false; final int circuitId = entryConnection.allocateCircuitId(circuit); circuit.initializeConnectingCircuit(entryConnection, circuitId); if(handler != null) handler.connectionCompleted(entryConnection); return true; } private boolean connectEntryNodeConnection(ConnectionImpl entryConnection, CircuitBuildHandler handler) { try { final Date start = new Date(); entryConnection.connect(); final Date now = new Date(); logger.debug("Connect completed in "+ (now.getTime() - start.getTime()) +" milliseconds."); return true; } catch (ConnectionConnectException e) { processConnectFailed(entryConnection, handler, e); return false; } catch (Exception e) { processConnectFailed(entryConnection, handler, e); logger.error("Unexpected exception connecting to entry node.", e); return false; } } private void processConnectFailed(ConnectionImpl entryConnection, CircuitBuildHandler handler, Throwable e) { entryConnection.removeCircuit(circuit); if(handler != null) handler.connectionFailed(e.getMessage()); } private ConnectionImpl createEntryConnection(Router entryRouter) { final ConnectionImpl existingConnection = connectionManager.findActiveLinkForRouter(entryRouter); if(existingConnection != null) return existingConnection; else return connectionManager.createConnection(entryRouter); } private boolean buildCircuit(List<Router> circuitPath, CircuitBuildHandler handler) { try { runCircuitBuild(circuitPath, handler); } catch(TorException e) { if(handler != null) handler.circuitBuildFailed(e.getMessage()); return false; } catch(Exception e) { logger.error("Unexpected exception building circuit.", e); if(handler != null) handler.circuitBuildFailed("Unexpected exception: "+ e.getMessage()); return false; } circuit.setConnected(); if(handler != null) handler.circuitBuildCompleted(circuit); return true; } private void runCircuitBuild(List<Router> circuitPath, CircuitBuildHandler handler) { final Router entryRouter = circuitPath.get(0); final CircuitNode firstNode = createTo(entryRouter); if(handler != null) handler.nodeAdded(firstNode); for(int i = 1; i < circuitPath.size(); i++) { final CircuitNode extendedNode = extendTo(circuitPath.get(i)); if(handler != null) handler.nodeAdded(extendedNode); } } CircuitNode createTo(Router targetRouter) { final CircuitNodeImpl newNode = new CircuitNodeImpl(targetRouter, null); sendCreateCell(newNode); receiveAndProcessCreateResponse(newNode); return newNode; } private void sendCreateCell(CircuitNodeImpl node) { final Cell cell = CellImpl.createCell(circuit.getCircuitId(), Cell.CREATE); cell.putByteArray(node.createOnionSkin()); circuit.sendCell(cell); } private void receiveAndProcessCreateResponse(CircuitNodeImpl node) { final Cell cell = circuit.receiveControlCellResponse(); if(cell == null) throw new TorException("Timeout building circuit"); processCreatedCell(node, cell); circuit.appendNode(node); } private void processCreatedCell(CircuitNodeImpl node, Cell cell) { final byte[] cellBytes = cell.getCellBytes(); final byte[] dhData = new byte[TorKeyAgreement.DH_LEN]; final byte[] hash = new byte[TorMessageDigest.TOR_DIGEST_SIZE]; int offset = Cell.CELL_HEADER_LEN; System.arraycopy(cellBytes, offset, dhData, 0, TorKeyAgreement.DH_LEN); offset += TorKeyAgreement.DH_LEN; System.arraycopy(cellBytes, offset, hash, 0, TorMessageDigest.TOR_DIGEST_SIZE); BigInteger peerPublic = new BigInteger(1, dhData); node.setSharedSecret(peerPublic, HexDigest.createFromDigestBytes(hash)); } CircuitNode extendTo(Router targetRouter) { if(circuit.getCircuitLength() == 0) throw new TorException("Cannot EXTEND an empty circuit"); final CircuitNodeImpl newNode = createExtendNode(targetRouter); final RelayCell cell = createRelayExtendCell(newNode); circuit.sendRelayCellToFinalNode(cell); try { receiveExtendResponse(newNode); circuit.appendNode(newNode); return newNode; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } private CircuitNodeImpl createExtendNode(Router router) { return new CircuitNodeImpl(router, circuit.getFinalCircuitNode()); } private RelayCell createRelayExtendCell(CircuitNodeImpl newNode) { final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), 0, RelayCell.RELAY_EXTEND); final Router router = newNode.getRouter(); cell.putByteArray(router.getAddress().getAddressDataBytes()); cell.putShort(router.getOnionPort()); cell.putByteArray( newNode.createOnionSkin()); cell.putByteArray(router.getIdentityKey().getFingerprint().getRawBytes()); return cell; } private void receiveExtendResponse(CircuitNodeImpl newNode) throws IOException { 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); throw new TorException("Circuit build failed: "+ msg); } else if (command != RelayCell.RELAY_EXTENDED) { throw new TorException("Unexpected response to RELAY_EXTEND. Command = "+ command); } byte[] dhPublic = new byte[TorKeyAgreement.DH_LEN]; cell.getByteArray(dhPublic); byte[] keyHash = new byte[TorMessageDigest.TOR_DIGEST_SIZE]; cell.getByteArray(keyHash); HexDigest packetDigest = HexDigest.createFromDigestBytes(keyHash); BigInteger peerPublic = new BigInteger(1, dhPublic); newNode.setSharedSecret(peerPublic, packetDigest); } }