]> WPIA git - gigi.git/blob - src/org/cacert/gigi/Launcher.java
Support reading configuration from file
[gigi.git] / src / org / cacert / gigi / Launcher.java
1 package org.cacert.gigi;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.InputStream;
7 import java.io.IOException;
8 import java.net.HttpURLConnection;
9 import java.net.InetSocketAddress;
10 import java.security.GeneralSecurityException;
11 import java.security.Key;
12 import java.security.KeyStore;
13 import java.security.KeyStoreException;
14 import java.security.NoSuchAlgorithmException;
15 import java.security.UnrecoverableKeyException;
16 import java.security.cert.Certificate;
17 import java.security.cert.CertificateException;
18 import java.security.cert.CertificateFactory;
19 import java.security.cert.X509Certificate;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Properties;
23 import java.util.TimeZone;
24
25 import javax.net.ssl.ExtendedSSLSession;
26 import javax.net.ssl.SNIHostName;
27 import javax.net.ssl.SNIServerName;
28 import javax.net.ssl.SSLEngine;
29 import javax.net.ssl.SSLParameters;
30 import javax.net.ssl.SSLSession;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.cacert.gigi.api.GigiAPI;
34 import org.cacert.gigi.email.EmailProvider;
35 import org.cacert.gigi.natives.SetUID;
36 import org.cacert.gigi.util.CipherInfo;
37 import org.cacert.gigi.util.PEM;
38 import org.cacert.gigi.util.ServerConstants;
39 import org.eclipse.jetty.http.HttpFields;
40 import org.eclipse.jetty.http.HttpHeader;
41 import org.eclipse.jetty.http.HttpVersion;
42 import org.eclipse.jetty.server.Connector;
43 import org.eclipse.jetty.server.Handler;
44 import org.eclipse.jetty.server.HttpConfiguration;
45 import org.eclipse.jetty.server.HttpConfiguration.Customizer;
46 import org.eclipse.jetty.server.HttpConnectionFactory;
47 import org.eclipse.jetty.server.Request;
48 import org.eclipse.jetty.server.SecureRequestCustomizer;
49 import org.eclipse.jetty.server.Server;
50 import org.eclipse.jetty.server.ServerConnector;
51 import org.eclipse.jetty.server.SessionManager;
52 import org.eclipse.jetty.server.SslConnectionFactory;
53 import org.eclipse.jetty.server.handler.ContextHandler;
54 import org.eclipse.jetty.server.handler.HandlerList;
55 import org.eclipse.jetty.server.handler.HandlerWrapper;
56 import org.eclipse.jetty.server.handler.ResourceHandler;
57 import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
58 import org.eclipse.jetty.servlet.ServletContextHandler;
59 import org.eclipse.jetty.servlet.ServletHolder;
60 import org.eclipse.jetty.util.log.Log;
61 import org.eclipse.jetty.util.resource.Resource;
62 import org.eclipse.jetty.util.ssl.SslContextFactory;
63
64 public class Launcher {
65
66     class ExtendedForwarded implements Customizer {
67
68         @Override
69         public void customize(Connector connector, HttpConfiguration config, Request request) {
70             HttpFields httpFields = request.getHttpFields();
71
72             String ip = httpFields.getStringField("X-Real-IP");
73             String proto = httpFields.getStringField("X-Real-Proto");
74             String cert = httpFields.getStringField("X-Client-Cert");
75             request.setSecure("https".equals(proto));
76             request.setScheme(proto);
77             if ( !"https".equals(proto)) {
78                 cert = null;
79
80             }
81             if (cert != null) {
82                 X509Certificate[] certs = new X509Certificate[1];
83                 try {
84                     certs[0] = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(PEM.decode("CERTIFICATE", cert)));
85                     request.setAttribute("javax.servlet.request.X509Certificate", certs);
86                 } catch (CertificateException e) {
87                     e.printStackTrace();
88                 }
89             }
90             if (ip != null) {
91                 String[] parts = ip.split(":");
92                 if (parts.length == 2) {
93                     request.setRemoteAddr(InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1])));
94                 }
95             }
96
97         }
98     }
99
100     public static void main(String[] args) throws Exception {
101         System.setProperty("jdk.tls.ephemeralDHKeySize", "4096");
102         InputStream in;
103         if (args.length >= 1) {
104             in = new FileInputStream(new File(args[0]));
105         } else {
106             in = System.in;
107         }
108         new Launcher().boot(in);
109     }
110
111     Server s;
112
113     GigiConfig conf;
114
115     public synchronized void boot(InputStream in) throws Exception {
116         Locale.setDefault(Locale.ENGLISH);
117         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
118         HttpURLConnection.setFollowRedirects(false);
119
120         conf = GigiConfig.parse(in);
121         ServerConstants.init(conf.getMainProps());
122         initEmails(conf);
123
124         s = new Server();
125
126         initConnectors();
127         initHandlers();
128
129         s.start();
130         if ((ServerConstants.getSecurePort() <= 1024 || ServerConstants.getPort() <= 1024) && !System.getProperty("os.name").toLowerCase().contains("win")) {
131             SetUID uid = new SetUID();
132             if ( !uid.setUid(65536 - 2, 65536 - 2).getSuccess()) {
133                 Log.getLogger(Launcher.class).warn("Couldn't set uid!");
134             }
135         }
136     }
137
138     private HttpConfiguration createHttpConfiguration() {
139         // SSL HTTP Configuration
140         HttpConfiguration httpsConfig = new HttpConfiguration();
141         httpsConfig.setSendServerVersion(false);
142         httpsConfig.setSendXPoweredBy(false);
143         return httpsConfig;
144     }
145
146     private void initConnectors() throws GeneralSecurityException, IOException {
147         HttpConfiguration httpConfig = createHttpConfiguration();
148         if (conf.getMainProps().getProperty("proxy", "false").equals("true")) {
149             httpConfig.addCustomizer(new ExtendedForwarded());
150             s.setConnectors(new Connector[] {
151                     ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
152             });
153         } else {
154             HttpConfiguration httpsConfig = createHttpConfiguration();
155             // for client-cert auth
156             httpsConfig.addCustomizer(new SecureRequestCustomizer());
157             s.setConnectors(new Connector[] {
158                     ConnectorsLauncher.createConnector(conf, s, httpsConfig, true), ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
159             });
160         }
161     }
162
163     private void initEmails(GigiConfig conf) throws GeneralSecurityException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
164         KeyStore privateStore = conf.getPrivateStore();
165         Certificate mail = null;
166         Key k = null;
167         if (privateStore != null && privateStore.containsAlias("mail")) {
168             mail = privateStore.getCertificate("mail");
169             k = privateStore.getKey("mail", conf.getPrivateStorePw().toCharArray());
170         }
171         EmailProvider.initSystem(conf.getMainProps(), mail, k);
172     }
173
174     private static class ConnectorsLauncher {
175
176         private ConnectorsLauncher() {}
177
178         protected static ServerConnector createConnector(GigiConfig conf, Server s, HttpConfiguration httpConfig, boolean doHttps) throws GeneralSecurityException, IOException {
179             ServerConnector connector;
180             if (doHttps) {
181                 connector = new ServerConnector(s, createConnectionFactory(conf), new HttpConnectionFactory(httpConfig));
182             } else {
183                 connector = new ServerConnector(s, new HttpConnectionFactory(httpConfig));
184             }
185             connector.setHost(conf.getMainProps().getProperty("host"));
186             if (doHttps) {
187                 connector.setPort(ServerConstants.getSecurePort());
188             } else {
189                 connector.setPort(ServerConstants.getPort());
190             }
191             connector.setAcceptQueueSize(100);
192             return connector;
193         }
194
195         private static SslConnectionFactory createConnectionFactory(GigiConfig conf) throws GeneralSecurityException, IOException {
196             final SslContextFactory sslContextFactory = generateSSLContextFactory(conf, "www");
197             final SslContextFactory secureContextFactory = generateSSLContextFactory(conf, "secure");
198             secureContextFactory.setWantClientAuth(true);
199             secureContextFactory.setNeedClientAuth(true);
200             final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static");
201             final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api");
202             apiContextFactory.setWantClientAuth(true);
203             try {
204                 secureContextFactory.start();
205                 staticContextFactory.start();
206                 apiContextFactory.start();
207             } catch (Exception e) {
208                 e.printStackTrace();
209             }
210             return new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()) {
211
212                 @Override
213                 public boolean shouldRestartSSL() {
214                     return true;
215                 }
216
217                 @Override
218                 public SSLEngine restartSSL(SSLSession sslSession) {
219                     SSLEngine e2 = null;
220                     if (sslSession instanceof ExtendedSSLSession) {
221                         ExtendedSSLSession es = (ExtendedSSLSession) sslSession;
222                         List<SNIServerName> names = es.getRequestedServerNames();
223                         for (SNIServerName sniServerName : names) {
224                             if (sniServerName instanceof SNIHostName) {
225                                 SNIHostName host = (SNIHostName) sniServerName;
226                                 String hostname = host.getAsciiName();
227                                 if (hostname.equals(ServerConstants.getWwwHostName())) {
228                                     e2 = sslContextFactory.newSSLEngine();
229                                 } else if (hostname.equals(ServerConstants.getStaticHostName())) {
230                                     e2 = staticContextFactory.newSSLEngine();
231                                 } else if (hostname.equals(ServerConstants.getSecureHostName())) {
232                                     e2 = secureContextFactory.newSSLEngine();
233                                 } else if (hostname.equals(ServerConstants.getApiHostName())) {
234                                     e2 = apiContextFactory.newSSLEngine();
235                                 }
236                                 break;
237                             }
238                         }
239                     }
240                     if (e2 == null) {
241                         e2 = sslContextFactory.newSSLEngine(sslSession.getPeerHost(), sslSession.getPeerPort());
242                     }
243                     e2.setUseClientMode(false);
244                     return e2;
245                 }
246             };
247         }
248
249         private static SslContextFactory generateSSLContextFactory(GigiConfig conf, String alias) throws GeneralSecurityException, IOException {
250             SslContextFactory scf = new SslContextFactory() {
251
252                 String[] ciphers = null;
253
254                 @Override
255                 public void customize(SSLEngine sslEngine) {
256                     super.customize(sslEngine);
257
258                     SSLParameters ssl = sslEngine.getSSLParameters();
259                     ssl.setUseCipherSuitesOrder(true);
260                     if (ciphers == null) {
261                         ciphers = CipherInfo.filter(sslEngine.getSupportedCipherSuites());
262                     }
263
264                     ssl.setCipherSuites(ciphers);
265                     sslEngine.setSSLParameters(ssl);
266
267                 }
268
269             };
270             scf.setRenegotiationAllowed(false);
271
272             scf.setProtocol("TLS");
273             scf.setIncludeProtocols("TLSv1", "TLSv1.1", "TLSv1.2");
274             scf.setTrustStore(conf.getTrustStore());
275             KeyStore privateStore = conf.getPrivateStore();
276             scf.setKeyStorePassword(conf.getPrivateStorePw());
277             scf.setKeyStore(privateStore);
278             scf.setCertAlias(alias);
279             return scf;
280         }
281     }
282
283     private void initHandlers() throws GeneralSecurityException, IOException {
284         HandlerList hl = new HandlerList();
285         hl.setHandlers(new Handler[] {
286                 ContextLauncher.generateStaticContext(), ContextLauncher.generateGigiContexts(conf.getMainProps(), conf.getTrustStore()), ContextLauncher.generateAPIContext()
287         });
288         s.setHandler(hl);
289     }
290
291     private static class ContextLauncher {
292
293         private ContextLauncher() {}
294
295         protected static Handler generateGigiContexts(Properties conf, KeyStore trust) {
296             ServletHolder webAppServlet = new ServletHolder(new Gigi(conf, trust));
297
298             ContextHandler ch = generateGigiServletContext(webAppServlet);
299             ch.setVirtualHosts(new String[] {
300                     ServerConstants.getWwwHostName()
301             });
302             ContextHandler chSecure = generateGigiServletContext(webAppServlet);
303             chSecure.setVirtualHosts(new String[] {
304                     ServerConstants.getSecureHostName()
305             });
306
307             HandlerList hl = new HandlerList();
308             hl.setHandlers(new Handler[] {
309                     ch, chSecure
310             });
311             return hl;
312         }
313
314         private static ContextHandler generateGigiServletContext(ServletHolder webAppServlet) {
315             final ResourceHandler rh = generateResourceHandler();
316             rh.setResourceBase("static/www");
317
318             HandlerWrapper hw = new PolicyRedirector();
319             hw.setHandler(rh);
320
321             ServletContextHandler servlet = new ServletContextHandler(ServletContextHandler.SESSIONS);
322             servlet.setInitParameter(SessionManager.__SessionCookieProperty, "SomeCA-Session");
323             servlet.addServlet(webAppServlet, "/*");
324             ErrorPageErrorHandler epeh = new ErrorPageErrorHandler();
325             epeh.addErrorPage(404, "/error");
326             epeh.addErrorPage(403, "/denied");
327             servlet.setErrorHandler(epeh);
328
329             HandlerList hl = new HandlerList();
330             hl.setHandlers(new Handler[] {
331                     hw, servlet
332             });
333
334             ContextHandler ch = new ContextHandler();
335             ch.setHandler(hl);
336             return ch;
337         }
338
339         protected static Handler generateStaticContext() {
340             final ResourceHandler rh = generateResourceHandler();
341             rh.setResourceBase("static/static");
342
343             ContextHandler ch = new ContextHandler();
344             ch.setHandler(rh);
345             ch.setVirtualHosts(new String[] {
346                     ServerConstants.getStaticHostName()
347             });
348
349             return ch;
350         }
351
352         private static ResourceHandler generateResourceHandler() {
353             ResourceHandler rh = new ResourceHandler() {
354
355                 @Override
356                 protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) {
357                     super.doResponseHeaders(response, resource, mimeType);
358                     response.setDateHeader(HttpHeader.EXPIRES.asString(), System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 7);
359                 }
360             };
361             rh.setEtags(true);
362             return rh;
363         }
364
365         protected static Handler generateAPIContext() {
366             ServletContextHandler sch = new ServletContextHandler();
367
368             sch.addVirtualHosts(new String[] {
369                     ServerConstants.getApiHostName()
370             });
371             sch.addServlet(new ServletHolder(new GigiAPI()), "/*");
372             return sch;
373         }
374
375     }
376
377 }