3 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
5 * This program and the accompanying materials are made available under the
6 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 * and is available at http://www.eclipse.org/legal/epl-v10.html
10 package org.opendaylight.controller.protocol_plugin.openflow.core.internal;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.nio.ByteBuffer;
16 import java.nio.channels.AsynchronousCloseException;
17 import java.nio.channels.SelectionKey;
18 import java.nio.channels.Selector;
19 import java.nio.channels.SocketChannel;
20 import java.security.KeyStore;
21 import java.security.SecureRandom;
22 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.SSLSession;
28 import javax.net.ssl.TrustManagerFactory;
29 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
30 import org.opendaylight.controller.protocol_plugin.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 switch
54 private ByteBuffer peerNetData; // encrypted message from the switch
55 private FileInputStream kfd = null, tfd = null;
57 public SecureMessageReadWriteService(SocketChannel socket, Selector selector) throws Exception {
59 this.selector = selector;
60 this.factory = new BasicFactory();
63 createSecureChannel(socket);
64 createBuffers(sslEngine);
65 } catch (Exception e) {
72 * Bring up secure channel using SSL Engine
74 * @param socket TCP socket channel
77 private void createSecureChannel(SocketChannel socket) throws Exception {
78 String keyStoreFile = System.getProperty("controllerKeyStore");
79 String keyStorePassword = System.getProperty("controllerKeyStorePassword");
80 String trustStoreFile = System.getProperty("controllerTrustStore");
81 String trustStorePassword = System.getProperty("controllerTrustStorePassword");
83 if (keyStoreFile != null) {
84 keyStoreFile = keyStoreFile.trim();
86 if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
87 throw new FileNotFoundException("controllerKeyStore not specified in ./configuration/config.ini");
89 if (keyStorePassword != null) {
90 keyStorePassword = keyStorePassword.trim();
92 if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
93 throw new FileNotFoundException("controllerKeyStorePassword not specified in ./configuration/config.ini");
95 if (trustStoreFile != null) {
96 trustStoreFile = trustStoreFile.trim();
98 if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
99 throw new FileNotFoundException("controllerTrustStore not specified in ./configuration/config.ini");
101 if (trustStorePassword != null) {
102 trustStorePassword = trustStorePassword.trim();
104 if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
105 throw new FileNotFoundException("controllerTrustStorePassword not specified in ./configuration/config.ini");
108 KeyStore ks = KeyStore.getInstance("JKS");
109 KeyStore ts = KeyStore.getInstance("JKS");
110 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
111 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
112 kfd = new FileInputStream(keyStoreFile);
113 tfd = new FileInputStream(trustStoreFile);
114 ks.load(kfd, keyStorePassword.toCharArray());
115 ts.load(tfd, trustStorePassword.toCharArray());
116 kmf.init(ks, keyStorePassword.toCharArray());
119 SecureRandom random = new SecureRandom();
122 SSLContext sslContext = SSLContext.getInstance("TLS");
123 sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random);
124 sslEngine = sslContext.createSSLEngine();
125 sslEngine.setUseClientMode(false);
126 sslEngine.setNeedClientAuth(true);
128 // Do initial handshake
129 doHandshake(socket, sslEngine);
131 this.clientSelectionKey = this.socket.register(this.selector,
132 SelectionKey.OP_READ);
136 * Sends the OF message out over the socket channel. The message is
137 * encrypted by SSL Engine.
139 * @param msg OF message to be sent
143 public void asyncSend(OFMessage msg) throws Exception {
144 synchronized (myAppData) {
145 int msgLen = msg.getLengthU();
146 if (myAppData.remaining() < msgLen) {
147 // increase the buffer size so that it can contain this message
148 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
152 newBuffer.put(myAppData);
153 myAppData = newBuffer;
156 synchronized (myAppData) {
157 msg.writeTo(myAppData);
159 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
160 logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
161 runDelegatedTasks(sslEngineResult, sslEngine);
163 if (!socket.isOpen()) {
168 socket.write(myNetData);
169 if (myNetData.hasRemaining()) {
175 if (myAppData.hasRemaining()) {
177 this.clientSelectionKey = this.socket.register(
178 this.selector, SelectionKey.OP_WRITE, this);
181 this.clientSelectionKey = this.socket.register(
182 this.selector, SelectionKey.OP_READ, this);
185 logger.trace("Message sent: {}", msg.toString());
190 * Resumes sending the remaining messages in the outgoing buffer
194 public void resumeSend() throws Exception {
195 synchronized (myAppData) {
197 sslEngineResult = sslEngine.wrap(myAppData, myNetData);
198 logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
199 runDelegatedTasks(sslEngineResult, sslEngine);
201 if (!socket.isOpen()) {
206 socket.write(myNetData);
207 if (myNetData.hasRemaining()) {
213 if (myAppData.hasRemaining()) {
215 this.clientSelectionKey = this.socket.register(this.selector,
216 SelectionKey.OP_WRITE, this);
219 this.clientSelectionKey = this.socket.register(this.selector,
220 SelectionKey.OP_READ, this);
226 * Reads the incoming network data from the socket, decryptes them and then
227 * retrieves the OF messages.
229 * @return list of OF messages
233 public List<OFMessage> readMessages() throws Exception {
234 if (!socket.isOpen()) {
238 List<OFMessage> msgs = null;
242 bytesRead = socket.read(peerNetData);
244 logger.debug("Message read operation failed");
245 throw new AsynchronousCloseException();
250 sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
251 if (peerNetData.hasRemaining()) {
252 peerNetData.compact();
256 logger.trace("sslEngine unwrap result: {}", sslEngineResult);
257 runDelegatedTasks(sslEngineResult, sslEngine);
258 } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK) &&
259 peerNetData.hasRemaining() && (--countDown > 0));
261 if (countDown == 0) {
262 logger.trace("countDown reaches 0. peerNetData pos {} lim {}", peerNetData.position(), peerNetData.limit());
266 msgs = factory.parseMessages(peerAppData);
267 if (peerAppData.hasRemaining()) {
268 peerAppData.compact();
273 this.clientSelectionKey = this.socket.register(
274 this.selector, SelectionKey.OP_READ, this);
280 * If the result indicates that we have outstanding tasks to do,
281 * go ahead and run them in this thread.
283 private void runDelegatedTasks(SSLEngineResult result,
284 SSLEngine engine) throws Exception {
286 if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
288 while ((runnable = engine.getDelegatedTask()) != null) {
289 logger.debug("\trunning delegated task...");
292 HandshakeStatus hsStatus = engine.getHandshakeStatus();
293 if (hsStatus == HandshakeStatus.NEED_TASK) {
295 "handshake shouldn't need additional tasks");
297 logger.debug("\tnew HandshakeStatus: {}", hsStatus);
301 private void doHandshake(SocketChannel socket, SSLEngine engine) throws Exception {
302 SSLSession session = engine.getSession();
303 ByteBuffer myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
304 ByteBuffer peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
305 ByteBuffer myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
306 ByteBuffer peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
309 engine.beginHandshake();
310 SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
312 // Process handshaking message
313 while (hs != SSLEngineResult.HandshakeStatus.FINISHED &&
314 hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
317 // Receive handshaking data from peer
318 if (socket.read(peerNetData) < 0) {
319 throw new AsynchronousCloseException();
322 // Process incoming handshaking data
324 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
325 peerNetData.compact();
326 hs = res.getHandshakeStatus();
329 switch (res.getStatus()) {
337 // Empty the local network packet buffer.
340 // Generate handshaking data
341 res = engine.wrap(myAppData, myNetData);
342 hs = res.getHandshakeStatus();
345 switch (res.getStatus()) {
349 // Send the handshaking data to peer
350 while (myNetData.hasRemaining()) {
351 if (socket.write(myNetData) < 0) {
352 throw new AsynchronousCloseException();
360 // Handle blocking tasks
362 while ((runnable = engine.getDelegatedTask()) != null) {
363 logger.debug("\trunning delegated task...");
366 hs = engine.getHandshakeStatus();
367 if (hs == HandshakeStatus.NEED_TASK) {
369 "handshake shouldn't need additional tasks");
371 logger.debug("\tnew HandshakeStatus: {}", hs);
377 private void createBuffers(SSLEngine engine) {
378 SSLSession session = engine.getSession();
379 this.myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
380 this.peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
381 this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
382 this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
386 public void stop() throws IOException {
387 this.sslEngine = null;
388 this.sslEngineResult = null;
389 this.myAppData = null;
390 this.myNetData = null;
391 this.peerAppData = null;
392 this.peerNetData = null;
394 if (this.kfd != null) {
398 if (this.tfd != null) {