Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature to validate certificate for a SSL connection #76

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
<description>Auto updating launcher for JavaFX Applications</description>
<url>https://github.com/edvin/fxlauncher</url>

<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
</dependencies>

<distributionManagement>
<repository>
<id>sonatype-nexus-staging</id>
Expand Down Expand Up @@ -76,13 +84,28 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>fxlauncher.Launcher</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/fxlauncher/AbstractLauncher.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<URL> libs = manifest.files.stream().filter(LibraryFile::loadForCurrentPlatform).map(it -> it.toURL(cacheDir)).collect(Collectors.toList());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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");
}
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/fxlauncher/CertificateInKeystoreTrustStrategy.java
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/fxlauncher/CreateManifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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"));
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/fxlauncher/FXManifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class FXManifest {
@XmlElement
public String cacheDir;
@XmlElement
public String certDigest;
@XmlElement
public Boolean acceptDowngrade = false;
@XmlElement
public String preloadNativeLibraries;
Expand Down Expand Up @@ -113,6 +115,11 @@ public Path resolveCacheDir(Map<String, String> namedParams) {
return path;
}

public String resolveCertDigestStr(Map<String, String> namedParams) {
if (namedParams == null) namedParams = Collections.emptyMap();
return namedParams.containsKey("cert-digest") ? namedParams.get("cert-digest") : this.certDigest;
}

public String getWhatsNewPage()
{
return whatsNewPage;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down