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