From df068cf23c43a0103914dd8d61df99afb01cf0b8 Mon Sep 17 00:00:00 2001 From: theli Date: Mon, 15 May 2006 09:41:29 +0000 Subject: [PATCH] *) adding first version of native SSL support for yacy VERY EXPERIMENTAL! See: http://www.yacy-forum.de/viewtopic.php?p=18516 git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@2096 6c8d7289-2bf4-0310-a012-ef5d649a1542 --- htroot/Connections_p.java | 4 +- htroot/Status.java | 3 + htroot/Status_p.inc | 2 +- source/de/anomic/server/serverCore.java | 131 +++++++++++- source/de/anomic/server/serverCoreSocket.java | 197 ++++++++++++++++++ yacy.init | 15 ++ 6 files changed, 345 insertions(+), 7 deletions(-) create mode 100644 source/de/anomic/server/serverCoreSocket.java diff --git a/htroot/Connections_p.java b/htroot/Connections_p.java index 7f521a87e..60c636fc2 100644 --- a/htroot/Connections_p.java +++ b/htroot/Connections_p.java @@ -162,11 +162,13 @@ public final class Connections_p { int userPort = currentSession.getUserPort(); if (userAddress == null) continue; + boolean isSSL = currentSession.isSSL(); + String dest = null; String prot = null; serverHandler cmdObj = currentSession.getCommandObj(); if (cmdObj instanceof httpd) { - prot = "http"; + prot = isSSL ? "https":"http"; // getting the http command object diff --git a/htroot/Status.java b/htroot/Status.java index a14d40fc3..24ff245b1 100644 --- a/htroot/Status.java +++ b/htroot/Status.java @@ -149,6 +149,9 @@ public class Status { prop.put("extPortFormat",0); } prop.put("host", serverCore.publicLocalIP()); + + // ssl support + prop.put("sslSupport",env.getConfig("keyStore", "").length() == 0 ? 0:1); // port forwarding: hostname and port if ((serverCore.portForwardingEnabled) && (serverCore.portForwarding != null)) { diff --git a/htroot/Status_p.inc b/htroot/Status_p.inc index 4992e94d9..29b915c03 100644 --- a/htroot/Status_p.inc +++ b/htroot/Status_p.inc @@ -20,7 +20,7 @@ Peer host - #[host]#:#[port]# #(extPortFormat)#::(Binding to interface: #[extPort]#)#(/extPortFormat)# + #[host]#:#[port]# #(extPortFormat)#::| (Binding to interface: #[extPort]#)#(/extPortFormat)# #(sslSupport)#::| SSL: enabled#(/sslSupport)#   diff --git a/source/de/anomic/server/serverCore.java b/source/de/anomic/server/serverCore.java index c144df067..70c5694b5 100644 --- a/source/de/anomic/server/serverCore.java +++ b/source/de/anomic/server/serverCore.java @@ -46,6 +46,7 @@ package de.anomic.server; // standard server +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -62,9 +63,17 @@ import java.net.SocketException; import java.net.URL; import java.net.UnknownHostException; import java.nio.channels.ClosedByInterruptException; +import java.security.KeyStore; import java.util.Enumeration; import java.util.Hashtable; +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + import org.apache.commons.pool.impl.GenericObjectPool; import org.apache.commons.pool.impl.GenericObjectPool.Config; @@ -97,6 +106,7 @@ public final class serverCore extends serverAbstractThread implements serverThre public static boolean useStaticIP = false; public static serverPortForwarding portForwarding = null; + private SSLSocketFactory sslSocketFactory = null; private ServerSocket socket; // listener serverLog log; // log object private int timeout; // connection time-out of the socket @@ -186,6 +196,9 @@ public final class serverCore extends serverAbstractThread implements serverThre // initialize logger this.log = new serverLog("SERVER"); + // init the ssl socket factory + this.sslSocketFactory = initSSLFactory(); + // init servercore init(); } @@ -479,10 +492,22 @@ public final class serverCore extends serverAbstractThread implements serverThre this.log.logFinest( "* waiting for connections, " + this.theSessionPool.getNumActive() + " sessions running, " + this.theSessionPool.getNumIdle() + " sleeping"); + + announceThreadBlockApply(); // wait for new connection - announceThreadBlockApply(); Socket controlSocket = this.socket.accept(); + + // wrap this socket + if (this.sslSocketFactory != null) { + controlSocket = new serverCoreSocket(controlSocket); + + // if the current connection is SSL we need to do a handshake + if (((serverCoreSocket)controlSocket).isSSL()) { + controlSocket = negotiateSSL(controlSocket); + } + } + announceThreadBlockRelease(); String cIP = clientAddress(controlSocket); @@ -977,7 +1002,7 @@ public final class serverCore extends serverAbstractThread implements serverThre } } } catch (InterruptedException ex) { - serverLog.logFiner("SESSION-POOL","Interruption of thread '" + this.getName() + "' detected."); + serverLog.logFiner("SESSION-POOL","Interruption of thread '" + this.getName() + "' detected."); } finally { if (serverCore.this.theSessionPool != null && !this.destroyed) serverCore.this.theSessionPool.invalidateObject(this); @@ -1000,7 +1025,11 @@ public final class serverCore extends serverAbstractThread implements serverThre // TODO: check if we want to allow this socket to connect us // getting input and output stream for communication with client - this.in = new PushbackInputStream(this.controlSocket.getInputStream()); + if (this.controlSocket.getInputStream() instanceof PushbackInputStream) { + this.in = (PushbackInputStream) this.controlSocket.getInputStream(); + } else { + this.in = new PushbackInputStream(this.controlSocket.getInputStream()); + } this.out = this.controlSocket.getOutputStream(); // reseting the command counter @@ -1015,8 +1044,11 @@ public final class serverCore extends serverAbstractThread implements serverThre try { this.out.flush(); - this.controlSocket.shutdownInput(); - this.controlSocket.shutdownOutput(); + // maybe this doesn't work for all SSL socket implementations + if (!(this.controlSocket instanceof SSLSocket)) { + this.controlSocket.shutdownInput(); + this.controlSocket.shutdownOutput(); + } this.in.close(); this.out.close(); @@ -1191,6 +1223,10 @@ public final class serverCore extends serverAbstractThread implements serverThre } //announceMoreExecTime(System.currentTimeMillis() - this.start); } + + public boolean isSSL() { + return this.controlSocket instanceof SSLSocket; + } } @@ -1280,4 +1316,89 @@ public final class serverCore extends serverAbstractThread implements serverThre serverCore.this.close(); } } + + private SSLSocketFactory initSSLFactory() { + + // getting the keystore file name + String keyStoreFileName = this.switchboard.getConfig("keyStore", ""); + if (keyStoreFileName.length() == 0) return null; + + // getting the keystore pwd + String keyStorePwd = this.switchboard.getConfig("keyStorePassword", ""); + if (keyStorePwd.length() == 0) return null; + + // get the ssl context + try { + this.log.logInfo("Initializing SSL support ..."); + + // creating a new keystore instance of type (java key store) + this.log.logFine("Initializing keystore ..."); + KeyStore ks = KeyStore.getInstance("JKS"); + + // loading keystore data from file + this.log.logFine("Loading keystore file " + keyStoreFileName); + FileInputStream stream = new FileInputStream(keyStoreFileName); + ks.load(stream, keyStorePwd.toCharArray()); + + // creating a keystore factory + this.log.logFine("Initializing key manager factory ..."); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks,keyStorePwd.toCharArray()); + + // initializing the ssl context + this.log.logFine("Initializing SSL context ..."); + SSLContext sslcontext = SSLContext.getInstance("TLS"); + sslcontext.init(kmf.getKeyManagers(), null, null); + + SSLSocketFactory factory = sslcontext.getSocketFactory(); + this.log.logInfo("SSL support initialized successfully"); + return factory; + } catch (Exception e) { + String errorMsg = "FATAL ERROR: Unable to initialize the SSL Socket factory. " + e.getMessage(); + this.log.logSevere(errorMsg); + System.out.println(errorMsg); + System.exit(0); + return null; + } + } + + public Socket negotiateSSL(Socket sock) throws Exception { + + SSLSocket sslsock; + + try { + sslsock=(SSLSocket)this.sslSocketFactory.createSocket( + sock, + sock.getInetAddress().getHostName(), + sock.getPort(), + true); + + sslsock.addHandshakeCompletedListener( + new HandshakeCompletedListener() { + public void handshakeCompleted( + HandshakeCompletedEvent event) { + System.out.println("Handshake finished!"); + System.out.println( + "\t CipherSuite:" + event.getCipherSuite()); + System.out.println( + "\t SessionId " + event.getSession()); + System.out.println( + "\t PeerHost " + event.getSession().getPeerHost()); + } + } + ); + + sslsock.setUseClientMode(false); + String[] suites = sslsock.getSupportedCipherSuites(); + sslsock.setEnabledCipherSuites(suites); +// start handshake + sslsock.startHandshake(); + + String cipherSuite = sslsock.getSession().getCipherSuite(); + + return sslsock; + } catch (Exception e) { + throw e; + } + } } diff --git a/source/de/anomic/server/serverCoreSocket.java b/source/de/anomic/server/serverCoreSocket.java new file mode 100644 index 000000000..69d050241 --- /dev/null +++ b/source/de/anomic/server/serverCoreSocket.java @@ -0,0 +1,197 @@ +//robotsParser.java +//------------------------------------- +//part of YACY +// +//(C) 2006 by Martin Thelian +// +//last change: $LastChangedDate: 2006-05-12 16:35:56 +0200 (Fr, 12 Mai 2006) $ by $LastChangedBy: theli $ +//Revision: $LastChangedRevision: 2086 $ +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with this program; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//Using this software in any meaning (reading, learning, copying, compiling, +//running) means that you agree that the Author(s) is (are) not responsible +//for cost, loss of data or any harm that may be caused directly or indirectly +//by usage of this softare or this documentation. The usage of this software +//is on your own risk. The installation and usage (starting/running) of this +//software may allow other people or application to access your computer and +//any attached devices and is highly dependent on the configuration of the +//software which must be done by the user of the software; the author(s) is +//(are) also not responsible for proper configuration and usage of the +//software, even if provoked by documentation provided together with +//the software. +// +//Any changes to this file according to the GPL as documented in the file +//gpl.txt aside this file in the shipment you received can be done to the +//lines that follows this copyright notice here, but changes must not be +//done inside the copyright notive above. A re-distribution must contain +//the intact and unchanged copyright notice. +//Contributions and changes to the program code must be marked as such. + +package de.anomic.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +public class serverCoreSocket extends Socket { + + private PushbackInputStream input = null; + private Socket sock = null; + private boolean isSSL = false; + private String sslType = null; + + public serverCoreSocket(Socket sock) throws IOException { + this.sock = sock; + + // determine the socket type + detectSSL(); + } + + public boolean isSSL() { + return this.isSSL; + } + + private void detectSSL() throws IOException { + InputStream in = getInputStream(); + + // read the first 6 bytes to determine the protocol type + byte[] preRead = new byte[5]; + int read = in.read(preRead); + + int idx = 0; + if ((preRead[0] & 0xFF) == 22) { + // we have detected the ContentType field. + // 22 means "handshake" + idx = 1; + } else { + // SSL messages have two preceding bytes + // byte nr 3 specifies the handshake type + // 3 means "ClientHello" + int handshakeType = preRead[2] & 0x00FF; + if (handshakeType == 1) this.isSSL = true; + idx = 3; + } + + // determine the protocol version + if (preRead[idx] == 3 && (preRead[idx+1] >= 0 && preRead[idx+1] <= 2)) { + switch (preRead[idx+1]) { + case 0: + this.sslType = "SSL_3"; + break; + case 1: + this.sslType = "TLS_1"; + break; + case 2: + this.sslType = "TLS_1_1"; + break; + } + this.isSSL = true; + } else { + // maybe SSL_2, but we can not be sure + } + + // unread pre read bytes + ((PushbackInputStream) in).unread(preRead,0,read); + } + + public InetAddress getInetAddress() { + return this.sock.getInetAddress(); + } + + public InetAddress getLocalAddress() { + return this.sock.getLocalAddress(); + } + + public int getPort() { + return this.sock.getPort(); + } + + public int getLocalPort() { + return this.sock.getLocalPort(); + } + + public SocketAddress getRemoteSocketAddress() { + return this.sock.getRemoteSocketAddress(); + } + + public SocketAddress getLocalSocketAddress() { + return this.sock.getLocalSocketAddress(); + } + + public SocketChannel getChannel() { + return this.sock.getChannel(); + } + + + public InputStream getInputStream() throws IOException { + if (this.input == null) { + this.input = new PushbackInputStream(this.sock.getInputStream(),100); + } + return this.input; + } + + public OutputStream getOutputStream() throws IOException { + return this.sock.getOutputStream(); + } + + + public synchronized void close() throws IOException { + this.sock.close(); + } + + public void shutdownInput() throws IOException { + this.sock.shutdownInput(); + } + + public void shutdownOutput() throws IOException { + this.sock.shutdownOutput(); + } + + public String toString() { + return this.sock.toString(); + } + + public boolean isConnected() { + return this.sock.isConnected(); + } + + public boolean isBound() { + return this.sock.isBound(); + } + + public boolean isClosed() { + return this.sock.isClosed(); + } + + public boolean isInputShutdown() { + return this.sock.isInputShutdown(); + } + + public boolean isOutputShutdown() { + return this.sock.isOutputShutdown(); + } + + public synchronized void setSoTimeout(int timeout) throws SocketException { + this.sock.setSoTimeout(timeout); + } + +} diff --git a/yacy.init b/yacy.init index e917e995f..6456e8875 100644 --- a/yacy.init +++ b/yacy.init @@ -15,6 +15,21 @@ # 192.168.0.1:8080 port = 8080 +# SSL support: +# With this you can access your peer using https://localhost:8080 +# +# For testing purposes, you can create a keystore with a self-signed certificate, +# using the following command: +# +# |> keytool -keystore mySrvKeystore -genkey -keyalg RSA -alias mycert +# +# Then configure the keyStore properties accordingly, e.g. +# keyStore = c:/yacy/DATA/SETTINGS/mySrvKeystore +# keyStorePassword = mypwd +# +keyStore = +keyStorePassword = + # peer-to-peer construction for distributed search # we have several stages: # 1st: a file within every distribution that has a list of URLs: