]> WPIA git - gigi.git/blob - src/org/cacert/gigi/Launcher.java
5f767a63956a3da4ce8cb4be5256e8403b77da68
[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     private boolean isSystemPort(int port) {
116         return 1 <= port && port <= 1024;
117     }
118
119     public synchronized void boot(InputStream in) throws Exception {
120         Locale.setDefault(Locale.ENGLISH);
121         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
122         HttpURLConnection.setFollowRedirects(false);
123
124         conf = GigiConfig.parse(in);
125         ServerConstants.init(conf.getMainProps());
126         initEmails(conf);
127
128         s = new Server();
129
130         initConnectors();
131         initHandlers();
132
133         s.start();
134         if ((isSystemPort(ServerConstants.getSecurePort()) || isSystemPort(ServerConstants.getPort())) && !System.getProperty("os.name").toLowerCase().contains("win")) {
135             SetUID uid = new SetUID();
136             if ( !uid.setUid(65536 - 2, 65536 - 2).getSuccess()) {
137                 Log.getLogger(Launcher.class).warn("Couldn't set uid!");
138             }
139         }
140     }
141
142     private HttpConfiguration createHttpConfiguration() {
143         // SSL HTTP Configuration
144         HttpConfiguration httpsConfig = new HttpConfiguration();
145         httpsConfig.setSendServerVersion(false);
146         httpsConfig.setSendXPoweredBy(false);
147         return httpsConfig;
148     }
149
150     private void initConnectors() throws GeneralSecurityException, IOException {
151         HttpConfiguration httpConfig = createHttpConfiguration();
152         if (conf.getMainProps().getProperty("proxy", "false").equals("true")) {
153             httpConfig.addCustomizer(new ExtendedForwarded());
154             s.setConnectors(new Connector[] {
155                     ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
156             });
157         } else {
158             HttpConfiguration httpsConfig = createHttpConfiguration();
159             // for client-cert auth
160             httpsConfig.addCustomizer(new SecureRequestCustomizer());
161             s.setConnectors(new Connector[] {
162                     ConnectorsLauncher.createConnector(conf, s, httpsConfig, true), ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
163             });
164         }
165     }
166
167     private void initEmails(GigiConfig conf) throws GeneralSecurityException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
168         KeyStore privateStore = conf.getPrivateStore();
169         Certificate mail = null;
170         Key k = null;
171         if (privateStore != null && privateStore.containsAlias("mail")) {
172             mail = privateStore.getCertificate("mail");
173             k = privateStore.getKey("mail", conf.getPrivateStorePw().toCharArray());
174         }
175         EmailProvider.initSystem(conf.getMainProps(), mail, k);
176     }
177
178     private static class ConnectorsLauncher {
179
180         private ConnectorsLauncher() {}
181
182         protected static ServerConnector createConnector(GigiConfig conf, Server s, HttpConfiguration httpConfig, boolean doHttps) throws GeneralSecurityException, IOException {
183             ServerConnector connector;
184             int port;
185             if (doHttps) {
186                 connector = new ServerConnector(s, createConnectionFactory(conf), new HttpConnectionFactory(httpConfig));
187                 port = ServerConstants.getSecurePort();
188             } else {
189                 connector = new ServerConnector(s, new HttpConnectionFactory(httpConfig));
190                 port = ServerConstants.getPort();
191             }
192             if (port == -1) {
193                 connector.setInheritChannel(true);
194             } else {
195                 connector.setHost(conf.getMainProps().getProperty("host"));
196                 connector.setPort(port);
197             }
198             connector.setAcceptQueueSize(100);
199             return connector;
200         }
201
202         private static SslConnectionFactory createConnectionFactory(GigiConfig conf) throws GeneralSecurityException, IOException {
203             final SslContextFactory sslContextFactory = generateSSLContextFactory(conf, "www");
204             final SslContextFactory secureContextFactory = generateSSLContextFactory(conf, "secure");
205             secureContextFactory.setWantClientAuth(true);
206             secureContextFactory.setNeedClientAuth(true);
207             final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static");
208             final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api");
209             apiContextFactory.setWantClientAuth(true);
210             try {
211                 secureContextFactory.start();
212                 staticContextFactory.start();
213                 apiContextFactory.start();
214             } catch (Exception e) {
215                 e.printStackTrace();
216             }
217             return new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()) {
218
219                 @Override
220                 public boolean shouldRestartSSL() {
221                     return true;
222                 }
223
224                 @Override
225                 public SSLEngine restartSSL(SSLSession sslSession) {
226                     SSLEngine e2 = null;
227                     if (sslSession instanceof ExtendedSSLSession) {
228                         ExtendedSSLSession es = (ExtendedSSLSession) sslSession;
229                         List<SNIServerName> names = es.getRequestedServerNames();
230                         for (SNIServerName sniServerName : names) {
231                             if (sniServerName instanceof SNIHostName) {
232                                 SNIHostName host = (SNIHostName) sniServerName;
233                                 String hostname = host.getAsciiName();
234                                 if (hostname.equals(ServerConstants.getWwwHostName())) {
235                                     e2 = sslContextFactory.newSSLEngine();
236                                 } else if (hostname.equals(ServerConstants.getStaticHostName())) {
237                                     e2 = staticContextFactory.newSSLEngine();
238                                 } else if (hostname.equals(ServerConstants.getSecureHostName())) {
239                                     e2 = secureContextFactory.newSSLEngine();
240                                 } else if (hostname.equals(ServerConstants.getApiHostName())) {
241                                     e2 = apiContextFactory.newSSLEngine();
242                                 }
243                                 break;
244                             }
245                         }
246                     }
247                     if (e2 == null) {
248                         e2 = sslContextFactory.newSSLEngine(sslSession.getPeerHost(), sslSession.getPeerPort());
249                     }
250                     e2.setUseClientMode(false);
251                     return e2;
252                 }
253             };
254         }
255
256         private static SslContextFactory generateSSLContextFactory(GigiConfig conf, String alias) throws GeneralSecurityException, IOException {
257             SslContextFactory scf = new SslContextFactory() {
258
259                 String[] ciphers = null;
260
261                 @Override
262                 public void customize(SSLEngine sslEngine) {
263                     super.customize(sslEngine);
264
265                     SSLParameters ssl = sslEngine.getSSLParameters();
266                     ssl.setUseCipherSuitesOrder(true);
267                     if (ciphers == null) {
268                         ciphers = CipherInfo.filter(sslEngine.getSupportedCipherSuites());
269                     }
270
271                     ssl.setCipherSuites(ciphers);
272                     sslEngine.setSSLParameters(ssl);
273
274                 }
275
276             };
277             scf.setRenegotiationAllowed(false);
278
279             scf.setProtocol("TLS");
280             scf.setIncludeProtocols("TLSv1", "TLSv1.1", "TLSv1.2");
281             scf.setTrustStore(conf.getTrustStore());
282             KeyStore privateStore = conf.getPrivateStore();
283             scf.setKeyStorePassword(conf.getPrivateStorePw());
284             scf.setKeyStore(privateStore);
285             scf.setCertAlias(alias);
286             return scf;
287         }
288     }
289
290     private void initHandlers() throws GeneralSecurityException, IOException {
291         HandlerList hl = new HandlerList();
292         hl.setHandlers(new Handler[] {
293                 ContextLauncher.generateStaticContext(), ContextLauncher.generateGigiContexts(conf.getMainProps(), conf.getTrustStore()), ContextLauncher.generateAPIContext()
294         });
295         s.setHandler(hl);
296     }
297
298     private static class ContextLauncher {
299
300         private ContextLauncher() {}
301
302         protected static Handler generateGigiContexts(Properties conf, KeyStore trust) {
303             ServletHolder webAppServlet = new ServletHolder(new Gigi(conf, trust));
304
305             ContextHandler ch = generateGigiServletContext(webAppServlet);
306             ch.setVirtualHosts(new String[] {
307                     ServerConstants.getWwwHostName()
308             });
309             ContextHandler chSecure = generateGigiServletContext(webAppServlet);
310             chSecure.setVirtualHosts(new String[] {
311                     ServerConstants.getSecureHostName()
312             });
313
314             HandlerList hl = new HandlerList();
315             hl.setHandlers(new Handler[] {
316                     ch, chSecure
317             });
318             return hl;
319         }
320
321         private static ContextHandler generateGigiServletContext(ServletHolder webAppServlet) {
322             final ResourceHandler rh = generateResourceHandler();
323             rh.setResourceBase("static/www");
324
325             HandlerWrapper hw = new PolicyRedirector();
326             hw.setHandler(rh);
327
328             ServletContextHandler servlet = new ServletContextHandler(ServletContextHandler.SESSIONS);
329             servlet.setInitParameter(SessionManager.__SessionCookieProperty, "SomeCA-Session");
330             servlet.addServlet(webAppServlet, "/*");
331             ErrorPageErrorHandler epeh = new ErrorPageErrorHandler();
332             epeh.addErrorPage(404, "/error");
333             epeh.addErrorPage(403, "/denied");
334             servlet.setErrorHandler(epeh);
335
336             HandlerList hl = new HandlerList();
337             hl.setHandlers(new Handler[] {
338                     hw, servlet
339             });
340
341             ContextHandler ch = new ContextHandler();
342             ch.setHandler(hl);
343             return ch;
344         }
345
346         protected static Handler generateStaticContext() {
347             final ResourceHandler rh = generateResourceHandler();
348             rh.setResourceBase("static/static");
349
350             ContextHandler ch = new ContextHandler();
351             ch.setHandler(rh);
352             ch.setVirtualHosts(new String[] {
353                     ServerConstants.getStaticHostName()
354             });
355
356             return ch;
357         }
358
359         private static ResourceHandler generateResourceHandler() {
360             ResourceHandler rh = new ResourceHandler() {
361
362                 @Override
363                 protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) {
364                     super.doResponseHeaders(response, resource, mimeType);
365                     response.setDateHeader(HttpHeader.EXPIRES.asString(), System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 7);
366                 }
367             };
368             rh.setEtags(true);
369             return rh;
370         }
371
372         protected static Handler generateAPIContext() {
373             ServletContextHandler sch = new ServletContextHandler();
374
375             sch.addVirtualHosts(new String[] {
376                     ServerConstants.getApiHostName()
377             });
378             sch.addServlet(new ServletHolder(new GigiAPI()), "/*");
379             return sch;
380         }
381
382     }
383
384 }