Log TLS auth failure
[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(
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         sslEngine.setEnabledCipherSuites(new String[] {
136                 "SSL_RSA_WITH_RC4_128_MD5",
137                 "SSL_RSA_WITH_RC4_128_SHA",
138                 "TLS_RSA_WITH_AES_128_CBC_SHA",
139                 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
140                 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
141                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
142                 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
143                 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
144                 "SSL_RSA_WITH_DES_CBC_SHA",
145                 "SSL_DHE_RSA_WITH_DES_CBC_SHA",
146                 "SSL_DHE_DSS_WITH_DES_CBC_SHA",
147                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
148                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
149                 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
150                 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
151                 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"});
152
153         // Do initial handshake
154         doHandshake(socket, sslEngine);
155
156         this.socket.register(this.selector, SelectionKey.OP_READ);
157     }
158
159     /**
160      * Sends the OF message out over the socket channel. The message is
161      * encrypted by SSL Engine.
162      *
163      * @param msg
164      *            OF message to be sent
165      * @throws Exception
166      */
167     @Override
168     public void asyncSend(OFMessage msg) throws Exception {
169         synchronized (myAppData) {
170             int msgLen = msg.getLengthU();
171             if (myAppData.remaining() < msgLen) {
172                 // increase the buffer size so that it can contain this message
173                 ByteBuffer newBuffer = ByteBuffer.allocateDirect(myAppData
174                         .capacity() + msgLen);
175                 myAppData.flip();
176                 newBuffer.put(myAppData);
177                 myAppData = newBuffer;
178             }
179         }
180         synchronized (myAppData) {
181             msg.writeTo(myAppData);
182             myAppData.flip();
183             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
184             logger.trace("asyncSend sslEngine wrap: {}", sslEngineResult);
185             runDelegatedTasks(sslEngineResult, sslEngine);
186
187             if (!socket.isOpen()) {
188                 return;
189             }
190
191             myNetData.flip();
192             socket.write(myNetData);
193             if (myNetData.hasRemaining()) {
194                 myNetData.compact();
195             } else {
196                 myNetData.clear();
197             }
198
199             if (myAppData.hasRemaining()) {
200                 myAppData.compact();
201                 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
202             } else {
203                 myAppData.clear();
204                 this.socket.register(this.selector, SelectionKey.OP_READ, this);
205             }
206
207             logger.trace("Message sent: {}", msg);
208         }
209     }
210
211     /**
212      * Resumes sending the remaining messages in the outgoing buffer
213      *
214      * @throws Exception
215      */
216     @Override
217     public void resumeSend() throws Exception {
218         synchronized (myAppData) {
219             myAppData.flip();
220             sslEngineResult = sslEngine.wrap(myAppData, myNetData);
221             logger.trace("resumeSend sslEngine wrap: {}", sslEngineResult);
222             runDelegatedTasks(sslEngineResult, sslEngine);
223
224             if (!socket.isOpen()) {
225                 return;
226             }
227
228             myNetData.flip();
229             socket.write(myNetData);
230             if (myNetData.hasRemaining()) {
231                 myNetData.compact();
232             } else {
233                 myNetData.clear();
234             }
235
236             if (myAppData.hasRemaining()) {
237                 myAppData.compact();
238                 this.socket.register(this.selector, SelectionKey.OP_WRITE, this);
239             } else {
240                 myAppData.clear();
241                 this.socket.register(this.selector, SelectionKey.OP_READ, this);
242             }
243         }
244     }
245
246     /**
247      * Reads the incoming network data from the socket, decryptes them and then
248      * retrieves the OF messages.
249      *
250      * @return list of OF messages
251      * @throws Exception
252      */
253     @Override
254     public List<OFMessage> readMessages() throws Exception {
255         if (!socket.isOpen()) {
256             return null;
257         }
258
259         List<OFMessage> msgs = null;
260         int bytesRead = -1;
261         int countDown = 50;
262
263         bytesRead = socket.read(peerNetData);
264         if (bytesRead < 0) {
265             logger.debug("Message read operation failed");
266             throw new AsynchronousCloseException();
267         }
268
269         do {
270             peerNetData.flip();
271             sslEngineResult = sslEngine.unwrap(peerNetData, peerAppData);
272             if (peerNetData.hasRemaining()) {
273                 peerNetData.compact();
274             } else {
275                 peerNetData.clear();
276             }
277             logger.trace("sslEngine unwrap result: {}", sslEngineResult);
278             runDelegatedTasks(sslEngineResult, sslEngine);
279         } while ((sslEngineResult.getStatus() == SSLEngineResult.Status.OK)
280                 && peerNetData.hasRemaining() && (--countDown > 0));
281
282         if (countDown == 0) {
283             logger.trace("countDown reaches 0. peerNetData pos {} lim {}",
284                     peerNetData.position(), peerNetData.limit());
285         }
286
287         peerAppData.flip();
288         msgs = factory.parseMessages(peerAppData);
289         if (peerAppData.hasRemaining()) {
290             peerAppData.compact();
291         } else {
292             peerAppData.clear();
293         }
294
295         this.socket.register(this.selector, SelectionKey.OP_READ, this);
296
297         return msgs;
298     }
299
300     /**
301      * If the result indicates that we have outstanding tasks to do, go ahead
302      * and run them in this thread.
303      */
304     private void runDelegatedTasks(SSLEngineResult result, SSLEngine engine)
305             throws Exception {
306
307         if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
308             Runnable runnable;
309             while ((runnable = engine.getDelegatedTask()) != null) {
310                 logger.debug("\trunning delegated task...");
311                 runnable.run();
312             }
313             HandshakeStatus hsStatus = engine.getHandshakeStatus();
314             if (hsStatus == HandshakeStatus.NEED_TASK) {
315                 throw new Exception("handshake shouldn't need additional tasks");
316             }
317             logger.debug("\tnew HandshakeStatus: {}", hsStatus);
318         }
319     }
320
321     private void doHandshake(SocketChannel socket, SSLEngine engine)
322             throws Exception {
323         SSLSession session = engine.getSession();
324         ByteBuffer myAppData = ByteBuffer.allocate(session
325                 .getApplicationBufferSize());
326         ByteBuffer peerAppData = ByteBuffer.allocate(session
327                 .getApplicationBufferSize());
328         ByteBuffer myNetData = ByteBuffer.allocate(session
329                 .getPacketBufferSize());
330         ByteBuffer peerNetData = ByteBuffer.allocate(session
331                 .getPacketBufferSize());
332
333         // Begin handshake
334         engine.beginHandshake();
335         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
336
337         // Process handshaking message
338         while (hs != SSLEngineResult.HandshakeStatus.FINISHED
339                 && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
340             switch (hs) {
341             case NEED_UNWRAP:
342                 // Receive handshaking data from peer
343                 if (socket.read(peerNetData) < 0) {
344                     throw new AsynchronousCloseException();
345                 }
346
347                 // Process incoming handshaking data
348                 peerNetData.flip();
349                 SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
350                 peerNetData.compact();
351                 hs = res.getHandshakeStatus();
352
353                 // Check status
354                 switch (res.getStatus()) {
355                 case OK:
356                     // Handle OK status
357                     break;
358                 }
359                 break;
360
361             case NEED_WRAP:
362                 // Empty the local network packet buffer.
363                 myNetData.clear();
364
365                 // Generate handshaking data
366                 res = engine.wrap(myAppData, myNetData);
367                 hs = res.getHandshakeStatus();
368
369                 // Check status
370                 switch (res.getStatus()) {
371                 case OK:
372                     myNetData.flip();
373
374                     // Send the handshaking data to peer
375                     while (myNetData.hasRemaining()) {
376                         if (socket.write(myNetData) < 0) {
377                             throw new AsynchronousCloseException();
378                         }
379                     }
380                     break;
381                 }
382                 break;
383
384             case NEED_TASK:
385                 // Handle blocking tasks
386                 Runnable runnable;
387                 while ((runnable = engine.getDelegatedTask()) != null) {
388                     logger.debug("\trunning delegated task...");
389                     runnable.run();
390                 }
391                 hs = engine.getHandshakeStatus();
392                 if (hs == HandshakeStatus.NEED_TASK) {
393                     throw new Exception(
394                             "handshake shouldn't need additional tasks");
395                 }
396                 logger.debug("\tnew HandshakeStatus: {}", hs);
397                 break;
398             }
399         }
400     }
401
402     private void createBuffers(SSLEngine engine) {
403         SSLSession session = engine.getSession();
404         this.myAppData = ByteBuffer
405                 .allocate(session.getApplicationBufferSize());
406         this.peerAppData = ByteBuffer.allocate(session
407                 .getApplicationBufferSize());
408         this.myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
409         this.peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
410     }
411
412     @Override
413     public void stop() throws IOException {
414         this.sslEngine = null;
415         this.sslEngineResult = null;
416         this.myAppData = null;
417         this.myNetData = null;
418         this.peerAppData = null;
419         this.peerNetData = null;
420
421         if (this.kfd != null) {
422             this.kfd.close();
423             this.kfd = null;
424         }
425         if (this.tfd != null) {
426             this.tfd.close();
427             this.tfd = null;
428         }
429     }
430 }