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