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 */
036public final class JCASupport {
037
038
039        /**
040         * Checks if unlimited cryptographic strength is supported. If not
041         * download the appropriate jurisdiction policy files for your Java
042         * edition:
043         *
044         * <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>
045         *
046         * <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>
047         *
048         * @return {@code true} if unlimited cryptographic strength is
049         *         supported, {@code false} if not.
050         */
051        public static boolean isUnlimitedStrength() {
052
053                try {
054                        return Cipher.getMaxAllowedKeyLength("AES") >= 256;
055                } catch (NoSuchAlgorithmException e) {
056                        return false;
057                }
058        }
059        
060        
061        /**
062         * Checks if the specified JOSE algorithm is supported by the default
063         * system JCA provider(s).
064         *
065         * @param alg The JOSE algorithm. Must not be {@code null}.
066         *
067         * @return {@code true} if the JOSE algorithm is supported, else
068         *         {@code false}.
069         */
070        public static boolean isSupported(final Algorithm alg) {
071                
072                if (alg instanceof JWSAlgorithm) {
073                        return isSupported((JWSAlgorithm)alg);
074                }
075                if (alg instanceof JWEAlgorithm) {
076                        return isSupported((JWEAlgorithm)alg);
077                }
078                if (alg instanceof EncryptionMethod) {
079                        return isSupported((EncryptionMethod)alg);
080                }
081                throw new IllegalArgumentException("Unexpected algorithm class: " + alg.getClass().getCanonicalName());
082        }
083        
084        
085        /**
086         * Checks if a JOSE algorithm is supported by the the specified JCA
087         * provider.
088         *
089         * @param alg      The JOSE algorithm. Must not be {@code null}.
090         * @param provider The JCA provider. Must not be {@code null}.
091         *
092         * @return {@code true} if the JOSE algorithm is supported, else
093         *         {@code false}.
094         */
095        public static boolean isSupported(final Algorithm alg, final Provider provider) {
096                
097                if (alg instanceof JWSAlgorithm) {
098                        return isSupported((JWSAlgorithm)alg, provider);
099                }
100                if (alg instanceof JWEAlgorithm) {
101                        return isSupported((JWEAlgorithm)alg, provider);
102                }
103                if (alg instanceof EncryptionMethod) {
104                        return isSupported((EncryptionMethod)alg, provider);
105                }
106                throw new IllegalArgumentException("Unexpected algorithm class: " + alg.getClass().getCanonicalName());
107        }
108
109
110        /**
111         * Checks if the specified JWS algorithm is supported by the default
112         * system JCA provider(s).
113         *
114         * @param alg The JWS algorithm. Must not be {@code null}.
115         *
116         * @return {@code true} if the JWS algorithm is supported, else
117         *         {@code false}.
118         */
119        public static boolean isSupported(final JWSAlgorithm alg) {
120                
121                if (alg.getName().equals(Algorithm.NONE.getName())) {
122                        return true;
123                }
124
125                for (Provider p: Security.getProviders()) {
126
127                        if (isSupported(alg, p)) {
128                                return true;
129                        }
130                }
131
132                return false;
133        }
134
135
136        /**
137         * Checks if a JWS algorithm is supported by the the specified JCA
138         * provider.
139         *
140         * @param alg      The JWS algorithm. Must not be {@code null}.
141         * @param provider The JCA provider. Must not be {@code null}.
142         *
143         * @return {@code true} if the JWS algorithm is supported, else
144         *         {@code false}.
145         */
146        public static boolean isSupported(final JWSAlgorithm alg, final Provider provider) {
147
148                if (JWSAlgorithm.Family.HMAC_SHA.contains(alg)) {
149                        String jcaName;
150                        if (alg.equals(JWSAlgorithm.HS256)) {
151                                jcaName = "HMACSHA256";
152                        } else if (alg.equals(JWSAlgorithm.HS384)) {
153                                jcaName = "HMACSHA384";
154                        } else if (alg.equals(JWSAlgorithm.HS512)) {
155                                jcaName = "HMACSHA512";
156                        } else {
157                                return false;
158                        }
159                        return provider.getService("KeyGenerator", jcaName) != null;
160                }
161
162                if (JWSAlgorithm.Family.RSA.contains(alg)) {
163                        String jcaName;
164                        String jcaNameAlt = null;
165                        if (alg.equals(JWSAlgorithm.RS256)) {
166                                jcaName = "SHA256withRSA";
167                        } else if (alg.equals(JWSAlgorithm.RS384)) {
168                                jcaName = "SHA384withRSA";
169                        } else if (alg.equals(JWSAlgorithm.RS512)) {
170                                jcaName = "SHA512withRSA";
171                        } else if (alg.equals(JWSAlgorithm.PS256)) {
172                                jcaName = "RSASSA-PSS";
173                                jcaNameAlt = "SHA256withRSAandMGF1";
174                        } else if (alg.equals(JWSAlgorithm.PS384)) {
175                                jcaName = "RSASSA-PSS";
176                                jcaNameAlt = "SHA384withRSAandMGF1";
177                        } else if (alg.equals(JWSAlgorithm.PS512)) {
178                                jcaName = "RSASSA-PSS";
179                                jcaNameAlt = "SHA512withRSAandMGF1";
180                        } else {
181                                return false;
182                        }
183                        // Also try with alternative JCA name if set
184                        return provider.getService("Signature", jcaName) != null ||
185                                (jcaNameAlt != null && provider.getService("Signature", jcaNameAlt) != null);
186                }
187
188                if (JWSAlgorithm.Family.EC.contains(alg)) {
189                        String jcaName;
190                        if (alg.equals(JWSAlgorithm.ES256)) {
191                                jcaName = "SHA256withECDSA";
192                        } else if (alg.equals(JWSAlgorithm.ES384)) {
193                                jcaName = "SHA384withECDSA";
194                        } else if (alg.equals(JWSAlgorithm.ES512)) {
195                                jcaName = "SHA512withECDSA";
196                        } else {
197                                return false;
198                        }
199                        return provider.getService("Signature", jcaName) != null;
200                }
201
202                return false;
203        }
204
205
206        /**
207         * Checks if the specified JWE algorithm is supported by the default
208         * system JCA provider(s).
209         *
210         * @param alg The JWE algorithm. Must not be {@code null}.
211         *
212         * @return {@code true} if the JWE algorithm is supported, else
213         *         {@code false}.
214         */
215        public static boolean isSupported(final JWEAlgorithm alg) {
216
217                for (Provider p: Security.getProviders()) {
218
219                        if (isSupported(alg, p)) {
220                                return true;
221                        }
222                }
223
224                return false;
225        }
226
227
228        /**
229         * Checks if a JWE algorithm is supported by the the specified JCA
230         * provider.
231         *
232         * @param alg      The JWE algorithm. Must not be {@code null}.
233         * @param provider The JCA provider. Must not be {@code null}.
234         *
235         * @return {@code true} if the JWE algorithm is supported, else
236         *         {@code false}.
237         */
238        public static boolean isSupported(final JWEAlgorithm alg, final Provider provider) {
239
240                String jcaName;
241
242                if (JWEAlgorithm.Family.RSA.contains(alg)) {
243                        if (alg.equals(JWEAlgorithm.RSA1_5)) {
244                                jcaName = "RSA/ECB/PKCS1Padding";
245                        } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
246                                jcaName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
247                        } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
248                                jcaName = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
249                        } else {
250                                return false;
251                        }
252
253                        // Do direct test
254                        try {
255                                Cipher.getInstance(jcaName, provider);
256                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
257                                return false;
258                        }
259                        return true;
260                }
261
262                if (JWEAlgorithm.Family.AES_KW.contains(alg)) {
263                        return provider.getService("Cipher", "AESWrap") != null;
264                }
265
266                if (JWEAlgorithm.Family.ECDH_ES.contains(alg)) {
267                        return provider.getService("KeyAgreement", "ECDH") != null;
268                }
269
270                if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) {
271                        // Do direct test
272                        try {
273                                Cipher.getInstance("AES/GCM/NoPadding", provider);
274                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
275                                return false;
276                        }
277                        return true;
278                }
279
280                if (JWEAlgorithm.Family.PBES2.contains(alg)) {
281                        String hmac;
282                        if (alg.equals(JWEAlgorithm.PBES2_HS256_A128KW)) {
283                                hmac = "HmacSHA256";
284                        } else if (alg.equals(JWEAlgorithm.PBES2_HS384_A192KW)) {
285                                hmac = "HmacSHA384";
286                        } else {
287                                hmac = "HmacSHA512";
288                        }
289                        return provider.getService("KeyGenerator", hmac) != null;
290                }
291                
292                return JWEAlgorithm.DIR.equals(alg); // Always supported
293        }
294
295
296        /**
297         * Checks if the specified JWE encryption method is supported by the
298         * default system JCA provider(s).
299         *
300         * @param enc The JWE encryption method. Must not be {@code null}.
301         *
302         * @return {@code true} if the JWE algorithm is supported, else
303         *         {@code false}.
304         */
305        public static boolean isSupported(final EncryptionMethod enc) {
306
307                for (Provider p: Security.getProviders()) {
308
309                        if (isSupported(enc, p)) {
310                                return true;
311                        }
312                }
313
314                return false;
315        }
316
317
318        /**
319         * Checks if a JWE encryption method is supported by the specified
320         * JCA provider.
321         *
322         * @param enc      The JWE encryption method. Must not be {@code null}.
323         * @param provider The JCA provider. Must not be {@code null}.
324         *
325         * @return {@code true} if the JWE encryption method is supported, else
326         *         {@code false}.
327         */
328        public static boolean isSupported(final EncryptionMethod enc, final Provider provider) {
329
330                if (EncryptionMethod.Family.AES_CBC_HMAC_SHA.contains(enc)) {
331                        // Do direct test
332                        try {
333                                Cipher.getInstance("AES/CBC/PKCS5Padding", provider);
334                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
335                                return false;
336                        }
337                        // Check hmac
338                        String hmac;
339                        if (enc.equals(EncryptionMethod.A128CBC_HS256)) {
340                                hmac = "HmacSHA256";
341                        } else if (enc.equals(EncryptionMethod.A192CBC_HS384)) {
342                                hmac = "HmacSHA384";
343                        } else {
344                                hmac = "HmacSHA512";
345                        }
346                        return provider.getService("KeyGenerator", hmac) != null;
347                }
348
349                if (EncryptionMethod.Family.AES_GCM.contains(enc)) {
350                        // Do direct test
351                        try {
352                                Cipher.getInstance("AES/GCM/NoPadding", provider);
353                        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
354                                return false;
355                        }
356                        return true;
357                }
358
359                return false;
360        }
361
362
363        /**
364         * Prevents public instantiation.
365         */
366        private JCASupport() {
367
368        }
369}