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