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