package org.torproject.jtor.circuits.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.torproject.jtor.TorException; import org.torproject.jtor.circuits.ConnectionClosedException; import org.torproject.jtor.circuits.ConnectionConnectException; import org.torproject.jtor.circuits.cells.Cell; import org.torproject.jtor.data.IPv4Address; /** * This class performs a Version 2 handshake as described in section 2 of * tor-spec.txt. The handshake is considered complete after VERSIONS and * NETINFO cells have been exchanged between the two sides. */ public class ConnectionHandshakeV2 { private final static int[] SUPPORTED_CONNECTION_VERSIONS = {1,2}; private final ConnectionImpl connection; private final SSLSocket socket; private final Object lock = new Object(); private boolean hasRenegotiated = false; private boolean isFinishedHandshake = false; private final List<Integer> remoteVersions; private int remoteTimestamp; private IPv4Address myAddress; private final List<IPv4Address> remoteAddresses; ConnectionHandshakeV2(ConnectionImpl connection, SSLSocket socket) { this.connection = connection; this.socket = socket; this.socket.addHandshakeCompletedListener(new HandshakeCompletedListener() { public void handshakeCompleted(HandshakeCompletedEvent event) { processHandshakeCompleted(event); } }); this.remoteVersions = new ArrayList<Integer>(); this.remoteAddresses = new ArrayList<IPv4Address>(); } void runHandshake() throws IOException, InterruptedException { socket.startHandshake(); waitForHandshakeFinished(); sendVersions(); receiveVersions(); sendNetinfo(); recvNetinfo(); } int getRemoteTimestamp() { return remoteTimestamp; } IPv4Address getMyAddress() { return myAddress; } private void signalFinished() { synchronized (lock) { isFinishedHandshake = true; lock.notifyAll(); } } private void processHandshakeCompleted(HandshakeCompletedEvent event) { if(hasRenegotiated) { signalFinished(); return; } SSLSession session = socket.getSession(); session.invalidate(); hasRenegotiated = true; try { socket.startHandshake(); socket.getInputStream().read(new byte[0]); } catch (IOException e) { throw new TorException(e); } } private void waitForHandshakeFinished() throws InterruptedException { synchronized(lock) { while(!isFinishedHandshake) lock.wait(); } } private void sendVersions() throws IOException { final Cell cell = CellImpl.createVarCell(0, Cell.VERSIONS, SUPPORTED_CONNECTION_VERSIONS.length * 2); for(int v: SUPPORTED_CONNECTION_VERSIONS) cell.putShort(v); connection.sendCell(cell); } private void receiveVersions() throws IOException { try { Cell c = connection.readConnectionControlCell(); while(c.cellBytesRemaining() >= 2) remoteVersions.add(c.getShort()); } catch (ConnectionClosedException e) { throw new ConnectionConnectException("Connection closed while performing handshake"); } } private void sendNetinfo() throws IOException { final Cell cell = CellImpl.createCell(0, Cell.NETINFO); // XXX this is a mess Date now = new Date(); cell.putInt((int)(now.getTime() / 1000)); cell.putByte(4); cell.putByte(4); cell.putByteArray(connection.getRouter().getAddress().getAddressDataBytes()); cell.putByte(1); cell.putByte(4); cell.putByte(4); cell.putInt(0); connection.sendCell(cell); } private void recvNetinfo() throws IOException { try { final Cell cell = connection.readConnectionControlCell(); // XXX verify command == NETINFO remoteTimestamp = cell.getInt(); myAddress = readAddress(cell); final int addressCount = cell.getByte(); for(int i = 0; i < addressCount; i++) remoteAddresses.add(readAddress(cell)); } catch (ConnectionClosedException e) { throw new ConnectionConnectException("Connection closed while performing handshake"); } } private IPv4Address readAddress(Cell cell) { final int type = cell.getByte(); final int len = cell.getByte(); return new IPv4Address(cell.getInt()); } }