From 571c29f92a073fb19f4e0ab4633dfb367b67d33e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 5 Sep 2019 12:08:58 +0200 Subject: [PATCH] make Tor connections work with direct TLS --- .../siacs/conversations/utils/Resolver.java | 6 +- .../utils/SocksSocketFactory.java | 22 +++- .../conversations/xmpp/XmppConnection.java | 111 ++++++++---------- 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 96ce63c90..8ba1c4cd7 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -76,12 +76,16 @@ public class Resolver { Result result = new Result(); result.hostname = DNSName.from(hostname); result.port = port; - result.directTls = port == 443 || port == 5223; + result.directTls = useDirectTls(port); result.authenticated = true; return Collections.singletonList(result); } + public static boolean useDirectTls(final int port) { + return port == 443 || port == 5223; + } + public static List resolve(String domain) { final List ipResults = fromIpAddress(domain); if (ipResults.size() > 0) { diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java index eab9acfcb..48e0c1d55 100644 --- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java +++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java @@ -21,7 +21,7 @@ public class SocksSocketFactory { byte[] response = new byte[2]; proxyIs.read(response); if (response[0] != 0x05 || response[1] != 0x00) { - throw new SocksConnectionException(); + throw new SocksConnectionException("Socks 5 handshake failed"); } byte[] dest = destination.getBytes(); ByteBuffer request = ByteBuffer.allocate(7 + dest.length); @@ -33,7 +33,13 @@ public class SocksSocketFactory { response = new byte[7 + dest.length]; proxyIs.read(response); if (response[1] != 0x00) { - throw new SocksConnectionException(); + if (response[1] == 0x04) { + throw new HostNotFoundException("Host unreachable"); + } + if (response[1] == 0x05) { + throw new HostNotFoundException("Connection refused"); + } + throw new SocksConnectionException("Unable to connect to destination "+(int) (response[1])); } } @@ -61,11 +67,19 @@ public class SocksSocketFactory { return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port); } - static class SocksConnectionException extends IOException { - + private static class SocksConnectionException extends IOException { + SocksConnectionException(String message) { + super(message); + } } public static class SocksProxyNotFoundException extends IOException { } + + public static class HostNotFoundException extends SocksConnectionException { + HostNotFoundException(String message) { + super(message); + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 049a8fe68..fe384ebf2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -267,8 +267,18 @@ public class XmppConnection implements Runnable { destination = account.getHostname(); this.verifiedHostname = destination; } - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor"); - localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); + + final int port = account.getPort(); + final boolean directTls = Resolver.useDirectTls(port); + + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor. directTls="+directTls); + localSocket = SocksSocketFactory.createSocketOverTor(destination, port); + + if (directTls) { + localSocket = upgradeSocketToTls(localSocket); + features.encryptionEnabled = true; + } + try { startXmpp(localSocket); } catch (InterruptedException e) { @@ -328,29 +338,13 @@ public class XmppConnection implements Runnable { + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); } - if (!features.encryptionEnabled) { - localSocket = new Socket(); - localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - } else { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - localSocket = tlsFactoryVerifier.factory.createSocket(); + localSocket = new Socket(); + localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - if (localSocket == null) { - throw new IOException("could not initialize ssl socket"); - } - - SSLSocketHelper.setSecurity((SSLSocket) localSocket); - SSLSocketHelper.setHostname((SSLSocket) localSocket, account.getServer()); - SSLSocketHelper.setApplicationProtocol((SSLSocket) localSocket, "xmpp-client"); - - localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - - if (!tlsFactoryVerifier.verifier.verify(account.getServer(), verifiedHostname, ((SSLSocket) localSocket).getSession())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); - FileBackend.close(localSocket); - throw new StateChangingException(Account.State.TLS_ERROR); - } + if (features.encryptionEnabled) { + localSocket = upgradeSocketToTls(localSocket); } + localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); if (startXmpp(localSocket)) { localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this @@ -384,6 +378,8 @@ public class XmppConnection implements Runnable { this.changeStatus(e.state); } catch (final UnknownHostException | ConnectException e) { this.changeStatus(Account.State.SERVER_NOT_FOUND); + } catch (final SocksSocketFactory.HostNotFoundException e) { + this.changeStatus(Account.State.SERVER_NOT_FOUND); } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { this.changeStatus(Account.State.TOR_NOT_AVAILABLE); } catch (final IOException | XmlPullParserException e) { @@ -796,46 +792,41 @@ public class XmppConnection implements Runnable { private void switchOverToTls() throws XmlPullParserException, IOException { tagReader.readTag(); + final Socket socket = this.socket; + final SSLSocket sslSocket = upgradeSocketToTls(socket); + tagReader.setInputStream(sslSocket.getInputStream()); + tagWriter.setOutputStream(sslSocket.getOutputStream()); + sendStartStream(); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established"); + features.encryptionEnabled = true; + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + SSLSocketHelper.log(account, sslSocket); + processStream(); + } else { + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); + } + sslSocket.close(); + } + + private SSLSocket upgradeSocketToTls(final Socket socket) throws IOException { + final TlsFactoryVerifier tlsFactoryVerifier; try { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - final InetAddress address = socket == null ? null : socket.getInetAddress(); - - if (address == null) { - throw new IOException("could not setup ssl"); - } - - final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); - - - if (sslSocket == null) { - throw new IOException("could not initialize ssl socket"); - } - - SSLSocketHelper.setSecurity(sslSocket); - SSLSocketHelper.setHostname(sslSocket, account.getServer()); - SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client"); - - if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); - throw new StateChangingException(Account.State.TLS_ERROR); - } - tagReader.setInputStream(sslSocket.getInputStream()); - tagWriter.setOutputStream(sslSocket.getOutputStream()); - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established"); - features.encryptionEnabled = true; - final Tag tag = tagReader.readTag(); - if (tag != null && tag.isStart("stream")) { - SSLSocketHelper.log(account, sslSocket); - processStream(); - } else { - throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); - } - sslSocket.close(); - } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); + tlsFactoryVerifier = getTlsFactoryVerifier(); + } catch (final NoSuchAlgorithmException | KeyManagementException e) { throw new StateChangingException(Account.State.TLS_ERROR); } + final InetAddress address = socket.getInetAddress(); + final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); + SSLSocketHelper.setSecurity(sslSocket); + SSLSocketHelper.setHostname(sslSocket, account.getServer()); + SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client"); + if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); + FileBackend.close(sslSocket); + throw new StateChangingException(Account.State.TLS_ERROR); + } + return sslSocket; } private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {