Merge branch 'tlsSelection'
authorFelix Dörre <felix@dogcraft.de>
Sat, 21 Jun 2014 14:46:11 +0000 (16:46 +0200)
committerFelix Dörre <felix@dogcraft.de>
Sat, 21 Jun 2014 14:46:11 +0000 (16:46 +0200)
config/.gitignore
doc/generateTruststore.sh [new file with mode: 0644]
src/org/cacert/gigi/Launcher.java
src/org/cacert/gigi/util/CipherInfo.java [new file with mode: 0644]

index 2892772d02e6483555ed5ed539b97ad3b843d9d1..cc6b0ea7c3abf767996a636d873de029a4f6e4d6 100644 (file)
@@ -1,2 +1,3 @@
 
 keystore.pkcs12
+cacerts.jks
diff --git a/doc/generateTruststore.sh b/doc/generateTruststore.sh
new file mode 100644 (file)
index 0000000..8092e92
--- /dev/null
@@ -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"
index 1d9716541a7eac7ebc532bf7757b7836001bb93e..56306a98ba28d0d86c6cc1c0b3343bffca57a1db 100644 (file)
@@ -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<? extends CRL> 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 (file)
index 0000000..5860c11
--- /dev/null
@@ -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<CipherInfo> {
+       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 <br>
+        * 4: DHE, AES||CAMELLIA, keysize >= 256<br>
+        * 3: ECDHE|| DHE, AES||CAMELLIA<br>
+        * 2: ECDHE||DHE<br>
+        * 1: RSA||DSA <br>
+        * 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<br>
+        * GCM<br>
+        * Cipher {@link #CIPHER_RANKING}<br>
+        * Cipher {@link #keySize}<br>
+        * HMAC<br>
+        * HMAC size<br>
+        * 
+        * @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<String>) cig.names
+                                       .keySet());
+                       cipherRanking = ciphers;
+               }
+               return cipherRanking;
+       }
+       private static String[] filterCiphers(Iterable<String> toFilter) {
+               TreeSet<CipherInfo> chosenCiphers = new TreeSet<CipherInfo>();
+               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));
+       }
+}