diff --git a/build.savant b/build.savant index 29d1ac1..39d3fb2 100644 --- a/build.savant +++ b/build.savant @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ savantVersion = "1.0.0" jacksonVersion = "2.10.1" -project(group: "io.fusionauth", name: "fusionauth-jwt", version: "3.1.6", licenses: ["ApacheV2_0"]) { +project(group: "io.fusionauth", name: "fusionauth-jwt", version: "3.1.7", licenses: ["ApacheV2_0"]) { workflow { standard() diff --git a/fusionauth-jwt.iml b/fusionauth-jwt.iml index 1274e4b..60e51b2 100644 --- a/fusionauth-jwt.iml +++ b/fusionauth-jwt.iml @@ -16,33 +16,33 @@ - + - + - + - + - + - + diff --git a/src/main/java/io/fusionauth/der/Tag.java b/src/main/java/io/fusionauth/der/Tag.java index 6ec1d8a..9dbd4e3 100644 --- a/src/main/java/io/fusionauth/der/Tag.java +++ b/src/main/java/io/fusionauth/der/Tag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,15 +49,41 @@ public class Tag { */ public static final int OctetString = 4; + /** + * PrintableString Tag + *

+ * 19 decimal, 0x13 hex + *

+ */ + public static final int PrintableString = 19; + /** * Sequence Tag *

+ * 16 decimal, 0x10 hex, 0b00010000 binary + *

* Because the Sequence tag is always in a constructed form (not primitive), the tag will present as 0x30 because * the 6th bit is a 1 indicating a constructed form. So the raw sequence of 0b00010000 becomes * 0b00110000 which is 48 decimal. */ public static final int Sequence = 48; + /** + * Set and Set of + *

+ * 17 decimal, 0x11 hex + *

+ */ + public static final int Set = 17; + + /** + * UTCTime Tag + *

+ * 23 decimal, 0x17 hex + *

+ */ + public static final int UTCTime = 23; + /** * True if this Tag is primitive. False if this Tag is constructed. */ @@ -141,8 +167,14 @@ public String getName() { return "Object Identifier"; case OctetString: return "Octet String"; + case PrintableString: + return "PrintableString"; case Sequence: return "Sequence"; + case Set: + return "Set"; + case UTCTime: + return "UTCTime"; default: return "Other"; } diff --git a/src/main/java/io/fusionauth/jwt/CryptoProvider.java b/src/main/java/io/fusionauth/jwt/CryptoProvider.java new file mode 100644 index 0000000..275bffc --- /dev/null +++ b/src/main/java/io/fusionauth/jwt/CryptoProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package io.fusionauth.jwt; + +import javax.crypto.Mac; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Signature; + +/** + * @author Daniel DeGroff + */ +public class CryptoProvider { + private static final boolean USE_BC_FIPS; + + private CryptoProvider() { + } + + public static Mac getMacInstance(String name) throws NoSuchAlgorithmException { + return USE_BC_FIPS ? getBCFipsMacInstance(name) : Mac.getInstance(name); + } + + public static Signature getSignatureInstance(String name) throws NoSuchAlgorithmException { + return USE_BC_FIPS ? getBCFipsSignatureInstance(name) : Signature.getInstance(name); + } + + private static Mac getBCFipsMacInstance(String name) throws NoSuchAlgorithmException { + try { + return Mac.getInstance(name, "BCFIPS"); + } catch (NoSuchProviderException e) { + // Should not happen since we checked during static initialization + throw new RuntimeException(e); + } + } + + private static Signature getBCFipsSignatureInstance(String name) throws NoSuchAlgorithmException { + try { + return Signature.getInstance(name, "BCFIPS"); + } catch (NoSuchProviderException e) { + // Should not happen since we checked during static initialization + throw new RuntimeException(e); + } + } + + static { + Signature signature = null; + try { + signature = Signature.getInstance("SHA256withRSA", "BCFIPS"); + } catch (Exception e) { + if (!(e instanceof NoSuchProviderException)) { + throw new RuntimeException(e); + } + } + + USE_BC_FIPS = signature != null; + } +} diff --git a/src/main/java/io/fusionauth/jwt/ec/ECSigner.java b/src/main/java/io/fusionauth/jwt/ec/ECSigner.java index 2ef3f1c..dc19c22 100644 --- a/src/main/java/io/fusionauth/jwt/ec/ECSigner.java +++ b/src/main/java/io/fusionauth/jwt/ec/ECSigner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import io.fusionauth.jwt.InvalidKeyTypeException; import io.fusionauth.jwt.JWTSigningException; import io.fusionauth.jwt.MissingPrivateKeyException; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.Signer; import io.fusionauth.jwt.domain.Algorithm; import io.fusionauth.pem.domain.PEM; @@ -138,7 +139,7 @@ public byte[] sign(String message) { Objects.requireNonNull(message); try { - Signature signature = Signature.getInstance(algorithm.getName()); + Signature signature = CryptoProvider.getSignatureInstance(algorithm.getName()); signature.initSign(privateKey, new SecureRandom()); signature.update((message).getBytes(StandardCharsets.UTF_8)); byte[] derEncoded = signature.sign(); diff --git a/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java b/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java index abb044e..e019259 100644 --- a/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java +++ b/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.fusionauth.jwt.InvalidKeyTypeException; import io.fusionauth.jwt.JWTVerifierException; import io.fusionauth.jwt.MissingPublicKeyException; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.Verifier; import io.fusionauth.jwt.domain.Algorithm; import io.fusionauth.pem.domain.PEM; @@ -126,7 +127,7 @@ public void verify(Algorithm algorithm, byte[] message, byte[] signature) { Objects.requireNonNull(signature); try { - Signature verifier = Signature.getInstance(algorithm.getName()); + Signature verifier = CryptoProvider.getSignatureInstance(algorithm.getName()); verifier.initVerify(publicKey); verifier.update(message); diff --git a/src/main/java/io/fusionauth/jwt/hmac/HMACSigner.java b/src/main/java/io/fusionauth/jwt/hmac/HMACSigner.java index 88c00ec..2e9a039 100644 --- a/src/main/java/io/fusionauth/jwt/hmac/HMACSigner.java +++ b/src/main/java/io/fusionauth/jwt/hmac/HMACSigner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.fusionauth.jwt.hmac; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.JWTSigningException; import io.fusionauth.jwt.Signer; import io.fusionauth.jwt.domain.Algorithm; @@ -126,7 +127,7 @@ public byte[] sign(String message) { Objects.requireNonNull(message); try { - Mac mac = Mac.getInstance(algorithm.getName()); + Mac mac = CryptoProvider.getMacInstance(algorithm.getName()); mac.init(new SecretKeySpec(secret, algorithm.getName())); return mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); } catch (InvalidKeyException | NoSuchAlgorithmException e) { diff --git a/src/main/java/io/fusionauth/jwt/hmac/HMACVerifier.java b/src/main/java/io/fusionauth/jwt/hmac/HMACVerifier.java index 489c449..1db18a0 100644 --- a/src/main/java/io/fusionauth/jwt/hmac/HMACVerifier.java +++ b/src/main/java/io/fusionauth/jwt/hmac/HMACVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.fusionauth.jwt.hmac; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.InvalidJWTSignatureException; import io.fusionauth.jwt.JWTVerifierException; import io.fusionauth.jwt.Verifier; @@ -107,7 +108,7 @@ public void verify(Algorithm algorithm, byte[] message, byte[] signature) { Objects.requireNonNull(signature); try { - Mac mac = Mac.getInstance(algorithm.getName()); + Mac mac = CryptoProvider.getMacInstance(algorithm.getName()); mac.init(new SecretKeySpec(secret, algorithm.getName())); byte[] actualSignature = mac.doFinal(message); diff --git a/src/main/java/io/fusionauth/jwt/rsa/RSASigner.java b/src/main/java/io/fusionauth/jwt/rsa/RSASigner.java index 8205336..6dd070f 100644 --- a/src/main/java/io/fusionauth/jwt/rsa/RSASigner.java +++ b/src/main/java/io/fusionauth/jwt/rsa/RSASigner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.fusionauth.jwt.rsa; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.InvalidKeyLengthException; import io.fusionauth.jwt.JWTSigningException; import io.fusionauth.jwt.MissingPrivateKeyException; @@ -138,7 +139,7 @@ public byte[] sign(String message) { Objects.requireNonNull(message); try { - Signature signature = Signature.getInstance(algorithm.getName()); + Signature signature = CryptoProvider.getSignatureInstance(algorithm.getName()); signature.initSign(privateKey); signature.update(message.getBytes(StandardCharsets.UTF_8)); return signature.sign(); diff --git a/src/main/java/io/fusionauth/jwt/rsa/RSAVerifier.java b/src/main/java/io/fusionauth/jwt/rsa/RSAVerifier.java index 7c8c14a..eb13c8b 100644 --- a/src/main/java/io/fusionauth/jwt/rsa/RSAVerifier.java +++ b/src/main/java/io/fusionauth/jwt/rsa/RSAVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.fusionauth.jwt.rsa; +import io.fusionauth.jwt.CryptoProvider; import io.fusionauth.jwt.InvalidJWTSignatureException; import io.fusionauth.jwt.InvalidKeyLengthException; import io.fusionauth.jwt.JWTVerifierException; @@ -125,7 +126,7 @@ public void verify(Algorithm algorithm, byte[] message, byte[] signature) { Objects.requireNonNull(signature); try { - Signature verifier = Signature.getInstance(algorithm.getName()); + Signature verifier = CryptoProvider.getSignatureInstance(algorithm.getName()); verifier.initVerify(publicKey); verifier.update(message); if (!verifier.verify(signature)) { diff --git a/src/test/java/io/fusionauth/der/TagTest.java b/src/test/java/io/fusionauth/der/TagTest.java index 27721d9..6f3809f 100644 --- a/src/test/java/io/fusionauth/der/TagTest.java +++ b/src/test/java/io/fusionauth/der/TagTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ public class TagTest { @Test public void binaryAssertions() { - // I know these won't change - this is for understanding. + // I know these won't change - this is for (my) understanding. assertEquals(0b00000000, 0); // General assertEquals(0b01000000, 64); // Application @@ -50,12 +50,12 @@ public void tag() { // Context specific primitive Integer assertEquals(new Tag(0b10000010).tagClass, TagClass.ContextSpecific); assertTrue(new Tag(0b10000010).is(Tag.Integer)); - assertTrue(new Tag(0b00000010).isPrimitive()); + assertTrue(new Tag(0b10000010).isPrimitive()); // Universal primitive Object Identifier assertEquals(new Tag(0b00000110).tagClass, TagClass.Universal); assertTrue(new Tag(0b00000110).is(Tag.ObjectIdentifier)); - assertTrue(new Tag(0b00000010).isPrimitive()); + assertTrue(new Tag(0b00000110).isPrimitive()); // Context specific primitive Object Identifier assertEquals(new Tag(0b10000110).tagClass, TagClass.ContextSpecific); @@ -93,5 +93,20 @@ public void tag() { assertEquals(new Tag(0xA1).value, 1); assertEquals(new Tag(0xA1).tagClass, TagClass.ContextSpecific); + + // Universal PrintableString + assertEquals(new Tag(0b00010011).tagClass, TagClass.Universal); + assertTrue(new Tag(0b00010011).is(Tag.PrintableString)); + assertTrue(new Tag(0b00010011).isPrimitive()); + + // Universal Set + assertEquals(new Tag(0b00010001).tagClass, TagClass.Universal); + assertTrue(new Tag(0b00010001).is(Tag.Set)); + assertTrue(new Tag(0b00010001).isPrimitive()); + + // Universal UTCTime + assertEquals(new Tag(0b00010111).tagClass, TagClass.Universal); + assertTrue(new Tag(0b00010111).is(Tag.UTCTime)); + assertTrue(new Tag(0b00010111).isPrimitive()); } } diff --git a/src/test/java/io/fusionauth/jwt/BaseTest.java b/src/test/java/io/fusionauth/jwt/BaseTest.java index 327b516..76bdfdb 100644 --- a/src/test/java/io/fusionauth/jwt/BaseTest.java +++ b/src/test/java/io/fusionauth/jwt/BaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import io.fusionauth.jwt.json.Mapper; import org.testng.Assert; +import org.testng.annotations.BeforeSuite; import java.io.IOException; import java.nio.file.Files; @@ -65,6 +66,11 @@ private static List deepSort(List list) { return sorted; } + @BeforeSuite + public void beforeSuite() { +// Security.addProvider(new BouncyCastleFipsProvider()); + } + @SuppressWarnings("unchecked") protected void assertJSONEquals(Object object, String jsonFile) throws IOException { Map actual = Mapper.deserialize(Mapper.serialize(object), Map.class); diff --git a/src/test/java/io/fusionauth/jwt/JWTUtilsTest.java b/src/test/java/io/fusionauth/jwt/JWTUtilsTest.java index c4c2ba3..e4da714 100644 --- a/src/test/java/io/fusionauth/jwt/JWTUtilsTest.java +++ b/src/test/java/io/fusionauth/jwt/JWTUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2016-2020, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import io.fusionauth.pem.domain.PEM; import org.testng.annotations.Test; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; @@ -225,7 +224,7 @@ public void hmacSecretLengths() { @Test public void jws_x5t() { String encodedCertificate = "MIIC5jCCAc6gAwIBAgIQNCdDZLmeeL5H6O2BE+aQCjANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDEyRBREZTIFNpZ25pbmcgLSB1bWdjb25uZWN0LnVtdXNpYy5jb20wHhcNMTcxMDE4MTUyOTAzWhcNMTgxMDE4MTUyOTAzWjAvMS0wKwYDVQQDEyRBREZTIFNpZ25pbmcgLSB1bWdjb25uZWN0LnVtdXNpYy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnUl7AwWO1fjpijswRY40bs8jegA4Kz4ycM12h8PqD0CbydWyCnPmY/mzI8EPWsaT3uJ4QaYEEq+taNTu/GB8eFDs1flDb1JNjkZ2ECDZpdwgAS/z+RvI7D+tRARNUU7QvkMAOfFTb3zS4Cx52RoXlp3Bdrtzk9KaO/DJc7IoxLCAWuXL8kxuBRwfPzeQXX/i+wIRtkJAFotOq7j/XxgYO0/UzCenZDAr+Xbl8JfmrkFaegEQFwAC2/jlAP9OYjF39qD+9kI/HP9CcnXxoAIbq8lJkIKvuoURV9mErlel2Oj+tgvveq28NEV36RwqnfAqAIsAT4BTs739JUsnoHnKbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGesHLA8V2/4ljxwbjeBsBBk8fJ4DGVufKJJXBit7jb37/9/XVtkVg1Y2IuVoYnzpnOxAZ/Zizp8/HKH2bApqEOcAU3oZ471FZlzXAv1G51S0i1UUD/OWgc3z84pk9AMtWSka26GOWA4pb/Mw/nrBrG3R8NY6ZgLZQqbYR2GQBj5JXbDsJtzYkVXY6N5KmsBekVJ92ddjKMy5SfcGY0j3BFFsBOUpaONWgBFAD2rOH9FnwoY7tcTKa5u4MfwSXMYLal/Vk9kFAtBV2Uqe/MgitB8OgAGYYqGU8VRPVH4K/n8sx5EarZPXcOJkHbI/C70Puc0jxra4e4/2c4HqifMAYQ="; - byte[] derEncodedCertificate = Base64.getDecoder().decode(encodedCertificate.getBytes(Charset.forName("UTF-8"))); + byte[] derEncodedCertificate = Base64.getDecoder().decode(encodedCertificate.getBytes(StandardCharsets.UTF_8)); // Pass in Base64 encode certificate assertEquals(JWTUtils.generateJWS_x5t(encodedCertificate), "vDT213a_AF5eRdElKZla9-9dpc8");