diff --git a/implementation/common/pom.xml b/implementation/common/pom.xml index e08df08c..c5f7dee0 100644 --- a/implementation/common/pom.xml +++ b/implementation/common/pom.xml @@ -51,6 +51,11 @@ org.jboss.logging jboss-logging-processor + + org.junit.jupiter + junit-jupiter + test + diff --git a/implementation/common/src/main/java/io/smallrye/jwt/algorithm/KeyEncryptionAlgorithm.java b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/KeyEncryptionAlgorithm.java index 0462e00c..2a54544d 100644 --- a/implementation/common/src/main/java/io/smallrye/jwt/algorithm/KeyEncryptionAlgorithm.java +++ b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/KeyEncryptionAlgorithm.java @@ -6,30 +6,36 @@ * @see https://tools.ietf.org/html/rfc7518#section-4 */ public enum KeyEncryptionAlgorithm { - RSA_OAEP("RSA-OAEP"), - RSA_OAEP_256("RSA-OAEP-256"), - ECDH_ES("ECDH-ES"), - ECDH_ES_A28KW("ECDH-ES+128KW"), - ECDH_ES_A192KW("ECDH-ES+192KW"), - ECDH_ES_A256KW("ECDH-ES+A256KW"), - A128KW("A128KW"), - A192KW("A192KW"), - A256KW("A256KW"), - PBES2_HS256_A128KW("PBES2-HS256+A128KW"), - PBES2_HS384_A192KW("PBES2-HS384+A192KW"), - PBES2_HS512_A256KW("PBES2-HS512+A256KW"); + RSA_OAEP("RSA-OAEP", 2048), + RSA_OAEP_256("RSA-OAEP-256", 2048), + ECDH_ES("ECDH-ES", 256), + ECDH_ES_A128KW("ECDH-ES+128KW", 128), + ECDH_ES_A192KW("ECDH-ES+192KW", 192), + ECDH_ES_A256KW("ECDH-ES+A256KW", 256), + A128KW("A128KW", 128), + A192KW("A192KW", 192), + A256KW("A256KW", 256), + PBES2_HS256_A128KW("PBES2-HS256+A128KW", 256), + PBES2_HS384_A192KW("PBES2-HS384+A192KW", 384), + PBES2_HS512_A256KW("PBES2-HS512+A256KW", 512); private String algorithmName; + private int keySizeBits; - private KeyEncryptionAlgorithm(String algorithmName) { + private KeyEncryptionAlgorithm(String algorithmName, int keySizeBits) { this.algorithmName = algorithmName; - } - - public String getAlgorithm() { - return algorithmName; + this.keySizeBits = keySizeBits; } public static KeyEncryptionAlgorithm fromAlgorithm(String algorithmName) { return KeyEncryptionAlgorithm.valueOf(algorithmName.replaceAll("-", "_").replaceAll("\\+", "_")); } + + public int getKeySizeBits() { + return keySizeBits; + } + + public String getAlgorithm() { + return algorithmName; + } } diff --git a/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SignatureAlgorithm.java b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SignatureAlgorithm.java index f727d4de..4602ce4a 100644 --- a/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SignatureAlgorithm.java +++ b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SignatureAlgorithm.java @@ -6,18 +6,28 @@ * @see https://tools.ietf.org/html/rfc7518#section-3 */ public enum SignatureAlgorithm { - RS256, - RS384, - RS512, - ES256, - ES384, - ES512, - HS256, - HS384, - HS512, - PS256, - PS384, - PS512; + RS256(256), + RS384(384), + RS512(512), + ES256(256), + ES384(384), + ES512(512), + HS256(256), + HS384(384), + HS512(512), + PS256(256), + PS384(384), + PS512(512); + + private int keySize; + + private SignatureAlgorithm(int keySize) { + this.keySize = keySize; + } + + public int getKeySizeBits() { + return this.keySize; + } public String getAlgorithm() { return this.name(); diff --git a/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SymmetricKeyAlgorithm.java b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SymmetricKeyAlgorithm.java new file mode 100644 index 00000000..38e9bb5f --- /dev/null +++ b/implementation/common/src/main/java/io/smallrye/jwt/algorithm/SymmetricKeyAlgorithm.java @@ -0,0 +1,27 @@ +package io.smallrye.jwt.algorithm; + +public enum SymmetricKeyAlgorithm { + HS256("HS256", 256), + HS384("HS384", 384), + HS512("HS512", 512); + + private String algorithmName; + private int keySize; + + private SymmetricKeyAlgorithm(String algorithmName, int keySize) { + this.algorithmName = algorithmName; + this.keySize = keySize; + } + + public String getAlgorithm() { + return algorithmName; + } + + public int getKeySize() { + return keySize; + } + + public static SymmetricKeyAlgorithm fromAlgorithm(String algorithmName) { + return SymmetricKeyAlgorithm.valueOf(algorithmName.replaceAll("-", "_").replaceAll("\\+", "_")); + } +} \ No newline at end of file diff --git a/implementation/common/src/main/java/io/smallrye/jwt/util/KeyUtils.java b/implementation/common/src/main/java/io/smallrye/jwt/util/KeyUtils.java index 292464ca..207d77cc 100644 --- a/implementation/common/src/main/java/io/smallrye/jwt/util/KeyUtils.java +++ b/implementation/common/src/main/java/io/smallrye/jwt/util/KeyUtils.java @@ -31,6 +31,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.SecureRandom; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; @@ -64,6 +65,7 @@ public final class KeyUtils { private static final String RSA = "RSA"; private static final String EC = "EC"; + private static final String HMAC = "HMAC"; private KeyUtils() { } @@ -243,7 +245,47 @@ public static PublicKey decodePublicKey(String pemEncoded) throws GeneralSecurit public static SecretKey createSecretKeyFromSecret(String secret) { byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); - return new SecretKeySpec(secretBytes, "AES"); + return createSecretKeyFromSecret(secretBytes, "AES"); + } + + public static SecretKey createSecretKeyFromSecret(byte[] secretBytes, String algo) { + return new SecretKeySpec(secretBytes, algo); + } + + /** + * Generates a SecretKey. + * + * @param algo key encryption algorithm. + * @return SecretKey. + * @throws NoSuchAlgorithmException algorithm not found. + */ + public static SecretKey generateSecretKey(KeyEncryptionAlgorithm algo) throws NoSuchAlgorithmException { + final String algoName = getSymmetricAlgoName(algo); + + int keySizeBits = algo.getKeySizeBits() / 8; + byte[] bytes = new byte[keySizeBits]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(bytes); + + return createSecretKeyFromSecret(bytes, algoName); + } + + /** + * Generates a SecretKey. + * + * @param algo signature algorithm. + * @return SecretKey. + * @throws NoSuchAlgorithmException algorithm not found. + */ + public static SecretKey generateSecretKey(SignatureAlgorithm algo) throws NoSuchAlgorithmException { + final String algoName = getSymmetricAlgoName(algo); + + int keySizeBits = algo.getKeySizeBits() / 8; + byte[] bytes = new byte[keySizeBits]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(bytes); + + return createSecretKeyFromSecret(bytes, algoName); } /** @@ -273,6 +315,20 @@ public static PublicKey decodeEncryptionPublicKey(String pemEncoded, KeyEncrypti return kf.generatePublic(spec); } + static String getSymmetricAlgoName(KeyEncryptionAlgorithm algo) throws NoSuchAlgorithmException { + if (algo.name().startsWith("PBES2")) { + return HMAC; + } + throw JWTUtilMessages.msg.unsupportedAlgorithm(algo.name()); + } + + static String getSymmetricAlgoName(SignatureAlgorithm algo) throws NoSuchAlgorithmException { + if (algo.name().startsWith("HS")) { + return HMAC; + } + throw JWTUtilMessages.msg.unsupportedAlgorithm(algo.name()); + } + static String keyFactoryAlgorithm(SignatureAlgorithm algo) throws NoSuchAlgorithmException { if (algo.name().startsWith("RS") || algo.name().startsWith("PS")) { return RSA; diff --git a/implementation/common/src/test/java/io/smallrye/jwt/util/KeyUtilsTest.java b/implementation/common/src/test/java/io/smallrye/jwt/util/KeyUtilsTest.java new file mode 100644 index 00000000..e0e001a8 --- /dev/null +++ b/implementation/common/src/test/java/io/smallrye/jwt/util/KeyUtilsTest.java @@ -0,0 +1,43 @@ +package io.smallrye.jwt.util; + +import io.smallrye.jwt.algorithm.KeyEncryptionAlgorithm; +import io.smallrye.jwt.algorithm.SignatureAlgorithm; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import javax.crypto.SecretKey; +import java.security.NoSuchAlgorithmException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class KeyUtilsTest { + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.INCLUDE, names = { "PBES2_HS256_A128KW", "PBES2_HS384_A192KW", "PBES2_HS512_A256KW" }) + void givenSymmetricKey_thenReturnSecretKey(KeyEncryptionAlgorithm algo) throws NoSuchAlgorithmException { + SecretKey keySpec = KeyUtils.generateSecretKey(algo); + assertEquals(algo.getKeySizeBits() / 8, keySpec.getEncoded().length); + assertEquals("HMAC", keySpec.getAlgorithm()); + } + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.EXCLUDE, names = { "PBES2_HS256_A128KW", "PBES2_HS384_A192KW", "PBES2_HS512_A256KW" }) + void givenAsSymmetricKey_thenThrowNoSuchAlgorithmException(KeyEncryptionAlgorithm algo) { + assertThrows(NoSuchAlgorithmException.class, () -> KeyUtils.generateSecretKey(algo)); + } + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.INCLUDE, names = { "HS256", "HS384", "HS512" }) + void givenSymmetricKey_thenReturnSecretKey(SignatureAlgorithm algo) throws NoSuchAlgorithmException { + SecretKey keySpec = KeyUtils.generateSecretKey(algo); + assertEquals(algo.getKeySizeBits() / 8, keySpec.getEncoded().length); + assertEquals("HMAC", keySpec.getAlgorithm()); + } + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.EXCLUDE, names = { "HS256", "HS384", "HS512" }) + void givenAsSymmetricKey_thenThrowNoSuchAlgorithmException(SignatureAlgorithm algo) { + assertThrows(NoSuchAlgorithmException.class, () -> KeyUtils.generateSecretKey(algo)); + } +} \ No newline at end of file