add ssl server certificate pinning

This commit is contained in:
Craig Raw 2021-04-02 12:27:04 +02:00
parent 771bd1545c
commit 5d91f033c0
11 changed files with 254 additions and 48 deletions

View file

@ -76,6 +76,7 @@ public class AppController implements Initializable {
public static final double TAB_LABEL_GRAPHIC_OPACITY_INACTIVE = 0.8; public static final double TAB_LABEL_GRAPHIC_OPACITY_INACTIVE = 0.8;
public static final double TAB_LABEL_GRAPHIC_OPACITY_ACTIVE = 0.95; public static final double TAB_LABEL_GRAPHIC_OPACITY_ACTIVE = 0.95;
public static final String LOADING_TRANSACTIONS_MESSAGE = "Loading wallet, select Transactions tab to view..."; public static final String LOADING_TRANSACTIONS_MESSAGE = "Loading wallet, select Transactions tab to view...";
public static final String CONNECTION_FAILED_PREFIX = "Connection failed: ";
@FXML @FXML
private MenuItem saveTransaction; private MenuItem saveTransaction;
@ -1451,8 +1452,7 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void connectionFailed(ConnectionFailedEvent event) { public void connectionFailed(ConnectionFailedEvent event) {
String reason = event.getException().getCause() != null ? event.getException().getCause().getMessage() : event.getException().getMessage(); String status = CONNECTION_FAILED_PREFIX + event.getMessage();
String status = "Connection failed: " + reason;
statusUpdated(new StatusEvent(status)); statusUpdated(new StatusEvent(status));
} }
@ -1465,7 +1465,7 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void disconnection(DisconnectionEvent event) { public void disconnection(DisconnectionEvent event) {
serverToggle.setDisable(false); serverToggle.setDisable(false);
if(!AppServices.isConnecting() && !AppServices.isConnected() && !statusBar.getText().startsWith("Connection error")) { if(!AppServices.isConnecting() && !AppServices.isConnected() && !statusBar.getText().startsWith(CONNECTION_FAILED_PREFIX)) {
statusUpdated(new StatusEvent("Disconnected")); statusUpdated(new StatusEvent("Disconnected"));
} }
if(statusTimeline == null || statusTimeline.getStatus() != Animation.Status.RUNNING) { if(statusTimeline == null || statusTimeline.getStatus() != Animation.Status.RUNNING) {

View file

@ -208,6 +208,31 @@ public class AppServices {
connectionService.setRestartOnFailure(false); connectionService.setRestartOnFailure(false);
} }
if(failEvent.getSource().getException() instanceof TlsServerException && failEvent.getSource().getException().getCause() != null) {
TlsServerException tlsServerException = (TlsServerException)failEvent.getSource().getException();
connectionService.setRestartOnFailure(false);
if(tlsServerException.getCause().getMessage().contains("PKIX path building failed")) {
File crtFile = Config.get().getElectrumServerCert();
if(crtFile != null) {
AppServices.showErrorDialog("SSL Handshake Failed", "The configured server certificate at " + crtFile.getAbsolutePath() + " did not match the certificate provided by the server at " + tlsServerException.getServer().getHost() + "." +
"\n\nThis may indicate a man-in-the-middle attack!" +
"\n\nChange the configured server certificate if you would like to proceed.");
} else {
crtFile = Storage.getCertificateFile(tlsServerException.getServer().getHost());
if(crtFile != null) {
Optional<ButtonType> optButton = AppServices.showErrorDialog("SSL Handshake Failed", "The certificate provided by the server at " + tlsServerException.getServer().getHost() + " appears to have changed." +
"\n\nThis may indicate a man-in-the-middle attack!" +
"\n\nDo you still want to proceed?", ButtonType.NO, ButtonType.YES);
if(optButton.isPresent() && optButton.get() == ButtonType.YES) {
crtFile.delete();
Platform.runLater(() -> restartService(connectionService));
return;
}
}
}
}
}
onlineProperty.removeListener(onlineServicesListener); onlineProperty.removeListener(onlineServicesListener);
onlineProperty.setValue(false); onlineProperty.setValue(false);
onlineProperty.addListener(onlineServicesListener); onlineProperty.addListener(onlineServicesListener);

View file

@ -1,5 +1,7 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.net.TlsServerException;
public class ConnectionFailedEvent { public class ConnectionFailedEvent {
private final Throwable exception; private final Throwable exception;
@ -10,4 +12,26 @@ public class ConnectionFailedEvent {
public Throwable getException() { public Throwable getException() {
return exception; return exception;
} }
public String getMessage() {
if(exception instanceof TlsServerException) {
return exception.getMessage();
}
Throwable cause = (exception.getCause() != null ? exception.getCause() : exception);
cause = (cause.getCause() != null ? cause.getCause() : cause);
String message = splitCamelCase(cause.getClass().getSimpleName().replace("Exception", "Error"));
return message + " (" + cause.getMessage() + ")";
}
static String splitCamelCase(String s) {
return s.replaceAll(
String.format("%s|%s|%s",
"(?<=[A-Z])(?=[A-Z][a-z])",
"(?<=[^A-Z])(?=[A-Z])",
"(?<=[A-Za-z])(?=[^A-Za-z])"
),
" "
);
}
} }

View file

@ -24,6 +24,8 @@ import java.lang.reflect.Type;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
@ -44,6 +46,7 @@ public class Storage {
public static final String WINDOWS_SPARROW_DIR = "Sparrow"; public static final String WINDOWS_SPARROW_DIR = "Sparrow";
public static final String WALLETS_DIR = "wallets"; public static final String WALLETS_DIR = "wallets";
public static final String WALLETS_BACKUP_DIR = "backup"; public static final String WALLETS_BACKUP_DIR = "backup";
public static final String CERTS_DIR = "certs";
public static final String HEADER_MAGIC_1 = "SPRW1"; public static final String HEADER_MAGIC_1 = "SPRW1";
private static final int BINARY_HEADER_LENGTH = 28; private static final int BINARY_HEADER_LENGTH = 28;
public static final String TEMP_BACKUP_EXTENSION = "tmp"; public static final String TEMP_BACKUP_EXTENSION = "tmp";
@ -384,6 +387,37 @@ public class Storage {
return walletsDir; return walletsDir;
} }
public static File getCertificateFile(String host) {
File certsDir = getCertsDir();
File[] certs = certsDir.listFiles((dir, name) -> name.equals(host));
if(certs.length > 0) {
return certs[0];
}
return null;
}
public static void saveCertificate(String host, Certificate cert) {
try(FileWriter writer = new FileWriter(new File(getCertsDir(), host))) {
writer.write("-----BEGIN CERTIFICATE-----\n");
writer.write(Base64.getEncoder().encodeToString(cert.getEncoded()).replaceAll("(.{64})", "$1\n"));
writer.write("\n-----END CERTIFICATE-----\n");
} catch(CertificateEncodingException e) {
log.error("Error encoding PEM certificate", e);
} catch(IOException e) {
log.error("Error writing PEM certificate", e);
}
}
static File getCertsDir() {
File certsDir = new File(getSparrowDir(), CERTS_DIR);
if(!certsDir.exists()) {
certsDir.mkdirs();
}
return certsDir;
}
static File getSparrowDir() { static File getSparrowDir() {
if(Network.get() != Network.MAINNET) { if(Network.get() != Network.MAINNET) {
return new File(getSparrowHome(), Network.get().getName()); return new File(getSparrowHome(), Network.get().getName());

View file

@ -39,7 +39,7 @@ public enum Protocol {
}, },
SSL { SSL {
@Override @Override
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException { public Transport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if(isOnionAddress(server)) { if(isOnionAddress(server)) {
return new TorTcpOverTlsTransport(server); return new TorTcpOverTlsTransport(server);
} }
@ -57,7 +57,7 @@ public enum Protocol {
} }
@Override @Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) throws NoSuchAlgorithmException, KeyManagementException { public Transport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new ProxyTcpOverTlsTransport(server, proxy); return new ProxyTcpOverTlsTransport(server, proxy);
} }
@ -68,27 +68,27 @@ public enum Protocol {
}, },
HTTP { HTTP {
@Override @Override
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException { public Transport getTransport(HostAndPort server) {
throw new UnsupportedOperationException("No transport supported for HTTP"); throw new UnsupportedOperationException("No transport supported for HTTP");
} }
@Override @Override
public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { public Transport getTransport(HostAndPort server, File serverCert) {
throw new UnsupportedOperationException("No transport supported for HTTP"); throw new UnsupportedOperationException("No transport supported for HTTP");
} }
@Override @Override
public Transport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { public Transport getTransport(HostAndPort server, HostAndPort proxy) {
throw new UnsupportedOperationException("No transport supported for HTTP"); throw new UnsupportedOperationException("No transport supported for HTTP");
} }
@Override @Override
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) {
throw new UnsupportedOperationException("No transport supported for HTTP"); throw new UnsupportedOperationException("No transport supported for HTTP");
} }
}; };
public abstract Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException; public abstract Transport getTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException;
public abstract Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException; public abstract Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException;

View file

@ -18,7 +18,7 @@ public class ProxyTcpOverTlsTransport extends TcpOverTlsTransport {
private final HostAndPort proxy; private final HostAndPort proxy;
public ProxyTcpOverTlsTransport(HostAndPort server, HostAndPort proxy) throws KeyManagementException, NoSuchAlgorithmException { public ProxyTcpOverTlsTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server); super(server);
this.proxy = proxy; this.proxy = proxy;
} }
@ -34,7 +34,7 @@ public class ProxyTcpOverTlsTransport extends TcpOverTlsTransport {
Socket underlying = new Socket(new Proxy(Proxy.Type.SOCKS, proxyAddr)); Socket underlying = new Socket(new Proxy(Proxy.Type.SOCKS, proxyAddr));
underlying.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(DEFAULT_PORT))); underlying.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)));
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(underlying, proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT), true); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(underlying, proxy.getHost(), proxy.getPortOrDefault(DEFAULT_PROXY_PORT), true);
sslSocket.startHandshake(); startHandshake(sslSocket);
return sslSocket; return sslSocket;
} }

View file

@ -1,6 +1,9 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.io.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.io.File; import java.io.File;
@ -8,35 +11,23 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.security.*; import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class TcpOverTlsTransport extends TcpTransport { public class TcpOverTlsTransport extends TcpTransport {
private static final Logger log = LoggerFactory.getLogger(TcpOverTlsTransport.class);
public static final int DEFAULT_PORT = 50002; public static final int DEFAULT_PORT = 50002;
protected final SSLSocketFactory sslSocketFactory; protected final SSLSocketFactory sslSocketFactory;
public TcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException { public TcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException, CertificateException, KeyStoreException, IOException {
super(server); super(server);
TrustManager[] trustAllCerts = new TrustManager[]{ TrustManager[] trustManagers = getTrustManagers(Storage.getCertificateFile(server.getHost()));
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom()); sslContext.init(null, trustManagers, new SecureRandom());
this.sslSocketFactory = sslContext.getSocketFactory(); this.sslSocketFactory = sslContext.getSocketFactory();
} }
@ -44,25 +35,77 @@ public class TcpOverTlsTransport extends TcpTransport {
public TcpOverTlsTransport(HostAndPort server, File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { public TcpOverTlsTransport(HostAndPort server, File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server); super(server);
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile)); TrustManager[] trustManagers = getTrustManagers(crtFile);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("electrumx", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null); sslContext.init(null, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory(); sslSocketFactory = sslContext.getSocketFactory();
} }
private TrustManager[] getTrustManagers(File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if(crtFile == null) {
return new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
if(certs.length == 0) {
throw new CertificateException("No server certificate provided");
}
certs[0].checkValidity();
}
}
};
}
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
if(certificate instanceof X509Certificate) {
try {
X509Certificate x509Certificate = (X509Certificate)certificate;
x509Certificate.checkValidity();
} catch(CertificateException e) {
crtFile.delete();
return getTrustManagers(null);
}
}
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("electrum-server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
return trustManagerFactory.getTrustManagers();
}
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
sslSocket.startHandshake(); startHandshake(sslSocket);
return sslSocket; return sslSocket;
} }
protected void startHandshake(SSLSocket sslSocket) throws IOException {
sslSocket.addHandshakeCompletedListener(event -> {
if(Storage.getCertificateFile(server.getHost()) == null) {
try {
Certificate[] certs = event.getPeerCertificates();
if(certs.length > 0) {
Storage.saveCertificate(server.getHost(), certs[0]);
}
} catch(SSLPeerUnverifiedException e) {
log.warn("Attempting to retrieve certificate for unverified peer", e);
}
}
});
sslSocket.startHandshake();
}
} }

View file

@ -10,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import javax.net.ssl.SSLHandshakeException;
import java.io.*; import java.io.*;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -192,6 +193,8 @@ public class TcpTransport implements Transport, Closeable {
try { try {
socket = createSocket(); socket = createSocket();
running = true; running = true;
} catch(SSLHandshakeException e) {
throw new TlsServerException(server, e);
} catch(IOException e) { } catch(IOException e) {
throw new ServerException(e); throw new ServerException(e);
} }

View file

@ -0,0 +1,59 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import java.io.File;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
public class TlsServerException extends ServerException {
private final HostAndPort server;
public TlsServerException(HostAndPort server) {
this.server = server;
}
public TlsServerException(HostAndPort server, String message) {
super(message);
this.server = server;
}
public TlsServerException(HostAndPort server, Throwable cause) {
this(server, getMessage(cause, server), cause);
}
public TlsServerException(HostAndPort server, String message, Throwable cause) {
super(message, cause);
this.server = server;
}
public HostAndPort getServer() {
return server;
}
private static String getMessage(Throwable cause, HostAndPort server) {
if(cause != null) {
if(cause.getMessage().contains("PKIX path building failed")) {
File configCrtFile = Config.get().getElectrumServerCert();
File savedCrtFile = Storage.getCertificateFile(server.getHost());
if(configCrtFile != null) {
return "Provided server certificate from " + server.getHost() + " did not match configured certificate at " + configCrtFile.getAbsolutePath();
} else if(savedCrtFile != null) {
return "Provided server certificate from " + server.getHost() + " did not match previously saved certificate at " + savedCrtFile.getAbsolutePath();
}
return "Provided server certificate from " + server.getHost() + " was invalid: " + (cause.getCause() != null ? cause.getCause().getMessage() : cause.getMessage());
} else if(cause.getCause() instanceof CertificateNotYetValidException) {
return cause.getMessage().replace("NotBefore:", "Certificate only valid from");
} else if(cause.getCause() instanceof CertificateExpiredException) {
return cause.getMessage().replace("NotAfter:", "Certificate expired at");
}
return cause.getMessage();
}
return "SSL Handshake Error";
}
}

View file

@ -17,7 +17,7 @@ import java.security.cert.CertificateException;
public class TorTcpOverTlsTransport extends TcpOverTlsTransport { public class TorTcpOverTlsTransport extends TcpOverTlsTransport {
private static final Logger log = LoggerFactory.getLogger(TorTcpOverTlsTransport.class); private static final Logger log = LoggerFactory.getLogger(TorTcpOverTlsTransport.class);
public TorTcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException { public TorTcpOverTlsTransport(HostAndPort server) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
super(server); super(server);
} }
@ -42,7 +42,7 @@ public class TorTcpOverTlsTransport extends TcpOverTlsTransport {
} }
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, server.getHost(), server.getPortOrDefault(DEFAULT_PORT), true); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, server.getHost(), server.getPortOrDefault(DEFAULT_PORT), true);
sslSocket.startHandshake(); startHandshake(sslSocket);
return sslSocket; return sslSocket;
} }

View file

@ -11,6 +11,7 @@ import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
@ -35,13 +36,13 @@ import org.slf4j.LoggerFactory;
import tornadofx.control.Field; import tornadofx.control.Field;
import tornadofx.control.Form; import tornadofx.control.Form;
import javax.net.ssl.SSLHandshakeException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Random; import java.util.Random;
public class ServerPreferencesController extends PreferencesDetailController { public class ServerPreferencesController extends PreferencesDetailController {
@ -545,10 +546,27 @@ public class ServerPreferencesController extends PreferencesDetailController {
private void showConnectionFailure(Throwable exception) { private void showConnectionFailure(Throwable exception) {
log.error("Connection error", exception); log.error("Connection error", exception);
String reason = exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage(); String reason = exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage();
if(exception.getCause() != null && exception.getCause() instanceof SSLHandshakeException) { if(exception instanceof TlsServerException && exception.getCause() != null) {
reason = "SSL Handshake Error\n" + reason; TlsServerException tlsServerException = (TlsServerException)exception;
} if(exception.getCause().getMessage().contains("PKIX path building failed")) {
if(exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind")) { File configCrtFile = Config.get().getElectrumServerCert();
File savedCrtFile = Storage.getCertificateFile(tlsServerException.getServer().getHost());
if(configCrtFile == null && savedCrtFile != null) {
Optional<ButtonType> optButton = AppServices.showErrorDialog("SSL Handshake Failed", "The certificate provided by the server at " + tlsServerException.getServer().getHost() + " appears to have changed." +
"\n\nThis may indicate a man-in-the-middle attack!" +
"\n\nDo you still want to proceed?", ButtonType.NO, ButtonType.YES);
if(optButton.isPresent()) {
if(optButton.get() == ButtonType.YES) {
savedCrtFile.delete();
Platform.runLater(this::startElectrumConnection);
return;
}
}
}
}
reason = tlsServerException.getMessage() + "\n\n" + reason;
} else if(exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind")) {
reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?"; reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?";
} }