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      private static volatile Throwable unavailabilityCauseBcProv;
45      private static volatile Throwable unavailabilityCauseBcPkix;
46      private static volatile Throwable unavailabilityCauseBcTls;
47      private static volatile Provider bcProviderJce;
48      private static volatile Provider bcProviderJsse;
49      private static volatile Class<? extends SSLEngine> bcSSLEngineClass;
50      private static volatile boolean attemptedLoading;
51  
52      /**
53       * Indicate whether the BouncyCastle Java Crypto Extensions provider is available.
54       */
55      public static boolean isBcProvAvailable() {
56          ensureLoaded();
57          return unavailabilityCauseBcProv == null;
58      }
59  
60      /**
61       * Indicate whether the BouncyCastle Public-Key Infrastructure utilities are available.
62       */
63      public static boolean isBcPkixAvailable() {
64          ensureLoaded();
65          return unavailabilityCauseBcPkix == null;
66      }
67  
68      /**
69       * Indicate whether the BouncyCastle Java Secure Socket Extensions provider is available.
70       */
71      public static boolean isBcTlsAvailable() {
72          ensureLoaded();
73          return unavailabilityCauseBcTls == null;
74      }
75  
76      /**
77       * @return the cause if unavailable. {@code null} if available.
78       */
79      public static Throwable unavailabilityCauseBcProv() {
80          ensureLoaded();
81          return unavailabilityCauseBcProv;
82      }
83  
84      /**
85       * @return the cause if unavailable. {@code null} if available.
86       */
87      public static Throwable unavailabilityCauseBcPkix() {
88          ensureLoaded();
89          return unavailabilityCauseBcPkix;
90      }
91  
92      /**
93       * @return the cause if unavailable. {@code null} if available.
94       */
95      public static Throwable unavailabilityCauseBcTls() {
96          ensureLoaded();
97          return unavailabilityCauseBcTls;
98      }
99  
100     /**
101      * Indicates whether the given SSLEngine is implemented by BouncyCastle.
102      */
103     public static boolean isBcJsseInUse(SSLEngine engine) {
104         ensureLoaded();
105         Class<? extends SSLEngine> bcEngineClass = bcSSLEngineClass;
106         return bcEngineClass != null && bcEngineClass.isInstance(engine);
107     }
108 
109     /**
110      * Get the BouncyCastle Java Crypto Extensions provider, or throw an exception if it is unavailable.
111      */
112     public static Provider getBcProviderJce() {
113         ensureLoaded();
114         Throwable cause = unavailabilityCauseBcProv;
115         Provider provider = bcProviderJce;
116         if (cause != null || provider == null) {
117             throw new IllegalStateException(cause);
118         }
119         return provider;
120     }
121 
122     /**
123      * Get the BouncyCastle Java Secure Socket Extensions provider, or throw an exception if it is unavailable.
124      */
125     public static Provider getBcProviderJsse() {
126         ensureLoaded();
127         Throwable cause = unavailabilityCauseBcTls;
128         Provider provider = bcProviderJsse;
129         if (cause != null || provider == null) {
130             throw new IllegalStateException(cause);
131         }
132         return provider;
133     }
134 
135     /**
136      * Returns the public {@link SSLEngine} sub-class that is used by bouncy-castle or {@code null} if
137      * it can't be loaded.
138      *
139      * @return engine class.
140      */
141     public static Class<? extends SSLEngine> getBcSSLEngineClass() {
142         ensureLoaded();
143         return bcSSLEngineClass;
144     }
145 
146     /**
147      * Reset the loaded providers. Useful for testing, to redo the loading under different conditions.
148      */
149     static void reset() {
150         attemptedLoading = false;
151         unavailabilityCauseBcProv = null;
152         unavailabilityCauseBcPkix = null;
153         unavailabilityCauseBcTls = null;
154         bcProviderJce = null;
155         bcProviderJsse = null;
156         bcSSLEngineClass = null;
157     }
158 
159     private static void ensureLoaded() {
160         if (!attemptedLoading) {
161             tryLoading();
162         }
163     }
164 
165     @SuppressWarnings("unchecked")
166     private static void tryLoading() {
167         AccessController.doPrivileged(new PrivilegedAction<Object>() {
168             @Override
169             public Object run() {
170                 try {
171                     // Check for bcprov-jdk18on or bc-fips:
172                     Provider provider = Security.getProvider(BC_PROVIDER_NAME);
173                     if (provider == null) {
174                         provider = Security.getProvider(BC_FIPS_PROVIDER_NAME);
175                     }
176                     if (provider == null) {
177                         ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
178                         Class<Provider> bcProviderClass;
179                         try {
180                             bcProviderClass = (Class<Provider>) Class.forName(BC_PROVIDER, true, classLoader);
181                         } catch (ClassNotFoundException e) {
182                             try {
183                                 bcProviderClass = (Class<Provider>) Class.forName(BC_FIPS_PROVIDER, true, classLoader);
184                             } catch (ClassNotFoundException ex) {
185                                 ThrowableUtil.addSuppressed(e, ex);
186                                 throw e;
187                             }
188                         }
189                         provider = bcProviderClass.getConstructor().newInstance();
190                     }
191                     bcProviderJce = provider;
192                     logger.debug("Bouncy Castle provider available");
193                 } catch (Throwable e) {
194                     logger.debug("Cannot load Bouncy Castle provider", e);
195                     unavailabilityCauseBcProv = e;
196                 }
197 
198                 try {
199                     // Check for bcpkix-jdk18on:
200                     ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
201                     Provider provider = bcProviderJce;
202                     if (provider != null) {
203                         // Use provider class loader in case it was loaded by the system loader.
204                         classLoader = provider.getClass().getClassLoader();
205                     }
206                     Class.forName(BC_PEMPARSER, true, classLoader);
207                     logger.debug("Bouncy Castle PKIX available");
208                 } catch (Throwable e) {
209                     logger.debug("Cannot load Bouncy Castle PKIX", e);
210                     unavailabilityCauseBcPkix = e;
211                 }
212 
213                 try {
214                     // Check for bctls-jdk18on:
215                     ClassLoader classLoader = BouncyCastleUtil.class.getClassLoader();
216                     Provider provider = Security.getProvider(BC_JSSE_PROVIDER_NAME);
217                     if (provider != null) {
218                         // Use provider class loader in case it was loaded by the system loader.
219                         classLoader = provider.getClass().getClassLoader();
220                     } else {
221                         Class<?> providerClass = Class.forName(BC_JSSE_PROVIDER, true, classLoader);
222                         provider = (Provider) providerClass.getConstructor().newInstance();
223                     }
224                     bcSSLEngineClass = (Class<? extends SSLEngine>) Class.forName(BC_JSSE_SSLENGINE, true, classLoader);
225                     Class.forName(BC_JSSE_ALPN_SELECTOR, true, classLoader);
226                     bcProviderJsse = provider;
227                     logger.debug("Bouncy Castle JSSE available");
228                 } catch (Throwable e) {
229                     logger.debug("Cannot load Bouncy Castle TLS", e);
230                     unavailabilityCauseBcTls = e;
231                 }
232                 attemptedLoading = true;
233                 return null;
234             }
235         });
236     }
237 
238     private BouncyCastleUtil() {
239     }
240 }