cc446941511154a596fa7dc0a174b953227bce7b
[openflowplugin.git] / openflowplugin / src / main / java / org / opendaylight / openflowplugin / openflow / core / internal / SecureMessageReadWriteService.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.openflowplugin.openflow.core.internal;
10
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
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;
35
36 /**
37  * This class implements methods to read/write messages over an established
38  * socket channel. The data exchange is encrypted/decrypted by SSLEngine.
39  */
40 public class SecureMessageReadWriteService implements IMessageReadWrite {
41     private static final Logger logger = LoggerFactory
42             .getLogger(SecureMessageReadWriteService.class);
43
44     private Selector selector;
45     private SelectionKey clientSelectionKey;
46     private SocketChannel socket;
47     private BasicFactory factory;
48
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
54                                     // switch
55     private ByteBuffer peerNetData; // encrypted message from the switch
56     private FileInputStream kfd = null, tfd = null;
57
58     public SecureMessageReadWriteService(SocketChannel socket, Selector selector)
59             throws Exception {
60         this.socket = socket;
61         this.selector = selector;
62         this.factory = new BasicFactory();
63
64         try {
65             createSecureChannel(socket);
66             createBuffers(sslEngine);
67         } catch (Exception e) {
68             stop();
69             throw e;
70         }
71     }
72
73     /**
74      * Bring up secure channel using SSL Engine
75      *
76      * @param socket
77      *            TCP socket channel
78      * @throws Exception
79      */
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");
87
88         if (keyStoreFile != null) {
89             keyStoreFile = keyStoreFile.trim();
90         }
91         if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
92             throw new FileNotFoundException(
93                     "controllerKeyStore not specified in ./configuration/config.ini");
94         }
95         if (keyStorePassword != null) {
96             keyStorePassword = keyStorePassword.trim();
97         }
98         if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
99             throw new FileNotFoundException(
100                     "controllerKeyStorePassword not specified in ./configuration/config.ini");
101         }
102         if (trustStoreFile != null) {
103             trustStoreFile = trustStoreFile.trim();
104         }
105         if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
106             throw new FileNotFoundException(
107                     "controllerTrustStore not specified in ./configuration/config.ini");
108         }
109         if (trustStorePassword != null) {
110             trustStorePassword = trustStorePassword.trim();
111         }
112         if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
113             throw new FileNotFoundException(
114                     "controllerTrustStorePassword not specified in ./configuration/config.ini");
115         }
116
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());
126         tmf.init(ts);
127
128         SecureRandom random = new SecureRandom();
129         random.nextInt();
130
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);
136
137         // Do initial handshake
138         doHandshake(socket, sslEngine);
139
140         this.clientSelectionKey = this.socket.register(this.selector,
141                 SelectionKey.OP_READ);
142     }
143
144     /**
145      * Sends the OF message out over the socket channel. The message is
146      * encrypted by SSL Engine.
147      *
148      * @param msg
149      *            OF message to be sent
150      * @throws Exception
151      */
152     @Override
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);
160                 myAppData.flip();
161                 newBuffer.put(myAppData);
162                 myAppData = newBuffer;
163             }
164         }
165         synchronized (myAppData) {
166             msg.writeTo(myAppData);
167             myAppData.flip();
168             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
169             logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
170             runDelegatedTasks(sslEngineResult, sslEngine);
171
172             if (!socket.isOpen()) {
173                 return;
174             }
175
176             myNetData.flip();
177             socket.write(myNetData);
178             if (myNetData.hasRemaining()) {
179                 myNetData.compact();
180             } else {
181                 myNetData.clear();
182             }
183
184             if (myAppData.hasRemaining()) {
185                 myAppData.compact();
186                 this.clientSelectionKey = this.socket.register(this.selector,
187                         SelectionKey.OP_WRITE, this);
188             } else {
189                 myAppData.clear();
190                 this.clientSelectionKey = this.socket.register(this.selector,
191                         SelectionKey.OP_READ, this);
192             }
193
194             logger.trace("Message sent: {}", msg);
195         }
196     }
197
198     /**
199      * Resumes sending the remaining messages in the outgoing buffer
200      *
201      * @throws Exception
202      */
203     @Override
204     public void resumeSend() throws Exception {
205         synchronized (myAppData) {
206             myAppData.flip();
207             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
208             logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
209             runDelegatedTasks(sslEngineResult, sslEngine);
210
211             if (!socket.isOpen()) {
212                 return;
213             }
214
215             myNetData.flip();
216             socket.write(myNetData);
217             if (myNetData.hasRemaining()) {
218                 myNetData.compact();
219             } else {
220                 myNetData.clear();
221             }
222
223             if (myAppData.hasRemaining()) {
224                 myAppData.compact();
225                 this.clientSelectionKey = this.socket.register(this.selector,
226                         SelectionKey.OP_WRITE, this);
227             } else {
228                 myAppData.clear();
229                 this.clientSelectionKey = this.socket.register(this.selector,
230                         SelectionKey.OP_READ, this);
231             }
232         }
233     }
234
235     /**
236      * Reads the incoming network data from the socket, decryptes them and then
237      * retrieves the OF messages.
238      *
239      * @return list of OF messages
240      * @throws Exception
241      */
242     @Override
243     public List<OFMessage> readMessages() throws Exception {
244         if (!socket.isOpen()) {
245             return null;
246         }
247
248         List<OFMessage> msgs = null;
249         int bytesRead = -1;
250         int countDown = 50;
251
252         bytesRead = socket.read(peerNetData);
253         if (bytesRead < 0) {
254             logger.debug("Message read operation failed");
255             throw new AsynchronousCloseException();
256         }
257
258         do {
259             peerNetData.flip();
260             sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
261             if (peerNetData.hasRemaining()) {
262                 peerNetData.compact();
263             } else {
264                 peerNetData.clear();
265             }
266             logger.trace("sslEngine unwrap result: {}", sslEngineResult);
267             runDelegatedTasks(sslEngineResult, sslEngine);
268         } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
269                 && peerNetData.hasRemaining() && (--countDown > 0));
270
271         if (countDown == 0) {
272             logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
273                     peerNetData.position(), peerNetData.limit());
274         }
275
276         peerAppData.flip();
277         msgs = factory.parseMessages(peerAppData);
278         if (peerAppData.hasRemaining()) {
279             peerAppData.compact();
280         } else {
281             peerAppData.clear();
282         }
283
284         this.clientSelectionKey = this.socket.register(this.selector,
285                 SelectionKey.OP_READ, this);
286
287         return msgs;
288     }
289
290     /**
291      * If the result indicates that we have outstanding tasks to do, go ahead
292      * and run them in this thread.
293      */
294     private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
295             throws Exception {
296
297         if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
298             Runnable runnable;
299             while ((runnable = engine.getDelegatedTask()) != null) {
300                 logger.debug("\trunning delegated task...");
301                 runnable.run();
302             }
303             HandshakeStatus hsStatus = engine.getHandshakeStatus();
304             if (hsStatus == HandshakeStatus.NEED_TASK) {
305                 throw new Exception("handshake shouldn't need additional tasks");
306             }
307             logger.debug("\tnew HandshakeStatus: {}", hsStatus);
308         }
309     }
310
311     private void doHandshake(SocketChannel socket, SSLEngine engine)
312             throws Exception {
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());
322
323         // Begin handshake
324         engine.beginHandshake();
325         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
326
327         // Process handshaking message
328         while (hs != SSLEngineResult.HandshakeStatus.FINISHED
329                 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
330             switch (hs) {
331             case NEED_UNWRAP:
332                 // Receive handshaking data from peer
333                 if (socket.read(peerNetData) < 0) {
334                     throw new AsynchronousCloseException();
335                 }
336
337                 // Process incoming handshaking data
338                 peerNetData.flip();
339                 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
340                 peerNetData.compact();
341                 hs = res.getHandshakeStatus();
342
343                 // Check status
344                 switch (res.getStatus()) {
345                 case OK:
346                     // Handle OK status
347                     break;
348                 }
349                 break;
350
351             case NEED_WRAP:
352                 // Empty the local network packet buffer.
353                 myNetData.clear();
354
355                 // Generate handshaking data
356                 res = engine.wrap(myAppData, myNetData);
357                 hs = res.getHandshakeStatus();
358
359                 // Check status
360                 switch (res.getStatus()) {
361                 case OK:
362                     myNetData.flip();
363
364                     // Send the handshaking data to peer
365                     while (myNetData.hasRemaining()) {
366                         if (socket.write(myNetData) < 0) {
367                             throw new AsynchronousCloseException();
368                         }
369                     }
370                     break;
371                 }
372                 break;
373
374             case NEED_TASK:
375                 // Handle blocking tasks
376                 Runnable runnable;
377                 while ((runnable = engine.getDelegatedTask()) != null) {
378                     logger.debug("\trunning delegated task...");
379                     runnable.run();
380                 }
381                 hs = engine.getHandshakeStatus();
382                 if (hs == HandshakeStatus.NEED_TASK) {
383                     throw new Exception(
384                             "handshake shouldn't need additional tasks");
385                 }
386                 logger.debug("\tnew HandshakeStatus: {}", hs);
387                 break;
388             }
389         }
390     }
391
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());
400     }
401
402     @Override
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;
410
411         if (this.kfd != null) {
412             this.kfd.close();
413             this.kfd = null;
414         }
415         if (this.tfd != null) {
416             this.tfd.close();
417             this.tfd = null;
418         }
419     }
420 }