View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.ssl;
17  
18  import io.netty.handler.ssl.util.BouncyCastleUtil;
19  import io.netty.util.internal.EmptyArrays;
20  import io.netty.util.internal.PlatformDependent;
21  import io.netty.util.internal.SuppressJava6Requirement;
22  import io.netty.util.internal.logging.InternalLogger;
23  import io.netty.util.internal.logging.InternalLoggerFactory;
24  
25  import javax.net.ssl.SSLContext;
26  import javax.net.ssl.SSLEngine;
27  import javax.net.ssl.SSLParameters;
28  import java.lang.reflect.InvocationHandler;
29  import java.lang.reflect.Method;
30  import java.lang.reflect.Proxy;
31  import java.security.AccessController;
32  import java.security.PrivilegedExceptionAction;
33  import java.security.SecureRandom;
34  import java.util.List;
35  import java.util.function.BiFunction;
36  
37  import static io.netty.handler.ssl.SslUtils.getSSLContext;
38  
39  @SuppressJava6Requirement(reason = "Usage guarded by java version check")
40  final class BouncyCastleAlpnSslUtils {
41      private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastleAlpnSslUtils.class);
42      private static final Method SET_APPLICATION_PROTOCOLS;
43      private static final Method GET_APPLICATION_PROTOCOL;
44      private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL;
45      private static final Method SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR;
46      private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR;
47      private static final Class<?> BC_APPLICATION_PROTOCOL_SELECTOR;
48      private static final Method BC_APPLICATION_PROTOCOL_SELECTOR_SELECT;
49      private static final boolean SUPPORTED;
50  
51      static {
52          Method setApplicationProtocols;
53          Method getApplicationProtocol;
54          Method getHandshakeApplicationProtocol;
55          Method setHandshakeApplicationProtocolSelector;
56          Method getHandshakeApplicationProtocolSelector;
57          Method bcApplicationProtocolSelectorSelect;
58          Class<?> bcApplicationProtocolSelector;
59          boolean supported;
60  
61          try {
62              if (!BouncyCastleUtil.isBcTlsAvailable()) {
63                  throw new IllegalStateException(BouncyCastleUtil.unavailabilityCauseBcTls());
64              }
65              SSLContext context = getSSLContext(BouncyCastleUtil.getBcProviderJsse(), new SecureRandom());
66              SSLEngine engine = context.createSSLEngine();
67              Class<? extends SSLEngine> engineClass = engine.getClass();
68              // We need to use the class returned by BounceCastleUtil below to access the methods as the engine
69              // returned by createSSLEngine might be package-private and so would not allow us to access the methods
70              // even thought the interface itself that it implements is public and so the methods are public.
71              // See https://github.com/netty/netty/issues/15627
72              final Class<? extends SSLEngine> bcEngineClass = BouncyCastleUtil.getBcSSLEngineClass();
73              if (bcEngineClass == null || !bcEngineClass.isAssignableFrom(engineClass)) {
74                  throw new IllegalStateException("Unexpected engine class: " + engineClass);
75              }
76  
77              final SSLParameters bcSslParameters = engine.getSSLParameters();
78              final Class<?> bCSslParametersClass = bcSslParameters.getClass();
79              setApplicationProtocols = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
80                  @Override
81                  public Method run() throws Exception {
82                      return bCSslParametersClass.getMethod("setApplicationProtocols", String[].class);
83                  }
84              });
85              setApplicationProtocols.invoke(bcSslParameters, new Object[]{EmptyArrays.EMPTY_STRINGS});
86  
87              getApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
88                  @Override
89                  public Method run() throws Exception {
90                      return bcEngineClass.getMethod("getApplicationProtocol");
91                  }
92              });
93              getApplicationProtocol.invoke(engine);
94  
95              getHandshakeApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
96                  @Override
97                  public Method run() throws Exception {
98                      return bcEngineClass.getMethod("getHandshakeApplicationProtocol");
99                  }
100             });
101             getHandshakeApplicationProtocol.invoke(engine);
102 
103             final Class<?> testBCApplicationProtocolSelector = Class.forName(
104                     "org.bouncycastle.jsse.BCApplicationProtocolSelector", true, engineClass.getClassLoader());
105             bcApplicationProtocolSelector = testBCApplicationProtocolSelector;
106 
107             bcApplicationProtocolSelectorSelect = AccessController.doPrivileged(
108                     new PrivilegedExceptionAction<Method>() {
109                         @Override
110                         public Method run() throws Exception {
111                             return testBCApplicationProtocolSelector.getMethod("select", Object.class, List.class);
112                         }
113                     });
114 
115             setHandshakeApplicationProtocolSelector =
116                     AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
117                         @Override
118                         public Method run() throws Exception {
119                             return bcEngineClass.getMethod("setBCHandshakeApplicationProtocolSelector",
120                                     testBCApplicationProtocolSelector);
121                         }
122                     });
123 
124             getHandshakeApplicationProtocolSelector =
125                     AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
126                         @Override
127                         public Method run() throws Exception {
128                             return bcEngineClass.getMethod("getBCHandshakeApplicationProtocolSelector");
129                         }
130                     });
131             getHandshakeApplicationProtocolSelector.invoke(engine);
132             supported = true;
133         } catch (Throwable t) {
134             logger.error("Unable to initialize BouncyCastleAlpnSslUtils.", t);
135             setApplicationProtocols = null;
136             getApplicationProtocol = null;
137             getHandshakeApplicationProtocol = null;
138             setHandshakeApplicationProtocolSelector = null;
139             getHandshakeApplicationProtocolSelector = null;
140             bcApplicationProtocolSelectorSelect = null;
141             bcApplicationProtocolSelector = null;
142             supported = false;
143         }
144         SET_APPLICATION_PROTOCOLS = setApplicationProtocols;
145         GET_APPLICATION_PROTOCOL = getApplicationProtocol;
146         GET_HANDSHAKE_APPLICATION_PROTOCOL = getHandshakeApplicationProtocol;
147         SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = setHandshakeApplicationProtocolSelector;
148         GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = getHandshakeApplicationProtocolSelector;
149         BC_APPLICATION_PROTOCOL_SELECTOR_SELECT = bcApplicationProtocolSelectorSelect;
150         BC_APPLICATION_PROTOCOL_SELECTOR = bcApplicationProtocolSelector;
151         SUPPORTED = supported;
152     }
153 
154     private BouncyCastleAlpnSslUtils() {
155     }
156 
157     static String getApplicationProtocol(SSLEngine sslEngine) {
158         try {
159             return (String) GET_APPLICATION_PROTOCOL.invoke(sslEngine);
160         } catch (UnsupportedOperationException ex) {
161             throw ex;
162         } catch (Exception ex) {
163             throw new IllegalStateException(ex);
164         }
165     }
166 
167     static void setApplicationProtocols(SSLEngine engine, List<String> supportedProtocols) {
168         String[] protocolArray = supportedProtocols.toArray(EmptyArrays.EMPTY_STRINGS);
169         try {
170             SSLParameters bcSslParameters = engine.getSSLParameters();
171             SET_APPLICATION_PROTOCOLS.invoke(bcSslParameters, new Object[]{protocolArray});
172             engine.setSSLParameters(bcSslParameters);
173         } catch (UnsupportedOperationException ex) {
174             throw ex;
175         } catch (Exception ex) {
176             throw new IllegalStateException(ex);
177         }
178         if (PlatformDependent.javaVersion() >= 9) {
179             JdkAlpnSslUtils.setApplicationProtocols(engine, supportedProtocols);
180         }
181     }
182 
183     static String getHandshakeApplicationProtocol(SSLEngine sslEngine) {
184         try {
185             return (String) GET_HANDSHAKE_APPLICATION_PROTOCOL.invoke(sslEngine);
186         } catch (UnsupportedOperationException ex) {
187             throw ex;
188         } catch (Exception ex) {
189             throw new IllegalStateException(ex);
190         }
191     }
192 
193     static void setHandshakeApplicationProtocolSelector(
194             SSLEngine engine, final BiFunction<SSLEngine, List<String>, String> selector) {
195         try {
196             Object selectorProxyInstance = Proxy.newProxyInstance(
197                     BouncyCastleAlpnSslUtils.class.getClassLoader(),
198                     new Class[]{BC_APPLICATION_PROTOCOL_SELECTOR},
199                     new InvocationHandler() {
200                         @Override
201                         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
202                             if (method.getName().equals("select")) {
203                                 try {
204                                     return selector.apply((SSLEngine) args[0], (List<String>) args[1]);
205                                 } catch (ClassCastException e) {
206                                     throw new RuntimeException("BCApplicationProtocolSelector select method " +
207                                             "parameter of invalid type.", e);
208                                 }
209                             } else {
210                                 throw new UnsupportedOperationException(String.format("Method '%s' not supported.",
211                                         method.getName()));
212                             }
213                         }
214                     });
215 
216             SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine, selectorProxyInstance);
217         } catch (UnsupportedOperationException ex) {
218             throw ex;
219         } catch (Exception ex) {
220             throw new IllegalStateException(ex);
221         }
222     }
223 
224     static BiFunction<SSLEngine, List<String>, String> getHandshakeApplicationProtocolSelector(SSLEngine engine) {
225         try {
226             final Object selector = GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine);
227             return new BiFunction<SSLEngine, List<String>, String>() {
228                 @Override
229                 public String apply(SSLEngine sslEngine, List<String> strings) {
230                     try {
231                         return (String) BC_APPLICATION_PROTOCOL_SELECTOR_SELECT.invoke(selector, sslEngine, strings);
232                     } catch (Exception e) {
233                         throw new RuntimeException("Could not call getHandshakeApplicationProtocolSelector", e);
234                     }
235                 }
236             };
237         } catch (UnsupportedOperationException ex) {
238             throw ex;
239         } catch (Exception ex) {
240             throw new IllegalStateException(ex);
241         }
242     }
243 
244     static boolean isAlpnSupported() {
245         return SUPPORTED;
246     }
247 }