From e8a7caacd8c29ce2b9d7ebc302daaebaf55327b9 Mon Sep 17 00:00:00 2001 From: Niteesh Gupta Date: Tue, 6 Jun 2017 12:29:49 +0530 Subject: [PATCH] Feature to validate certificate for a SSL connection --- pom.xml | 23 ++++++++++ .../java/fxlauncher/AbstractLauncher.java | 24 ++++++++++ .../CertificateInKeystoreTrustStrategy.java | 44 +++++++++++++++++++ src/main/java/fxlauncher/CreateManifest.java | 7 +++ src/main/java/fxlauncher/FXManifest.java | 9 ++++ 5 files changed, 107 insertions(+) create mode 100644 src/main/java/fxlauncher/CertificateInKeystoreTrustStrategy.java diff --git a/pom.xml b/pom.xml index 3957c59..7d9d25a 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,14 @@ Auto updating launcher for JavaFX Applications https://github.com/edvin/fxlauncher + + + org.apache.httpcomponents + httpclient + 4.5.2 + + + sonatype-nexus-staging @@ -76,13 +84,28 @@ org.apache.maven.plugins maven-jar-plugin 2.6 + + + org.apache.maven.plugins + maven-assembly-plugin + + jar-with-dependencies + fxlauncher.Launcher + + + package + + single + + + org.apache.maven.plugins diff --git a/src/main/java/fxlauncher/AbstractLauncher.java b/src/main/java/fxlauncher/AbstractLauncher.java index 4099562..3d9a241 100644 --- a/src/main/java/fxlauncher/AbstractLauncher.java +++ b/src/main/java/fxlauncher/AbstractLauncher.java @@ -1,6 +1,7 @@ package fxlauncher; import javafx.application.Application; +import org.apache.http.ssl.SSLContexts; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -20,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyManagementException; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -67,6 +69,20 @@ protected void checkSSLIgnoreflag() throws KeyManagementException, NoSuchAlgorit } } + /** + * Check if the SSL connection needs to validate the custom self signed SSL certificate + * + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + protected void checkSSLCertMatchFlag() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + String certDigest = manifest.resolveCertDigestStr(getParameters().getNamed()); + if (certDigest != null && !certDigest.isEmpty()) { + setupSSLCertMatchStrategy(certDigest); + } + } + protected ClassLoader createClassLoader(Path cacheDir) { List libs = manifest.files.stream().filter(LibraryFile::loadForCurrentPlatform).map(it -> it.toURL(cacheDir)).collect(Collectors.toList()); @@ -210,6 +226,8 @@ protected void syncManifest() throws Exception { log.info("offline selected"); return; } + /** validating cert digest and injecting it; As the first instance of connection happens here */ + checkSSLCertMatchFlag(); try { FXManifest remoteManifest = FXManifest.load(manifest.getFXAppURI()); @@ -253,6 +271,12 @@ public X509Certificate[] getAcceptedIssuers() { HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); } + private void setupSSLCertMatchStrategy(String certDigest) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + log.info("Found Cert digest :: "+certDigest); + SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new CertificateInKeystoreTrustStrategy(certDigest, "fxlauncher", null)).build(); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + } + protected boolean checkIgnoreUpdateErrorSetting() { return getParameters().getUnnamed().contains("--stopOnUpDateErrors"); } diff --git a/src/main/java/fxlauncher/CertificateInKeystoreTrustStrategy.java b/src/main/java/fxlauncher/CertificateInKeystoreTrustStrategy.java new file mode 100644 index 0000000..8699c1a --- /dev/null +++ b/src/main/java/fxlauncher/CertificateInKeystoreTrustStrategy.java @@ -0,0 +1,44 @@ +package fxlauncher; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.http.conn.ssl.TrustStrategy; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.logging.Logger; + +public class CertificateInKeystoreTrustStrategy implements TrustStrategy { + + private static final Logger log = Logger.getLogger("CertificateInKeystoreTrustStrategy"); + private String certDigest; + private String alias; + private KeyStore keyStore; + + public CertificateInKeystoreTrustStrategy(String certDigest, String alias, KeyStore keyStore) { + this.certDigest = certDigest; + this.alias = alias; + this.keyStore = keyStore; + } + + @Override + public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + try { + log.info("Comparing the expected and actual certificates"); + X509Certificate cert = this.keyStore != null ? (X509Certificate) this.keyStore.getCertificate(this.alias) : null; + String actualCert; + if (x509Certificates == null || x509Certificates.length == 0) + throw new RuntimeException("err-server-presented-no-certificates"); + else if(!(actualCert = DigestUtils.sha1Hex(x509Certificates[0].getEncoded())).equals(this.certDigest)) { + log.info("Unexpected certificate certdigest found: "+this.certDigest+" actual:: "+actualCert); + log.info("Presented certificate is "+x509Certificates[0]); + throw new RuntimeException("err-https-certificate-mismatch"); + } + return cert == null || (cert == x509Certificates[0]); + } + catch(KeyStoreException kse) { + throw new RuntimeException("err-ketStore-not-found"); + } + } +} diff --git a/src/main/java/fxlauncher/CreateManifest.java b/src/main/java/fxlauncher/CreateManifest.java index cca748d..6b672b9 100644 --- a/src/main/java/fxlauncher/CreateManifest.java +++ b/src/main/java/fxlauncher/CreateManifest.java @@ -28,6 +28,7 @@ public static void main(String[] args) throws IOException, URISyntaxException { Path appPath = Paths.get(args[2]); String cacheDir = null; + String certDigest = null; Boolean acceptDowngrade = null; String parameters = null; String whatsNew = null; @@ -46,6 +47,10 @@ public static void main(String[] args) throws IOException, URISyntaxException { if (named.containsKey("cache-dir")) cacheDir = named.get("cache-dir"); + // Configure certDigest + if (named.containsKey("cert-digest")) + certDigest = named.get("cert-digest"); + // Configure acceptDowngrade if (named.containsKey("accept-downgrade")) acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade")); @@ -75,6 +80,7 @@ public static void main(String[] args) throws IOException, URISyntaxException { StringBuilder rest = new StringBuilder(); for (String raw : params.getRaw()) { if (raw.startsWith("--cache-dir=")) continue; + if (raw.startsWith("--cert-digest=")) continue; if (raw.startsWith("--accept-downgrade=")) continue; if (raw.startsWith("--include-extensions=")) continue; if (raw.startsWith("--preload-native-libraries=")) continue; @@ -91,6 +97,7 @@ public static void main(String[] args) throws IOException, URISyntaxException { FXManifest manifest = create(baseURI, launchClass, appPath); if (cacheDir != null) manifest.cacheDir = cacheDir; + if (certDigest != null) manifest.certDigest = certDigest; if (acceptDowngrade != null) manifest.acceptDowngrade = acceptDowngrade; if (parameters != null) manifest.parameters = parameters; if (preloadNativeLibraries != null) manifest.preloadNativeLibraries = preloadNativeLibraries; diff --git a/src/main/java/fxlauncher/FXManifest.java b/src/main/java/fxlauncher/FXManifest.java index 4038333..3352a4a 100644 --- a/src/main/java/fxlauncher/FXManifest.java +++ b/src/main/java/fxlauncher/FXManifest.java @@ -38,6 +38,8 @@ public class FXManifest { @XmlElement public String cacheDir; @XmlElement + public String certDigest; + @XmlElement public Boolean acceptDowngrade = false; @XmlElement public String preloadNativeLibraries; @@ -113,6 +115,11 @@ public Path resolveCacheDir(Map namedParams) { return path; } + public String resolveCertDigestStr(Map namedParams) { + if (namedParams == null) namedParams = Collections.emptyMap(); + return namedParams.containsKey("cert-digest") ? namedParams.get("cert-digest") : this.certDigest; + } + public String getWhatsNewPage() { return whatsNewPage; @@ -135,6 +142,7 @@ public boolean equals(Object o) { if (wrapperStyle != null ? !wrapperStyle.equals(that.wrapperStyle) : that.wrapperStyle != null) return false; if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false; if (cacheDir != null ? !cacheDir.equals(that.cacheDir) : that.cacheDir != null) return false; + if (certDigest != null ? !certDigest.equals(that.certDigest) : that.certDigest != null) return false; if (lingeringUpdateScreen != null ? !lingeringUpdateScreen.equals(that.lingeringUpdateScreen) : that.lingeringUpdateScreen != null) return false; return acceptDowngrade != null ? acceptDowngrade.equals(that.acceptDowngrade) : that.acceptDowngrade == null; } @@ -151,6 +159,7 @@ public int hashCode() { result = 31 * result + (wrapperStyle != null ? wrapperStyle.hashCode() : 0); result = 31 * result + (parameters != null ? parameters.hashCode() : 0); result = 31 * result + (cacheDir != null ? cacheDir.hashCode() : 0); + result = 31 * result + (certDigest != null ? certDigest.hashCode() : 0); result = 31 * result + (acceptDowngrade != null ? acceptDowngrade.hashCode() : 0); return result; }