diff options
author | Christian S <christian@pix-art.de> | 2016-01-13 17:48:30 +0100 |
---|---|---|
committer | Christian S <christian@pix-art.de> | 2016-01-13 17:48:30 +0100 |
commit | 4c2dc7082855581d875c51d97650c7b4a435abe5 (patch) | |
tree | 818ebaea4bfa9743e0acf85abc92afca7b51449a /src/main/java/eu/siacs/conversations/xmpp | |
parent | c133b1d7e1cc9a3a3426d26e35cc038b508e1691 (diff) | |
parent | 3b3aa5b5841494ba02fe327f910558eb40e662b5 (diff) |
Merge branch 'siacs/master' into development
Diffstat (limited to 'src/main/java/eu/siacs/conversations/xmpp')
-rw-r--r-- | src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java | 178 |
1 files changed, 109 insertions, 69 deletions
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index c64fa196d..a56a64aff 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -20,7 +20,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.math.BigInteger; import java.net.ConnectException; import java.net.IDN; @@ -35,12 +34,9 @@ import java.security.Principal; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; @@ -66,6 +62,7 @@ import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.DNSHelper; +import eu.siacs.conversations.utils.SSLSocketHelper; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; @@ -247,6 +244,7 @@ public class XmppConnection implements Runnable { } Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR"); socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort()); + startXmpp(); } else if (DNSHelper.isIp(account.getServer().toString())) { socket = new Socket(); try { @@ -254,13 +252,12 @@ public class XmppConnection implements Runnable { } catch (IOException e) { throw new UnknownHostException(); } + startXmpp(); } else { - final Bundle result = DNSHelper.getSRVRecord(account.getServer(),mXmppConnectionService); + final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); final ArrayList<Parcelable>values = result.getParcelableArrayList("values"); - int i = 0; - boolean socketError = true; - while (socketError && values.size() > i) { - final Bundle namePort = (Bundle) values.get(i); + for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) { + final Bundle namePort = (Bundle) iterator.next(); try { String srvRecordServer; try { @@ -271,48 +268,57 @@ public class XmppConnection implements Runnable { } final int srvRecordPort = namePort.getInt("port"); final String srvIpServer = namePort.getString("ip"); + // if tls is true, encryption is implied and must not be started + features.encryptionEnabled = namePort.getBoolean("tls"); final InetSocketAddress addr; if (srvIpServer != null) { addr = new InetSocketAddress(srvIpServer, srvRecordPort); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": using values from dns " + srvRecordServer - + "[" + srvIpServer + "]:" + srvRecordPort); + + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled); } else { addr = new InetSocketAddress(srvRecordServer, srvRecordPort); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort); + + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled); } - socket = new Socket(); - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - socketError = false; + + if (!features.encryptionEnabled) { + socket = new Socket(); + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + } else { + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); + socket = tlsFactoryVerifier.factory.createSocket(); + + if (socket == null) { + throw new IOException("could not initialize ssl socket"); + } + + SSLSocketHelper.setSecurity((SSLSocket) socket); + SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); + SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); + + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + } + + if (startXmpp()) + break; // successfully connected to server that speaks xmpp + } catch(final SecurityException e) { + throw e; } catch (final Throwable e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")"); - i++; + if (!iterator.hasNext()) { + throw new UnknownHostException(); + } } } - if (socketError) { - throw new UnknownHostException(); - } - } - final OutputStream out = socket.getOutputStream(); - tagWriter.setOutputStream(out); - final InputStream in = socket.getInputStream(); - tagReader.setInputStream(in); - tagWriter.beginDocument(); - sendStartStream(); - Tag nextTag; - while ((nextTag = tagReader.readTag()) != null) { - if (nextTag.isStart("stream")) { - processStream(); - break; - } else { - throw new IOException("unknown tag on connect"); - } - } - if (socket.isConnected()) { - socket.close(); } + processStream(); } catch (final IncompatibleServerException e) { this.changeStatus(Account.State.INCOMPATIBLE_SERVER); } catch (final SecurityException e) { @@ -344,6 +350,66 @@ public class XmppConnection implements Runnable { } } + /** + * Starts xmpp protocol, call after connecting to socket + * @return true if server returns with valid xmpp, false otherwise + * @throws IOException Unknown tag on connect + * @throws XmlPullParserException Bad Xml + * @throws NoSuchAlgorithmException Other error + */ + private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { + tagWriter.setOutputStream(socket.getOutputStream()); + tagReader.setInputStream(socket.getInputStream()); + tagWriter.beginDocument(); + sendStartStream(); + Tag nextTag; + while ((nextTag = tagReader.readTag()) != null) { + if (nextTag.isStart("stream")) { + return true; + } else { + throw new IOException("unknown tag on connect"); + } + } + if (socket.isConnected()) { + socket.close(); + } + return false; + } + + private static class TlsFactoryVerifier { + private final SSLSocketFactory factory; + private final HostnameVerifier verifier; + + public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException { + this.factory = factory; + this.verifier = verifier; + if (factory == null || verifier == null) { + throw new IOException("could not setup ssl"); + } + } + } + + private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { + final SSLContext sc = SSLContext.getInstance("TLS"); + MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); + KeyManager[] keyManager; + if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { + keyManager = new KeyManager[]{mKeyManager}; + } else { + keyManager = null; + } + sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); + final SSLSocketFactory factory = sc.getSocketFactory(); + final HostnameVerifier verifier; + if (mInteractive) { + verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); + } else { + verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); + } + + return new TlsFactoryVerifier(factory, verifier); + } + @Override public void run() { try { @@ -605,53 +671,27 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } + + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); try { - final SSLContext sc = SSLContext.getInstance("TLS"); - MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); - KeyManager[] keyManager; - if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { - keyManager = new KeyManager[]{ mKeyManager }; - } else { - keyManager = null; - } - sc.init(keyManager,new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()},mXmppConnectionService.getRNG()); - final SSLSocketFactory factory = sc.getSocketFactory(); - final HostnameVerifier verifier; - if (mInteractive) { - verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); - } else { - verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); - } + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); final InetAddress address = socket == null ? null : socket.getInetAddress(); - if (factory == null || address == null || verifier == null) { + if (address == null) { throw new IOException("could not setup ssl"); } - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); + final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); if (sslSocket == null) { throw new IOException("could not initialize ssl socket"); } - final String[] supportProtocols; - final Collection<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); - - sslSocket.setEnabledProtocols(supportProtocols); - - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sslSocket.getSupportedCipherSuites()); - //Log.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites)); - if (cipherSuites.length > 0) { - sslSocket.setEnabledCipherSuites(cipherSuites); - } + SSLSocketHelper.setSecurity(sslSocket); - if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); throw new SecurityException(); } |