View Javadoc
1   /*
2    * Copyright 2025 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.util;
17  
18  import io.netty.util.internal.ThrowableUtil;
19  import io.netty.util.internal.logging.InternalLogger;
20  import io.netty.util.internal.logging.InternalLoggerFactory;
21  
22  import java.security.AccessController;
23  import java.security.PrivilegedAction;
24  import java.security.Provider;
25  import java.security.Security;
26  import javax.net.ssl.SSLEngine;
27  
28  /**
29   * Contains methods that can be used to detect if BouncyCastle is available.
30   */
31  public final class BouncyCastleUtil {
32      private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastleUtil.class);
33  
34      private static final String BC_PROVIDER_NAME = "BC";
35      private static final String BC_PROVIDER = "org.bouncycastle.jce.provider.BouncyCastleProvider";
36      private static final String BC_FIPS_PROVIDER_NAME = "BCFIPS";
37      private static final String BC_FIPS_PROVIDER = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider";
38      private static final String BC_JSSE_PROVIDER_NAME = "BCJSSE";
39      private static final String BC_JSSE_PROVIDER = "org.bouncycastle.jsse.provider.BouncyCastleJsseProvider";
40      private static final String BC_PEMPARSER = "org.bouncycastle.openssl.PEMParser";
41      private static final String BC_JSSE_SSLENGINE = "org.bouncycastle.jsse.BCSSLEngine";
42      private static final String BC_JSSE_ALPN_SELECTOR = "org.bouncycastle.jsse.BCApplicationProtocolSelector";
43  
44      /**
45       * Indicate whether the BouncyCastle Java Crypto Extensions provider is available.
46       */
47      public static boolean isBcProvAvailable() {
48          BcProv.ensureLoaded();
49          return BcProv.unavailabilityCauseBcProv == null;
50      }
51  
52      /**
53       * Indicate whether the BouncyCastle Public-Key Infrastructure utilities are available.
54       */
55      public static boolean isBcPkixAvailable() {
56          BcPkix.ensureLoaded();
57          return BcPkix.unavailabilityCauseBcPkix == null;
58      }
59  
60      /**
61       * Indicate whether the BouncyCastle Java Secure Socket Extensions provider is available.
62       */
63      public static boolean isBcTlsAvailable() {
64          BcTls.ensureLoaded();
65          return BcTls.unavailabilityCauseBcTls == null;
66      }
67  
68      /**
69       * @return the cause if unavailable. {@code null} if available.
70       */
71      public static Throwable unavailabilityCauseBcProv() {
72          BcProv.ensureLoaded();
73          return BcProv.unavailabilityCauseBcProv;
74      }
75  
76      /**
77       * @return the cause if unavailable. {@code null} if available.
78       */
79      public static Throwable unavailabilityCauseBcPkix() {
80          BcPkix.ensureLoaded();
81          return BcPkix.unavailabilityCauseBcPkix;
82      }
83  
84      /**
85       * @return the cause if unavailable. {@code null} if available.
86       */
87      public static Throwable unavailabilityCauseBcTls() {
88          BcTls.ensureLoaded();
89          return BcTls.unavailabilityCauseBcTls;
90      }
91  
92      /**
93       * Indicates whether the given SSLEngine is implemented by BouncyCastle.
94       */
95      public static boolean isBcJsseInUse(SSLEngine engine) {
96          BcTls.ensureLoaded();
97          Class<? extends SSLEngine> bcEngineClass = BcTls.bcSSLEngineClass;
98          return bcEngineClass != null && bcEngineClass.isInstance(engine);
99      }
100 
101     /**
102      * Get the BouncyCastle Java Crypto Extensions provider, or throw an exception if it is unavailable.
103      */
104     public static Provider getBcProviderJce() {
105         BcProv.ensureLoaded();
106         Throwable cause = BcProv.unavailabilityCauseBcProv;
107         Provider provider = BcProv.bcProviderJce;
108         if (cause != null || provider == null) {
109             throw new IllegalStateException(cause);
110         }
111         return provider;
112     }
113 
114     /**
115      * Get the BouncyCastle Java Secure Socket Extensions provider, or throw an exception if it is unavailable.
116      */
117     public static Provider getBcProviderJsse() {
118         BcTls.ensureLoaded();
119         Throwable cause = BcTls.unavailabilityCauseBcTls;
120         Provider provider = BcTls.bcProviderJsse;
121         if (cause != null || provider == null) {
122             throw new IllegalStateException(cause);
123         }
124         return provider;
125     }
126 
127     /**
128      * Returns the public {@link SSLEngine} sub-class that is used by bouncy-castle or {@code null} if
129      * it can't be loaded.
130      *
131      * @return engine class.
132      */
133     public static Class<? extends SSLEngine> getBcSSLEngineClass() {
134         BcTls.ensureLoaded();
135         return BcTls.bcSSLEngineClass;
136     }
137 
138     /**
139      * Reset the loaded providers. Useful for testing, to redo the loading under different conditions.
140      */
141     static void reset() {
142         BcProv.attemptedLoading = false;
143         BcPkix.attemptedLoading = false;
144         BcTls.attemptedLoading = false;
145         BcProv.unavailabilityCauseBcProv = null;
146         BcPkix.unavailabilityCauseBcPkix = null;
147         BcTls.unavailabilityCauseBcTls = null;
148         BcProv.bcProviderJce = null;
149         BcTls.bcProviderJsse = null;
150         BcTls.bcSSLEngineClass = null;
151     }
152 
153     private static final class BcProv {
154         static volatile Throwable unavailabilityCauseBcProv;
155         static volatile Provider bcProviderJce;
156         static volatile boolean attemptedLoading;
157 
158         @SuppressWarnings("unchecked")
159         private static void ensureLoaded() {
160             if (!attemptedLoading) {
161                 AccessController.doPrivileged((PrivilegedAction<?>) () -> {
162                     try {
163                         // Check for bcprov-jdk18on or bc-fips:
164                         Provider provider = Security.getProvider(BC_PROVIDER_NAME);
165                         if (provider == null) {
166                             provider = Security.getProvider(BC_FIPS_PROVIDER_NAME);
167                         }
168                         if (provider == null) {
169                             ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
170                             Class<Provider> bcProviderClass;
171                             try {
172                                 bcProviderClass = (Class<Provider>) Class.forName(
173                                         BC_PROVIDER, true, classLoader);
174                             } catch (ClassNotFoundException e) {
175                                 try {
176                                     bcProviderClass = (Class<Provider>) Class.forName(
177                                             BC_FIPS_PROVIDER, true, classLoader);
178                                 } catch (ClassNotFoundException ex) {
179                                     ThrowableUtil.addSuppressed(e, ex);
180                                     throw e;
181                                 }
182                             }
183                             provider = bcProviderClass.getConstructor().newInstance();
184                         }
185                         bcProviderJce = provider;
186                         logger.debug("Bouncy Castle provider available");
187                     } catch (Throwable e) {
188                         String msg = "Bouncy Castle provider (bcprov-jdk18on or bc-fips) was not loaded";
189                         if (includeLoadingErrorStackTrace(e)) {
190                             logger.debug(msg, e);
191                         } else {
192                             logger.debug(msg);
193                         }
194                         unavailabilityCauseBcProv = e;
195                     }
196                     return null;
197                 });
198                 attemptedLoading = true;
199             }
200         }
201     }
202 
203     private static final class BcPkix {
204         static volatile Throwable unavailabilityCauseBcPkix;
205         static volatile boolean attemptedLoading;
206 
207         private static void ensureLoaded() {
208             if (!attemptedLoading) {
209                 BcProv.ensureLoaded(); // For BcProv.bcProviderJce
210                 AccessController.doPrivileged((PrivilegedAction<?>) () -> {
211                     try {
212                         // Check for bcpkix-jdk18on:
213                         ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
214                         Provider provider = BcProv.bcProviderJce;
215                         if (provider != null) {
216                             // Use provider class loader in case it was loaded by the system loader.
217                             classLoader = provider.getClass().getClassLoader();
218                         }
219                         Class.forName(BC_PEMPARSER, true, classLoader);
220                         logger.debug("Bouncy Castle PKIX available");
221                     } catch (Throwable e) {
222                         String msg = "Bouncy Castle PKIX (bcpkix-jdk18on) was not loaded";
223                         if (includeLoadingErrorStackTrace(e)) {
224                             logger.debug(msg, e);
225                         } else {
226                             logger.debug(msg);
227                         }
228                         unavailabilityCauseBcPkix = e;
229                     }
230                     return null;
231                 });
232                 attemptedLoading = true;
233             }
234         }
235     }
236 
237     private static final class BcTls {
238         static volatile Throwable unavailabilityCauseBcTls;
239         static volatile Provider bcProviderJsse;
240         static volatile Class<? extends SSLEngine> bcSSLEngineClass;
241         static volatile boolean attemptedLoading;
242 
243         @SuppressWarnings("unchecked")
244         private static void ensureLoaded() {
245             if (!attemptedLoading) {
246                 AccessController.doPrivileged((PrivilegedAction<?>) () -> {
247                     try {
248                         // Check for bctls-jdk18on:
249                         ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
250                         Provider provider = Security.getProvider(BC_JSSE_PROVIDER_NAME);
251                         if (provider != null) {
252                             // Use provider class loader in case it was loaded by the system loader.
253                             classLoader = provider.getClass().getClassLoader();
254                         } else {
255                             Class<?> providerClass = Class.forName(BC_JSSE_PROVIDER, true, classLoader);
256                             provider = (Provider) providerClass.getConstructor().newInstance();
257                         }
258                         bcSSLEngineClass = (Class<? extends SSLEngine>) Class.forName(
259                                 BC_JSSE_SSLENGINE, true, classLoader);
260                         Class.forName(BC_JSSE_ALPN_SELECTOR, true, classLoader);
261                         bcProviderJsse = provider;
262                         logger.debug("Bouncy Castle JSSE available");
263                     } catch (Throwable e) {
264                         String msg = "Bouncy Castle TLS (bctls-jdk18on) was not loaded";
265                         if (includeLoadingErrorStackTrace(e)) {
266                             logger.debug(msg, e);
267                         } else {
268                             logger.debug(msg);
269                         }
270                         unavailabilityCauseBcTls = e;
271                     }
272                     return null;
273                 });
274                 attemptedLoading = true;
275             }
276         }
277     }
278 
279     private static boolean includeLoadingErrorStackTrace(Throwable e) {
280         // Only include the stack trace if we have TRACE logging enabled,
281         // or if the exception is something other than ClassNotFoundException.
282         // The ClassNotFoundException is what we would expect to see if the
283         // BC JAR files are just not on the classpath.
284         return logger.isTraceEnabled() || !(e instanceof ClassNotFoundException);
285     }
286 
287     private BouncyCastleUtil() {
288     }
289 }