2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.nio.ByteBuffer;
15 import java.nio.channels.AsynchronousCloseException;
16 import java.nio.channels.SelectionKey;
17 import java.nio.channels.Selector;
18 import java.nio.channels.SocketChannel;
19 import java.security.KeyStore;
20 import java.security.SecureRandom;
21 import java.util.List;
23 import javax.net.ssl.KeyManagerFactory;
24 import javax.net.ssl.SSLContext;
25 import javax.net.ssl.SSLEngine;
26 import javax.net.ssl.SSLEngineResult;
27 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
28 import javax.net.ssl.SSLSession;
29 import javax.net.ssl.TrustManagerFactory;
31 import org.opendaylight.controller.protocol_plugin.openflow.core.IMessageReadWrite;
32 import org.openflow.protocol.OFMessage;
33 import org.openflow.protocol.factory.BasicFactory;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * This class implements methods to read/write messages over an established
39 * socket channel. The data exchange is encrypted/decrypted by SSLEngine.
41 public class SecureMessageReadWriteService implements IMessageReadWrite {
42 private static final Logger logger = LoggerFactory
43 .getLogger(SecureMessageReadWriteService.class);
45 private Selector selector;
46 private SocketChannel socket;
47 private BasicFactory factory;
49 private SSLEngine sslEngine;
50 private SSLEngineResult sslEngineResult; // results from sslEngine last operation
51 private ByteBuffer myAppData; // clear text message to be sent
52 private ByteBuffer myNetData; // encrypted message to be sent
53 private ByteBuffer peerAppData; // clear text message received from the
55 private ByteBuffer peerNetData; // encrypted message from the switch
56 private FileInputStream kfd = null, tfd = null;
57 private final String keyStoreFileDefault = "./configuration/tlsKeyStore";
58 private final String trustStoreFileDefault = "./configuration/tlsTrustStore";
59 private final String keyStorePasswordPropName = "controllerKeyStorePassword";
60 private final String trustStorePasswordPropName = "controllerTrustStorePassword";
61 private static String keyStorePassword = null;
62 private static String trustStorePassword = null;
64 public SecureMessageReadWriteService(SocketChannel socket, Selector selector)
67 this.selector = selector;
68 this.factory = new BasicFactory();
71 createSecureChannel(socket);
72 createBuffers(sslEngine);
73 } catch (Exception e) {
74 logger.warn("Failed to setup TLS connection {} {}", socket, e);
81 * Bring up secure channel using SSL Engine
87 private void createSecureChannel(SocketChannel socket) throws Exception {
88 String keyStoreFile = System.getProperty("controllerKeyStore");
89 String trustStoreFile = System.getProperty("controllerTrustStore");
90 String keyStorePasswordProp = System.getProperty(keyStorePasswordPropName);
91 String trustStorePasswordProp = System.getProperty(trustStorePasswordPropName);
93 if (keyStoreFile != null) {
94 keyStoreFile = keyStoreFile.trim();
96 keyStoreFile = keyStoreFileDefault;
98 if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
99 throw new FileNotFoundException("TLS KeyStore file not found.");
102 if ((keyStorePassword == null) || ((keyStorePasswordProp != null) && !keyStorePasswordProp.isEmpty())) {
103 keyStorePassword = keyStorePasswordProp;
105 if (keyStorePassword != null) {
106 keyStorePassword = keyStorePassword.trim();
107 System.setProperty(keyStorePasswordPropName, "");
109 if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
110 throw new FileNotFoundException("TLS KeyStore Password not provided.");
112 if (trustStoreFile != null) {
113 trustStoreFile = trustStoreFile.trim();
115 trustStoreFile = trustStoreFileDefault;
117 if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
118 throw new FileNotFoundException("TLS TrustStore file not found");
121 if ((trustStorePassword == null) || ((trustStorePasswordProp != null) && !trustStorePasswordProp.isEmpty())) {
122 trustStorePassword = trustStorePasswordProp;
124 if (trustStorePassword != null) {
125 trustStorePassword = trustStorePassword.trim();
126 System.setProperty(trustStorePasswordPropName, "");
128 if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
129 throw new FileNotFoundException("TLS TrustStore Password not provided.");
132 KeyStore ks = KeyStore.getInstance("JKS");
133 KeyStore ts = KeyStore.getInstance("JKS");
134 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
135 TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
136 kfd = new FileInputStream(keyStoreFile);
137 tfd = new FileInputStream(trustStoreFile);
138 ks.load(kfd, keyStorePassword.toCharArray());
139 ts.load(tfd, trustStorePassword.toCharArray());
140 kmf.init(ks, keyStorePassword.toCharArray());
143 SecureRandom random = new SecureRandom();
146 SSLContext sslContext = SSLContext.getInstance("TLS");
147 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random);
148 sslEngine = sslContext.createSSLEngine();
149 sslEngine.setUseClientMode(false);
150 sslEngine.setNeedClientAuth(true);
151 sslEngine.setEnabledCipherSuites(new String[] {
152 "SSL_RSA_WITH_RC4_128_MD5",
153 "SSL_RSA_WITH_RC4_128_SHA",
154 "TLS_RSA_WITH_AES_128_CBC_SHA",
155 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
156 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
157 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
158 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
159 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
160 "SSL_RSA_WITH_DES_CBC_SHA",
161 "SSL_DHE_RSA_WITH_DES_CBC_SHA",
162 "SSL_DHE_DSS_WITH_DES_CBC_SHA",
163 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
164 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
165 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
166 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
167 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"});
169 // Do initial handshake
170 doHandshake(socket, sslEngine);
172 this.socket.register(this.selector, SelectionKey.OP_READ);
176 * Sends the OF message out over the socket channel. The message is
177 * encrypted by SSL Engine.
180 * OF message to be sent
184 public void asyncSend(OFMessage msg) throws Exception {
185 synchronized (myAppData) {
186 int msgLen = msg.getLengthU();
187 if (myAppData.remaining() < msgLen) {
188 // increase the buffer size so that it can contain this message
189 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
190 .capacity() + msgLen);
192 newBuffer.put(myAppData);
193 myAppData = newBuffer;
196 synchronized (myAppData) {
197 msg.writeTo(myAppData);
199 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
200 logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
201 runDelegatedTasks(sslEngineResult, sslEngine);
203 if (!socket.isOpen()) {
208 socket.write(myNetData);
209 if (myNetData.hasRemaining()) {
215 if (myAppData.hasRemaining()) {
217 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
220 this.socket.register(this.selector, SelectionKey.OP_READ, this);
223 logger.trace("Message sent: {}", msg);
228 * Resumes sending the remaining messages in the outgoing buffer
233 public void resumeSend() throws Exception {
234 synchronized (myAppData) {
236 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
237 logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
238 runDelegatedTasks(sslEngineResult, sslEngine);
240 if (!socket.isOpen()) {
245 socket.write(myNetData);
246 if (myNetData.hasRemaining()) {
252 if (myAppData.hasRemaining()) {
254 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
257 this.socket.register(this.selector, SelectionKey.OP_READ, this);
263 * Reads the incoming network data from the socket, decryptes them and then
264 * retrieves the OF messages.
266 * @return list of OF messages
270 public List<OFMessage> readMessages() throws Exception {
271 if (!socket.isOpen()) {
275 List<OFMessage> msgs = null;
279 bytesRead = socket.read(peerNetData);
281 logger.debug("Message read operation failed");
282 throw new AsynchronousCloseException();
287 sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
288 if (peerNetData.hasRemaining()) {
289 peerNetData.compact();
293 logger.trace("sslEngine unwrap result: {}", sslEngineResult);
294 runDelegatedTasks(sslEngineResult, sslEngine);
295 } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
296 && peerNetData.hasRemaining() && (--countDown > 0));
298 if (countDown == 0) {
299 logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
300 peerNetData.position(), peerNetData.limit());
305 msgs = factory.parseMessages(peerAppData);
306 if (peerAppData.hasRemaining()) {
307 peerAppData.compact();
311 } catch (Exception e) {
313 logger.debug("Caught exception: ", e);
316 this.socket.register(this.selector, SelectionKey.OP_READ, this);
322 * If the result indicates that we have outstanding tasks to do, go ahead
323 * and run them in this thread.
325 private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
328 if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
330 while ((runnable = engine.getDelegatedTask()) != null) {
331 logger.debug("\trunning delegated task...");
334 HandshakeStatus hsStatus = engine.getHandshakeStatus();
335 if (hsStatus == HandshakeStatus.NEED_TASK) {
336 throw new Exception("handshake shouldn't need additional tasks");
338 logger.debug("\tnew HandshakeStatus: {}", hsStatus);
342 private void doHandshake(SocketChannel socket, SSLEngine engine)
344 SSLSession session = engine.getSession();
345 ByteBuffer myAppData = ByteBuffer.allocate(session
346 .getApplicationBufferSize());
347 ByteBuffer peerAppData = ByteBuffer.allocate(session
348 .getApplicationBufferSize());
349 ByteBuffer myNetData = ByteBuffer.allocate(session
350 .getPacketBufferSize());
351 ByteBuffer peerNetData = ByteBuffer.allocate(session
352 .getPacketBufferSize());
355 engine.beginHandshake();
356 SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
358 // Process handshaking message
359 while (hs != SSLEngineResult.HandshakeStatus.FINISHED
360 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
363 // Receive handshaking data from peer
364 if (socket.read(peerNetData) < 0) {
365 throw new AsynchronousCloseException();
368 // Process incoming handshaking data
370 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
371 peerNetData.compact();
372 hs = res.getHandshakeStatus();
375 switch (res.getStatus()) {
383 // Empty the local network packet buffer.
386 // Generate handshaking data
387 res = engine.wrap(myAppData, myNetData);
388 hs = res.getHandshakeStatus();
391 switch (res.getStatus()) {
395 // Send the handshaking data to peer
396 while (myNetData.hasRemaining()) {
397 if (socket.write(myNetData) < 0) {
398 throw new AsynchronousCloseException();
406 // Handle blocking tasks
408 while ((runnable = engine.getDelegatedTask()) != null) {
409 logger.debug("\trunning delegated task...");
412 hs = engine.getHandshakeStatus();
413 if (hs == HandshakeStatus.NEED_TASK) {
415 "handshake shouldn't need additional tasks");
417 logger.debug("\tnew HandshakeStatus: {}", hs);
423 private void createBuffers(SSLEngine engine) {
424 SSLSession session = engine.getSession();
425 this.myAppData = ByteBuffer
426 .allocate(session.getApplicationBufferSize());
427 this.peerAppData = ByteBuffer.allocate(session
428 .getApplicationBufferSize() * 20);
429 this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
430 this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize() * 20);
434 public void stop() throws IOException {
435 this.sslEngine = null;
436 this.sslEngineResult = null;
437 this.myAppData = null;
438 this.myNetData = null;
439 this.peerAppData = null;
440 this.peerNetData = null;
442 if (this.kfd != null) {
446 if (this.tfd != null) {