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;
22 import javax.net.ssl.KeyManagerFactory;
23 import javax.net.ssl.SSLContext;
24 import javax.net.ssl.SSLEngine;
25 import javax.net.ssl.SSLEngineResult;
26 import javax.net.ssl.SSLSession;
27 import javax.net.ssl.TrustManagerFactory;
28 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
29 import org.opendaylight.controller.protocol_plugin.openflow.core.IMessageReadWrite;
30 import org.openflow.protocol.OFMessage;
31 import org.openflow.protocol.factory.BasicFactory;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * This class implements methods to read/write messages over an established
37 * socket channel. The data exchange is encrypted/decrypted by SSLEngine.
39 public class SecureMessageReadWriteService implements IMessageReadWrite {
40 private static final Logger logger = LoggerFactory
41 .getLogger(SecureMessageReadWriteService.class);
43 private Selector selector;
44 private SocketChannel socket;
45 private BasicFactory factory;
47 private SSLEngine sslEngine;
48 private SSLEngineResult sslEngineResult; // results from sslEngine last operation
49 private ByteBuffer myAppData; // clear text message to be sent
50 private ByteBuffer myNetData; // encrypted message to be sent
51 private ByteBuffer peerAppData; // clear text message received from the
53 private ByteBuffer peerNetData; // encrypted message from the switch
54 private FileInputStream kfd = null, tfd = null;
56 public SecureMessageReadWriteService(SocketChannel socket, Selector selector)
59 this.selector = selector;
60 this.factory = new BasicFactory();
63 createSecureChannel(socket);
64 createBuffers(sslEngine);
65 } catch (Exception e) {
66 logger.warn("Failed to setup TLS connection {} {}", socket, e);
73 * Bring up secure channel using SSL Engine
79 private void createSecureChannel(SocketChannel socket) throws Exception {
80 String keyStoreFile = System.getProperty("controllerKeyStore");
81 String keyStorePassword = System
82 .getProperty("controllerKeyStorePassword");
83 String trustStoreFile = System.getProperty("controllerTrustStore");
84 String trustStorePassword = System
85 .getProperty("controllerTrustStorePassword");
87 if (keyStoreFile != null) {
88 keyStoreFile = keyStoreFile.trim();
90 if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
91 throw new FileNotFoundException(
92 "controllerKeyStore not specified in ./configuration/config.ini");
94 if (keyStorePassword != null) {
95 keyStorePassword = keyStorePassword.trim();
97 if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
98 throw new FileNotFoundException(
99 "controllerKeyStorePassword not specified in ./configuration/config.ini");
101 if (trustStoreFile != null) {
102 trustStoreFile = trustStoreFile.trim();
104 if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
105 throw new FileNotFoundException(
106 "controllerTrustStore not specified in ./configuration/config.ini");
108 if (trustStorePassword != null) {
109 trustStorePassword = trustStorePassword.trim();
111 if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
112 throw new FileNotFoundException(
113 "controllerTrustStorePassword not specified in ./configuration/config.ini");
116 KeyStore ks = KeyStore.getInstance("JKS");
117 KeyStore ts = KeyStore.getInstance("JKS");
118 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
119 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
120 kfd = new FileInputStream(keyStoreFile);
121 tfd = new FileInputStream(trustStoreFile);
122 ks.load(kfd, keyStorePassword.toCharArray());
123 ts.load(tfd, trustStorePassword.toCharArray());
124 kmf.init(ks, keyStorePassword.toCharArray());
127 SecureRandom random = new SecureRandom();
130 SSLContext sslContext = SSLContext.getInstance("TLS");
131 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random);
132 sslEngine = sslContext.createSSLEngine();
133 sslEngine.setUseClientMode(false);
134 sslEngine.setNeedClientAuth(true);
135 sslEngine.setEnabledCipherSuites(new String[] {
136 "SSL_RSA_WITH_RC4_128_MD5",
137 "SSL_RSA_WITH_RC4_128_SHA",
138 "TLS_RSA_WITH_AES_128_CBC_SHA",
139 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
140 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
141 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
142 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
143 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
144 "SSL_RSA_WITH_DES_CBC_SHA",
145 "SSL_DHE_RSA_WITH_DES_CBC_SHA",
146 "SSL_DHE_DSS_WITH_DES_CBC_SHA",
147 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
148 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
149 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
150 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
151 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"});
153 // Do initial handshake
154 doHandshake(socket, sslEngine);
156 this.socket.register(this.selector, SelectionKey.OP_READ);
160 * Sends the OF message out over the socket channel. The message is
161 * encrypted by SSL Engine.
164 * OF message to be sent
168 public void asyncSend(OFMessage msg) throws Exception {
169 synchronized (myAppData) {
170 int msgLen = msg.getLengthU();
171 if (myAppData.remaining() < msgLen) {
172 // increase the buffer size so that it can contain this message
173 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
174 .capacity() + msgLen);
176 newBuffer.put(myAppData);
177 myAppData = newBuffer;
180 synchronized (myAppData) {
181 msg.writeTo(myAppData);
183 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
184 logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
185 runDelegatedTasks(sslEngineResult, sslEngine);
187 if (!socket.isOpen()) {
192 socket.write(myNetData);
193 if (myNetData.hasRemaining()) {
199 if (myAppData.hasRemaining()) {
201 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
204 this.socket.register(this.selector, SelectionKey.OP_READ, this);
207 logger.trace("Message sent: {}", msg);
212 * Resumes sending the remaining messages in the outgoing buffer
217 public void resumeSend() throws Exception {
218 synchronized (myAppData) {
220 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
221 logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
222 runDelegatedTasks(sslEngineResult, sslEngine);
224 if (!socket.isOpen()) {
229 socket.write(myNetData);
230 if (myNetData.hasRemaining()) {
236 if (myAppData.hasRemaining()) {
238 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
241 this.socket.register(this.selector, SelectionKey.OP_READ, this);
247 * Reads the incoming network data from the socket, decryptes them and then
248 * retrieves the OF messages.
250 * @return list of OF messages
254 public List<OFMessage> readMessages() throws Exception {
255 if (!socket.isOpen()) {
259 List<OFMessage> msgs = null;
263 bytesRead = socket.read(peerNetData);
265 logger.debug("Message read operation failed");
266 throw new AsynchronousCloseException();
271 sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
272 if (peerNetData.hasRemaining()) {
273 peerNetData.compact();
277 logger.trace("sslEngine unwrap result: {}", sslEngineResult);
278 runDelegatedTasks(sslEngineResult, sslEngine);
279 } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
280 && peerNetData.hasRemaining() && (--countDown > 0));
282 if (countDown == 0) {
283 logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
284 peerNetData.position(), peerNetData.limit());
288 msgs = factory.parseMessages(peerAppData);
289 if (peerAppData.hasRemaining()) {
290 peerAppData.compact();
295 this.socket.register(this.selector, SelectionKey.OP_READ, this);
301 * If the result indicates that we have outstanding tasks to do, go ahead
302 * and run them in this thread.
304 private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
307 if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
309 while ((runnable = engine.getDelegatedTask()) != null) {
310 logger.debug("\trunning delegated task...");
313 HandshakeStatus hsStatus = engine.getHandshakeStatus();
314 if (hsStatus == HandshakeStatus.NEED_TASK) {
315 throw new Exception("handshake shouldn't need additional tasks");
317 logger.debug("\tnew HandshakeStatus: {}", hsStatus);
321 private void doHandshake(SocketChannel socket, SSLEngine engine)
323 SSLSession session = engine.getSession();
324 ByteBuffer myAppData = ByteBuffer.allocate(session
325 .getApplicationBufferSize());
326 ByteBuffer peerAppData = ByteBuffer.allocate(session
327 .getApplicationBufferSize());
328 ByteBuffer myNetData = ByteBuffer.allocate(session
329 .getPacketBufferSize());
330 ByteBuffer peerNetData = ByteBuffer.allocate(session
331 .getPacketBufferSize());
334 engine.beginHandshake();
335 SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
337 // Process handshaking message
338 while (hs != SSLEngineResult.HandshakeStatus.FINISHED
339 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
342 // Receive handshaking data from peer
343 if (socket.read(peerNetData) < 0) {
344 throw new AsynchronousCloseException();
347 // Process incoming handshaking data
349 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
350 peerNetData.compact();
351 hs = res.getHandshakeStatus();
354 switch (res.getStatus()) {
362 // Empty the local network packet buffer.
365 // Generate handshaking data
366 res = engine.wrap(myAppData, myNetData);
367 hs = res.getHandshakeStatus();
370 switch (res.getStatus()) {
374 // Send the handshaking data to peer
375 while (myNetData.hasRemaining()) {
376 if (socket.write(myNetData) < 0) {
377 throw new AsynchronousCloseException();
385 // Handle blocking tasks
387 while ((runnable = engine.getDelegatedTask()) != null) {
388 logger.debug("\trunning delegated task...");
391 hs = engine.getHandshakeStatus();
392 if (hs == HandshakeStatus.NEED_TASK) {
394 "handshake shouldn't need additional tasks");
396 logger.debug("\tnew HandshakeStatus: {}", hs);
402 private void createBuffers(SSLEngine engine) {
403 SSLSession session = engine.getSession();
404 this.myAppData = ByteBuffer
405 .allocate(session.getApplicationBufferSize());
406 this.peerAppData = ByteBuffer.allocate(session
407 .getApplicationBufferSize());
408 this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
409 this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
413 public void stop() throws IOException {
414 this.sslEngine = null;
415 this.sslEngineResult = null;
416 this.myAppData = null;
417 this.myNetData = null;
418 this.peerAppData = null;
419 this.peerNetData = null;
421 if (this.kfd != null) {
425 if (this.tfd != null) {