001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.jca;
019
020
021import java.security.NoSuchAlgorithmException;
022import java.security.Provider;
023import java.security.Security;
024import javax.crypto.Cipher;
025import javax.crypto.NoSuchPaddingException;
026
027import com.nimbusds.jose.Algorithm;
028import com.nimbusds.jose.EncryptionMethod;
029import com.nimbusds.jose.JWEAlgorithm;
030import com.nimbusds.jose.JWSAlgorithm;
031
032
033/**
034 * Java Cryptography Architecture (JCA) support helper.
035 *
036 * @version 2021-09-22
037 */
038public final class JCASupport {
039
040
041        /**
042         * Checks if unlimited cryptographic strength is supported. If not
043         * download the appropriate jurisdiction policy files for your Java
044         * edition:
045         *
046         * <p><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html">JCE Unlimited Strength Jurisdiction Policy Files for Java 7</a>
047         *
048         * <p><a href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html">JCE Unlimited Strength Jurisdiction Policy Files for Java 8</a>
049         *
050         * @return {@code true} if unlimited cryptographic strength is
051         *         supported, {@code false} if not.
052         */
053        public static boolean isUnlimitedStrength() {
054
055                try {
056                        return Cipher.getMaxAllowedKeyLength("AES") >= 256;
057                } catch (NoSuchAlgorithmException e) {
058                        return false;
059                }
060        }
061        
062        
063        /**
064         * Checks if the specified JOSE algorithm is supported by the default
065         * system JCA provider(s).
066         *
067         * @param alg The JOSE algorithm. Must not be {@code null}.
068         *
069         * @return {@code true} if the JOSE algorithm is supported, else
070         *         {@code false}.
071         */
072        public static boolean isSupported(final Algorithm alg) {
073                
074                if (alg instanceof JWSAlgorithm) {
075                        return isSupported((JWSAlgorithm)alg);
076                }
077                if (alg instanceof JWEAlgorithm) {
078                        return isSupported((JWEAlgorithm)alg);
079                }
080                if (alg instanceof EncryptionMethod) {
081                        return isSupported((EncryptionMethod)alg);
082                }
083                throw new IllegalArgumentException("Unexpected algorithm class: " + alg.getClass().getCanonicalName());
084        }
085        
086        
087        /**
088         * Checks if a JOSE algorithm is supported by the the specified JCA
089         * provider.
090         *
091         * @param alg      The JOSE algorithm. Must not be {@code null}.
092         * @param provider The JCA provider. Must not be {@code null}.
093         *
094         * @return {@code true} if the JOSE algorithm is supported, else
095         *         {@code false}.
096         */
097        public static boolean isSupported(final Algorithm alg, final Provider provider) {
098                
099                if (alg instanceof JWSAlgorithm) {
100                        return isSupported((JWSAlgorithm)alg, provider);
101                }
102                if (alg instanceof JWEAlgorithm) {
103                        return isSupported((JWEAlgorithm)alg, provider);
104                }
105                if (alg instanceof EncryptionMethod) {
106                        return isSupported((EncryptionMethod)alg, provider);
107                }
108                throw new IllegalArgumentException("Unexpected algorithm class: " + alg.getClass().getCanonicalName());
109        }
110
111
112        /**
113         * Checks if the specified JWS algorithm is supported by the default
114         * system JCA provider(s).
115         *
116         * @param alg The JWS algorithm. Must not be {@code null}.
117         *
118         * @return {@code true} if the JWS algorithm is supported, else
119         *         {@code false}.
120         */
121        public static boolean isSupported(final JWSAlgorithm alg) {
122                
123                if (alg.getName().equals(Algorithm.NONE.getName())) {
124                        return true;
125                }
126
127                for (Provider p: Security.getProviders()) {
128
129                        if (isSupported(alg, p)) {
130                                return true;
131                        }
132                }
133
134                return false;
135        }
136
137
138        /**
139         * Checks if a JWS algorithm is supported by the the specified JCA
140         * provider.
141         *
142         * @param alg      The JWS algorithm. Must not be {@code null}.
143         * @param provider The JCA provider. Must not be {@code null}.
144         *
145         * @return {@code true} if the JWS algorithm is supported, else
146         *         {@code false}.
147         */
148        public static boolean isSupported(final JWSAlgorithm alg, final Provider provider) {
149
150                if (JWSAlgorithm.Family.HMAC_SHA.contains(alg)) {
151                        String jcaName;
152                        if (alg.equals(JWSAlgorithm.HS256)) {
153                                jcaName = "HMACSHA256";
154                        } else if (alg.equals(JWSAlgorithm.HS384)) {
155                                jcaName = "HMACSHA384";
156                        } else if (alg.equals(JWSAlgorithm.HS512)) {
157                                jcaName = "HMACSHA512";
158                        } else {
159                                return false;
160                        }
161                        return provider.getService("KeyGenerator", jcaName) != null;
162                }
163
164                if (JWSAlgorithm.Family.RSA.contains(alg)) {
165                        String jcaName;
166                        String jcaNameAlt = null;
167                        if (alg.equals(JWSAlgorithm.RS256)) {
168                                jcaName = "SHA256withRSA";
169                        } else if (alg.equals(JWSAlgorithm.RS384)) {
170                                jcaName = "SHA384withRSA";
171                        } else if (alg.equals(JWSAlgorithm.RS512)) {
172                                jcaName = "SHA512withRSA";
173                        } else if (alg.equals(JWSAlgorithm.PS256)) {
174                                jcaName = "RSASSA-PSS";
175                                jcaNameAlt = "SHA256withRSAandMGF1";
176                        } else if (alg.equals(JWSAlgorithm.PS384)) {
177                                jcaName = "RSASSA-PSS";
178                                jcaNameAlt = "SHA384withRSAandMGF1";
179                        } else if (alg.equals(JWSAlgorithm.PS512)) {
180                                jcaName = "RSASSA-PSS";
181                                jcaNameAlt = "SHA512withRSAandMGF1";
182                        } else {
183                                return false;
184                        }
185                        // Also try with alternative JCA name if set
186                        return provider.getService("Signature", jcaName) != null ||
187                                (jcaNameAlt != null && provider.getService("Signature", jcaNameAlt) != null);
188                }
189
190                if (JWSAlgorithm.Family.EC.contains(alg)) {
191                        String jcaName;
192                        if (alg.equals(JWSAlgorithm.ES256)) {
193                                jcaName = "SHA256withECDSA";
194                        } else if (alg.equals(JWSAlgorithm.ES384)) {
195                                jcaName = "SHA384withECDSA";
196                        } else if (alg.equals(JWSAlgorithm.ES512)) {
197                                jcaName = "SHA512withECDSA";
198                        } else {
199                                return false;
200                        }
201                        return provider.getService("Signature", jcaName) != null;
202                }
203
204                return false;
205        }
206
207
208        /**
209         * Checks if the specified JWE algorithm is supported by the default
210         * system JCA provider(s).
211         *
212         * @param alg The JWE algorithm. Must not be {@code null}.
213         *
214         * @return {@code true} if the JWE algorithm is supported, else
215         *         {@code false}.
216         */
217        public static boolean isSupported(final JWEAlgorithm alg) {
218
219                for (Provider p: Security.getProviders()) {
220
221                        if (isSupported(alg, p)) {
222                                return true;
223                        }
224                }
225
226                return false;
227        }
228
229
230        /**
231         * Checks if a JWE algorithm is supported by the the specified JCA
232         * provider.
233         *
234         * @param alg      The JWE algorithm. Must not be {@code null}.
235         * @param provider The JCA provider. Must not be {@code null}.
236         *
237         * @return {@code true} if the JWE algorithm is supported, else
238         *         {@code false}.
239         */
240        public static boolean isSupported(final JWEAlgorithm alg, final Provider provider) {
241
242                String jcaName;
243
244                if (JWEAlgorithm.Family.RSA.contains(alg)) {
245                        if (alg.equals(JWEAlgorithm.RSA1_5)) {
246                                jcaName = "RSA/ECB/PKCS1Padding";
247                        } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
248                                jcaName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
249                        } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
250                                jcaName = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
251                        } else if (alg.equals(JWEAlgorithm.RSA_OAEP_512)) {
252                                jcaName = "RSA/ECB/OAEPWithSHA-512AndMGF1Padding";
253                        } else {
254                                return false;
255                        }
256
257                        // Do direct test
258                        try {
259                                Cipher.getInstance(jcaName, provider);
260                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
261                                return false;
262                        }
263                        return true;
264                }
265
266                if (JWEAlgorithm.Family.AES_KW.contains(alg)) {
267                        return provider.getService("Cipher", "AESWrap") != null;
268                }
269
270                if (JWEAlgorithm.Family.ECDH_ES.contains(alg)) {
271                        return provider.getService("KeyAgreement", "ECDH") != null;
272                }
273
274                if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) {
275                        // Do direct test
276                        try {
277                                Cipher.getInstance("AES/GCM/NoPadding", provider);
278                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
279                                return false;
280                        }
281                        return true;
282                }
283
284                if (JWEAlgorithm.Family.PBES2.contains(alg)) {
285                        String hmac;
286                        if (alg.equals(JWEAlgorithm.PBES2_HS256_A128KW)) {
287                                hmac = "HmacSHA256";
288                        } else if (alg.equals(JWEAlgorithm.PBES2_HS384_A192KW)) {
289                                hmac = "HmacSHA384";
290                        } else {
291                                hmac = "HmacSHA512";
292                        }
293                        return provider.getService("KeyGenerator", hmac) != null;
294                }
295                
296                return JWEAlgorithm.DIR.equals(alg); // Always supported
297        }
298
299
300        /**
301         * Checks if the specified JWE encryption method is supported by the
302         * default system JCA provider(s).
303         *
304         * @param enc The JWE encryption method. Must not be {@code null}.
305         *
306         * @return {@code true} if the JWE algorithm is supported, else
307         *         {@code false}.
308         */
309        public static boolean isSupported(final EncryptionMethod enc) {
310
311                for (Provider p: Security.getProviders()) {
312
313                        if (isSupported(enc, p)) {
314                                return true;
315                        }
316                }
317
318                return false;
319        }
320
321
322        /**
323         * Checks if a JWE encryption method is supported by the specified
324         * JCA provider.
325         *
326         * @param enc      The JWE encryption method. Must not be {@code null}.
327         * @param provider The JCA provider. Must not be {@code null}.
328         *
329         * @return {@code true} if the JWE encryption method is supported, else
330         *         {@code false}.
331         */
332        public static boolean isSupported(final EncryptionMethod enc, final Provider provider) {
333
334                if (EncryptionMethod.Family.AES_CBC_HMAC_SHA.contains(enc)) {
335                        // Do direct test
336                        try {
337                                Cipher.getInstance("AES/CBC/PKCS5Padding", provider);
338                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
339                                return false;
340                        }
341                        // Check hmac
342                        String hmac;
343                        if (enc.equals(EncryptionMethod.A128CBC_HS256)) {
344                                hmac = "HmacSHA256";
345                        } else if (enc.equals(EncryptionMethod.A192CBC_HS384)) {
346                                hmac = "HmacSHA384";
347                        } else {
348                                hmac = "HmacSHA512";
349                        }
350                        return provider.getService("KeyGenerator", hmac) != null;
351                }
352
353                if (EncryptionMethod.Family.AES_GCM.contains(enc)) {
354                        // Do direct test
355                        try {
356                                Cipher.getInstance("AES/GCM/NoPadding", provider);
357                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
358                                return false;
359                        }
360                        return true;
361                }
362
363                return false;
364        }
365
366
367        /**
368         * Prevents public instantiation.
369         */
370        private JCASupport() {
371
372        }
373}