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
import java.io.IOException;
import java.io.PrintWriter;
+import java.security.KeyStore;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
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();
}
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;
}
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();
connector = new ServerConnector(s);
}
connector.setHost(conf.getMainProps().getProperty("host"));
- if(doHttps) {
+ if (doHttps) {
connector.setPort(ServerConstants.getSecurePort());
} else {
connector.setPort(ServerConstants.getPort());
};
}
- 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[] {
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;
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) {
}
}
}
-
- 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();
-
- }
}
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;
"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) {
}
}
- return test(sch, domain.getSuffix());
+ return test(sch, domain.getSuffix(), u);
} catch (IOException e) {
return "Connecton failed";
}
}
}
- 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());
}
}
- 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) {
--- /dev/null
+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);
+ }
+
+}
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;
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 {
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) {