package com.subgraph.orchid.connections; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.security.cert.CertificateException; import javax.security.cert.X509Certificate; import com.subgraph.orchid.ConnectionHandshakeException; import com.subgraph.orchid.ConnectionIOException; /** * 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 extends ConnectionHandshake { private static class HandshakeFinishedMonitor implements HandshakeCompletedListener { final Object lock = new Object(); boolean isFinished; public void handshakeCompleted(HandshakeCompletedEvent event) { synchronized(lock) { this.isFinished = true; lock.notifyAll(); } } public void waitFinished() throws InterruptedException { synchronized(lock) { while(!isFinished) { lock.wait(); } } } } ConnectionHandshakeV2(ConnectionImpl connection, SSLSocket socket) { super(connection, socket); } void runHandshake() throws IOException, InterruptedException, ConnectionIOException { // Swap in V1-only ciphers for second handshake as a workaround for: // // https://trac.torproject.org/projects/tor/ticket/4591 // socket.setEnabledCipherSuites(ConnectionSocketFactory.V1_CIPHERS_ONLY); final HandshakeFinishedMonitor monitor = new HandshakeFinishedMonitor(); socket.addHandshakeCompletedListener(monitor); socket.startHandshake(); monitor.waitFinished(); socket.removeHandshakeCompletedListener(monitor); verifyIdentityKey(getIdentityKey()); sendVersions(2); receiveVersions(); sendNetinfo(); recvNetinfo(); } private PublicKey getIdentityKey() throws ConnectionHandshakeException { final X509Certificate identityCertificate = getIdentityCertificateFromSession(socket.getSession()); return identityCertificate.getPublicKey(); } private X509Certificate getIdentityCertificateFromSession(SSLSession session) throws ConnectionHandshakeException { try { X509Certificate[] chain = session.getPeerCertificateChain(); if(chain.length != 2) { throw new ConnectionHandshakeException("Expecting 2 certificate chain from router and received chain length "+ chain.length); } chain[0].verify(chain[1].getPublicKey()); return chain[1]; } catch (SSLPeerUnverifiedException e) { throw new ConnectionHandshakeException("No certificates received from router"); } catch (GeneralSecurityException e) { throw new ConnectionHandshakeException("Incorrect signature on certificate chain"); } catch (CertificateException e) { throw new ConnectionHandshakeException("Malformed certificate received"); } } }