]> WPIA git - gigi.git/commitdiff
[test-config] FIX: the ssl-pinger+ add various tests for that.
authorFelix Dörre <felix@dogcraft.de>
Fri, 29 Aug 2014 14:59:22 +0000 (16:59 +0200)
committerFelix Dörre <felix@dogcraft.de>
Fri, 29 Aug 2014 16:35:29 +0000 (18:35 +0200)
config/test.properties.template
src/org/cacert/gigi/Gigi.java
src/org/cacert/gigi/Launcher.java
src/org/cacert/gigi/ping/PingerDaemon.java
src/org/cacert/gigi/ping/SSLPinger.java
tests/org/cacert/gigi/ping/TestSSL.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ManagedTest.java
tests/org/cacert/gigi/testUtils/PingTest.java

index 19e2645b3041312cc0a7c5978958dbda1037c404..46c1d1d230e5e3b73a5ad3f0dc427da9fa6f6378 100644 (file)
@@ -28,3 +28,4 @@ domain.manage=http://you-installation-of-the/index.php
 domain.http=you-intstallation-for-the-textfiles
 domain.dnstest=the.dns.zone
 domain.testns=the.authorativ.ns.for.domain.dnstest
+domain.local=a.domain.that.resolves.to.localhost
index 53685824528db4caf546e8a6d61f921d05160fb9..3f0cb7980fea7bd20dbc2805ca680838996ff368 100644 (file)
@@ -2,6 +2,7 @@ package org.cacert.gigi;
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.security.KeyStore;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -63,14 +64,15 @@ public class Gigi extends HttpServlet {
 
     private static Gigi instance;
 
-    private PingerDaemon pinger = new PingerDaemon();
+    private PingerDaemon pinger;
 
-    public Gigi(Properties conf) {
+    public Gigi(Properties conf, KeyStore truststore) {
         if (instance != null) {
             throw new IllegalStateException("Multiple Gigi instances!");
         }
         instance = this;
         DatabaseConnection.init(conf);
+        pinger = new PingerDaemon(truststore);
         pinger.start();
     }
 
@@ -156,7 +158,7 @@ public class Gigi extends HttpServlet {
         final Page p = getPage(req.getPathInfo());
 
         if (p != null) {
-            if (!isSecure && (p.needsLogin() || p instanceof LoginPage || p instanceof RegisterPage)) {
+            if ( !isSecure && (p.needsLogin() || p instanceof LoginPage || p instanceof RegisterPage)) {
                 resp.sendRedirect("https://" + ServerConstants.getWwwHostNamePortSecure() + req.getPathInfo());
                 return;
             }
index 41ee8ac7551a665ec0800c5d79c5854df695de31..58758ffad99d1f9bfad4cbf810f42ae7d39ed900 100644 (file)
@@ -66,7 +66,7 @@ public class Launcher {
 
         HandlerList hl = new HandlerList();
         hl.setHandlers(new Handler[] {
-                generateStaticContext(), generateGigiContexts(conf.getMainProps()), generateAPIContext()
+                generateStaticContext(), generateGigiContexts(conf.getMainProps(), conf.getTrustStore()), generateAPIContext()
         });
         s.setHandler(hl);
         s.start();
@@ -86,7 +86,7 @@ public class Launcher {
             connector = new ServerConnector(s);
         }
         connector.setHost(conf.getMainProps().getProperty("host"));
-        if(doHttps) {
+        if (doHttps) {
             connector.setPort(ServerConstants.getSecurePort());
         } else {
             connector.setPort(ServerConstants.getPort());
@@ -163,8 +163,8 @@ public class Launcher {
         };
     }
 
-    private static Handler generateGigiContexts(Properties conf) {
-        ServletHolder webAppServlet = new ServletHolder(new Gigi(conf));
+    private static Handler generateGigiContexts(Properties conf, KeyStore trust) {
+        ServletHolder webAppServlet = new ServletHolder(new Gigi(conf, trust));
 
         ContextHandler ch = generateGigiServletContext(webAppServlet);
         ch.setVirtualHosts(new String[] {
index ac61f14cdcb0b6517809342d6ca379a107bd544a..cd36dc6681d7063744bb12a302fdd3db8c57bd94 100644 (file)
@@ -1,13 +1,10 @@
 package org.cacert.gigi.ping;
 
-import java.io.FileReader;
-import java.io.IOException;
+import java.security.KeyStore;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.HashMap;
-import java.util.Properties;
-
 import org.cacert.gigi.Domain;
 import org.cacert.gigi.User;
 import org.cacert.gigi.database.DatabaseConnection;
@@ -21,13 +18,19 @@ public class PingerDaemon extends Thread {
 
     private PreparedStatement enterPingResult;
 
+    private KeyStore truststore;
+
+    public PingerDaemon(KeyStore truststore) {
+        this.truststore = truststore;
+    }
+
     @Override
     public void run() {
         try {
             searchNeededPings = DatabaseConnection.getInstance().prepare("SELECT pingconfig.*, domains.domain, domains.memid FROM pingconfig LEFT JOIN domainPinglog ON domainPinglog.configId=pingconfig.id INNER JOIN domains ON domains.id=pingconfig.domainid WHERE domainPinglog.configId IS NULL ");
             enterPingResult = DatabaseConnection.getInstance().prepare("INSERT INTO domainPinglog SET configId=?, state=?, result=?, challenge=?");
             pingers.put("email", new EmailPinger());
-            pingers.put("ssl", new SSLPinger());
+            pingers.put("ssl", new SSLPinger(truststore));
             pingers.put("http", new HTTPFetch());
             pingers.put("dns", new DNSPinger());
         } catch (SQLException e) {
@@ -69,12 +72,4 @@ public class PingerDaemon extends Thread {
             }
         }
     }
-
-    public static void main(String[] args) throws IOException {
-        Properties conf = new Properties();
-        conf.load(new FileReader("config/gigi.properties"));
-        DatabaseConnection.init(conf);
-        new PingerDaemon().run();
-
-    }
 }
index 81c739372ca4a2925b2c852f9be92b263d5c9255..0b253c5f260bdda007da19b09ff16d560e68cc71 100644 (file)
@@ -3,23 +3,30 @@ package org.cacert.gigi.ping;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigInteger;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.Arrays;
 
 import javax.net.ssl.SNIHostName;
 import javax.net.ssl.SNIServerName;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 import javax.net.ssl.SSLParameters;
 import javax.security.cert.X509Certificate;
 
+import org.cacert.gigi.Certificate;
 import org.cacert.gigi.Domain;
 import org.cacert.gigi.User;
 
@@ -29,10 +36,15 @@ public class SSLPinger extends DomainPinger {
             "xmpp", "server-xmpp", "smtp", "imap"
     };
 
+    private KeyStore truststore;
+
+    public SSLPinger(KeyStore truststore) {
+        this.truststore = truststore;
+    }
+
     @Override
     public String ping(Domain domain, String configuration, User u) {
-        try {
-            SocketChannel sch = SocketChannel.open();
+        try (SocketChannel sch = SocketChannel.open()) {
             String[] parts = configuration.split(":", 2);
             sch.connect(new InetSocketAddress(domain.getSuffix(), Integer.parseInt(parts[0])));
             if (parts.length == 2) {
@@ -52,7 +64,7 @@ public class SSLPinger extends DomainPinger {
 
                 }
             }
-            return test(sch, domain.getSuffix());
+            return test(sch, domain.getSuffix(), u);
         } catch (IOException e) {
             return "Connecton failed";
         }
@@ -133,9 +145,18 @@ public class SSLPinger extends DomainPinger {
         }
     }
 
-    private String test(SocketChannel sch, String domain) {
+    private String test(SocketChannel sch, String domain, User subject) {
         try {
-            SSLContext sc = SSLContext.getDefault();
+            SSLContext sc = SSLContext.getInstance("SSL");
+            try {
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+                tmf.init(truststore);
+                sc.init(null, tmf.getTrustManagers(), new SecureRandom());
+            } catch (KeyManagementException e) {
+                e.printStackTrace();
+            } catch (KeyStoreException e) {
+                e.printStackTrace();
+            }
             SSLEngine se = sc.createSSLEngine();
             ByteBuffer enc_in = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
             ByteBuffer enc_out = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
@@ -182,11 +203,13 @@ public class SSLPinger extends DomainPinger {
                 }
 
             }
-            System.out.println("completed");
-            System.out.println(se.getSession().getCipherSuite());
             X509Certificate[] peerCertificateChain = se.getSession().getPeerCertificateChain();
-            for (X509Certificate x509Certificate : peerCertificateChain) {
-                System.out.println(x509Certificate.getSubjectDN().getName());
+            X509Certificate first = peerCertificateChain[0];
+
+            BigInteger serial = first.getSerialNumber();
+            Certificate c = Certificate.getBySerial(serial.toString(16));
+            if (c.getOwnerId() != subject.getId()) {
+                return "Owner mismatch";
             }
             return PING_SUCCEDED;
         } catch (NoSuchAlgorithmException e) {
diff --git a/tests/org/cacert/gigi/ping/TestSSL.java b/tests/org/cacert/gigi/ping/TestSSL.java
new file mode 100644 (file)
index 0000000..9b0e928
--- /dev/null
@@ -0,0 +1,224 @@
+package org.cacert.gigi.ping;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.cacert.gigi.Certificate;
+import org.cacert.gigi.CertificateProfile;
+import org.cacert.gigi.Certificate.CSRType;
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.pages.account.DomainOverview;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.PingTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.junit.Test;
+
+public class TestSSL extends PingTest {
+
+    private KeyPair kp;
+
+    private Certificate c;
+
+    @Test
+    public void sslAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(0, 0, true);
+    }
+
+    @Test
+    public void sslWongTypeAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(1, 0, true);
+    }
+
+    @Test
+    public void sslOneMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(2, 0, true);
+    }
+
+    @Test
+    public void sslBothMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(3, 0, true);
+    }
+
+    @Test
+    public void sslWrongTypeAndMailFail() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(1, 1, false);
+    }
+
+    /**
+     * @param sslVariant
+     *            <ul>
+     *            <li>0= all valid</li>
+     *            <li>1= wrong type</li>
+     *            <li>2= one server missing</li>
+     *            <li>3= both servers missing</li>
+     *            </ul>
+     * @param emailVariant
+     * @param successSSL
+     * @param successMail
+     * @throws IOException
+     * @throws InterruptedException
+     * @throws SQLException
+     * @throws GeneralSecurityException
+     * @throws GigiApiException
+     */
+
+    private void testEmailAndSSL(int sslVariant, int emailVariant, boolean successMail) throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        String test = getTestProps().getProperty("domain.local");
+
+        URL u = new URL("https://" + getServerName() + DomainOverview.PATH);
+
+        initailizeDomainForm(u);
+
+        createCertificate(test, CertificateProfile.getByName(sslVariant == 1 ? "client" : "server"));
+        SSLServerSocket sss = createSSLServer(kp.getPrivate(), c.cert());
+        int port = sss.getLocalPort();
+        SSLServerSocket sss2 = createSSLServer(kp.getPrivate(), c.cert());
+        int port2 = sss2.getLocalPort();
+        if (sslVariant == 3 || sslVariant == 2) {
+            sss2.close();
+            if (sslVariant == 3) {
+                sss.close();
+            }
+        }
+        String content = "newdomain=" + URLEncoder.encode(test, "UTF-8") + //
+                "&emailType=y&email=2&SSLType=y" + //
+                "&ssl-type-0=direct&ssl-port-0=" + port + //
+                "&ssl-type-1=direct&ssl-port-1=" + port2 + //
+                "&ssl-type-2=direct&ssl-port-2=" + //
+                "&ssl-type-3=direct&ssl-port-3=" + //
+                "&adddomain&csrf=" + csrf;
+        URL u2 = sendDomainForm(u, content);
+        boolean firstSucceeds = sslVariant != 0 && sslVariant != 2;
+        assertTrue(firstSucceeds ^ acceptSSLServer(sss));
+        boolean secondsSucceeds = sslVariant != 0;
+        assertTrue(secondsSucceeds ^ acceptSSLServer(sss2));
+
+        TestMail mail = getMailReciever().recieve();
+        if (emailVariant == 0) {
+            String link = mail.extractLink();
+            new URL(link).openConnection().getHeaderField("");
+        }
+        waitForPings(3);
+
+        String newcontent = IOUtils.readURL(cookie(u2.openConnection(), cookie));
+        Pattern pat = Pattern.compile("<td>ssl</td>\\s*<td>success</td>");
+        Matcher matcher = pat.matcher(newcontent);
+        assertTrue(newcontent, firstSucceeds ^ matcher.find());
+        assertTrue(newcontent, secondsSucceeds ^ matcher.find());
+        assertFalse(newcontent, matcher.find());
+        pat = Pattern.compile("<td>email</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successMail ^ pat.matcher(newcontent).find());
+    }
+
+    private void createCertificate(String test, CertificateProfile profile) throws GeneralSecurityException, IOException, SQLException, InterruptedException, GigiApiException {
+        kp = generateKeypair();
+        String csr = generatePEMCSR(kp, "CN=" + test);
+        c = new Certificate(userid, "/CN=" + test, "sha256", csr, CSRType.CSR, profile);
+        c.issue(null, "2y").waitFor(60000);
+    }
+
+    private boolean acceptSSLServer(SSLServerSocket sss) throws IOException {
+        try {
+            Socket s = sss.accept();
+            s.getOutputStream().write('b');
+            s.getOutputStream().close();
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    private SSLServerSocket createSSLServer(final PrivateKey priv, final X509Certificate cert) throws Error, IOException {
+        SSLContext sc;
+        try {
+            sc = SSLContext.getInstance("SSL");
+            sc.init(new KeyManager[] {
+                new X509KeyManager() {
+
+                    @Override
+                    public String[] getServerAliases(String keyType, Principal[] issuers) {
+                        return new String[] {
+                            "server"
+                        };
+                    }
+
+                    @Override
+                    public PrivateKey getPrivateKey(String alias) {
+                        return priv;
+                    }
+
+                    @Override
+                    public String[] getClientAliases(String keyType, Principal[] issuers) {
+                        throw new Error();
+                    }
+
+                    @Override
+                    public X509Certificate[] getCertificateChain(String alias) {
+                        return new X509Certificate[] {
+                            cert
+                        };
+                    }
+
+                    @Override
+                    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+                        throw new Error();
+                    }
+
+                    @Override
+                    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+                        return "server";
+                    }
+
+                }
+            }, new TrustManager[] {
+                new X509TrustManager() {
+
+                    @Override
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+
+                    @Override
+                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+
+                    @Override
+                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+                }
+            }, new SecureRandom());
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            throw new Error(e);
+        } catch (KeyManagementException e) {
+            e.printStackTrace();
+            throw new Error(e);
+        }
+
+        SSLServerSocketFactory sssf = sc.getServerSocketFactory();
+        return (SSLServerSocket) sssf.createServerSocket(0);
+    }
+
+}
index ed5f1eec2c831c4fbb5ac5511dcc3004ef33f0ef..e9b23e28a082e7fa443f511a413c710a3b0d07da 100644 (file)
@@ -4,9 +4,13 @@ import static org.junit.Assert.*;
 
 import java.io.BufferedReader;
 import java.io.DataOutputStream;
+import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.HttpURLConnection;
@@ -426,7 +430,26 @@ public class ManagedTest {
     public static KeyPair generateKeypair() throws GeneralSecurityException {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
         kpg.initialize(4096);
-        return kpg.generateKeyPair();
+        KeyPair keyPair = null;
+        File f = new File("testKeypair");
+        if (f.exists()) {
+            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
+                keyPair = (KeyPair) ois.readObject();
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } else {
+            keyPair = kpg.generateKeyPair();
+            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
+                oos.writeObject(keyPair);
+                oos.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return keyPair;
     }
 
     public static String generatePEMCSR(KeyPair kp, String dn) throws GeneralSecurityException, IOException {
index 6c57d76dcd80d30173f6b00f4c2ff133e7a1e18c..b8c0d9105211bae9b4c699601f803df84d213959 100644 (file)
@@ -27,7 +27,8 @@ public abstract class PingTest extends ClientTest {
 
     protected void waitForPings(int count) throws SQLException, InterruptedException {
         PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT COUNT(*) FROM domainPinglog");
-        while (true) {
+        long start = System.currentTimeMillis();
+        while (System.currentTimeMillis() - start < 10000) {
             ResultSet rs = ps.executeQuery();
             rs.next();
             if (rs.getInt(1) >= count) {