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