diff --git a/htroot/ConfigBasic.html b/htroot/ConfigBasic.html index dacc4fd97..e58dbfe6e 100644 --- a/htroot/ConfigBasic.html +++ b/htroot/ConfigBasic.html @@ -93,7 +93,7 @@
    - with SSL (https enabled) + with SSL (https enabled#(withsslenabled)#:: on port #[sslport]# #(/withsslenabled)#)
#(upnp)#::
diff --git a/htroot/ConfigBasic.java b/htroot/ConfigBasic.java index 8207e395e..7563d9857 100644 --- a/htroot/ConfigBasic.java +++ b/htroot/ConfigBasic.java @@ -102,7 +102,8 @@ public class ConfigBasic { port = env.getConfigLong("port", 8090); //this allows a low port, but it will only get one, if the user edits the config himself. ssl = env.getConfigBool("server.https", false); } - + if (ssl) prop.put("withsslenabled_sslport",env.getHttpServer().getSslPort()); + // check if peer name already exists final Seed oldSeed = sb.peers.lookupByName(peerName); if (oldSeed == null && @@ -134,7 +135,7 @@ public class ConfigBasic { final YaCyHttpServer theServerCore = env.getHttpServer(); env.setConfig("port", port); env.setConfig("server.https", ssl); - + // redirect the browser to the new port reconnect = true; diff --git a/source/net/yacy/http/Jetty8HttpServerImpl.java b/source/net/yacy/http/Jetty8HttpServerImpl.java index 9e8557214..141f55944 100644 --- a/source/net/yacy/http/Jetty8HttpServerImpl.java +++ b/source/net/yacy/http/Jetty8HttpServerImpl.java @@ -24,13 +24,18 @@ package net.yacy.http; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.security.KeyStore; import java.util.EnumSet; import java.util.Enumeration; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; import javax.servlet.DispatcherType; @@ -41,6 +46,7 @@ import net.yacy.http.servlets.YaCyDefaultServlet; import net.yacy.http.servlets.YaCyProxyServlet; import net.yacy.http.servlets.SolrServlet.Servlet404; import net.yacy.search.Switchboard; +import net.yacy.utils.PKCS12Tool; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; @@ -50,9 +56,11 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; /** * class to embedded Jetty 8 http server into YaCy @@ -60,6 +68,7 @@ import org.eclipse.jetty.servlet.ServletHolder; public class Jetty8HttpServerImpl implements YaCyHttpServer { private final Server server; + private final int sslport = 8443; // the port to use for https /** * @param port TCP Port to listen for http requests @@ -71,9 +80,25 @@ public class Jetty8HttpServerImpl implements YaCyHttpServer { SelectChannelConnector connector = new SelectChannelConnector(); connector.setPort(port); connector.setName("httpd:"+Integer.toString(port)); - //connector.setThreadPool(new QueuedThreadPool(20)); server.addConnector(connector); - + + // add ssl/https connector + boolean useSSL = sb.getConfigBool("server.https", false); + if (useSSL) { + final SslContextFactory sslContextFactory = new SslContextFactory(); + final SSLContext sslContext = initSslContext(sb); + if (sslContext != null) { + sslContextFactory.setSslContext(sslContext); + + SslSelectChannelConnector sslconnector = new SslSelectChannelConnector(sslContextFactory); + sslconnector.setPort(sslport); + sslconnector.setName("ssld:" + Integer.toString(sslport)); // name must start with ssl (for withSSL() to work correctly) + + server.addConnector(sslconnector); + ConcurrentLog.info("SERVER", "SSL support initialized successfully on port " + sslport); + } + } + YacyDomainHandler domainHandler = new YacyDomainHandler(); domainHandler.setAlternativeResolver(sb.peers); @@ -153,10 +178,19 @@ public class Jetty8HttpServerImpl implements YaCyHttpServer { } @Override - public boolean withSSL() { - return false; // TODO: + public boolean withSSL() { + Connector[] clist = server.getConnectors(); + for (Connector c:clist) { + if (c.getName().startsWith("ssl")) return true; + } + return false; } + @Override + public int getSslPort() { + return sslport; + } + /** * reconnect with new port settings (after waiting milsec) - routine returns * immediately @@ -265,4 +299,104 @@ public class Jetty8HttpServerImpl implements YaCyHttpServer { return "Jetty " + Server.getVersion(); } + /** + * Init SSL Context from config settings + * @param sb Switchboard + * @return default or sslcontext according to config + */ + private SSLContext initSslContext(Switchboard sb) { + + // getting the keystore file name + String keyStoreFileName = sb.getConfig("keyStore", "").trim(); + + // getting the keystore pwd + String keyStorePwd = sb.getConfig("keyStorePassword", "").trim(); + + // take a look if we have something to import + final String pkcs12ImportFile = sb.getConfig("pkcs12ImportFile", "").trim(); + + // if no keyStore and no import is defined, then set the default key + if (keyStoreFileName.isEmpty() && keyStorePwd.isEmpty() && pkcs12ImportFile.isEmpty()) { + keyStoreFileName = "defaults/freeworldKeystore"; + keyStorePwd = "freeworld"; + sb.setConfig("keyStore", keyStoreFileName); + sb.setConfig("keyStorePassword", keyStorePwd); + } + + if (pkcs12ImportFile.length() > 0) { + ConcurrentLog.info("SERVER", "Import certificates from import file '" + pkcs12ImportFile + "'."); + + try { + // getting the password + final String pkcs12ImportPwd = sb.getConfig("pkcs12ImportPwd", "").trim(); + + // creating tool to import cert + final PKCS12Tool pkcsTool = new PKCS12Tool(pkcs12ImportFile,pkcs12ImportPwd); + + // creating a new keystore file + if (keyStoreFileName.isEmpty()) { + // using the default keystore name + keyStoreFileName = "DATA/SETTINGS/myPeerKeystore"; + + // creating an empty java keystore + final KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null,keyStorePwd.toCharArray()); + final FileOutputStream ksOut = new FileOutputStream(keyStoreFileName); + ks.store(ksOut, keyStorePwd.toCharArray()); + ksOut.close(); + + // storing path to keystore into config file + sb.setConfig("keyStore", keyStoreFileName); + } + + // importing certificate + pkcsTool.importToJKS(keyStoreFileName, keyStorePwd); + + // removing entries from config file + sb.setConfig("pkcs12ImportFile", ""); + sb.setConfig("keyStorePassword", ""); + + // deleting original import file + // TODO: should we do this + + } catch (final Exception e) { + ConcurrentLog.severe("SERVER", "Unable to import certificate from import file '" + pkcs12ImportFile + "'.",e); + } + } else if (keyStoreFileName.isEmpty()) return null; + + + // get the ssl context + try { + ConcurrentLog.info("SERVER","Initializing SSL support ..."); + + // creating a new keystore instance of type (java key store) + if (ConcurrentLog.isFine("SERVER")) ConcurrentLog.fine("SERVER", "Initializing keystore ..."); + final KeyStore ks = KeyStore.getInstance("JKS"); + + // loading keystore data from file + if (ConcurrentLog.isFine("SERVER")) ConcurrentLog.fine("SERVER","Loading keystore file " + keyStoreFileName); + final FileInputStream stream = new FileInputStream(keyStoreFileName); + ks.load(stream, keyStorePwd.toCharArray()); + stream.close(); + + // creating a keystore factory + if (ConcurrentLog.isFine("SERVER")) ConcurrentLog.fine("SERVER","Initializing key manager factory ..."); + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks,keyStorePwd.toCharArray()); + + // initializing the ssl context + if (ConcurrentLog.isFine("SERVER")) ConcurrentLog.fine("SERVER","Initializing SSL context ..."); + final SSLContext sslcontext = SSLContext.getInstance("TLS"); + sslcontext.init(kmf.getKeyManagers(), null, null); + + return sslcontext; + } catch (final Exception e) { + final String errorMsg = "FATAL ERROR: Unable to initialize the SSL Socket factory. " + e.getMessage(); + ConcurrentLog.severe("SERVER",errorMsg); + System.out.println(errorMsg); + System.exit(0); + return null; + } + } + } diff --git a/source/net/yacy/http/YaCyHttpServer.java b/source/net/yacy/http/YaCyHttpServer.java index e869c0572..eb39e997a 100644 --- a/source/net/yacy/http/YaCyHttpServer.java +++ b/source/net/yacy/http/YaCyHttpServer.java @@ -23,6 +23,7 @@ public interface YaCyHttpServer { abstract InetSocketAddress generateSocketAddress(String port) throws SocketException; abstract int getMaxSessionCount(); abstract int getJobCount(); + abstract int getSslPort(); abstract boolean withSSL(); abstract void reconnect(int milsec); abstract String getVersion(); diff --git a/source/net/yacy/migration.java b/source/net/yacy/migration.java index ddc45b602..2fff620c8 100644 --- a/source/net/yacy/migration.java +++ b/source/net/yacy/migration.java @@ -37,7 +37,11 @@ import net.yacy.search.Switchboard; import net.yacy.search.SwitchboardConstants; import com.google.common.io.Files; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; import net.yacy.cora.lod.vocabulary.Tagging; +import net.yacy.cora.protocol.TimeoutRequest; import net.yacy.cora.storage.Configuration.Entry; import net.yacy.cora.util.ConcurrentLog; import net.yacy.document.LibraryProvider; @@ -72,6 +76,16 @@ public class migration { migrateWorkFiles(sb); } installSkins(sb); // FIXME: yes, bad fix for quick release 0.47 + + // ssl/https support currently on hardcoded default port 8443 (v1.67/9563) + // make sure YaCy can start (disable ssl/https support if port is used) + try { + if (TimeoutRequest.ping("127.0.0.1", 8443, 3000)) { + sb.setConfig("server.https", false); + ConcurrentLog.info("MIGRATION", "disabled https support (reason: default port 8443 already used)"); + } + } catch (ExecutionException ex) { + } } /* * remove the static defaultfiles. We use them through a overlay now. diff --git a/source/net/yacy/search/Switchboard.java b/source/net/yacy/search/Switchboard.java index 7cb682705..0d600f8ce 100644 --- a/source/net/yacy/search/Switchboard.java +++ b/source/net/yacy/search/Switchboard.java @@ -3657,7 +3657,7 @@ public final class Switchboard extends serverSwitch { mySeed.put(Seed.UTC, GenericFormatter.UTCDiffString()); mySeed.setFlagAcceptRemoteCrawl(getConfigBool("crawlResponse", true)); mySeed.setFlagAcceptRemoteIndex(getConfigBool("allowReceiveIndex", true)); - mySeed.setFlagSSLAvailable(getConfigBool("server.https", false)); + mySeed.setFlagSSLAvailable(this.getHttpServer().withSSL() && getConfigBool("server.https", false)); } public void loadSeedLists() { diff --git a/source/net/yacy/yacy.java b/source/net/yacy/yacy.java index 15f002521..571e0691a 100644 --- a/source/net/yacy/yacy.java +++ b/source/net/yacy/yacy.java @@ -358,7 +358,7 @@ public final class yacy { final String browserPopUpPage = sb.getConfig(SwitchboardConstants.BROWSER_POP_UP_PAGE, "ConfigBasic.html"); //boolean properPW = (sb.getConfig("adminAccount", "").isEmpty()) && (sb.getConfig(httpd.ADMIN_ACCOUNT_B64MD5, "").length() > 0); //if (!properPW) browserPopUpPage = "ConfigBasic.html"; - Browser.openBrowser((false?"https":"http") + "://localhost:" + port + "/" + browserPopUpPage); + Browser.openBrowser((httpServer.withSSL()?"https://localhost:"+httpServer.getSslPort():"http://localhost:"+port) + "/" + browserPopUpPage); // Browser.openBrowser((server.withSSL()?"https":"http") + "://localhost:" + serverCore.getPortNr(port) + "/" + browserPopUpPage); } catch (final Throwable e) { // cannot open browser. This may be normal in headless environments