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