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