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.openflowplugin.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;
30 import org.opendaylight.openflowplugin.openflow.core.IMessageReadWrite;
31 import org.openflow.protocol.OFMessage;
32 import org.openflow.protocol.factory.BasicFactory;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * This class implements methods to read/write messages over an established
38 * socket channel. The data exchange is encrypted/decrypted by SSLEngine.
40 public class SecureMessageReadWriteService implements IMessageReadWrite {
41 private static final Logger logger = LoggerFactory
42 .getLogger(SecureMessageReadWriteService.class);
44 private Selector selector;
45 private SelectionKey clientSelectionKey;
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;
58 public SecureMessageReadWriteService(SocketChannel socket, Selector selector)
61 this.selector = selector;
62 this.factory = new BasicFactory();
65 createSecureChannel(socket);
66 createBuffers(sslEngine);
67 } catch (Exception e) {
74 * Bring up secure channel using SSL Engine
80 private void createSecureChannel(SocketChannel socket) throws Exception {
81 String keyStoreFile = System.getProperty("controllerKeyStore");
82 String keyStorePassword = System
83 .getProperty("controllerKeyStorePassword");
84 String trustStoreFile = System.getProperty("controllerTrustStore");
85 String trustStorePassword = System
86 .getProperty("controllerTrustStorePassword");
88 if (keyStoreFile != null) {
89 keyStoreFile = keyStoreFile.trim();
91 if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
92 throw new FileNotFoundException(
93 "controllerKeyStore not specified in ./configuration/config.ini");
95 if (keyStorePassword != null) {
96 keyStorePassword = keyStorePassword.trim();
98 if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
99 throw new FileNotFoundException(
100 "controllerKeyStorePassword not specified in ./configuration/config.ini");
102 if (trustStoreFile != null) {
103 trustStoreFile = trustStoreFile.trim();
105 if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
106 throw new FileNotFoundException(
107 "controllerTrustStore not specified in ./configuration/config.ini");
109 if (trustStorePassword != null) {
110 trustStorePassword = trustStorePassword.trim();
112 if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
113 throw new FileNotFoundException(
114 "controllerTrustStorePassword not specified in ./configuration/config.ini");
117 KeyStore ks = KeyStore.getInstance("JKS");
118 KeyStore ts = KeyStore.getInstance("JKS");
119 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
120 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
121 kfd = new FileInputStream(keyStoreFile);
122 tfd = new FileInputStream(trustStoreFile);
123 ks.load(kfd, keyStorePassword.toCharArray());
124 ts.load(tfd, trustStorePassword.toCharArray());
125 kmf.init(ks, keyStorePassword.toCharArray());
128 SecureRandom random = new SecureRandom();
131 SSLContext sslContext = SSLContext.getInstance("TLS");
132 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random);
133 sslEngine = sslContext.createSSLEngine();
134 sslEngine.setUseClientMode(false);
135 sslEngine.setNeedClientAuth(true);
137 // Do initial handshake
138 doHandshake(socket, sslEngine);
140 this.clientSelectionKey = this.socket.register(this.selector,
141 SelectionKey.OP_READ);
145 * Sends the OF message out over the socket channel. The message is
146 * encrypted by SSL Engine.
149 * OF message to be sent
153 public void asyncSend(OFMessage msg) throws Exception {
154 synchronized (myAppData) {
155 int msgLen = msg.getLengthU();
156 if (myAppData.remaining() < msgLen) {
157 // increase the buffer size so that it can contain this message
158 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
159 .capacity() + msgLen);
161 newBuffer.put(myAppData);
162 myAppData = newBuffer;
165 synchronized (myAppData) {
166 msg.writeTo(myAppData);
168 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
169 logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
170 runDelegatedTasks(sslEngineResult, sslEngine);
172 if (!socket.isOpen()) {
177 socket.write(myNetData);
178 if (myNetData.hasRemaining()) {
184 if (myAppData.hasRemaining()) {
186 this.clientSelectionKey = this.socket.register(this.selector,
187 SelectionKey.OP_WRITE, this);
190 this.clientSelectionKey = this.socket.register(this.selector,
191 SelectionKey.OP_READ, this);
194 logger.trace("Message sent: {}", msg);
199 * Resumes sending the remaining messages in the outgoing buffer
204 public void resumeSend() throws Exception {
205 synchronized (myAppData) {
207 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
208 logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
209 runDelegatedTasks(sslEngineResult, sslEngine);
211 if (!socket.isOpen()) {
216 socket.write(myNetData);
217 if (myNetData.hasRemaining()) {
223 if (myAppData.hasRemaining()) {
225 this.clientSelectionKey = this.socket.register(this.selector,
226 SelectionKey.OP_WRITE, this);
229 this.clientSelectionKey = this.socket.register(this.selector,
230 SelectionKey.OP_READ, this);
236 * Reads the incoming network data from the socket, decryptes them and then
237 * retrieves the OF messages.
239 * @return list of OF messages
243 public List<OFMessage> readMessages() throws Exception {
244 if (!socket.isOpen()) {
248 List<OFMessage> msgs = null;
252 bytesRead = socket.read(peerNetData);
254 logger.debug("Message read operation failed");
255 throw new AsynchronousCloseException();
260 sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
261 if (peerNetData.hasRemaining()) {
262 peerNetData.compact();
266 logger.trace("sslEngine unwrap result: {}", sslEngineResult);
267 runDelegatedTasks(sslEngineResult, sslEngine);
268 } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
269 && peerNetData.hasRemaining() && (--countDown > 0));
271 if (countDown == 0) {
272 logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
273 peerNetData.position(), peerNetData.limit());
277 msgs = factory.parseMessages(peerAppData);
278 if (peerAppData.hasRemaining()) {
279 peerAppData.compact();
284 this.clientSelectionKey = this.socket.register(this.selector,
285 SelectionKey.OP_READ, this);
291 * If the result indicates that we have outstanding tasks to do, go ahead
292 * and run them in this thread.
294 private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
297 if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
299 while ((runnable = engine.getDelegatedTask()) != null) {
300 logger.debug("\trunning delegated task...");
303 HandshakeStatus hsStatus = engine.getHandshakeStatus();
304 if (hsStatus == HandshakeStatus.NEED_TASK) {
305 throw new Exception("handshake shouldn't need additional tasks");
307 logger.debug("\tnew HandshakeStatus: {}", hsStatus);
311 private void doHandshake(SocketChannel socket, SSLEngine engine)
313 SSLSession session = engine.getSession();
314 ByteBuffer myAppData = ByteBuffer.allocate(session
315 .getApplicationBufferSize());
316 ByteBuffer peerAppData = ByteBuffer.allocate(session
317 .getApplicationBufferSize());
318 ByteBuffer myNetData = ByteBuffer.allocate(session
319 .getPacketBufferSize());
320 ByteBuffer peerNetData = ByteBuffer.allocate(session
321 .getPacketBufferSize());
324 engine.beginHandshake();
325 SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
327 // Process handshaking message
328 while (hs != SSLEngineResult.HandshakeStatus.FINISHED
329 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
332 // Receive handshaking data from peer
333 if (socket.read(peerNetData) < 0) {
334 throw new AsynchronousCloseException();
337 // Process incoming handshaking data
339 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
340 peerNetData.compact();
341 hs = res.getHandshakeStatus();
344 switch (res.getStatus()) {
352 // Empty the local network packet buffer.
355 // Generate handshaking data
356 res = engine.wrap(myAppData, myNetData);
357 hs = res.getHandshakeStatus();
360 switch (res.getStatus()) {
364 // Send the handshaking data to peer
365 while (myNetData.hasRemaining()) {
366 if (socket.write(myNetData) < 0) {
367 throw new AsynchronousCloseException();
375 // Handle blocking tasks
377 while ((runnable = engine.getDelegatedTask()) != null) {
378 logger.debug("\trunning delegated task...");
381 hs = engine.getHandshakeStatus();
382 if (hs == HandshakeStatus.NEED_TASK) {
384 "handshake shouldn't need additional tasks");
386 logger.debug("\tnew HandshakeStatus: {}", hs);
392 private void createBuffers(SSLEngine engine) {
393 SSLSession session = engine.getSession();
394 this.myAppData = ByteBuffer
395 .allocate(session.getApplicationBufferSize());
396 this.peerAppData = ByteBuffer.allocate(session
397 .getApplicationBufferSize());
398 this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
399 this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
403 public void stop() throws IOException {
404 this.sslEngine = null;
405 this.sslEngineResult = null;
406 this.myAppData = null;
407 this.myNetData = null;
408 this.peerAppData = null;
409 this.peerNetData = null;
411 if (this.kfd != null) {
415 if (this.tfd != null) {