From cf114fc7af333538faf6558d7aca0a75d26fae07 Mon Sep 17 00:00:00 2001 From: Sheng Yang Date: Thu, 28 Apr 2011 16:39:21 -0700 Subject: [PATCH] Enable SSL for mgmt servers and agents The port remains 8250. The keystore saved at /etc/cloud/management/cloud.keystore. We also include one fail-safe keystore/certificate for fallback if we are unable to generate certificate and keystore. If we use fail-safe keystore, a warning and calltrace would be show. Notice you need to upgrade agent, as well as systemVM's images. --- agent/src/com/cloud/agent/Agent.java | 32 +-- build/build-cloud.xml | 8 +- .../cloud/server/ConfigurationServerImpl.java | 134 ++++++++++++ utils/certs/cloud.keystore | Bin 0 -> 1210 bytes utils/src/com/cloud/utils/nio/Link.java | 78 +++++-- utils/src/com/cloud/utils/nio/NioClient.java | 37 +++- .../com/cloud/utils/nio/NioConnection.java | 199 +++++++++++++++++- .../com/cloud/utils/nio/TrustAllManager.java | 43 ++++ 8 files changed, 484 insertions(+), 47 deletions(-) create mode 100644 utils/certs/cloud.keystore create mode 100644 utils/src/com/cloud/utils/nio/TrustAllManager.java diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java index ffc85dde3f6..43e0ad02f54 100755 --- a/agent/src/com/cloud/agent/Agent.java +++ b/agent/src/com/cloud/agent/Agent.java @@ -366,28 +366,30 @@ public class Agent implements HandlerFactory, IAgentControl { _resource.disconnected(); - while (true) { + int inProgress = 0; + do { _shell.getBackoffAlgorithm().waitBeforeRetry(); s_logger.info("Lost connection to the server. Reconnecting...."); - int inProgress = 0; - if ((inProgress = _inProgress.get()) > 0) { + inProgress = _inProgress.get(); + if (inProgress > 0) { s_logger.info("Cannot connect because we still have " + inProgress + " commands in progress."); - continue; } + } while (inProgress > 0); - try { - final SocketChannel sch = SocketChannel.open(); - sch.configureBlocking(false); - sch.connect(link.getSocketAddress()); - - link.connect(sch); - return; - } catch(final IOException e) { - s_logger.error("Unable to establish connection with the server", e); - } - } + _connection.stop(); + _connection = new NioClient( + "Agent", + _shell.getHost(), + _shell.getPort(), + _shell.getWorkers(), + this); + do { + s_logger.info("Reconnecting..."); + _connection.start(); + _shell.getBackoffAlgorithm().waitBeforeRetry(); + } while (!_connection.isStartup()); } public void processStartupAnswer(Answer answer, Response response, Link link) { diff --git a/build/build-cloud.xml b/build/build-cloud.xml index fd2c48cfacb..321b4ebc415 100755 --- a/build/build-cloud.xml +++ b/build/build-cloud.xml @@ -174,7 +174,13 @@ - + + + + + + + diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 6de46dc3a37..0519b3ea952 100644 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -25,9 +25,13 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.math.BigInteger; +import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.net.UnknownHostException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.PreparedStatement; @@ -36,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.UUID; +import java.util.regex.Pattern; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -82,6 +87,7 @@ import com.cloud.utils.PropertiesUtil; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.db.DB; import com.cloud.utils.db.Transaction; +import com.cloud.utils.encoding.Base64.OutputStream; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.utils.script.Script; @@ -221,6 +227,9 @@ public class ConfigurationServerImpl implements ConfigurationServer { } } } + + // keystore for SSL/TLS connection + updateSSLKeystore(); // store the public and private keys in the database updateKeyPairs(); @@ -369,6 +378,131 @@ public class ConfigurationServerImpl implements ConfigurationServer { } } + private String getBase64Keystore(String keystorePath) throws IOException { + byte[] storeBytes = new byte[4094]; + int len = 0; + try { + len = new FileInputStream(keystorePath).read(storeBytes); + } catch (EOFException e) { + } catch (Exception e) { + throw new IOException("Cannot read the generated keystore file"); + } + if (len > 3000) { // Base64 codec would enlarge data by 1/3, and we have 4094 bytes in database entry at most + throw new IOException("KeyStore is too big for database! Length " + len); + } + + byte[] encodeBytes = new byte[len]; + System.arraycopy(storeBytes, 0, encodeBytes, 0, len); + + return new String(Base64.encodeBase64(encodeBytes)); + } + + @DB + private void createSSLKeystoreDBEntry(String encodedKeystore) throws IOException { + String insertSQL = "INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) " + + "VALUES ('Hidden','DEFAULT', 'management-server','ssl.keystore', '" + encodedKeystore +"','SSL Keystore for the management servers')"; + Transaction txn = Transaction.currentTxn(); + try { + PreparedStatement stmt = txn.prepareAutoCloseStatement(insertSQL); + stmt.executeUpdate(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("SSL Keystore inserted into database"); + } + } catch (SQLException ex) { + s_logger.error("SQL of the SSL Keystore failed", ex); + throw new IOException("SQL of the SSL Keystore failed"); + } + } + + private void generateDefaultKeystore(String keystorePath) throws IOException { + String cn = "Cloudstack User"; + String ou; + + try { + ou = InetAddress.getLocalHost().getCanonicalHostName(); + String[] group = ou.split("\\."); + + // Simple check to see if we got IP Address... + boolean isIPAddress = Pattern.matches("[0-9]$", group[group.length - 1]); + if (isIPAddress) { + ou = "cloud.com"; + } else { + ou = group[group.length - 1]; + for (int i = group.length - 2; i >= 0 && i >= group.length - 3; i--) + ou = group[i] + "." + ou; + } + } catch (UnknownHostException ex) { + s_logger.info("Fail to get user's domain name. Would use cloud.com. ", ex); + ou = "cloud.com"; + } + + String o = ou; + String c = "Unknown"; + String dname = "cn=" + cn + ", ou=" + ou +", o=" + o + ", c=" + c; + Script script = new Script(true, "keytool", 5000, null); + script.add("-genkey"); + script.add("-keystore", keystorePath); + script.add("-storepass", "vmops.com"); + script.add("-keypass", "vmops.com"); + script.add("-validity", "3650"); + script.add("-dname", dname); + String result = script.execute(); + if (result != null) { + throw new IOException("Fail to generate certificate!"); + } + } + + protected void updateSSLKeystore() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Processing updateSSLKeyStore"); + } + + String dbString = _configDao.getValue("ssl.keystore"); + String keystorePath = "/etc/cloud/management/cloud.keystore"; + File keystoreFile = new File(keystorePath); + boolean dbExisted = (dbString != null && !dbString.isEmpty()); + + try { + if (!dbExisted) { + if (!keystoreFile.exists()) { + generateDefaultKeystore(keystorePath); + s_logger.info("Generated SSL keystore."); + } + String base64Keystore = getBase64Keystore(keystorePath); + createSSLKeystoreDBEntry(base64Keystore); + s_logger.info("Stored SSL keystore to database."); + } else if (keystoreFile.exists()) { // and dbExisted + // Check if they are the same one, otherwise override with local keystore + String base64Keystore = getBase64Keystore(keystorePath); + if (base64Keystore.compareTo(dbString) != 0) { + _configDao.update("ssl.keystore", base64Keystore); + s_logger.info("Updated database keystore with local one."); + } + } else { // !keystoreFile.exists() and dbExisted + // Export keystore to local file + byte[] storeBytes = Base64.decodeBase64(dbString); + try { + String tmpKeystorePath = "/tmp/tmpkey"; + FileOutputStream fo = new FileOutputStream(tmpKeystorePath); + fo.write(storeBytes); + fo.close(); + Script script = new Script(true, "cp", 5000, null); + script.add(tmpKeystorePath); + script.add(keystorePath); + String result = script.execute(); + if (result != null) { + throw new IOException(); + } + } catch (Exception e) { + throw new IOException("Fail to create keystore file!", e); + } + s_logger.info("Stored database keystore to local."); + } + } catch (Exception ex) { + s_logger.warn("Would use fail-safe keystore to continue.", ex); + } + } + @DB protected void updateKeyPairs() { // Grab the SSH key pair and insert it into the database, if it is not present diff --git a/utils/certs/cloud.keystore b/utils/certs/cloud.keystore new file mode 100644 index 0000000000000000000000000000000000000000..76bd6855eb90bc12f98371b14dd8f5218aaf6126 GIT binary patch literal 1210 zcmezO_TO6u1_mY|W&~r_+{*0KN+3_aM8{|sknT5VV(d2HW8>0hV`O2}ViIIzWMyD! zVl4l7^OW(y8ns7>IdA%Q-(KFb^>5=uRhGp$m+}vtnZ4<=(~`-?^QF=|EfT|eA{OrD zdC^zKektq9{>{_Y#y?FCz1#oJ;Jhlw#9d-<_joKhvM-`l_M6ha#X@iHF1ek5XOib< zg@$Y;HAjcz7);!pIh>rX$qLn!jpqYZo0#h>+Wzv;N*(xrE|H*Skqt$6Kn@@rG` zr(Tk7kEfWqZaulX(S7&yWAC@}a4w$s@=Uao32(Z~+lg-beWUpf{g1y~@2jWp81#>6 z)vXJFVg$u5agX2G5cDdQU_za!Tftg7ZuRR~Oe&v4<`>K7vN8xCqcW%hE=oud0QxlUHxH2&Sc zj+&>Zw%?X^=Q(h2mZn#)i_wYzA$Dc+^snWUUa$1n!NNWT5=#+!rUsV4czbHl#Pkq| z=PzJpVq{`s@m;U_!GN2MU8~LGoCOOrD}zCZp#U)Um_u3EgxN#$vh(uG^9+Ry1VAEO z!ko!D`K2j($@#f3F>Yir2?H^ZI%Z*hXOOz$lEma}h0x;EA_F;bUPD6z6C(pd3nNPd zlPECP+|UxrrID>ojN5^!Q3vKdMj%I?sj-paZ+&p1Y^jiD(0#8tdT*XQ|0cLS$mYEZ z!(lo5hHZgrnXa)8N`b#4ZF?hr?LYc;m%+EwvqN*PH(EEyn&f{h4KR>o&nJ7QY@8)lpkIEfpG7S-q5}RoBt@QIc?ZoR^?>{Lq zOU}LM!FZfWlwo>8K)Bg6Z4U5WI6R>&^U*s#S@tWf!dkA|4=_Vb)ZJtvkuv=OW7 z4yozYc(8b#oc88rr4#cO*K_|;G)?;WJE@9M#rn*WkNZ{xPUu|GeE(t8)A?+Zy3^ts zszX&%7=5h&o%hsYZfs#-YHY}j_I8vC-}Yq5tVxz{7<=q~SkL($6#A&{%wF@0DhK3> z8+U69>2FooKFdMBY28k9Ce>%(!{UGX&wZ(G9)C5seMw)Y_Wb}Or**4O*p>$sKaJ~r z8NVc$SI#_+$NS8I4b8m9hySUBOY}(iefS=7aTWi^CZiWB2m3DxwxsM^WoEJ}10_u| z>oXYWFp04B1pOD-^6lQEtibBuF&jB2$ayC-iEw31wfOyP!^UYR_I@#TFSfg9a^{V9 YI7jmBIRQsBuFWc4^1{L*YW?F<00%_(IsgCw literal 0 HcmV?d00001 diff --git a/utils/src/com/cloud/utils/nio/Link.java b/utils/src/com/cloud/utils/nio/Link.java index 4cf0a6a01b8..f285eeda7d2 100755 --- a/utils/src/com/cloud/utils/nio/Link.java +++ b/utils/src/com/cloud/utils/nio/Link.java @@ -28,6 +28,11 @@ import java.nio.channels.SocketChannel; import java.nio.channels.WritableByteChannel; import java.util.concurrent.ConcurrentLinkedQueue; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + import org.apache.log4j.Logger; /** @@ -44,6 +49,8 @@ public class Link { private Object _attach; private boolean _readSize; + private SSLEngine _sslEngine; + public Link(InetSocketAddress addr, NioConnection connection) { _addr = addr; _connection = connection; @@ -70,6 +77,10 @@ public class Link { _key = key; } + public void setSSLEngine(SSLEngine sslEngine) { + _sslEngine = sslEngine; + } + /** * Static methods for reading from a channel in case * you need to add a client that doesn't require nio. @@ -190,8 +201,24 @@ public class Link { } _readBuffer.flip(); - byte[] result = new byte[_readBuffer.limit()]; - _readBuffer.get(result); + + ByteBuffer appBuf; + + SSLSession sslSession = _sslEngine.getSession(); + SSLEngineResult engResult; + + //TODO may need to adjust the buffer size + appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + engResult = _sslEngine.unwrap(_readBuffer, appBuf); + if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && + engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && + engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("SSL: SSLEngine return bad result! " + engResult); + } + + byte[] result = new byte[appBuf.position()]; + appBuf.flip(); + appBuf.get(result); _readBuffer.clear(); _readSize = true; @@ -258,29 +285,46 @@ public class Link { return true; } - data[0].mark(); - int remaining = data[0].getInt() + 4; - data[0].reset(); - - if (remaining > 65535) { - throw new IOException("Fail to send a too big packet! Size: " + remaining); + ByteBuffer pkgBuf; + SSLSession sslSession = _sslEngine.getSession(); + SSLEngineResult engResult; + + ByteBuffer headBuf = ByteBuffer.allocate(4); + ByteBuffer[] raw_data = new ByteBuffer[data.length - 1]; + System.arraycopy(data, 1, raw_data, 0, data.length - 1); + + pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + engResult = _sslEngine.wrap(raw_data, pkgBuf); + if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && + engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && + engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("SSL: SSLEngine return bad result! " + engResult); } - - while (remaining > 0) { + + int dataRemaining = pkgBuf.position(); + int headRemaining = 4; + pkgBuf.flip(); + headBuf.putInt(dataRemaining); + headBuf.flip(); + + while (headRemaining > 0) { if (s_logger.isTraceEnabled()) { - s_logger.trace("Writing " + remaining); + s_logger.trace("Writing Header " + headRemaining); } - long count = ch.write(data); - remaining -= count; + long count = ch.write(headBuf); + headRemaining -= count; + } + while (dataRemaining > 0) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Writing Data " + dataRemaining); + } + long count = ch.write(pkgBuf); + dataRemaining -= count; } } return false; } - public synchronized void connect(SocketChannel ch) { - _connection.register(SelectionKey.OP_CONNECT, ch, this); - } - public InetSocketAddress getSocketAddress() { return _addr; } diff --git a/utils/src/com/cloud/utils/nio/NioClient.java b/utils/src/com/cloud/utils/nio/NioClient.java index 544a53a2708..8ddabc267a5 100755 --- a/utils/src/com/cloud/utils/nio/NioClient.java +++ b/utils/src/com/cloud/utils/nio/NioClient.java @@ -23,6 +23,9 @@ import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLContext; + import org.apache.log4j.Logger; public class NioClient extends NioConnection { @@ -45,23 +48,49 @@ public class NioClient extends NioConnection { _selector = Selector.open(); SocketChannel sch = SocketChannel.open(); - sch.configureBlocking(false); + sch.configureBlocking(true); s_logger.info("Connecting to " + _host + ":" + _port); if(_bindAddress != null) { s_logger.info("Binding outbound interface at " + _bindAddress); InetSocketAddress addr = new InetSocketAddress(_bindAddress, 0); - sch.socket().bind(addr); + sch.socket().bind(addr); } InetSocketAddress addr = new InetSocketAddress(_host, _port); - sch.connect(addr); + try { + sch.connect(addr); + } catch (IOException e) { + _selector.close(); + throw e; + } + + SSLEngine sslEngine = null; + try { + // Begin SSL handshake in BLOCKING mode + sch.configureBlocking(true); + + SSLContext sslContext = initSSLContext(true); + sslEngine = sslContext.createSSLEngine(_host, _port); + sslEngine.setUseClientMode(true); + + doHandshake(sch, sslEngine, true); + s_logger.info("SSL: Handshake done"); + } catch (Exception e) { + throw new IOException("SSL: Fail to init SSL! " + e); + } + sch.configureBlocking(false); Link link = new Link(addr, this); - SelectionKey key = sch.register(_selector, SelectionKey.OP_CONNECT); + link.setSSLEngine(sslEngine); + SelectionKey key = sch.register(_selector, SelectionKey.OP_READ); link.setKey(key); key.attach(link); + // Notice we've already connected due to the handshake, so let's get the + // remaining task done + Task task = _factory.create(Task.Type.CONNECT, link, null); + _executor.execute(task); } @Override diff --git a/utils/src/com/cloud/utils/nio/NioConnection.java b/utils/src/com/cloud/utils/nio/NioConnection.java index a2320cbaaf3..264fce8d4f0 100755 --- a/utils/src/com/cloud/utils/nio/NioConnection.java +++ b/utils/src/com/cloud/utils/nio/NioConnection.java @@ -17,15 +17,20 @@ */ package com.cloud.utils.nio; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; +import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; +import java.security.KeyStore; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -35,10 +40,19 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + import org.apache.log4j.Logger; import com.cloud.utils.concurrency.NamedThreadFactory; - +import com.cloud.utils.nio.TrustAllManager; /** * NioConnection abstracts the NIO socket operations. The Java implementation @@ -51,6 +65,7 @@ public abstract class NioConnection implements Runnable { protected Selector _selector; protected Thread _thread; protected boolean _isRunning; + protected boolean _isStartup; protected int _port; protected List _todos; protected HandlerFactory _factory; @@ -73,6 +88,16 @@ public abstract class NioConnection implements Runnable { _thread = new Thread(this, _name + "-Selector"); _isRunning = true; _thread.start(); + // Wait until we got init() done + synchronized(_thread) { + try { + _thread.wait(); + } catch (InterruptedException e) { + if (s_logger.isTraceEnabled()) { + s_logger.info("Interrupted start thread ", e); + } + } + } } public void stop() { @@ -87,14 +112,25 @@ public abstract class NioConnection implements Runnable { return _thread.isAlive(); } + public boolean isStartup() { + return _isStartup; + } + public void run() { - try { - init(); - } catch (IOException e) { - s_logger.error("Unable to initialize the threads.", e); - return; - } - + synchronized(_thread) { + try { + init(); + } catch (ConnectException e) { + s_logger.error("Unable to connect to remote"); + return; + } catch (IOException e) { + s_logger.error("Unable to initialize the threads.", e); + return; + } + _isStartup = true; + _thread.notifyAll(); + } + while (_isRunning) { try { _selector.select(); @@ -144,22 +180,165 @@ public abstract class NioConnection implements Runnable { abstract void init() throws IOException; abstract void registerLink(InetSocketAddress saddr, Link link); abstract void unregisterLink(InetSocketAddress saddr); - + + protected SSLContext initSSLContext(boolean isClient) throws Exception { + SSLContext sslContext = null; + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + KeyStore ks = KeyStore.getInstance("JKS"); + TrustManager[] tms; + + if (!isClient) { + char[] passphrase = "vmops.com".toCharArray(); + String keystorePath = "/etc/cloud/management/cloud.keystore"; + if (new File(keystorePath).exists()) { + ks.load(new FileInputStream(keystorePath), passphrase); + } else { + s_logger.warn("SSL: Fail to find the generated keystore. Loading fail-safe one to continue."); + ks.load(NioConnection.class.getResourceAsStream("/cloud.keystore"), passphrase); + } + kmf.init(ks, passphrase); + tmf.init(ks); + tms = tmf.getTrustManagers(); + } else { + ks.load(null, null); + kmf.init(ks, null); + tms = new TrustManager[1]; + tms[0] = new TrustAllManager(); + } + + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tms, null); + s_logger.info("SSL: SSLcontext has been initialized"); + + return sslContext; + } + + protected void doHandshake(SocketChannel ch, SSLEngine sslEngine, + boolean isClient) throws IOException { + s_logger.info("SSL: begin Handshake, isClient: " + isClient); + + SSLEngineResult engResult; + SSLSession sslSession = sslEngine.getSession(); + HandshakeStatus hsStatus; + ByteBuffer in_pkgBuf = + ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + ByteBuffer in_appBuf = + ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + ByteBuffer out_pkgBuf = + ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + ByteBuffer out_appBuf = + ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + int count; + + if (isClient) { + hsStatus = SSLEngineResult.HandshakeStatus.NEED_WRAP; + } else { + hsStatus = SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + + while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { + if (s_logger.isTraceEnabled()) { + s_logger.info("SSL: Handshake status " + hsStatus); + } + engResult = null; + if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { + out_pkgBuf.clear(); + out_appBuf.clear(); + out_appBuf.put("Hello".getBytes()); + engResult = sslEngine.wrap(out_appBuf, out_pkgBuf); + out_pkgBuf.flip(); + int remain = out_pkgBuf.limit(); + while (remain != 0) { + remain -= ch.write(out_pkgBuf); + if (remain < 0) { + throw new IOException("Too much bytes sent?"); + } + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { + in_appBuf.clear(); + // One packet may contained multiply operation + if (in_pkgBuf.position() == 0 || !in_pkgBuf.hasRemaining()) { + in_pkgBuf.clear(); + count = ch.read(in_pkgBuf); + if (count == -1) { + throw new IOException("Connection closed with -1 on reading size."); + } + in_pkgBuf.flip(); + } + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); + ByteBuffer tmp_pkgBuf = + ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + while (engResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { + // We need more packets to complete this operation + if (s_logger.isTraceEnabled()) { + s_logger.info("SSL: Buffer overflowed, getting more packets"); + } + tmp_pkgBuf.clear(); + count = ch.read(tmp_pkgBuf); + tmp_pkgBuf.flip(); + + in_pkgBuf.mark(); + in_pkgBuf.position(in_pkgBuf.limit()); + in_pkgBuf.limit(in_pkgBuf.limit() + tmp_pkgBuf.limit()); + in_pkgBuf.put(tmp_pkgBuf); + in_pkgBuf.reset(); + + in_appBuf.clear(); + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { + Runnable run; + while ((run = sslEngine.getDelegatedTask()) != null) { + if (s_logger.isTraceEnabled()) { + s_logger.info("SSL: Running delegated task!"); + } + run.run(); + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + throw new IOException("NOT a handshaking!"); + } + if (engResult != null && engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("Fail to handshake! " + engResult.getStatus()); + } + if (engResult != null) + hsStatus = engResult.getHandshakeStatus(); + else + hsStatus = sslEngine.getHandshakeStatus(); + } + } protected void accept(SelectionKey key) throws IOException { ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); Socket socket = socketChannel.socket(); - socketChannel.configureBlocking(false); socket.setKeepAlive(true); if (s_logger.isTraceEnabled()) { s_logger.trace("Connection accepted for " + socket); } + // Begin SSL handshake in BLOCKING mode + socketChannel.configureBlocking(true); + + SSLEngine sslEngine = null; + try { + SSLContext sslContext = initSSLContext(false); + sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(false); + sslEngine.setNeedClientAuth(false); + + doHandshake(socketChannel, sslEngine, false); + s_logger.info("SSL: Handshake done"); + } catch (Exception e) { + throw new IOException("SSL: Fail to init SSL! " + e); + } + + socketChannel.configureBlocking(false); InetSocketAddress saddr = (InetSocketAddress)socket.getRemoteSocketAddress(); Link link = new Link(saddr, this); + link.setSSLEngine(sslEngine); link.setKey(socketChannel.register(key.selector(), SelectionKey.OP_READ, link)); Task task = _factory.create(Task.Type.CONNECT, link, null); registerLink(saddr, link); diff --git a/utils/src/com/cloud/utils/nio/TrustAllManager.java b/utils/src/com/cloud/utils/nio/TrustAllManager.java new file mode 100644 index 00000000000..8462e806500 --- /dev/null +++ b/utils/src/com/cloud/utils/nio/TrustAllManager.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2011 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It 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 3 of the License, or 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, see . + * + */ + +package com.cloud.utils.nio; + +public class TrustAllManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) { + return true; + } + + public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) { + return true; + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + throws java.security.cert.CertificateException { + return; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + throws java.security.cert.CertificateException { + return; + } +}