From 6c66b3ecc23234fc5f5d98bcc7c54f66bfdeaab8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Sat, 21 Jun 2014 15:46:33 +0200 Subject: [PATCH] Implement cipher selection. --- config/.gitignore | 1 + doc/generateTruststore.sh | 11 + src/org/cacert/gigi/Launcher.java | 29 ++- src/org/cacert/gigi/util/CipherInfo.java | 288 +++++++++++++++++++++++ 4 files changed, 323 insertions(+), 6 deletions(-) create mode 100644 doc/generateTruststore.sh create mode 100644 src/org/cacert/gigi/util/CipherInfo.java diff --git a/config/.gitignore b/config/.gitignore index 2892772d..cc6b0ea7 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -1,2 +1,3 @@ keystore.pkcs12 +cacerts.jks diff --git a/doc/generateTruststore.sh b/doc/generateTruststore.sh new file mode 100644 index 00000000..8092e924 --- /dev/null +++ b/doc/generateTruststore.sh @@ -0,0 +1,11 @@ +# this script generates a simple self-signed keypair + +wget http://www.cacert.org/certs/root.crt +wget http://www.cacert.org/certs/class3.crt + +keytool -importcert -keystore ../config/cacerts.jks -file root.crt -alias root -storepass "changeit" +keytool -importcert -keystore ../config/cacerts.jks -file class3.crt -alias class3 -storepass "changeit" +rm root.crt +rm class3.crt + +keytool -list -keystore ../config/cacerts.jks -storepass "changeit" diff --git a/src/org/cacert/gigi/Launcher.java b/src/org/cacert/gigi/Launcher.java index 1d971654..56306a98 100644 --- a/src/org/cacert/gigi/Launcher.java +++ b/src/org/cacert/gigi/Launcher.java @@ -5,14 +5,15 @@ import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CRL; import java.security.cert.CertificateException; -import java.util.Collection; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import org.cacert.gigi.natives.SetUID; +import org.cacert.gigi.util.CipherInfo; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; @@ -86,18 +87,34 @@ public class Launcher { final TrustManager[] tm = tmFactory.getTrustManagers(); SslContextFactory scf = new SslContextFactory() { + + String[] ciphers = null; + @Override - protected TrustManager[] getTrustManagers(KeyStore trustStore, - Collection crls) throws Exception { - return tm; + public void customize(SSLEngine sslEngine) { + super.customize(sslEngine); + + SSLParameters ssl = sslEngine.getSSLParameters(); + ssl.setUseCipherSuitesOrder(true); + if (ciphers == null) { + ciphers = CipherInfo.filter(sslEngine + .getSupportedCipherSuites()); + } + + ssl.setCipherSuites(ciphers); + sslEngine.setSSLParameters(ssl); + } + }; scf.setWantClientAuth(true); KeyStore ks1 = KeyStore.getInstance("pkcs12"); ks1.load(new FileInputStream("config/keystore.pkcs12"), "".toCharArray()); + scf.setTrustStorePath("config/cacerts.jks"); + scf.setTrustStorePassword("changeit"); + scf.setProtocol("TLS"); scf.setKeyStore(ks1); - scf.setProtocol("TLSv1"); return scf; } } diff --git a/src/org/cacert/gigi/util/CipherInfo.java b/src/org/cacert/gigi/util/CipherInfo.java new file mode 100644 index 00000000..5860c119 --- /dev/null +++ b/src/org/cacert/gigi/util/CipherInfo.java @@ -0,0 +1,288 @@ +package org.cacert.gigi.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.TreeSet; + +import sun.security.ssl.SSLContextImpl; + +public class CipherInfo implements Comparable { + private static class CipherInfoGenerator { + private Class cipherSuite; + private Field cipherSuiteNameMap; + private Field exchange; + private Field cipher; + private Field keySize; + private Field algortihm; + private Field transformation; + private HashMap names; + private Field macAlg; + private Field macName; + private Field macSize; + + public CipherInfoGenerator() throws ReflectiveOperationException { + SSLContextImpl sc = new SSLContextImpl.TLS12Context(); + Method m = SSLContextImpl.class + .getDeclaredMethod("getSupportedCipherSuiteList"); + m.setAccessible(true); + Object o = m.invoke(sc); + Class cipherSuiteList = o.getClass(); + Method collection = cipherSuiteList.getDeclaredMethod("collection"); + collection.setAccessible(true); + Collection suites = (Collection) collection.invoke(o); + Object oneSuite = suites.iterator().next(); + cipherSuite = oneSuite.getClass(); + cipherSuiteNameMap = cipherSuite.getDeclaredField("nameMap"); + cipherSuiteNameMap.setAccessible(true); + names = (HashMap) cipherSuiteNameMap.get(null); + exchange = cipherSuite.getDeclaredField("keyExchange"); + exchange.setAccessible(true); + cipher = cipherSuite.getDeclaredField("cipher"); + cipher.setAccessible(true); + Class bulkCipher = cipher.getType(); + keySize = bulkCipher.getDeclaredField("keySize"); + keySize.setAccessible(true); + algortihm = bulkCipher.getDeclaredField("algorithm"); + algortihm.setAccessible(true); + transformation = bulkCipher.getDeclaredField("transformation"); + transformation.setAccessible(true); + + macAlg = cipherSuite.getDeclaredField("macAlg"); + macAlg.setAccessible(true); + Class mac = macAlg.getType(); + macName = mac.getDeclaredField("name"); + macName.setAccessible(true); + macSize = mac.getDeclaredField("size"); + macSize.setAccessible(true); + } + public CipherInfo generateInfo(String suiteName) + throws IllegalArgumentException, IllegalAccessException { + Object suite = names.get(suiteName); + String keyExchange = exchange.get(suite).toString(); + Object bulkCipher = cipher.get(suite); + Object mac = macAlg.get(suite); + + String transform = (String) transformation.get(bulkCipher); + String[] transformationParts = transform.split("/"); + int keysize = keySize.getInt(bulkCipher); + + String macNam = (String) macName.get(mac); + int macSiz = macSize.getInt(mac); + + String chaining = null; + String padding = null; + if (transformationParts.length > 1) { + chaining = transformationParts[1]; + padding = transformationParts[2]; + } + + return new CipherInfo(suiteName, keyExchange, + transformationParts[0], keysize * 8, chaining, padding, + macNam, macSiz * 8); + + } + } + String keyExchange; + String cipher; + int keySize; + String cipherChaining; + String cipherPadding; + String macName; + int macSize; + String suiteName; + + private CipherInfo(String suiteName, String keyExchange, String cipher, + int keySize, String cipherChaining, String cipherPadding, + String macName, int macSize) { + this.suiteName = suiteName; + this.keyExchange = keyExchange; + this.cipher = cipher; + this.keySize = keySize; + this.cipherChaining = cipherChaining; + this.cipherPadding = cipherPadding; + this.macName = macName; + this.macSize = macSize; + } + + static CipherInfoGenerator cig; + static { + try { + cig = new CipherInfoGenerator(); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + public static CipherInfo generateInfo(String name) { + if (cig == null) { + return null; + } + try { + return cig.generateInfo(name); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + public String getSuiteName() { + return suiteName; + } + /** + * 5: ECDHE, AES||CAMELLIA, keysize >=256
+ * 4: DHE, AES||CAMELLIA, keysize >= 256
+ * 3: ECDHE|| DHE, AES||CAMELLIA
+ * 2: ECDHE||DHE
+ * 1: RSA||DSA
+ * 0: Others + * + * @return the strength + */ + public int getStrength() { + if (cipher.equals("NULL") || cipher.equals("RC4") + || cipher.contains("DES")) { + return 0; + } + boolean ecdhe = keyExchange.startsWith("ECDHE"); + boolean dhe = keyExchange.startsWith("DHE"); + boolean pfs = ecdhe || dhe; + boolean goodCipher = cipher.equals("AES") || cipher.equals("CAMELLIA"); + if (ecdhe && goodCipher && keySize >= 256) { + return 5; + } + if (dhe && goodCipher && keySize >= 256) { + return 4; + } + if (pfs && goodCipher) { + return 3; + } + if (pfs) { + return 2; + } + if (keyExchange.equals("RSA") || keyExchange.equals("DSA")) { + return 1; + } + return 0; + } + private static final String[] CIPHER_RANKING = new String[]{"CAMELLIA", + "AES", "RC4", "3DES", "DES", "DES40"}; + + @Override + public String toString() { + return "CipherInfo [keyExchange=" + keyExchange + ", cipher=" + cipher + + ", keySize=" + keySize + ", cipherChaining=" + cipherChaining + + ", cipherPadding=" + cipherPadding + ", macName=" + macName + + ", macSize=" + macSize + "]"; + } + /** + * ECDHE
+ * GCM
+ * Cipher {@link #CIPHER_RANKING}
+ * Cipher {@link #keySize}
+ * HMAC
+ * HMAC size
+ * + * @return + */ + @Override + public int compareTo(CipherInfo o) { + int myStrength = getStrength(); + int oStrength = o.getStrength(); + if (myStrength > oStrength) { + return -1; + } + if (myStrength < oStrength) { + return 1; + } + // TODO sort SSL/TLS + boolean myEcdhe = keyExchange.startsWith("ECDHE"); + boolean oEcdhe = o.keyExchange.startsWith("ECDHE"); + if (myEcdhe && !oEcdhe) { + return -1; + } + if (!myEcdhe && oEcdhe) { + return 1; + } + boolean myGCM = "GCM".equals(cipherChaining); + boolean oGCM = "GCM".equals(o.cipherChaining); + if (myGCM && !oGCM) { + return -1; + } + if (!myGCM && oGCM) { + return 1; + } + if (!cipher.equals(o.cipher)) { + + for (String testCipher : CIPHER_RANKING) { + if (cipher.equals(testCipher)) { + return -1; + } + if (o.cipher.equals(testCipher)) { + return 1; + } + } + if (cipher.equals("NULL")) { + return 1; + } + if (o.cipher.equals("NULL")) { + return -1; + } + } + if (keySize > o.keySize) { + return -1; + } + if (keySize < o.keySize) { + return 1; + } + boolean mySHA = macName.startsWith("SHA"); + boolean oSHA = o.macName.startsWith("SHA"); + if (mySHA && !oSHA) { + return -1; + } + if (mySHA && !oSHA) { + return 1; + } + if (macSize > o.macSize) { + return -1; + } + if (macSize < o.macSize) { + return 1; + } + + return suiteName.compareTo(o.suiteName); + } + static String[] cipherRanking = null; + public static String[] getCompleteRanking() { + if (cipherRanking == null) { + String[] ciphers = filterCiphers((Iterable) cig.names + .keySet()); + cipherRanking = ciphers; + } + return cipherRanking; + } + private static String[] filterCiphers(Iterable toFilter) { + TreeSet chosenCiphers = new TreeSet(); + for (String o : toFilter) { + String s = o; + CipherInfo info = CipherInfo.generateInfo(s); + if (info != null) { + if (info.getStrength() > 1) { + chosenCiphers.add(info); + } + } + } + String[] ciphers = new String[chosenCiphers.size()]; + int counter = 0; + for (CipherInfo i : chosenCiphers) { + ciphers[counter++] = i.getSuiteName(); + } + return ciphers; + } + public static String[] filter(String[] supportedCipherSuites) { + return filterCiphers(Arrays.asList(supportedCipherSuites)); + } +} -- 2.39.2