Merge changes I5809ac06,I4362cd4e
[controller.git] / opendaylight / protocol_plugins / openflow / src / main / java / org / opendaylight / controller / protocol_plugin / 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.controller.protocol_plugin.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 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;
34
35 /**
36  * This class implements methods to read/write messages over an established
37  * socket channel. The data exchange is encrypted/decrypted by SSLEngine.
38  */
39 public class SecureMessageReadWriteService implements IMessageReadWrite {
40     private static final Logger logger = LoggerFactory
41             .getLogger(SecureMessageReadWriteService.class);
42
43     private Selector selector;
44     private SocketChannel socket;
45     private BasicFactory factory;
46
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
52                                     // switch
53     private ByteBuffer peerNetData; // encrypted message from the switch
54     private FileInputStream kfd = null, tfd = null;
55
56     public SecureMessageReadWriteService(SocketChannel socket, Selector selector)
57             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
75      *            TCP socket channel
76      * @throws Exception
77      */
78     private void createSecureChannel(SocketChannel socket) throws Exception {
79         String keyStoreFile = System.getProperty("controllerKeyStore");
80         String keyStorePassword = System
81                 .getProperty("controllerKeyStorePassword");
82         String trustStoreFile = System.getProperty("controllerTrustStore");
83         String trustStorePassword = System
84                 .getProperty("controllerTrustStorePassword");
85
86         if (keyStoreFile != null) {
87             keyStoreFile = keyStoreFile.trim();
88         }
89         if ((keyStoreFile == null) || keyStoreFile.isEmpty()) {
90             throw new FileNotFoundException(
91                     "controllerKeyStore not specified in ./configuration/config.ini");
92         }
93         if (keyStorePassword != null) {
94             keyStorePassword = keyStorePassword.trim();
95         }
96         if ((keyStorePassword == null) || keyStorePassword.isEmpty()) {
97             throw new FileNotFoundException(
98                     "controllerKeyStorePassword not specified in ./configuration/config.ini");
99         }
100         if (trustStoreFile != null) {
101             trustStoreFile = trustStoreFile.trim();
102         }
103         if ((trustStoreFile == null) || trustStoreFile.isEmpty()) {
104             throw new FileNotFoundException(
105                     "controllerTrustStore not specified in ./configuration/config.ini");
106         }
107         if (trustStorePassword != null) {
108             trustStorePassword = trustStorePassword.trim();
109         }
110         if ((trustStorePassword == null) || trustStorePassword.isEmpty()) {
111             throw new FileNotFoundException(
112                     "controllerTrustStorePassword not specified in ./configuration/config.ini");
113         }
114
115         KeyStore ks = KeyStore.getInstance("JKS");
116         KeyStore ts = KeyStore.getInstance("JKS");
117         KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
118         TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
119         kfd = new FileInputStream(keyStoreFile);
120         tfd = new FileInputStream(trustStoreFile);
121         ks.load(kfd, keyStorePassword.toCharArray());
122         ts.load(tfd, trustStorePassword.toCharArray());
123         kmf.init(ks, keyStorePassword.toCharArray());
124         tmf.init(ts);
125
126         SecureRandom random = new SecureRandom();
127         random.nextInt();
128
129         SSLContext sslContext = SSLContext.getInstance("TLS");
130         sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), random);
131         sslEngine = sslContext.createSSLEngine();
132         sslEngine.setUseClientMode(false);
133         sslEngine.setNeedClientAuth(true);
134         sslEngine.setEnabledCipherSuites(new String[] {
135                 "SSL_RSA_WITH_RC4_128_MD5",
136                 "SSL_RSA_WITH_RC4_128_SHA",
137                 "TLS_RSA_WITH_AES_128_CBC_SHA",
138                 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
139                 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
140                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
141                 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
142                 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
143                 "SSL_RSA_WITH_DES_CBC_SHA",
144                 "SSL_DHE_RSA_WITH_DES_CBC_SHA",
145                 "SSL_DHE_DSS_WITH_DES_CBC_SHA",
146                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
147                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
148                 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
149                 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
150                 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"});
151
152         // Do initial handshake
153         doHandshake(socket, sslEngine);
154
155         this.socket.register(this.selector, SelectionKey.OP_READ);
156     }
157
158     /**
159      * Sends the OF message out over the socket channel. The message is
160      * encrypted by SSL Engine.
161      *
162      * @param msg
163      *            OF message to be sent
164      * @throws Exception
165      */
166     @Override
167     public void asyncSend(OFMessage msg) throws Exception {
168         synchronized (myAppData) {
169             int msgLen = msg.getLengthU();
170             if (myAppData.remaining() < msgLen) {
171                 // increase the buffer size so that it can contain this message
172                 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
173                         .capacity() + msgLen);
174                 myAppData.flip();
175                 newBuffer.put(myAppData);
176                 myAppData = newBuffer;
177             }
178         }
179         synchronized (myAppData) {
180             msg.writeTo(myAppData);
181             myAppData.flip();
182             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
183             logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
184             runDelegatedTasks(sslEngineResult, sslEngine);
185
186             if (!socket.isOpen()) {
187                 return;
188             }
189
190             myNetData.flip();
191             socket.write(myNetData);
192             if (myNetData.hasRemaining()) {
193                 myNetData.compact();
194             } else {
195                 myNetData.clear();
196             }
197
198             if (myAppData.hasRemaining()) {
199                 myAppData.compact();
200                 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
201             } else {
202                 myAppData.clear();
203                 this.socket.register(this.selector, SelectionKey.OP_READ, this);
204             }
205
206             logger.trace("Message sent: {}", msg);
207         }
208     }
209
210     /**
211      * Resumes sending the remaining messages in the outgoing buffer
212      *
213      * @throws Exception
214      */
215     @Override
216     public void resumeSend() throws Exception {
217         synchronized (myAppData) {
218             myAppData.flip();
219             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
220             logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
221             runDelegatedTasks(sslEngineResult, sslEngine);
222
223             if (!socket.isOpen()) {
224                 return;
225             }
226
227             myNetData.flip();
228             socket.write(myNetData);
229             if (myNetData.hasRemaining()) {
230                 myNetData.compact();
231             } else {
232                 myNetData.clear();
233             }
234
235             if (myAppData.hasRemaining()) {
236                 myAppData.compact();
237                 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
238             } else {
239                 myAppData.clear();
240                 this.socket.register(this.selector, SelectionKey.OP_READ, this);
241             }
242         }
243     }
244
245     /**
246      * Reads the incoming network data from the socket, decryptes them and then
247      * retrieves the OF messages.
248      *
249      * @return list of OF messages
250      * @throws Exception
251      */
252     @Override
253     public List<OFMessage> readMessages() throws Exception {
254         if (!socket.isOpen()) {
255             return null;
256         }
257
258         List<OFMessage> msgs = null;
259         int bytesRead = -1;
260         int countDown = 50;
261
262         bytesRead = socket.read(peerNetData);
263         if (bytesRead < 0) {
264             logger.debug("Message read operation failed");
265             throw new AsynchronousCloseException();
266         }
267
268         do {
269             peerNetData.flip();
270             sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
271             if (peerNetData.hasRemaining()) {
272                 peerNetData.compact();
273             } else {
274                 peerNetData.clear();
275             }
276             logger.trace("sslEngine unwrap result: {}", sslEngineResult);
277             runDelegatedTasks(sslEngineResult, sslEngine);
278         } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
279                 && peerNetData.hasRemaining() && (--countDown > 0));
280
281         if (countDown == 0) {
282             logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
283                     peerNetData.position(), peerNetData.limit());
284         }
285
286         peerAppData.flip();
287         msgs = factory.parseMessages(peerAppData);
288         if (peerAppData.hasRemaining()) {
289             peerAppData.compact();
290         } else {
291             peerAppData.clear();
292         }
293
294         this.socket.register(this.selector, SelectionKey.OP_READ, this);
295
296         return msgs;
297     }
298
299     /**
300      * If the result indicates that we have outstanding tasks to do, go ahead
301      * and run them in this thread.
302      */
303     private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
304             throws Exception {
305
306         if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
307             Runnable runnable;
308             while ((runnable = engine.getDelegatedTask()) != null) {
309                 logger.debug("\trunning delegated task...");
310                 runnable.run();
311             }
312             HandshakeStatus hsStatus = engine.getHandshakeStatus();
313             if (hsStatus == HandshakeStatus.NEED_TASK) {
314                 throw new Exception("handshake shouldn't need additional tasks");
315             }
316             logger.debug("\tnew HandshakeStatus: {}", hsStatus);
317         }
318     }
319
320     private void doHandshake(SocketChannel socket, SSLEngine engine)
321             throws Exception {
322         SSLSession session = engine.getSession();
323         ByteBuffer myAppData = ByteBuffer.allocate(session
324                 .getApplicationBufferSize());
325         ByteBuffer peerAppData = ByteBuffer.allocate(session
326                 .getApplicationBufferSize());
327         ByteBuffer myNetData = ByteBuffer.allocate(session
328                 .getPacketBufferSize());
329         ByteBuffer peerNetData = ByteBuffer.allocate(session
330                 .getPacketBufferSize());
331
332         // Begin handshake
333         engine.beginHandshake();
334         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
335
336         // Process handshaking message
337         while (hs != SSLEngineResult.HandshakeStatus.FINISHED
338                 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
339             switch (hs) {
340             case NEED_UNWRAP:
341                 // Receive handshaking data from peer
342                 if (socket.read(peerNetData) < 0) {
343                     throw new AsynchronousCloseException();
344                 }
345
346                 // Process incoming handshaking data
347                 peerNetData.flip();
348                 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
349                 peerNetData.compact();
350                 hs = res.getHandshakeStatus();
351
352                 // Check status
353                 switch (res.getStatus()) {
354                 case OK:
355                     // Handle OK status
356                     break;
357                 }
358                 break;
359
360             case NEED_WRAP:
361                 // Empty the local network packet buffer.
362                 myNetData.clear();
363
364                 // Generate handshaking data
365                 res = engine.wrap(myAppData, myNetData);
366                 hs = res.getHandshakeStatus();
367
368                 // Check status
369                 switch (res.getStatus()) {
370                 case OK:
371                     myNetData.flip();
372
373                     // Send the handshaking data to peer
374                     while (myNetData.hasRemaining()) {
375                         if (socket.write(myNetData) < 0) {
376                             throw new AsynchronousCloseException();
377                         }
378                     }
379                     break;
380                 }
381                 break;
382
383             case NEED_TASK:
384                 // Handle blocking tasks
385                 Runnable runnable;
386                 while ((runnable = engine.getDelegatedTask()) != null) {
387                     logger.debug("\trunning delegated task...");
388                     runnable.run();
389                 }
390                 hs = engine.getHandshakeStatus();
391                 if (hs == HandshakeStatus.NEED_TASK) {
392                     throw new Exception(
393                             "handshake shouldn't need additional tasks");
394                 }
395                 logger.debug("\tnew HandshakeStatus: {}", hs);
396                 break;
397             }
398         }
399     }
400
401     private void createBuffers(SSLEngine engine) {
402         SSLSession session = engine.getSession();
403         this.myAppData = ByteBuffer
404                 .allocate(session.getApplicationBufferSize());
405         this.peerAppData = ByteBuffer.allocate(session
406                 .getApplicationBufferSize());
407         this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
408         this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
409     }
410
411     @Override
412     public void stop() throws IOException {
413         this.sslEngine = null;
414         this.sslEngineResult = null;
415         this.myAppData = null;
416         this.myNetData = null;
417         this.peerAppData = null;
418         this.peerNetData = null;
419
420         if (this.kfd != null) {
421             this.kfd.close();
422             this.kfd = null;
423         }
424         if (this.tfd != null) {
425             this.tfd.close();
426             this.tfd = null;
427         }
428     }
429 }