View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.crypto.ssl;
21  
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelFuture;
24  import io.netty.channel.ChannelFutureListener;
25  import io.netty.channel.ChannelHandler;
26  import io.netty.channel.ChannelPipeline;
27  import io.netty.channel.group.ChannelGroup;
28  import io.netty.channel.group.DefaultChannelGroup;
29  import io.netty.handler.ssl.SslHandler;
30  import io.netty.util.concurrent.DefaultEventExecutor;
31  import io.netty.util.concurrent.EventExecutor;
32  import io.netty.util.concurrent.Future;
33  import io.netty.util.concurrent.GenericFutureListener;
34  import org.waarp.common.logging.WaarpLogger;
35  import org.waarp.common.logging.WaarpLoggerFactory;
36  import org.waarp.common.utility.WaarpNettyUtil;
37  import org.waarp.common.utility.WaarpThreadFactory;
38  
39  import java.util.NoSuchElementException;
40  
41  /**
42   * Utilities for SSL support
43   */
44  public final class WaarpSslUtility {
45    /**
46     * Internal Logger
47     */
48    private static final WaarpLogger logger =
49        WaarpLoggerFactory.getLogger(WaarpSslUtility.class);
50  
51    /**
52     * EventExecutor associated with Ssl utility
53     */
54    private static final EventExecutor SSL_EVENT_EXECUTOR =
55        new DefaultEventExecutor(new WaarpThreadFactory("SSLEVENT"));
56    /**
57     * ChannelGroup for SSL
58     */
59    private static final ChannelGroup sslChannelGroup =
60        new DefaultChannelGroup("SslChannelGroup", SSL_EVENT_EXECUTOR);
61  
62    /**
63     * Closing channel with SSL close at first step
64     */
65    public static final ChannelFutureListener SSLCLOSE =
66        new ChannelFutureListener() {
67  
68          @Override
69          public final void operationComplete(final ChannelFuture future) {
70            if (future.channel().isActive()) {
71              future.channel().eventLoop()
72                    .submit(new SslThread(future.channel()));
73            }
74          }
75        };
76  
77    private WaarpSslUtility() {
78    }
79  
80    /**
81     * Add the Channel as SSL handshake will start soon
82     *
83     * @param channel
84     */
85    public static void addSslOpenedChannel(final Channel channel) {
86      sslChannelGroup.add(channel);
87    }
88  
89    /**
90     * Add a SslHandler in a pipeline when the channel is already active
91     *
92     * @param future might be null, condition to start to add the
93     *     handler to
94     *     the pipeline
95     * @param pipeline
96     * @param sslHandler
97     * @param listener action once the handshake is done
98     */
99    @SuppressWarnings({ "unchecked", "rawtypes" })
100   public static void addSslHandler(final ChannelFuture future,
101                                    final ChannelPipeline pipeline,
102                                    final ChannelHandler sslHandler,
103                                    final GenericFutureListener<? extends Future<? super Channel>> listener) {
104     if (future == null) {
105       logger.debug("Add SslHandler: {}", pipeline.channel());
106       pipeline.channel().config().setAutoRead(true);
107       pipeline.addFirst("SSL", sslHandler);
108       ((SslHandler) sslHandler).handshakeFuture().addListener(listener);
109     } else {
110       future.addListener(new GenericFutureListener() {
111         @Override
112         public final void operationComplete(final Future future) {
113           logger.debug("Add SslHandler: {}", pipeline.channel());
114           pipeline.channel().config().setAutoRead(true);
115           pipeline.addFirst("SSL", sslHandler);
116           ((SslHandler) sslHandler).handshakeFuture().addListener(listener);
117         }
118       });
119     }
120     logger.debug("Checked Ssl Handler to be added: {}", pipeline.channel());
121   }
122 
123   /**
124    * Wait for the handshake on the given channel (better to use addSslHandler
125    * when handler is added after
126    * channel is active)
127    *
128    * @param channel
129    *
130    * @return True if the Handshake is done correctly
131    */
132   public static boolean waitForHandshake(final Channel channel) {
133     final ChannelHandler handler = channel.pipeline().first();
134     if (handler instanceof SslHandler) {
135       logger.debug("Start handshake SSL: {}", channel);
136       final SslHandler sslHandler = (SslHandler) handler;
137       // Get the SslHandler and begin handshake ASAP.
138       // Get notified when SSL handshake is done.
139       final Future<Channel> handshakeFuture = sslHandler.handshakeFuture();
140       WaarpNettyUtil.awaitOrInterrupted(handshakeFuture,
141                                         sslHandler.getHandshakeTimeoutMillis() +
142                                         1000);
143       logger.debug("Handshake: {}:{}", handshakeFuture.isSuccess(), channel,
144                    handshakeFuture.cause());
145       if (!handshakeFuture.isSuccess()) {
146         channel.close().awaitUninterruptibly(100);
147         return false;
148       }
149     } else {
150       logger.info("SSL Not found but connected: {} {}",
151                   handler.getClass().getName());
152     }
153     return true;
154   }
155 
156   /**
157    * Waiting for the channel to be opened and ready (Client side) (blocking
158    * call)
159    *
160    * @param future a future on connect only
161    *
162    * @return the channel if correctly associated, else return null
163    */
164   public static Channel waitforChannelReady(final ChannelFuture future) {
165     // Wait until the connection attempt succeeds or fails.
166     WaarpNettyUtil.awaitOrInterrupted(future);
167     if (!future.isSuccess()) {
168       logger.error("Channel not connected", future.cause());
169       return null;
170     }
171     final Channel channel = future.channel();
172     if (waitForHandshake(channel)) {
173       return channel;
174     }
175     return null;
176   }
177 
178   /**
179    * Utility to force all channels to be closed
180    */
181   public static void forceCloseAllSslChannels() {
182     if (SSL_EVENT_EXECUTOR.isShutdown()) {
183       for (final Channel channel : sslChannelGroup) {
184         closingSslChannel(channel);
185       }
186       WaarpNettyUtil.awaitOrInterrupted(sslChannelGroup.close());
187       SSL_EVENT_EXECUTOR.shutdownGracefully();
188     }
189   }
190 
191   /**
192    * Utility method to close a channel in SSL mode correctly (if any)
193    *
194    * @param channel
195    */
196   public static ChannelFuture closingSslChannel(final Channel channel) {
197     if (channel.isActive()) {
198       removingSslHandler(null, channel, true);
199       logger.debug(
200           "Close the channel and returns the ChannelFuture: " + channel);
201       return channel.closeFuture();
202     }
203     if (channel.closeFuture().isDone()) {
204       return channel.closeFuture();
205     }
206     logger.debug("Already closed");
207     return channel.newSucceededFuture();
208   }
209 
210   /**
211    * Remove the SslHandler (if any) cleanly
212    *
213    * @param future if not null, wait for this future to be done to
214    *     removed
215    *     the sslhandler
216    * @param channel
217    * @param close True to close the channel, else to only remove the SslHandler
218    */
219   @SuppressWarnings({ "rawtypes", "unchecked" })
220   public static void removingSslHandler(final ChannelFuture future,
221                                         final Channel channel,
222                                         final boolean close) {
223     if (channel.isActive()) {
224       channel.config().setAutoRead(true);
225       final ChannelHandler handler = channel.pipeline().first();
226       if (handler instanceof SslHandler) {
227         final SslHandler sslHandler = (SslHandler) handler;
228         if (future != null) {
229           future.addListener(new GenericFutureListener() {
230             @Override
231             public final void operationComplete(final Future future) {
232               waitForSslClose(channel, sslHandler, close);
233             }
234           });
235         } else {
236           waitForSslClose(channel, sslHandler, close);
237         }
238       } else {
239         if (close) {
240           channel.close();
241         }
242       }
243     }
244   }
245 
246   private static void waitForSslClose(final Channel channel,
247                                       final SslHandler sslHandler,
248                                       final boolean close) {
249     logger.debug("Found SslHandler and wait for Ssl.closeOutbound() : {}",
250                  channel);
251     if (channel.isActive()) {
252       sslHandler.closeOutbound()
253                 .addListener(new GenericFutureListener<Future<? super Void>>() {
254                   @Override
255                   public final void operationComplete(
256                       final Future<? super Void> future) {
257                     logger.debug("Ssl closed: {}", channel);
258                     channel.pipeline().remove(sslHandler);
259                     if (close && channel.isActive()) {
260                       channel.close();
261                     }
262                   }
263                 });
264     }
265   }
266 
267   /**
268    * Wait for the channel with SSL to be closed
269    *
270    * @param channel
271    * @param delay
272    *
273    * @return True if an error occurs as an interruption
274    */
275   public static boolean waitForClosingSslChannel(final Channel channel,
276                                                  final long delay) {
277     if (!WaarpNettyUtil.awaitOrInterrupted(channel.closeFuture(), delay)) {
278       try {
279         channel.pipeline().remove(SslHandler.class);
280         logger.debug("try to close anyway");
281         if (channel.isActive()) {
282           WaarpNettyUtil.awaitOrInterrupted(channel.close(), delay);
283         }
284         return false;
285       } catch (final NoSuchElementException e) {
286         // ignore
287         if (channel.isActive()) {
288           WaarpNettyUtil.awaitOrInterrupted(channel.closeFuture(), delay);
289         }
290       }
291     }
292     return true;
293   }
294 
295   /**
296    * Thread used to ensure we are not in IO thread when waiting
297    */
298   private static class SslThread implements Runnable {
299     private final Channel channel;
300 
301     /**
302      * @param channel
303      */
304     private SslThread(final Channel channel) {
305       this.channel = channel;
306     }
307 
308     @Override
309     public void run() {
310       closingSslChannel(channel);
311     }
312 
313   }
314 
315 }