Implement XEP-0368: SRV records for XMPP over TLS
This commit is contained in:
parent
20ec9ff2c6
commit
217f6603c0
|
@ -19,6 +19,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import de.measite.minidns.Client;
|
import de.measite.minidns.Client;
|
||||||
|
@ -57,7 +58,7 @@ public class DNSHelper {
|
||||||
if (!b.containsKey("values")) {
|
if (!b.containsKey("values")) {
|
||||||
Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
|
Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
|
||||||
ArrayList<Parcelable> values = new ArrayList<>();
|
ArrayList<Parcelable> values = new ArrayList<>();
|
||||||
values.add(createNamePortBundle(host,5222));
|
values.add(createNamePortBundle(host, 5222, false));
|
||||||
b.putParcelableArrayList("values",values);
|
b.putParcelableArrayList("values",values);
|
||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
|
@ -96,57 +97,73 @@ public class DNSHelper {
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TlsSrv {
|
||||||
|
private final SRV srv;
|
||||||
|
private final boolean tls;
|
||||||
|
|
||||||
|
public TlsSrv(SRV srv, boolean tls) {
|
||||||
|
this.srv = srv;
|
||||||
|
this.tls = tls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
|
||||||
|
final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
|
||||||
|
for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
|
||||||
|
for (Record rr : rrset) {
|
||||||
|
Data d = rr.getPayload();
|
||||||
|
if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
|
||||||
|
SRV srv = (SRV) d;
|
||||||
|
if (!priorities.containsKey(srv.getPriority())) {
|
||||||
|
priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
|
||||||
|
}
|
||||||
|
priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
|
||||||
|
}
|
||||||
|
if (d instanceof A) {
|
||||||
|
A a = (A) d;
|
||||||
|
if (!ips4.containsKey(rr.getName())) {
|
||||||
|
ips4.put(rr.getName(), new ArrayList<String>());
|
||||||
|
}
|
||||||
|
ips4.get(rr.getName()).add(a.toString());
|
||||||
|
}
|
||||||
|
if (d instanceof AAAA) {
|
||||||
|
AAAA aaaa = (AAAA) d;
|
||||||
|
if (!ips6.containsKey(rr.getName())) {
|
||||||
|
ips6.put(rr.getName(), new ArrayList<String>());
|
||||||
|
}
|
||||||
|
ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
try {
|
try {
|
||||||
client.setTimeout(Config.PING_TIMEOUT * 1000);
|
client.setTimeout(Config.PING_TIMEOUT * 1000);
|
||||||
String qname = "_xmpp-client._tcp." + host;
|
final String qname = "_xmpp-client._tcp." + host;
|
||||||
|
final String tlsQname = "_xmpps-client._tcp." + host;
|
||||||
Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
|
Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
|
||||||
DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
|
|
||||||
|
|
||||||
TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<>();
|
final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
|
||||||
TreeMap<String, ArrayList<String>> ips4 = new TreeMap<>();
|
final Map<String, List<String>> ips4 = new TreeMap<>();
|
||||||
TreeMap<String, ArrayList<String>> ips6 = new TreeMap<>();
|
final Map<String, List<String>> ips6 = new TreeMap<>();
|
||||||
|
|
||||||
for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
|
fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
|
||||||
for (Record rr : rrset) {
|
fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
|
||||||
Data d = rr.getPayload();
|
|
||||||
if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
|
|
||||||
SRV srv = (SRV) d;
|
|
||||||
if (!priorities.containsKey(srv.getPriority())) {
|
|
||||||
priorities.put(srv.getPriority(),new ArrayList<SRV>());
|
|
||||||
}
|
|
||||||
priorities.get(srv.getPriority()).add(srv);
|
|
||||||
}
|
|
||||||
if (d instanceof A) {
|
|
||||||
A a = (A) d;
|
|
||||||
if (!ips4.containsKey(rr.getName())) {
|
|
||||||
ips4.put(rr.getName(), new ArrayList<String>());
|
|
||||||
}
|
|
||||||
ips4.get(rr.getName()).add(a.toString());
|
|
||||||
}
|
|
||||||
if (d instanceof AAAA) {
|
|
||||||
AAAA aaaa = (AAAA) d;
|
|
||||||
if (!ips6.containsKey(rr.getName())) {
|
|
||||||
ips6.put(rr.getName(), new ArrayList<String>());
|
|
||||||
}
|
|
||||||
ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<SRV> result = new ArrayList<>();
|
final List<TlsSrv> result = new ArrayList<>();
|
||||||
for (ArrayList<SRV> s : priorities.values()) {
|
for (final List<TlsSrv> s : priorities.values()) {
|
||||||
result.addAll(s);
|
result.addAll(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<Bundle> values = new ArrayList<>();
|
final ArrayList<Bundle> values = new ArrayList<>();
|
||||||
if (result.size() == 0) {
|
if (result.size() == 0) {
|
||||||
DNSMessage response;
|
DNSMessage response;
|
||||||
try {
|
try {
|
||||||
response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
|
response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
|
||||||
for (int i = 0; i < response.getAnswers().length; ++i) {
|
for (int i = 0; i < response.getAnswers().length; ++i) {
|
||||||
values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload()));
|
values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
|
Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
|
||||||
|
@ -154,37 +171,38 @@ public class DNSHelper {
|
||||||
try {
|
try {
|
||||||
response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
|
response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
|
||||||
for (int i = 0; i < response.getAnswers().length; ++i) {
|
for (int i = 0; i < response.getAnswers().length; ++i) {
|
||||||
values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload()));
|
values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
|
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
|
||||||
}
|
}
|
||||||
values.add(createNamePortBundle(host,5222));
|
values.add(createNamePortBundle(host, 5222, false));
|
||||||
bundle.putParcelableArrayList("values", values);
|
bundle.putParcelableArrayList("values", values);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
for (SRV srv : result) {
|
for (final TlsSrv tlsSrv : result) {
|
||||||
|
final SRV srv = tlsSrv.srv;
|
||||||
if (ips6.containsKey(srv.getName())) {
|
if (ips6.containsKey(srv.getName())) {
|
||||||
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
|
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
|
DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
|
||||||
for (int i = 0; i < response.getAnswers().length; ++i) {
|
for (int i = 0; i < response.getAnswers().length; ++i) {
|
||||||
values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload()));
|
values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
|
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ips4.containsKey(srv.getName())) {
|
if (ips4.containsKey(srv.getName())) {
|
||||||
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
|
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls));
|
||||||
} else {
|
} else {
|
||||||
DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
|
DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
|
||||||
for(int i = 0; i < response.getAnswers().length; ++i) {
|
for(int i = 0; i < response.getAnswers().length; ++i) {
|
||||||
values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
|
values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values.add(createNamePortBundle(srv.getName(), srv.getPort()));
|
values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls));
|
||||||
}
|
}
|
||||||
bundle.putParcelableArrayList("values", values);
|
bundle.putParcelableArrayList("values", values);
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
|
@ -195,28 +213,31 @@ public class DNSHelper {
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bundle createNamePortBundle(String name, int port) {
|
private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
|
||||||
Bundle namePort = new Bundle();
|
Bundle namePort = new Bundle();
|
||||||
namePort.putString("name", name);
|
namePort.putString("name", name);
|
||||||
|
namePort.putBoolean("tls", tls);
|
||||||
namePort.putInt("port", port);
|
namePort.putInt("port", port);
|
||||||
return namePort;
|
return namePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) {
|
private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
|
||||||
Bundle namePort = new Bundle();
|
Bundle namePort = new Bundle();
|
||||||
namePort.putString("name", name);
|
namePort.putString("name", name);
|
||||||
|
namePort.putBoolean("tls", tls);
|
||||||
namePort.putInt("port", port);
|
namePort.putInt("port", port);
|
||||||
if (ips!=null) {
|
if (ips!=null) {
|
||||||
ArrayList<String> ip = ips.get(name);
|
List<String> ip = ips.get(name);
|
||||||
Collections.shuffle(ip, new Random());
|
Collections.shuffle(ip, new Random());
|
||||||
namePort.putString("ip", ip.get(0));
|
namePort.putString("ip", ip.get(0));
|
||||||
}
|
}
|
||||||
return namePort;
|
return namePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bundle createNamePortBundle(String name, int port, Data data) {
|
private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) {
|
||||||
Bundle namePort = new Bundle();
|
Bundle namePort = new Bundle();
|
||||||
namePort.putString("name", name);
|
namePort.putString("name", name);
|
||||||
|
namePort.putBoolean("tls", tls);
|
||||||
namePort.putInt("port", port);
|
namePort.putInt("port", port);
|
||||||
if (data instanceof A) {
|
if (data instanceof A) {
|
||||||
namePort.putString("ip", data.toString());
|
namePort.putString("ip", data.toString());
|
||||||
|
|
|
@ -20,7 +20,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
|
@ -247,6 +247,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR");
|
||||||
socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort());
|
socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort());
|
||||||
|
startXmpp();
|
||||||
} else if (DNSHelper.isIp(account.getServer().toString())) {
|
} else if (DNSHelper.isIp(account.getServer().toString())) {
|
||||||
socket = new Socket();
|
socket = new Socket();
|
||||||
try {
|
try {
|
||||||
|
@ -254,6 +255,7 @@ public class XmppConnection implements Runnable {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UnknownHostException();
|
throw new UnknownHostException();
|
||||||
}
|
}
|
||||||
|
startXmpp();
|
||||||
} else {
|
} else {
|
||||||
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
|
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
|
||||||
final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
|
final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
|
||||||
|
@ -269,24 +271,46 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
final int srvRecordPort = namePort.getInt("port");
|
final int srvRecordPort = namePort.getInt("port");
|
||||||
final String srvIpServer = namePort.getString("ip");
|
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;
|
final InetSocketAddress addr;
|
||||||
if (srvIpServer != null) {
|
if (srvIpServer != null) {
|
||||||
addr = new InetSocketAddress(srvIpServer, srvRecordPort);
|
addr = new InetSocketAddress(srvIpServer, srvRecordPort);
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
||||||
+ ": using values from dns " + srvRecordServer
|
+ ": using values from dns " + srvRecordServer
|
||||||
+ "[" + srvIpServer + "]:" + srvRecordPort);
|
+ "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
|
||||||
} else {
|
} else {
|
||||||
addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
|
addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
||||||
+ ": using values from dns "
|
+ ": using values from dns "
|
||||||
+ srvRecordServer + ":" + srvRecordPort);
|
+ srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
|
||||||
}
|
}
|
||||||
socket = new Socket();
|
|
||||||
socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
|
if (!features.encryptionEnabled) {
|
||||||
tagWriter.setOutputStream(socket.getOutputStream());
|
socket = new Socket();
|
||||||
tagReader.setInputStream(socket.getInputStream());
|
socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
|
||||||
tagWriter.beginDocument();
|
} else {
|
||||||
sendStartStream();
|
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
|
||||||
|
socket = tlsFactoryVerifier.factory.createSocket();
|
||||||
|
|
||||||
|
if (socket == null) {
|
||||||
|
throw new IOException("could not initialize ssl socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
setSSLSocketSecurity((SSLSocket) socket);
|
||||||
|
this.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
|
||||||
|
this.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 Throwable e) {
|
} catch (final Throwable e) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
|
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
|
@ -295,18 +319,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tag nextTag;
|
processStream();
|
||||||
while ((nextTag = tagReader.readTag()) != null) {
|
|
||||||
if (nextTag.isStart("stream")) {
|
|
||||||
processStream();
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
throw new IOException("unknown tag on connect");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (socket.isConnected()) {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
} catch (final IncompatibleServerException e) {
|
} catch (final IncompatibleServerException e) {
|
||||||
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
|
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
|
||||||
} catch (final SecurityException e) {
|
} catch (final SecurityException e) {
|
||||||
|
@ -338,6 +351,99 @@ 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 void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
|
||||||
|
if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore any error, we just can't set the hostname...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) {
|
||||||
|
try {
|
||||||
|
if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||||
|
// can't call directly because of @hide?
|
||||||
|
//((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")});
|
||||||
|
android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}});
|
||||||
|
} else {
|
||||||
|
final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
|
||||||
|
// the concatenation of 8-bit, length prefixed protocol names, just one in our case...
|
||||||
|
// http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
|
||||||
|
final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8");
|
||||||
|
final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
|
||||||
|
lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
|
||||||
|
System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
|
||||||
|
method.invoke(socket, new Object[]{lengthPrefixedProtocols});
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// ignore any error, we just can't set the alpn protocol...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
@ -599,53 +705,42 @@ public class XmppConnection implements Runnable {
|
||||||
tagWriter.writeTag(startTLS);
|
tagWriter.writeTag(startTLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setSSLSocketSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
|
private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
|
||||||
tagReader.readTag();
|
tagReader.readTag();
|
||||||
try {
|
try {
|
||||||
final SSLContext sc = SSLContext.getInstance("TLS");
|
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
|
||||||
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 InetAddress address = socket == null ? null : socket.getInetAddress();
|
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");
|
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) {
|
if (sslSocket == null) {
|
||||||
throw new IOException("could not initialize ssl socket");
|
throw new IOException("could not initialize ssl socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String[] supportProtocols;
|
setSSLSocketSecurity(sslSocket);
|
||||||
final Collection<String> supportedProtocols = new LinkedList<>(
|
|
||||||
Arrays.asList(sslSocket.getSupportedProtocols()));
|
|
||||||
supportedProtocols.remove("SSLv3");
|
|
||||||
supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
|
|
||||||
|
|
||||||
sslSocket.setEnabledProtocols(supportProtocols);
|
if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) {
|
||||||
|
|
||||||
final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
|
|
||||||
sslSocket.getSupportedCipherSuites());
|
|
||||||
//Log.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites));
|
|
||||||
if (cipherSuites.length > 0) {
|
|
||||||
sslSocket.setEnabledCipherSuites(cipherSuites);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) {
|
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
|
||||||
throw new SecurityException();
|
throw new SecurityException();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue