From 372ebf2fff2ef49c03f8891a8d8cee52c69ac37e Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 3 Aug 2026 09:47:36 +0200 Subject: [PATCH] only save certificates passing hostname verification as ca validated, and retain not yet valid certificates --- .../sparrow/net/TcpOverTlsTransport.java | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TcpOverTlsTransport.java b/src/main/java/com/sparrowwallet/sparrow/net/TcpOverTlsTransport.java index 01fef26b..b8f8f272 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TcpOverTlsTransport.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TcpOverTlsTransport.java @@ -14,10 +14,14 @@ import java.net.InetSocketAddress; import java.security.*; import java.security.cert.*; import java.security.cert.Certificate; +import java.util.Collection; +import java.util.List; public class TcpOverTlsTransport extends TcpTransport { private static final Logger log = LoggerFactory.getLogger(TcpOverTlsTransport.class); public static final int PAD_TO_MULTIPLE_OF_BYTES = 96; + private static final int GENERAL_NAME_DNS = 2; + private static final int GENERAL_NAME_IP_ADDRESS = 7; protected final SSLSocketFactory sslSocketFactory; protected final boolean usingCaTrust; @@ -112,9 +116,9 @@ public class TcpOverTlsTransport extends TcpTransport { return getTrustManagers(null, host); } //Allow expired certificates for private servers where users may not have the expertise to renew - } catch(CertificateException e) { - crtFile.delete(); - return getTrustManagers(null, host); + } catch(CertificateNotYetValidException e) { + //A certificate that was valid when saved or configured can only appear to be not yet valid if the system clock is incorrect - retain it + log.warn("Certificate for " + host + " at " + crtFile.getAbsolutePath() + " is not yet valid, check the system clock is correct"); } } @@ -140,7 +144,7 @@ public class TcpOverTlsTransport extends TcpTransport { try { Certificate[] certs = event.getPeerCertificates(); if(certs.length > 0) { - if(isCaSigned(certs)) { + if(isCaValidated(certs, server)) { Storage.saveCaCertificate(server.getHost(), certs[0]); } else { Storage.saveCertificate(server.getHost(), certs[0]); @@ -171,7 +175,7 @@ public class TcpOverTlsTransport extends TcpTransport { return tmf.getTrustManagers(); } - private static boolean isCaSigned(Certificate[] certs) { + private static boolean isCaValidated(Certificate[] certs, HostAndPort server) { try { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore)null); @@ -195,12 +199,40 @@ public class TcpOverTlsTransport extends TcpTransport { String authType = x509Certs[0].getPublicKey().getAlgorithm().equals("EC") ? "ECDHE_ECDSA" : "RSA"; defaultTm.checkServerTrusted(x509Certs, authType); - return true; + + //Match the hostname as CA validated connections do, so that a certificate saved here can be validated when it is used + return Protocol.isOnionAddress(server) || matchesHostname(x509Certs[0], server.getHost()); } catch(Exception e) { return false; } } + private static boolean matchesHostname(X509Certificate cert, String host) throws CertificateParsingException { + Collection> subjectAltNames = cert.getSubjectAlternativeNames(); + if(subjectAltNames == null) { + return false; + } + + for(List subjectAltName : subjectAltNames) { + if(subjectAltName.size() > 1 && subjectAltName.get(0) instanceof Integer type && subjectAltName.get(1) instanceof String name) { + if((type == GENERAL_NAME_DNS && matchesDnsName(name, host)) || (type == GENERAL_NAME_IP_ADDRESS && name.equalsIgnoreCase(host))) { + return true; + } + } + } + + return false; + } + + private static boolean matchesDnsName(String dnsName, String host) { + if(dnsName.startsWith("*.")) { + int index = host.indexOf('.'); + return index > 0 && host.substring(index + 1).equalsIgnoreCase(dnsName.substring(2)); + } + + return dnsName.equalsIgnoreCase(host); + } + @Override protected int getDefaultPort() { return Protocol.SSL.getDefaultPort();