keystore.pkcs12
cacerts.jks
sql.properties
+gigi.properties
+test.properties
--- /dev/null
+host=127.0.0.1
+port=443
+emailProvider=org.cacert.gigi.email.Sendmail
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://
+sql.user=
+sql.password=
+++ /dev/null
-driver=com.mysql.jdbc.Driver
-url=
-user=
-password=
--- /dev/null
+type=local
+server=localhost:443
+mail=localhost:8474
+
+==== OR ===
+type=autonomous
+java=java -cp bin;/path/to/mysqlConnector.jar org.cacert.gigi.Launcher
+serverPort=4443
+mailPort=8473
--- /dev/null
+package org.cacert.gigi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+public class DevelLauncher {
+ public static void main(String[] args) throws Exception {
+ Properties mainProps = new Properties();
+ mainProps.load(new FileInputStream("config/gigi.properties"));
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--port")) {
+ mainProps.setProperty("port", args[i + 1]);
+ }
+ i++;
+ }
+
+ ByteArrayOutputStream chunkConfig = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(chunkConfig);
+ byte[] cacerts = Files.readAllBytes(Paths.get("config/cacerts.jks"));
+ byte[] keystore = Files.readAllBytes(Paths
+ .get("config/keystore.pkcs12"));
+
+ DevelLauncher.writeGigiConfig(dos, new byte[]{}, "changeit".getBytes(),
+ mainProps, cacerts, keystore);
+ dos.flush();
+ InputStream oldin = System.in;
+ System.setIn(new ByteArrayInputStream(chunkConfig.toByteArray()));
+ Launcher.main(args);
+ System.setIn(oldin);
+ }
+ public static void writeGigiConfig(DataOutputStream target,
+ byte[] keystorepw, byte[] truststorepw, Properties mainprop,
+ byte[] cacerts, byte[] keystore) throws IOException {
+ writeChunk(target, GigiConfig.GIGI_CONFIG_VERSION.getBytes());
+ writeChunk(target, keystorepw);
+ writeChunk(target, truststorepw);
+ ByteArrayOutputStream props = new ByteArrayOutputStream();
+ mainprop.store(props, "");
+ writeChunk(target, props.toByteArray());
+ writeChunk(target, cacerts);
+ writeChunk(target, keystore);
+
+ }
+ public static void writeChunk(DataOutputStream dos, byte[] chunk)
+ throws IOException {
+ dos.writeInt(chunk.length);
+ dos.write(chunk);
+ }
+ public static void launch(Properties props, File cacerts, File keystore)
+ throws IOException {
+ ByteArrayOutputStream config = new ByteArrayOutputStream();
+ props.store(config, "");
+ }
+}
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.HashMap;
+import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.email.EmailProvider;
import org.cacert.gigi.pages.LoginPage;
import org.cacert.gigi.pages.MainPage;
import org.cacert.gigi.pages.Page;
private String[] baseTemplate;
private HashMap<String, Page> pages = new HashMap<String, Page>();
+ public Gigi(Properties conf) {
+ EmailProvider.init(conf);
+ DatabaseConnection.init(conf);
+ }
@Override
public void init() throws ServletException {
pages.put("/login", new LoginPage("CACert - Login"));
--- /dev/null
+package org.cacert.gigi;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Properties;
+
+public class GigiConfig {
+ public static final String GIGI_CONFIG_VERSION = "GigiConfigV1.0";
+ byte[] cacerts;
+ byte[] keystore;
+ Properties mainProps = new Properties();
+ private char[] keystorpw;
+ private char[] truststorepw;
+
+ private GigiConfig() {
+ }
+ public byte[] getCacerts() {
+ return cacerts;
+ }
+ public byte[] getKeystore() {
+ return keystore;
+ }
+ public Properties getMainProps() {
+ return mainProps;
+ }
+
+ public static GigiConfig parse(InputStream input) throws IOException {
+ DataInputStream dis = new DataInputStream(input);
+ String version = new String(readChunk(dis));
+ if (!version.equals(GIGI_CONFIG_VERSION)) {
+ System.out.println("Invalid config format");
+ System.exit(0);
+ }
+ GigiConfig gc = new GigiConfig();
+ gc.keystorpw = transformSafe(readChunk(dis));
+ gc.truststorepw = transformSafe(readChunk(dis));
+ gc.mainProps.load(new ByteArrayInputStream(readChunk(dis)));
+ gc.cacerts = readChunk(dis);
+ gc.keystore = readChunk(dis);
+ return gc;
+ }
+ private static char[] transformSafe(byte[] readChunk) {
+ char[] res = new char[readChunk.length];
+ for (int i = 0; i < res.length; i++) {
+ res[i] = (char) readChunk[i];
+ readChunk[i] = 0;
+ }
+ return res;
+ }
+ private static byte[] readChunk(DataInputStream dis) throws IOException {
+ int length = dis.readInt();
+ byte[] contents = new byte[length];
+ dis.readFully(contents);
+ return contents;
+ }
+ public KeyStore getPrivateStore() throws GeneralSecurityException,
+ IOException {
+ KeyStore ks1 = KeyStore.getInstance("pkcs12");
+ ks1.load(new ByteArrayInputStream(keystore), keystorpw);
+ return ks1;
+ }
+ public KeyStore getTrustStore() throws GeneralSecurityException,
+ IOException {
+ KeyStore ks1 = KeyStore.getInstance("jks");
+ ks1.load(new ByteArrayInputStream(cacerts), truststorepw);
+ return ks1;
+ }
+}
package org.cacert.gigi;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
+import java.util.Properties;
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;
public class Launcher {
public static void main(String[] args) throws Exception {
- int port = 443;
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--port")) {
- port = Integer.parseInt(args[i + 1]);
- }
- i++;
- }
+ GigiConfig conf = GigiConfig.parse(System.in);
+
Server s = new Server();
// === SSL HTTP Configuration ===
HttpConfiguration https_config = new HttpConfiguration();
https_config.addCustomizer(new SecureRequestCustomizer());
ServerConnector connector = new ServerConnector(s,
- new SslConnectionFactory(generateSSLContextFactory(),
+ new SslConnectionFactory(generateSSLContextFactory(conf),
"http/1.1"), new HttpConnectionFactory(https_config));
- connector.setHost("127.0.0.1");
- connector.setPort(port);
+ connector.setHost(conf.getMainProps().getProperty("host"));
+ connector.setPort(Integer.parseInt(conf.getMainProps().getProperty(
+ "port")));
s.setConnectors(new Connector[]{connector});
HandlerList hl = new HandlerList();
hl.setHandlers(new Handler[]{generateStaticContext(),
- generateGigiContext()});
+ generateGigiContext(conf.getMainProps())});
s.setHandler(hl);
s.start();
if (connector.getPort() <= 1024
}
}
- private static ServletContextHandler generateGigiContext() {
+ private static ServletContextHandler generateGigiContext(Properties conf) {
ServletContextHandler servlet = new ServletContextHandler(
ServletContextHandler.SESSIONS);
servlet.setInitParameter(SessionManager.__SessionCookieProperty,
"CACert-Session");
- servlet.addServlet(new ServletHolder(new Gigi()), "/*");
+ servlet.addServlet(new ServletHolder(new Gigi(conf)), "/*");
return servlet;
}
return ch;
}
- private static SslContextFactory generateSSLContextFactory()
- throws NoSuchAlgorithmException, KeyStoreException, IOException,
- CertificateException, FileNotFoundException {
+ private static SslContextFactory generateSSLContextFactory(GigiConfig conf)
+ throws GeneralSecurityException, IOException {
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance("PKIX");
tmFactory.init((KeyStore) null);
- final TrustManager[] tm = tmFactory.getTrustManagers();
-
SslContextFactory scf = new SslContextFactory() {
String[] ciphers = null;
};
scf.setRenegotiationAllowed(false);
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.setTrustStore(conf.getTrustStore());
+ scf.setKeyStore(conf.getPrivateStore());
return scf;
}
}
package org.cacert.gigi.database;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
Connection c;
HashMap<String, PreparedStatement> statements = new HashMap<String, PreparedStatement>();
- static Properties credentials = new Properties();
- static {
- try {
- credentials.load(new FileInputStream("config/sql.properties"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
+ private static Properties credentials = new Properties();
Statement adHoc;
public DatabaseConnection() {
try {
- Class.forName(credentials.getProperty("driver"));
+ Class.forName(credentials.getProperty("sql.driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void tryConnect() {
try {
- c = DriverManager.getConnection(credentials.getProperty("url")
+ c = DriverManager.getConnection(credentials.getProperty("sql.url")
+ "?zeroDateTimeBehavior=convertToNull",
- credentials.getProperty("user"),
- credentials.getProperty("password"));
+ credentials.getProperty("sql.user"),
+ credentials.getProperty("sql.password"));
PreparedStatement ps = c
.prepareStatement("SET SESSION wait_timeout=?;");
ps.setInt(1, CONNECTION_TIMEOUT);
public static DatabaseConnection getInstance() {
return instances.get();
}
+ public static void init(Properties conf) {
+ if (credentials != null) {
+ throw new Error("Re-initiaizing is forbidden.");
+ }
+ credentials = conf;
+ }
}
package org.cacert.gigi.email;
import java.io.IOException;
+import java.util.Properties;
public abstract class EmailProvider {
public abstract void sendmail(String to, String subject, String message,
String from, String replyto, String toname, String fromname,
String errorsto, boolean extra) throws IOException;
- private static EmailProvider instance = new Sendmail();
+ private static EmailProvider instance;
public static EmailProvider getInstance() {
return instance;
}
+ public static void init(Properties conf) {
+ try {
+ Class<?> c = Class.forName(conf.getProperty("emailProvider"));
+ instance = (EmailProvider) c.getDeclaredConstructor(
+ Properties.class).newInstance(conf);
+ } catch (ReflectiveOperationException e) {
+ e.printStackTrace();
+ }
+ }
}
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
+import java.util.Properties;
import java.util.regex.Pattern;
public class Sendmail extends EmailProvider {
- protected Sendmail() {
+ protected Sendmail(Properties props) {
}
private static final Pattern NON_ASCII = Pattern
.compile("[^a-zA-Z0-9 .-\\[\\]!_@]");
--- /dev/null
+package org.cacert.gigi.email;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+
+class TestEmailProvider extends EmailProvider {
+ ServerSocket servs;
+ Socket client;
+ DataOutputStream out;
+ protected TestEmailProvider(Properties props) {
+ try {
+ servs = new ServerSocket(Integer.parseInt(props
+ .getProperty("emailProvider.port")), 10,
+ InetAddress.getByName("127.0.0.1"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ @Override
+ public synchronized void sendmail(String to, String subject,
+ String message, String from, String replyto, String toname,
+ String fromname, String errorsto, boolean extra) throws IOException {
+ boolean sent = false;
+ while (!sent) {
+ if (client == null || client.isClosed()) {
+ client = servs.accept();
+ out = new DataOutputStream(client.getOutputStream());
+ }
+ try {
+ write(to);
+ write(subject);
+ write(message);
+ write(from);
+ write(replyto);
+ out.flush();
+ sent = true;
+ } catch (IOException e) {
+ client = null;
+ }
+ }
+ }
+ private void write(String to) throws IOException {
+ if (to == null) {
+ out.writeUTF("<null>");
+ } else {
+ out.writeUTF(to);
+ }
+ }
+
+}
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.cacert.gigi.IOUtils;
import org.cacert.gigi.InitTruststore;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
-public class RegisterPageTest {
- private static final URL registerService;
+public class RegisterPageTest extends ManagedTest {
+ private final URL registerService;
static {
+ InitTruststore.run();
+ HttpURLConnection.setFollowRedirects(false);
+ }
+
+ public RegisterPageTest() {
URL u = null;
try {
- u = new URL("https://localhost/register");
+ u = new URL("https://" + getServerName() + "/register");
} catch (MalformedURLException e) {
e.printStackTrace();
}
registerService = u;
- InitTruststore.run();
- HttpURLConnection.setFollowRedirects(false);
}
-
@Before
public void setUp() throws Exception {
}
@Test
public void testSuccess() throws IOException {
- String startError = fetchStartErrorMessage("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
+ String startError = fetchStartErrorMessage("fname=ab&lname=b&email="
+ + URLEncoder.encode("felix+" + System.currentTimeMillis()
+ + "@dogcraft.de", "UTF-8")
+ + "&pword1=ap12UI.a'&pword2=ap12UI.a'&day=1&month=1&year=1910&cca_agree=1");
assertTrue(startError, startError.startsWith("</div>"));
+ TestMail tm = waitForMail();
+ Pattern link = Pattern.compile("http://[^\\s]+(?=\\s)");
+ Matcher m = link.matcher(tm.getMessage());
+ m.find();
+ System.out.println(tm.getSubject());
+ System.out.println(m.group(0));
}
-
@Test
public void testNoFname() throws IOException {
testFailedForm("lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
@Test
public void testNoDay() throws IOException {
+ System.out.println(registerService);
testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&month=1&year=1910&cca_agree=1");
}
@Test
}
}
- private static void testFailedForm(String query) throws IOException {
+ private void testFailedForm(String query) throws IOException {
String startError = fetchStartErrorMessage(query);
assertTrue(startError, !startError.startsWith("</div>"));
}
- private static String fetchStartErrorMessage(String query)
- throws IOException {
+ private String fetchStartErrorMessage(String query) throws IOException {
String d = runRegister(query);
String formFail = "<div class='formError'>";
int idx = d.indexOf(formFail);
return startError;
}
- public static void registerUser(String firstName, String lastName,
- String email, String password) {
+ public void registerUser(String firstName, String lastName, String email,
+ String password) {
try {
String query = "fname=" + URLEncoder.encode(firstName, "UTF-8")
+ "&lname=" + URLEncoder.encode(lastName, "UTF-8")
throw new Error(e);
}
}
- private static String runRegister(String param) throws IOException {
+ private String runRegister(String param) throws IOException {
HttpURLConnection uc = (HttpURLConnection) registerService
.openConnection();
uc.setDoOutput(true);
--- /dev/null
+package org.cacert.gigi.testUtils;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.cacert.gigi.DevelLauncher;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ManagedTest {
+ private static TestEmailReciever ter;
+ private static Process gigi;
+ private static String url = "localhost:4443";
+
+ public static String getServerName() {
+ return url;
+ }
+ static Properties testProps = new Properties();
+ @BeforeClass
+ public static void connectToServer() {
+ try {
+ testProps.load(new FileInputStream("config/test.properties"));
+ String type = testProps.getProperty("type");
+ if (type.equals("local")) {
+ url = testProps.getProperty("server");
+ String[] parts = testProps.getProperty("mail").split(":", 2);
+ ter = new TestEmailReciever(new InetSocketAddress(parts[0],
+ Integer.parseInt(parts[1])));
+ return;
+ }
+ url = "localhost:" + testProps.getProperty("serverPort");
+ gigi = Runtime.getRuntime().exec(testProps.getProperty("java"));
+ DataOutputStream toGigi = new DataOutputStream(
+ gigi.getOutputStream());
+ System.out.println("... starting server");
+ Properties mainProps = new Properties();
+ mainProps.load(new FileInputStream("config/gigi.properties"));
+ mainProps.setProperty("host", "127.0.0.1");
+ mainProps.setProperty("port", testProps.getProperty("serverPort"));
+ mainProps.setProperty("emailProvider",
+ "org.cacert.gigi.email.TestEmailProvider");
+ mainProps.setProperty("emailProvider.port", "8473");
+
+ byte[] cacerts = Files
+ .readAllBytes(Paths.get("config/cacerts.jks"));
+ byte[] keystore = Files.readAllBytes(Paths
+ .get("config/keystore.pkcs12"));
+
+ DevelLauncher.writeGigiConfig(toGigi, new byte[]{},
+ "changeit".getBytes(), mainProps, cacerts, keystore);
+ toGigi.flush();
+ // TODO wait for ready
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ final BufferedReader br = new BufferedReader(new InputStreamReader(
+ gigi.getErrorStream()));
+ String line;
+ while ((line = br.readLine()) != null
+ && !line.contains("Server:main: Started")) {
+ System.err.println(line);
+ }
+ new Thread() {
+ @Override
+ public void run() {
+ String line;
+ try {
+ while ((line = br.readLine()) != null) {
+ System.err.println(line);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ System.err.println(line);
+ if (line == null) {
+ throw new Error("Server startup failed");
+ }
+ ter = new TestEmailReciever(
+ new InetSocketAddress("localhost", 8473));
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+
+ }
+ @AfterClass
+ public static void tearDownServer() {
+ String type = testProps.getProperty("type");
+ if (type.equals("local")) {
+ return;
+ }
+ gigi.destroy();
+ }
+
+ @After
+ public void removeMails() {
+ ter.clearMails();
+ }
+
+ public TestMail waitForMail() {
+ try {
+ return ter.recieve();
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+}
--- /dev/null
+package org.cacert.gigi.testUtils;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.Socket;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class TestEmailReciever implements Runnable {
+ public class TestMail {
+ String to;
+ String subject;
+ String message;
+ String from;
+ String replyto;
+ public TestMail(String to, String subject, String message, String from,
+ String replyto) {
+ this.to = to;
+ this.subject = subject;
+ this.message = message;
+ this.from = from;
+ this.replyto = replyto;
+ }
+ public String getTo() {
+ return to;
+ }
+ public String getSubject() {
+ return subject;
+ }
+ public String getMessage() {
+ return message;
+ }
+ public String getFrom() {
+ return from;
+ }
+ public String getReplyto() {
+ return replyto;
+ }
+
+ }
+ private Socket s;
+ private DataInputStream dis;
+
+ public TestEmailReciever(SocketAddress target) throws IOException {
+ s = new Socket();
+ s.connect(target);
+ s.setKeepAlive(true);
+ s.setSoTimeout(1000 * 60 * 60);
+ dis = new DataInputStream(s.getInputStream());
+ new Thread(this).start();
+ }
+ LinkedBlockingQueue<TestMail> mails = new LinkedBlockingQueue<TestEmailReciever.TestMail>();
+
+ public TestMail recieve() throws InterruptedException {
+ return mails.poll(5, TimeUnit.SECONDS);
+ }
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ String to = dis.readUTF();
+ String subject = dis.readUTF();
+ String message = dis.readUTF();
+ String from = dis.readUTF();
+ String replyto = dis.readUTF();
+ mails.add(new TestMail(to, subject, message, from, replyto));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public void clearMails() {
+ mails.clear();
+ }
+
+}