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