/* * Copyright 2013 mpowers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.trsst.server; import java.io.IOException; import java.math.BigInteger; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.URL; import java.net.UnknownHostException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.Date; import org.apache.abdera.protocol.server.servlet.AbderaServlet; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.ssl.SslContextFactory; /** * Jetty-specific configuration to host an Abdera servlet that is configured to * serve the Trsst protocol. * * @author mpowers */ public class Server { private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this .getClass()); int port; String path; boolean secure; private org.eclipse.jetty.server.Server server; private ServletContextHandler context; public Server() throws Exception { this(0, null); } public Server(int port) throws Exception { this(port, null); } public Server(int port, String path) throws Exception { this(port, path, false); } public Server(boolean secure) throws Exception { this(0, null, secure); } public Server(String path, boolean secure) throws Exception { this(0, path, secure); } public Server(int port, String path, boolean secure) throws Exception { try { if (path != null) { if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } if (!path.startsWith("/")) { path = "/" + path; } } else { path = ""; } if (port == 0) { port = allocatePort(); } this.secure = secure; this.port = port; this.path = path; start(); } catch (Exception ioe) { log.error("could not start server on " + port + " : " + path, ioe); throw ioe; } } /** * Generates a new keystore containing a self-signed certificate. Would * prefer anon SSL ciphers, but this works albeit with scary warnings. * * @return a keystore to secure SSL connections. */ private KeyStore getKeyStore() { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator .getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair kp = keyPairGenerator.generateKeyPair(); X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder( new X500Name("CN=0.0.0.0, OU=None, O=None, L=None, C=None"), BigInteger.valueOf(new SecureRandom().nextInt()), new Date( System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30), new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)), new X500Name( "CN=0.0.0.0, OU=None, O=None, L=None, C=None"), SubjectPublicKeyInfo.getInstance(kp.getPublic() .getEncoded())); ContentSigner signer = new JcaContentSignerBuilder( "SHA256WithRSAEncryption").build(kp.getPrivate()); Certificate certificate = new JcaX509CertificateConverter() .getCertificate(v3CertGen.build(signer)); final KeyStore keystore = KeyStore.getInstance(KeyStore .getDefaultType()); keystore.load(null); // bogus: required to "initialize" keystore keystore.setEntry("jetty", new KeyStore.PrivateKeyEntry(kp.getPrivate(), new Certificate[] { certificate }), new KeyStore.PasswordProtection("ignored".toCharArray())); return keystore; } catch (NoSuchAlgorithmException e) { log.error( "Could not generate self-signed certificate: missing provider", e); } catch (OperatorCreationException e) { log.error("Could not generate self-signed certificate", e); } catch (CertificateException e) { log.error("Could not convert certificate to JCE", e); } catch (KeyStoreException e) { log.error("Could not generate keystore", e); } catch (IOException e) { log.error("Could not initialize keystore", e); } return null; } /** * Grabs a new server port. Borrowed from axiom. */ private int allocatePort() { try { ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort(); ss.close(); return port; } catch (IOException ex) { log.error("Unable to allocate TCP port; defaulting to 54445.", ex); return 54445; // arbitrary port } } public URL getServiceURL() { URL result = null; String protocol = secure ? "https" : "http"; try { result = new URL(protocol, "localhost", port, path); // default result = new URL(protocol, InetAddress.getLocalHost() .getHostAddress(), port, path); } catch (MalformedURLException e) { // accept default } catch (UnknownHostException e) { // accept default } return result; } public void start() { try { getJetty().start(); } catch (Exception e) { log.error("Error while starting server", e); } } public void stop() { try { server.stop(); } catch (Exception e) { log.error("Error while stopping server", e); } server.destroy(); } public ServletContextHandler getServletContextHandler() { return context; } protected ServletHolder createProvidingServletHolder() { ServletHolder servletHolder = new ServletHolder(new AbderaServlet()); servletHolder.setInitParameter( "org.apache.abdera.protocol.server.Provider", "com.trsst.server.AbderaProvider"); return servletHolder; } protected void configureContext(ServletContextHandler context) { ServletHolder servletHolder = createProvidingServletHolder(); context.addServlet(servletHolder, path + "/*"); HttpConfiguration http_config = new HttpConfiguration(); ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config)); http.setPort(port); if (secure) { http_config.setSecureScheme("https"); http_config.setSecurePort(8443); http_config.setOutputBufferSize(32768); final KeyStore keystore = getKeyStore(); SslContextFactory sslContextFactory = new SslContextFactory(true); sslContextFactory.setKeyStore(keystore); sslContextFactory.setCertAlias("jetty"); sslContextFactory.setKeyStorePassword("ignored"); sslContextFactory.setKeyManagerPassword("ignored"); sslContextFactory.setTrustAll(true); HttpConfiguration https_config = new HttpConfiguration(http_config); https_config.addCustomizer(new SecureRequestCustomizer()); ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(https_config)); https.setPort(port); server.setConnectors(new Connector[] { https }); } else { server.setConnectors(new Connector[] { http }); } } /** * Returns the jetty server. */ public org.eclipse.jetty.server.Server getJetty() { if (server == null) { server = new org.eclipse.jetty.server.Server(port); context = new ServletContextHandler( ServletContextHandler.NO_SESSIONS); context.setContextPath("/"); server.setHandler(context); configureContext(context); // FIXME: does not work: still getting session cookie // see http://stackoverflow.com/questions/20373461 // ((org.eclipse.jetty.server.session.HashSessionManager) context // .getSessionHandler().getSessionManager()) // .setUsingCookies(false); } return server; } }