make Tor connections work with direct TLS

This commit is contained in:
Daniel Gultsch 2019-09-05 12:08:58 +02:00
parent 7ec1b443ab
commit 571c29f92a
3 changed files with 74 additions and 65 deletions

View File

@ -76,12 +76,16 @@ public class Resolver {
Result result = new Result(); Result result = new Result();
result.hostname = DNSName.from(hostname); result.hostname = DNSName.from(hostname);
result.port = port; result.port = port;
result.directTls = port == 443 || port == 5223; result.directTls = useDirectTls(port);
result.authenticated = true; result.authenticated = true;
return Collections.singletonList(result); return Collections.singletonList(result);
} }
public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223;
}
public static List<Result> resolve(String domain) { public static List<Result> resolve(String domain) {
final List<Result> ipResults = fromIpAddress(domain); final List<Result> ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) { if (ipResults.size() > 0) {

View File

@ -21,7 +21,7 @@ public class SocksSocketFactory {
byte[] response = new byte[2]; byte[] response = new byte[2];
proxyIs.read(response); proxyIs.read(response);
if (response[0] != 0x05 || response[1] != 0x00) { if (response[0] != 0x05 || response[1] != 0x00) {
throw new SocksConnectionException(); throw new SocksConnectionException("Socks 5 handshake failed");
} }
byte[] dest = destination.getBytes(); byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length); ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
@ -33,7 +33,13 @@ public class SocksSocketFactory {
response = new byte[7 + dest.length]; response = new byte[7 + dest.length];
proxyIs.read(response); proxyIs.read(response);
if (response[1] != 0x00) { 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); 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 SocksProxyNotFoundException extends IOException {
} }
public static class HostNotFoundException extends SocksConnectionException {
HostNotFoundException(String message) {
super(message);
}
}
} }

View File

@ -267,8 +267,18 @@ public class XmppConnection implements Runnable {
destination = account.getHostname(); destination = account.getHostname();
this.verifiedHostname = destination; 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 { try {
startXmpp(localSocket); startXmpp(localSocket);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -328,29 +338,13 @@ public class XmppConnection implements Runnable {
+ result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled);
} }
if (!features.encryptionEnabled) { localSocket = new Socket();
localSocket = new Socket(); localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
localSocket = tlsFactoryVerifier.factory.createSocket();
if (localSocket == null) { if (features.encryptionEnabled) {
throw new IOException("could not initialize ssl socket"); localSocket = upgradeSocketToTls(localSocket);
}
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);
}
} }
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) { if (startXmpp(localSocket)) {
localSocket.setSoTimeout(0); //reset to 0; once the connection is established we dont want this localSocket.setSoTimeout(0); //reset to 0; once the connection is established we dont want this
@ -384,6 +378,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(e.state); this.changeStatus(e.state);
} catch (final UnknownHostException | ConnectException e) { } catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND); this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.HostNotFoundException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) { } catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
this.changeStatus(Account.State.TOR_NOT_AVAILABLE); this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException e) { } catch (final IOException | XmlPullParserException e) {
@ -796,46 +792,41 @@ public class XmppConnection implements Runnable {
private void switchOverToTls() throws XmlPullParserException, IOException { private void switchOverToTls() throws XmlPullParserException, IOException {
tagReader.readTag(); 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 { try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); tlsFactoryVerifier = getTlsFactoryVerifier();
final InetAddress address = socket == null ? null : socket.getInetAddress(); } catch (final NoSuchAlgorithmException | KeyManagementException e) {
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");
throw new StateChangingException(Account.State.TLS_ERROR); 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 { private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {