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