Secure fallback

pull/170/head
M66B 5 years ago
parent c678e37bbf
commit 0cefa93c1d

@ -265,11 +265,11 @@ public class MailService implements AutoCloseable {
try { try {
// Some devices resolve IPv6 addresses while not having IPv6 connectivity // Some devices resolve IPv6 addresses while not having IPv6 connectivity
InetAddress[] iaddrs = InetAddress.getAllByName(host); InetAddress[] iaddrs = InetAddress.getAllByName(host);
Log.i("Fallback count=" + iaddrs.length);
if (iaddrs.length > 1) if (iaddrs.length > 1)
for (InetAddress iaddr : iaddrs) for (InetAddress iaddr : iaddrs)
try { try {
Log.i("Falling back to " + iaddr.getHostAddress()); Log.i("Falling back to " + iaddr.getHostAddress());
factory.setCheckServer(false);
_connect(context, iaddr.getHostAddress(), port, user, password, factory); _connect(context, iaddr.getHostAddress(), port, user, password, factory);
return null; return null;
} catch (MessagingException ex1) { } catch (MessagingException ex1) {
@ -440,11 +440,19 @@ public class MailService implements AutoCloseable {
private static class SSLSocketFactoryService extends SSLSocketFactory { private static class SSLSocketFactoryService extends SSLSocketFactory {
// openssl s_client -connect host:port < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin // openssl s_client -connect host:port < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin
private SSLContext sslcontext; private String server;
private boolean secure;
private String trustedFingerprint;
private SSLSocketFactory factory;
private X509Certificate certificate; private X509Certificate certificate;
private boolean checkServer = true;
SSLSocketFactoryService(final String host, final boolean insecure, final String trustedFingerprint) throws GeneralSecurityException { SSLSocketFactoryService(String host, boolean insecure, String fingerprint) throws GeneralSecurityException {
this.server = host;
this.secure = !insecure;
this.trustedFingerprint = fingerprint;
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
try { try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore"); KeyStore ks = KeyStore.getInstance("AndroidCAStore");
@ -455,100 +463,109 @@ public class MailService implements AutoCloseable {
tmf.init((KeyStore) null); tmf.init((KeyStore) null);
} }
final X509TrustManager rtm = (X509TrustManager) tmf.getTrustManagers()[0]; TrustManager[] tms = tmf.getTrustManagers();
if (tms == null || tms.length == 0 || !(tms[0] instanceof X509TrustManager)) {
Log.e("Missing root trust manager");
sslContext.init(null, tms, null);
} else {
final X509TrustManager rtm = (X509TrustManager) tms[0];
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (secure)
rtm.checkClientTrusted(chain, authType);
}
X509TrustManager tm = new X509TrustManager() { @Override
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { certificate = chain[0];
if (insecure)
return; if (secure) {
// Get certificate fingerprint
String fingerprint;
try {
fingerprint = getFingerPrint(certificate);
} catch (Throwable ex) {
throw new CertificateException(ex);
}
rtm.checkClientTrusted(chain, authType); // Check if selected fingerprint
} if (fingerprint.equals(trustedFingerprint)) {
Log.i("Trusted selected fingerprint");
return;
}
@Override // Check certificates
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { rtm.checkServerTrusted(chain, authType);
certificate = chain[0];
if (insecure) // Check host name
return; List<String> names = getDnsNames(certificate);
for (String name : names)
if (matches(server, name)) {
Log.i("Trusted server=" + server + " name=" + name);
return;
}
String fingerprint; String error = server + " not in certificate: " + TextUtils.join(",", names);
try { Log.e(error);
fingerprint = getFingerPrint(certificate); throw new CertificateException(error);
} catch (Throwable ex) { }
throw new CertificateException(ex);
} }
if (fingerprint.equals(trustedFingerprint)) @Override
return; public X509Certificate[] getAcceptedIssuers() {
return rtm.getAcceptedIssuers();
if (checkServer) {
rtm.checkServerTrusted(chain, authType);
List<String> names = getDnsNames(certificate);
for (String name : names)
if (matches(host, name))
return;
String error = host + " not in certificate: " + TextUtils.join(",", names);
Log.e(error);
throw new CertificateException(error);
} }
} };
@Override sslContext.init(null, new TrustManager[]{tm}, null);
public X509Certificate[] getAcceptedIssuers() { }
return rtm.getAcceptedIssuers();
}
};
sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[]{tm}, null);
}
void setCheckServer(boolean value) { factory = sslContext.getSocketFactory();
checkServer = value;
} }
@Override @Override
public Socket createSocket() throws IOException { public Socket createSocket() throws IOException {
return sslcontext.getSocketFactory().createSocket(); Log.e("createSocket");
throw new IOException("createSocket");
} }
@Override @Override
public Socket createSocket(String host, int port) throws IOException { public Socket createSocket(String host, int port) throws IOException {
return sslcontext.getSocketFactory().createSocket(host, port); return factory.createSocket(server, port);
} }
@Override @Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return sslcontext.getSocketFactory().createSocket(s, host, port, autoClose); return factory.createSocket(s, server, port, autoClose);
} }
@Override @Override
public Socket createSocket(InetAddress address, int port) throws IOException { public Socket createSocket(InetAddress address, int port) throws IOException {
return sslcontext.getSocketFactory().createSocket(address, port); Log.e("createSocket(address, port)");
throw new IOException("createSocket");
} }
@Override @Override
public Socket createSocket(String host, int port, InetAddress clientAddress, int clientPort) throws IOException { public Socket createSocket(String host, int port, InetAddress clientAddress, int clientPort) throws IOException {
return sslcontext.getSocketFactory().createSocket(host, port, clientAddress, clientPort); return factory.createSocket(server, port, clientAddress, clientPort);
} }
@Override @Override
public Socket createSocket(InetAddress address, int port, InetAddress clientAddress, int clientPort) throws IOException { public Socket createSocket(InetAddress address, int port, InetAddress clientAddress, int clientPort) throws IOException {
return sslcontext.getSocketFactory().createSocket(address, port, clientAddress, clientPort); Log.e("createSocket(address, port, clientAddress, clientPort)");
throw new IOException("createSocket");
} }
@Override @Override
public String[] getDefaultCipherSuites() { public String[] getDefaultCipherSuites() {
return sslcontext.getSocketFactory().getDefaultCipherSuites(); return factory.getDefaultCipherSuites();
} }
@Override @Override
public String[] getSupportedCipherSuites() { public String[] getSupportedCipherSuites() {
return sslcontext.getSocketFactory().getSupportedCipherSuites(); return factory.getSupportedCipherSuites();
} }
private static boolean matches(String server, String name) { private static boolean matches(String server, String name) {

Loading…
Cancel
Save