diff --git a/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java b/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java index 7e4cd54f5da..90a06e2e661 100644 --- a/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java +++ b/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java @@ -19,4 +19,5 @@ public class CryptoConstants { /** Name of Java security provider used with fips BouncyCastle. Should be used in FIPS environment */ public static final String BCFIPS_PROVIDER_ID = "BCFIPS"; + } diff --git a/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java b/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java index dee1f6bf28f..e02b6dd7edd 100644 --- a/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java +++ b/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java @@ -25,7 +25,8 @@ public class CryptoIntegration { synchronized (lock) { if (cryptoProvider == null) { cryptoProvider = detectProvider(classLoader); - logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER); + logger.debugv("java security provider: {0}", BouncyIntegration.PROVIDER); + } } } @@ -54,7 +55,7 @@ public class CryptoIntegration { throw new IllegalStateException("Multiple crypto providers loaded with the classLoader: " + classLoader + ". Make sure only one cryptoProvider available on the classpath. Available providers: " +foundProviders); } else { - logger.infof("Detected crypto provider: %s", foundProviders.get(0).getClass().getName()); + logger.debugf("Detected crypto provider: %s", foundProviders.get(0).getClass().getName()); return foundProviders.get(0); } } diff --git a/common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java b/common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java index 2609e5ff358..901b04d6872 100644 --- a/common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java +++ b/common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java @@ -1,8 +1,27 @@ package org.keycloak.common.crypto; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.Signature; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; import java.security.spec.ECParameterSpec; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; + +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; + /** * Abstraction to handle differences between the APIs for non-fips and fips mode * @@ -58,4 +77,24 @@ public interface CryptoProvider { */ ECParameterSpec createECParams(String curveName); + KeyPairGenerator getKeyPairGen(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException; + + KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException; + + Cipher getAesCbcCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException; + + Cipher getAesGcmCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException; + + SecretKeyFactory getSecretKeyFact(String keyAlgorithm) throws NoSuchAlgorithmException, NoSuchProviderException; + + KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException; + + CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException; + + CertStore getCertStore(CollectionCertStoreParameters collectionCertStoreParameters) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException; + + CertPathBuilder getCertPathBuilder() throws NoSuchAlgorithmException, NoSuchProviderException; + + Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException, NoSuchProviderException; + } diff --git a/common/src/main/java/org/keycloak/common/util/BouncyIntegration.java b/common/src/main/java/org/keycloak/common/util/BouncyIntegration.java index 6022d936b5f..8c25439aef6 100755 --- a/common/src/main/java/org/keycloak/common/util/BouncyIntegration.java +++ b/common/src/main/java/org/keycloak/common/util/BouncyIntegration.java @@ -19,7 +19,6 @@ package org.keycloak.common.util; import org.jboss.logging.Logger; import org.keycloak.common.crypto.CryptoIntegration; -import org.keycloak.common.crypto.CryptoConstants; import java.security.Provider; import java.security.Security; @@ -37,7 +36,8 @@ public class BouncyIntegration { private static String loadProvider() { Provider provider = CryptoIntegration.getProvider().getBouncyCastleProvider(); if (provider == null) { - throw new RuntimeException("Failed to load required security provider: BouncyCastleProvider or BouncyCastleFipsProvider"); + return Security.getProviders()[0].getName(); + // throw new RuntimeException("Failed to load required security provider: BouncyCastleProvider or BouncyCastleFipsProvider"); } if (Security.getProvider(provider.getName()) == null) { Security.addProvider(provider); diff --git a/common/src/main/java/org/keycloak/common/util/DerUtils.java b/common/src/main/java/org/keycloak/common/util/DerUtils.java index d3d2062783c..f00e547d524 100755 --- a/common/src/main/java/org/keycloak/common/util/DerUtils.java +++ b/common/src/main/java/org/keycloak/common/util/DerUtils.java @@ -30,6 +30,8 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import org.keycloak.common.crypto.CryptoIntegration; + /** * Extract PrivateKey, PublicKey, and X509Certificate from a DER encoded byte array or file. Usually * generated from openssl @@ -52,7 +54,7 @@ public final class DerUtils { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyFactory kf =CryptoIntegration.getProvider().getKeyFactory("RSA"); return kf.generatePrivate(spec); } @@ -63,12 +65,12 @@ public final class DerUtils { public static PublicKey decodePublicKey(byte[] der, String type) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { X509EncodedKeySpec spec = new X509EncodedKeySpec(der); - KeyFactory kf = KeyFactory.getInstance(type, BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(type); return kf.generatePublic(spec); } public static X509Certificate decodeCertificate(InputStream is) throws Exception { - CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyIntegration.PROVIDER); + CertificateFactory cf = CryptoIntegration.getProvider().getX509CertFactory(); X509Certificate cert = (X509Certificate) cf.generateCertificate(is); is.close(); return cert; @@ -77,7 +79,7 @@ public final class DerUtils { public static PrivateKey decodePrivateKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); - KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("RSA"); return kf.generatePrivate(spec); } } diff --git a/common/src/main/java/org/keycloak/common/util/KeyUtils.java b/common/src/main/java/org/keycloak/common/util/KeyUtils.java index 362f2245ea2..0cbc2c690bb 100644 --- a/common/src/main/java/org/keycloak/common/util/KeyUtils.java +++ b/common/src/main/java/org/keycloak/common/util/KeyUtils.java @@ -17,8 +17,6 @@ package org.keycloak.common.util; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; @@ -30,6 +28,11 @@ import java.security.PublicKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.RSAPublicKeySpec; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.keycloak.common.crypto.CryptoIntegration; + /** * @author Stian Thorgersen */ @@ -46,7 +49,7 @@ public class KeyUtils { public static KeyPair generateRsaKeyPair(int keysize) { try { - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyPairGenerator generator = CryptoIntegration.getProvider().getKeyPairGen("RSA"); generator.initialize(keysize); KeyPair keyPair = generator.generateKeyPair(); return keyPair; diff --git a/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java b/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java index 406e6f99036..b46b92f815a 100755 --- a/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java +++ b/common/src/main/java/org/keycloak/common/util/KeystoreUtil.java @@ -18,6 +18,7 @@ package org.keycloak.common.util; import org.keycloak.common.constants.GenericConstants; +import org.keycloak.common.crypto.CryptoIntegration; import java.io.File; import java.io.FileInputStream; @@ -65,12 +66,7 @@ public class KeystoreUtil { InputStream stream = FindFile.findFile(keystoreFile); try { - KeyStore keyStore = null; - if (format == KeystoreFormat.JKS) { - keyStore = KeyStore.getInstance(format.toString()); - } else { - keyStore = KeyStore.getInstance(format.toString(), BouncyIntegration.PROVIDER); - } + KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(format); keyStore.load(stream, storePassword.toCharArray()); PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray()); diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java index 03d41405803..3e17a09f807 100644 --- a/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java +++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java @@ -34,7 +34,7 @@ import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.jose.jwe.JWE; import org.keycloak.jose.jwe.JWEKeyStorage; import org.keycloak.jose.jwe.JWEUtils; @@ -117,15 +117,14 @@ public abstract class AesCbcHmacShaEncryptionProvider implements JWEEncryptionPr private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyIntegration.PROVIDER); - AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes); + Cipher cipher = CryptoIntegration.getProvider().getAesCbcCipher(); + AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParamSpec); return cipher.doFinal(contentBytes); } - private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyIntegration.PROVIDER); + Cipher cipher = CryptoIntegration.getProvider().getAesCbcCipher(); AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParamSpec); return cipher.doFinal(encryptedBytes); diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java index 6f47bcae21c..fe3befc691c 100644 --- a/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java +++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java @@ -27,7 +27,7 @@ import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.jose.jwe.JWE; import org.keycloak.jose.jwe.JWEKeyStorage; import org.keycloak.jose.jwe.JWEUtils; @@ -89,7 +89,7 @@ public abstract class AesGcmEncryptionProvider implements JWEEncryptionProvider } private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey, byte[] aad) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", BouncyIntegration.PROVIDER); + Cipher cipher = CryptoIntegration.getProvider().getAesGcmCipher(); GCMParameterSpec gcmParams = new GCMParameterSpec(AUTH_TAG_SIZE_BYTE * 8, ivBytes); cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmParams); cipher.updateAAD(aad); @@ -99,7 +99,7 @@ public abstract class AesGcmEncryptionProvider implements JWEEncryptionProvider } private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey, byte[] aad) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", BouncyIntegration.PROVIDER); + Cipher cipher = CryptoIntegration.getProvider().getAesGcmCipher(); GCMParameterSpec gcmParams = new GCMParameterSpec(AUTH_TAG_SIZE_BYTE * 8, ivBytes); cipher.init(Cipher.DECRYPT_MODE, aesKey, gcmParams); cipher.updateAAD(aad); diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java index d44c09197e9..14d6ee9b1ff 100755 --- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java +++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java @@ -104,7 +104,7 @@ public class JWKParser { ECParameterSpec params = CryptoIntegration.getProvider().createECParams(name); ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params); - KeyFactory kf = KeyFactory.getInstance("ECDSA"); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("ECDSA"); return kf.generatePublic(pubKeySpec); } catch (Exception e) { throw new RuntimeException(e); diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java index 220478a0158..0848fcd1d6f 100644 --- a/core/src/test/java/org/keycloak/jose/JWETest.java +++ b/core/src/test/java/org/keycloak/jose/JWETest.java @@ -52,13 +52,13 @@ public abstract class JWETest { @ClassRule public static CryptoInitRule cryptoInitRule = new CryptoInitRule(); - private static final String PAYLOAD = "Hello world! How are you man? I hope you are fine. This is some quite a long text, which is much longer than just simple 'Hello World'"; + protected static final String PAYLOAD = "Hello world! How are you man? I hope you are fine. This is some quite a long text, which is much longer than just simple 'Hello World'"; - private static final byte[] HMAC_SHA256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16 }; - private static final byte[] AES_128_KEY = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + protected static final byte[] HMAC_SHA256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16 }; + protected static final byte[] AES_128_KEY = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - private static final byte[] HMAC_SHA512_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - private static final byte[] AES_256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + protected static final byte[] HMAC_SHA512_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + protected static final byte[] AES_256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; @Test public void testDirect_Aes128CbcHmacSha256() throws Exception { @@ -79,7 +79,7 @@ public abstract class JWETest { } - private void testDirectEncryptAndDecrypt(Key aesKey, Key hmacKey, String encAlgorithm, String payload, boolean sysout) throws Exception { + protected void testDirectEncryptAndDecrypt(Key aesKey, Key hmacKey, String encAlgorithm, String payload, boolean sysout) throws Exception { JWEHeader jweHeader = new JWEHeader(JWEConstants.DIRECT, encAlgorithm, null); JWE jwe = new JWE() .header(jweHeader) diff --git a/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java b/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java index b051fd7ce67..214a072b330 100644 --- a/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java +++ b/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java @@ -23,10 +23,10 @@ import java.util.List; import org.junit.ClassRule; import org.junit.Test; import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.PemUtils; import org.keycloak.crypto.JavaAlgorithm; +import org.keycloak.crypto.KeyType; import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.rule.CryptoInitRule; import org.keycloak.util.JsonSerialization; @@ -61,7 +61,7 @@ public abstract class JWKTest { @Test public void publicRs256() throws Exception { - KeyPair keyPair = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER ).generateKeyPair(); + KeyPair keyPair = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA).generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); X509Certificate certificate = generateV1SelfSignedCertificate(keyPair, "Test"); @@ -96,7 +96,7 @@ public abstract class JWKTest { @Test public void publicRs256Chain() throws Exception { - KeyPair keyPair = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER).generateKeyPair(); + KeyPair keyPair = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA).generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); List certificates = Arrays.asList(generateV1SelfSignedCertificate(keyPair, "Test"), generateV1SelfSignedCertificate(keyPair, "Intermediate")); @@ -137,7 +137,7 @@ public abstract class JWKTest { @Test public void publicEs256() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", BouncyIntegration.PROVIDER); + KeyPairGenerator keyGen = CryptoIntegration.getProvider().getKeyPairGen(KeyType.EC); SecureRandom randomGen = new SecureRandom(); ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); keyGen.initialize(ecSpec, randomGen); diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java index 00efafa7c8f..5b2c0434ca4 100644 --- a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java +++ b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java @@ -1,11 +1,28 @@ package org.keycloak.crypto.def; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.Provider; import java.security.Security; +import java.security.Signature; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; import java.security.spec.ECParameterSpec; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; + import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec; @@ -16,6 +33,9 @@ import org.keycloak.common.crypto.ECDSACryptoProvider; import org.keycloak.common.crypto.CertificateUtilsProvider; import org.keycloak.common.crypto.PemUtilsProvider; import org.keycloak.common.crypto.UserIdentityExtractorProvider; +import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; +import org.keycloak.crypto.JavaAlgorithm; /** * @author Marek Posolda @@ -85,4 +105,65 @@ public class DefaultCryptoProvider implements CryptoProvider { return clazz.cast(new BCOCSPProvider()); } + + @Override + public KeyPairGenerator getKeyPairGen(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return KeyPairGenerator.getInstance(algorithm, BouncyIntegration.PROVIDER); + } + + + @Override + public KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return KeyFactory.getInstance(algorithm, BouncyIntegration.PROVIDER); + } + + + @Override + public Cipher getAesCbcCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + return Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyIntegration.PROVIDER); + } + + @Override + public Cipher getAesGcmCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + return Cipher.getInstance("AES/GCM/NoPadding", BouncyIntegration.PROVIDER); + } + + @Override + public SecretKeyFactory getSecretKeyFact(String keyAlgorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return SecretKeyFactory.getInstance(keyAlgorithm, BouncyIntegration.PROVIDER); + } + + @Override + public KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException { + if (format == KeystoreFormat.JKS) { + return KeyStore.getInstance(format.toString()); + } else { + return KeyStore.getInstance(format.toString(), BouncyIntegration.PROVIDER); + } + } + + @Override + public CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException { + return CertificateFactory.getInstance("X.509", BouncyIntegration.PROVIDER); + } + + @Override + public CertStore getCertStore(CollectionCertStoreParameters certStoreParams) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + + return CertStore.getInstance("Collection", certStoreParams, BouncyIntegration.PROVIDER); + + } + + + @Override + public CertPathBuilder getCertPathBuilder() throws NoSuchAlgorithmException, NoSuchProviderException { + return CertPathBuilder.getInstance("PKIX", BouncyIntegration.PROVIDER); + } + + @Override + public Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException, NoSuchProviderException { + return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName), BouncyIntegration.PROVIDER); + + } + } diff --git a/crypto/elytron/pom.xml b/crypto/elytron/pom.xml new file mode 100644 index 00000000000..564a5e00c8b --- /dev/null +++ b/crypto/elytron/pom.xml @@ -0,0 +1,72 @@ + + + + + + keycloak-crypto-parent + org.keycloak + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-crypto-elytron + Keycloak Crypto Wildfly Elytron Integration + + + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-core + test + test-jar + + + + org.keycloak + keycloak-server-spi + + + org.keycloak + keycloak-server-spi-private + + + + org.wildfly.security + wildfly-elytron + + + + org.jboss.logging + jboss-logging + provided + + + junit + junit + test + + + + + \ No newline at end of file diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java new file mode 100644 index 00000000000..ffc16e19610 --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.security.Key; + +import javax.crypto.Cipher; + +import org.keycloak.jose.jwe.JWEKeyStorage; +import org.keycloak.jose.jwe.JWEKeyStorage.KeyUse; +import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider; +import org.keycloak.jose.jwe.enc.JWEEncryptionProvider; + +/** + * @author David Anderson + */ +public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider { + + @Override + public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception { + Cipher cipher = Cipher.getInstance("AESWrap_128"); + cipher.init(Cipher.UNWRAP_MODE, encryptionKey); + return cipher.unwrap(encodedCek, "AES", Cipher.SECRET_KEY).getEncoded(); + } + + @Override + public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception { + Cipher cipher = Cipher.getInstance("AESWrap_128"); + cipher.init(Cipher.WRAP_MODE, encryptionKey); + return cipher.wrap(keyStorage.getCEKKey(KeyUse.ENCRYPTION, false)); + } + + +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java new file mode 100644 index 00000000000..0bf862b937d --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtils.java @@ -0,0 +1,313 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.keycloak.common.crypto.CertificateUtilsProvider; +import org.wildfly.security.asn1.ASN1; +import org.wildfly.security.asn1.DERDecoder; +import org.wildfly.security.x500.X500; +import org.wildfly.security.x500.cert.AuthorityKeyIdentifierExtension; +import org.wildfly.security.x500.cert.BasicConstraintsExtension; +import org.wildfly.security.x500.cert.CertificatePoliciesExtension; +import org.wildfly.security.x500.cert.CertificatePoliciesExtension.PolicyInformation; +import org.wildfly.security.x500.cert.ExtendedKeyUsageExtension; +import org.wildfly.security.x500.cert.KeyUsage; +import org.wildfly.security.x500.cert.KeyUsageExtension; +import org.wildfly.security.x500.cert.SubjectKeyIdentifierExtension; +import org.wildfly.security.x500.cert.X509CertificateBuilder; +import org.wildfly.security.x500.cert.X509CertificateExtension; + +/** + * The Class CertificateUtils provides utility functions for generation + * and usage of X.509 certificates + * + * @author David Anderson + */ +public class ElytronCertificateUtils implements CertificateUtilsProvider { + + /** + * Generates version 3 {@link java.security.cert.X509Certificate}. + * + * @param keyPair the key pair + * @param caPrivateKey the CA private key + * @param caCert the CA certificate + * @param subject the subject name + * + * @return the x509 certificate + * + * @throws Exception the exception + */ + @Override + public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, + X509Certificate caCert, + String subject) throws Exception { + try { + + X500Principal subjectdn = subjectToX500Principle(subject); + X500Principal issuerdn = subjectdn; + if (caCert != null) { + issuerdn = caCert.getSubjectX500Principal(); + } + + // Validity + ZonedDateTime notBefore = ZonedDateTime.ofInstant(new Date(System.currentTimeMillis()).toInstant(), + ZoneId.systemDefault()); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, 3); + Date validityEndDate = new Date(calendar.getTime().getTime()); + ZonedDateTime notAfter = ZonedDateTime.ofInstant(validityEndDate.toInstant(), + ZoneId.systemDefault()); + // Serial Number + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt())); + // Extended Key Usage + ArrayList ekuList = new ArrayList(); + ekuList.add(X500.OID_KP_EMAIL_PROTECTION); + ekuList.add(X500.OID_KP_SERVER_AUTH); + + X509CertificateBuilder cbuilder = new X509CertificateBuilder() + .setSubjectDn(subjectdn) + .setIssuerDn(issuerdn) + + .setNotValidBefore(notBefore) + .setNotValidAfter(notAfter) + + .setSigningKey(keyPair.getPrivate()) + .setPublicKey(keyPair.getPublic()) + + .setSerialNumber(serialNumber) + + .setSignatureAlgorithmName("SHA256withRSA") + + .setSigningKey(caPrivateKey) + + // Subject Key Identifier Extension + .addExtension(new SubjectKeyIdentifierExtension(keyPair.getPublic().getEncoded())) + + // Authority Key Identifier + .addExtension(new AuthorityKeyIdentifierExtension(keyPair.getPublic().getEncoded(), null, null)) + + // Key Usage + .addExtension( + new KeyUsageExtension(KeyUsage.digitalSignature, KeyUsage.keyCertSign, KeyUsage.cRLSign)) + + .addExtension(new ExtendedKeyUsageExtension(false, ekuList)) + + // Basic Constraints + .addExtension(new BasicConstraintsExtension(true, true, 0)); + + return cbuilder.build(); + + } catch (Exception e) { + throw new RuntimeException("Error creating X509v3Certificate.", e); + } + } + + /** + * Generate version 1 self signed {@link java.security.cert.X509Certificate}.. + * + * @param caKeyPair the CA key pair + * @param subject the subject name + * + * @return the x509 certificate + * + * @throws Exception the exception + */ + @Override + public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) { + return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis())); + } + + @Override + public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, + BigInteger serialNumber) { + try { + + X500Principal subjectdn = subjectToX500Principle(subject); + + ZonedDateTime notBefore = ZonedDateTime.ofInstant( + (new Date(System.currentTimeMillis() - 100000)).toInstant(), + ZoneId.systemDefault()); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, 10); + Date validityEndDate = new Date(calendar.getTime().getTime()); + ZonedDateTime notAfter = ZonedDateTime.ofInstant(validityEndDate.toInstant(), + ZoneId.systemDefault()); + + X509CertificateBuilder cbuilder = new X509CertificateBuilder() + .setSubjectDn(subjectdn) + .setIssuerDn(subjectdn) + .setNotValidBefore(notBefore) + .setNotValidAfter(notAfter) + + .setSigningKey(caKeyPair.getPrivate()) + .setPublicKey(caKeyPair.getPublic()) + + .setSerialNumber(serialNumber) + + .setSignatureAlgorithmName("SHA256withRSA"); + + return cbuilder.build(); + + } catch (Exception e) { + throw new RuntimeException("Error creating X509v1Certificate.", e); + } + } + + + // Some subject names will not conform to the RFC format + private static X500Principal subjectToX500Principle(String subject) { + if(!subject.startsWith("CN=")) { + subject = "CN="+subject; + } + return new X500Principal(subject); + } + + @Override + public List getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException { + byte[] policy = cert.getExtensionValue("2.5.29.32"); + + System.out.println("Policy: " + new String(policy)); + DERDecoder decPolicy = new DERDecoder(policy); + + int type = decPolicy.peekType(); + System.out.println("type " + type); + + DERDecoder der = new DERDecoder(decPolicy.decodeOctetString()); + + List policyList =new ArrayList<>(); + + while (der.hasNextElement()) { + switch (der.peekType()) { + case ASN1.SEQUENCE_TYPE: + der.startSequence(); + break; + case ASN1.OBJECT_IDENTIFIER_TYPE: + policyList.add(der.decodeObjectIdentifier()); + der.endSequence(); + break; + default: + der.skipElement(); + + } + } + + return policyList; + } + + @Override + public List getCRLDistributionPoints(X509Certificate cert) throws IOException { + byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID); + if (data == null) { + return Collections.emptyList(); + } + List distPointUrls = new ArrayList<>(); + DERDecoder der = new DERDecoder(data); + + der = new DERDecoder(der.decodeOctetString()); + + while ( der.hasNextElement() ) { + switch (der.peekType()) { + case ASN1.SEQUENCE_TYPE: + der.startSequence(); + break; + case ASN1.UTF8_STRING_TYPE: + distPointUrls.add(der.decodeUtf8String()); + break; + case 0xa0: + der.decodeImplicit(0xa0); + byte[] edata = der.decodeOctetString(); + while(!Character.isLetterOrDigit(edata[0])) { + edata = Arrays.copyOfRange(edata, 1, edata.length); + } + distPointUrls.add(new String(edata)); + break; + default: + der.skipElement(); + + } + } + + return distPointUrls; + } + + @Override + public X509Certificate createServicesTestCertificate(String dn, Date startDate, Date expiryDate, KeyPair keyPair, + String... certificatePolicyOid) { + + try { + X500Principal subjectdn = subjectToX500Principle(dn); + X500Principal issuerdn = subjectToX500Principle(dn); + + ZonedDateTime notValidBefore = ZonedDateTime.ofInstant(startDate.toInstant(), ZoneId.systemDefault()); + ZonedDateTime notValidAfter = ZonedDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()); + + X509CertificateBuilder cbuilder = new X509CertificateBuilder() + .setSubjectDn(subjectdn) + .setIssuerDn(issuerdn) + + .setNotValidBefore(notValidBefore) + .setNotValidAfter(notValidAfter) + + .setSigningKey(keyPair.getPrivate()) + .setPublicKey(keyPair.getPublic()) + + .addExtension(createPoliciesExtension(certificatePolicyOid)) + + .setSignatureAlgorithmName("SHA256withRSA"); + + return cbuilder.build(); + } catch ( DateTimeException | CertificateException e ) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private X509CertificateExtension createPoliciesExtension(String[] certificatePolicyOid) { + + List policyList = new ArrayList<>(); + for(String policyOid : certificatePolicyOid) { + policyList.add(new PolicyInformation(policyOid)); + + } + + return new CertificatePoliciesExtension(false, policyList); + + } + +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java new file mode 100644 index 00000000000..ca75b06d02e --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronECDSACryptoProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.io.IOException; +import java.math.BigInteger; + +import org.keycloak.common.crypto.ECDSACryptoProvider; +import org.wildfly.security.asn1.DERDecoder; +import org.wildfly.security.asn1.DEREncoder; + +/** + * @author David Anderson + */ +public class ElytronECDSACryptoProvider implements ECDSACryptoProvider { + + @Override + public byte[] concatenatedRSToASN1DER(final byte[] signature, int signLength) throws IOException { + int len = signLength / 2; + int arraySize = len + 1; + + byte[] r = new byte[arraySize]; + byte[] s = new byte[arraySize]; + System.arraycopy(signature, 0, r, 1, len); + System.arraycopy(signature, len, s, 1, len); + BigInteger rBigInteger = new BigInteger(r); + BigInteger sBigInteger = new BigInteger(s); + + DEREncoder seq = new DEREncoder(); + + seq.startSequence(); + seq.encodeInteger(rBigInteger); + seq.encodeInteger(sBigInteger); + + return seq.getEncoded(); + + } + + @Override + public byte[] asn1derToConcatenatedRS(final byte[] derEncodedSignatureValue, int signLength) throws IOException { + int len = signLength / 2; + + DERDecoder der = new DERDecoder(derEncodedSignatureValue); + der.startSequence(); + byte[] r = der.decodeInteger().toByteArray(); + byte[] s = der.decodeInteger().toByteArray(); + byte[] concatenatedSignatureValue = new byte[signLength]; + System.arraycopy(r, 0, concatenatedSignatureValue, 0, len); + System.arraycopy(s, 0, concatenatedSignatureValue, len, len); + + return concatenatedSignatureValue; + } + +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java new file mode 100644 index 00000000000..7054578da4e --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronOCSPProvider.java @@ -0,0 +1,160 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.io.IOException; +import java.net.URI; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CRLReason; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import javax.net.ssl.TrustManagerFactory; + +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.utils.OCSPProvider; +import org.wildfly.security.asn1.ASN1; +import org.wildfly.security.asn1.DERDecoder; +import org.wildfly.security.ssl.X509RevocationTrustManager; +import org.wildfly.security.x500.X500; + + +/** + * @author David Anderson + */ +public class ElytronOCSPProvider extends OCSPProvider { + + private final static Logger logger = Logger.getLogger(ElytronOCSPProvider.class.getName()); + + /** + * Requests certificate revocation status using OCSP. + * + * @param cert the certificate to be checked + * @param issuerCertificate the issuer certificate + * @param responderURIs the OCSP responder URIs + * @param responderCert the OCSP responder certificate + * @param date if null, the current time is used. + * @return a revocation status + * @throws CertPathValidatorException + */ + @Override + protected OCSPRevocationStatus check(KeycloakSession session, X509Certificate cert, X509Certificate issuerCertificate, List responderURIs, X509Certificate responderCert, Date date) throws CertPathValidatorException { + if (responderURIs == null || responderURIs.size() == 0) + throw new IllegalArgumentException("Need at least one responder"); + + try { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null,"pass".toCharArray()); + trustStore.setCertificateEntry("trust", cert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + + X509RevocationTrustManager trustMgr = X509RevocationTrustManager.builder() + .setOcspResponderCert(responderCert) + .setTrustStore(trustStore) + .setTrustManagerFactory(trustManagerFactory) + .build() + ; + + X509Certificate[] certs = { cert }; + trustMgr.checkClientTrusted(certs, cert.getType()); + } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) { + logger.warn("OSCP Response check failed.", e); + return unknownStatus(); + } + + return new OCSPRevocationStatus() { + + @Override + public RevocationStatus getRevocationStatus() { + return RevocationStatus.GOOD; + } + + @Override + public Date getRevocationTime() { + return null; + } + + @Override + public CRLReason getRevocationReason() { + return null; + } + + }; + } + + /** + * Extracts OCSP responder URI from X509 AIA v3 extension, if available. There can be + * multiple responder URIs encoded in the certificate. + * + * @param cert + * @return a list of available responder URIs. + * @throws CertificateEncodingException + */ + @Override + protected List getResponderURIs(X509Certificate cert) throws CertificateEncodingException { + + LinkedList responderURIs = new LinkedList<>(); + + byte[] authinfob = cert.getExtensionValue(X500.OID_PE_AUTHORITY_INFO_ACCESS); + DERDecoder der = new DERDecoder(authinfob); + + der = new DERDecoder(der.decodeOctetString()); + + while ( der.hasNextElement() ) { + switch (der.peekType()) { + case ASN1.SEQUENCE_TYPE: + der.startSequence(); + break; + case ASN1.OBJECT_IDENTIFIER_TYPE: + String oid = der.decodeObjectIdentifier(); + if ("1.3.6.1.5.5.7.48.1".equals(oid)) { + byte[] uri = der.drainElementValue(); + responderURIs.add(new String(uri)); + } + break; + case ASN1.IA5_STRING_TYPE: + break; + case ASN1.UTF8_STRING_TYPE: + responderURIs.add(der.decodeUtf8String()); + break; + case 0xa0: + der.decodeImplicit(0xa0); + byte[] edata = der.decodeOctetString(); + while(!Character.isLetterOrDigit(edata[0])) { + edata = Arrays.copyOfRange(edata, 1, edata.length); + } + responderURIs.add(new String(edata)); + break; + default: + der.skipElement(); + + } + } + + return responderURIs; + } +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronPEMUtilsProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronPEMUtilsProvider.java new file mode 100644 index 00000000000..f005469a39c --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronPEMUtilsProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.security.Key; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.Base64; + +import org.jboss.logging.Logger; +import org.keycloak.common.crypto.PemUtilsProvider; +import org.keycloak.common.util.DerUtils; +import org.keycloak.common.util.PemException; + +/** + * @author David Anderson + */ +public class ElytronPEMUtilsProvider extends PemUtilsProvider { + + Logger log = Logger.getLogger(ElytronPEMUtilsProvider.class); + + @Override + protected String encode(Object obj) { + String encoded = null; + if(obj instanceof Key ) { + byte[] b = ((Key)obj).getEncoded(); + encoded = Base64.getEncoder().encodeToString(b); + } else if(obj instanceof Certificate) { + byte[] c; + try { + c = ((Certificate)obj).getEncoded(); + encoded = Base64.getEncoder().encodeToString(c); + } catch (CertificateEncodingException e) { + log.warn("Failed to encoded certificate.", e); + throw new RuntimeException(e); + } + } + return encoded; + } + + @Override + public PrivateKey decodePrivateKey(String pem) { + if (pem == null) { + return null; + } + + try { + byte[] der = pemToDer(pem); + return DerUtils.decodePrivateKey(der); + } catch (Exception e) { + throw new PemException(e); + } + } + +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryption256JWEAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryption256JWEAlgorithmProvider.java new file mode 100644 index 00000000000..a80c60ea1d8 --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryption256JWEAlgorithmProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.security.AlgorithmParameters; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +/** + * @author David Anderson + */ +public class ElytronRsaKeyEncryption256JWEAlgorithmProvider extends ElytronRsaKeyEncryptionJWEAlgorithmProvider { + + public ElytronRsaKeyEncryption256JWEAlgorithmProvider(String jcaAlgorithmName) { + super(jcaAlgorithmName); + } + + @Override + protected void initCipher(Cipher cipher, int mode, Key key) throws Exception { + AlgorithmParameters algp = AlgorithmParameters.getInstance("OAEP"); + AlgorithmParameterSpec paramSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, + PSource.PSpecified.DEFAULT); + algp.init(paramSpec); + cipher.init(mode, key, algp); + } +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java new file mode 100644 index 00000000000..42431bc48ac --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.security.Key; + +import javax.crypto.Cipher; + +import org.keycloak.jose.jwe.JWEKeyStorage; +import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider; +import org.keycloak.jose.jwe.enc.JWEEncryptionProvider; + +/** + * @author David Anderson + */ +public class ElytronRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmProvider { + + private final String jcaAlgorithmName; + + public ElytronRsaKeyEncryptionJWEAlgorithmProvider(String jcaAlgorithmName) { + this.jcaAlgorithmName = jcaAlgorithmName; + } + + @Override + public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception { + Cipher cipher = getCipherProvider(); + initCipher(cipher, Cipher.DECRYPT_MODE, privateKey); + return cipher.doFinal(encodedCek); + } + + @Override + public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception { + Cipher cipher = getCipherProvider(); + initCipher(cipher, Cipher.ENCRYPT_MODE, publicKey); + byte[] cekBytes = keyStorage.getCekBytes(); + return cipher.doFinal(cekBytes); + } + + private Cipher getCipherProvider() throws Exception { + return Cipher.getInstance(jcaAlgorithmName); + } + + protected void initCipher(Cipher cipher, int mode, Key key) throws Exception { + cipher.init(mode, key); + } +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java new file mode 100644 index 00000000000..eb5c4283326 --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronUserIdentityExtractorProvider.java @@ -0,0 +1,189 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import org.jboss.logging.Logger; +import org.keycloak.common.crypto.UserIdentityExtractor; +import org.keycloak.common.crypto.UserIdentityExtractorProvider; +import org.wildfly.security.asn1.ASN1; +import org.wildfly.security.asn1.DERDecoder; +import org.wildfly.security.asn1.OidsUtil; +import org.wildfly.security.x500.GeneralName; +import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder; + +/** + * @author David Anderson + */ +public class ElytronUserIdentityExtractorProvider extends UserIdentityExtractorProvider { + + private static final Logger logger = Logger.getLogger(ElytronUserIdentityExtractorProvider.class.getName()); + + class X500NameRDNExtractorElytronProvider extends X500NameRDNExtractor { + + private String x500NameStyle; + Function x500Name; + + public X500NameRDNExtractorElytronProvider(String attrName, Function x500Name) { + //this.x500NameStyle = BCStyle.INSTANCE.attrNameToOID(attrName); + this.x500NameStyle = OidsUtil.attributeNameToOid(OidsUtil.Category.RDN, attrName); + this.x500Name = x500Name; + } + + @Override + public Object extractUserIdentity(X509Certificate[] certs) { + + if (certs == null || certs.length == 0) + throw new IllegalArgumentException(); + + Principal name = x500Name.apply(certs); + X500AttributePrincipalDecoder xDecoder = new X500AttributePrincipalDecoder(x500NameStyle); + String cn = xDecoder.apply(name); + + return cn; + + } + } + + /** + * Extracts the subject identifier from the subjectAltName extension. + */ + class SubjectAltNameExtractorEltronProvider extends SubjectAltNameExtractor { + + // User Principal Name. Used typically by Microsoft in certificates for + // Smart Card Login + private static final String UPN_OID = "1.3.6.1.4.1.311.20.2.3"; + + private final int generalName; + + /** + * Creates a new instance + * + * @param generalName an integer representing the general name. See + * {@link X509Certificate#getSubjectAlternativeNames()} + */ + SubjectAltNameExtractorEltronProvider(int generalName) { + this.generalName = generalName; + } + + @Override + public Object extractUserIdentity(X509Certificate[] certs) { + if (certs == null || certs.length == 0) { + throw new IllegalArgumentException(); + } + String subjectName = null; + + logger.info("SubjPrinc " + certs[0].getSubjectX500Principal()); + Collection> subjectAlternativeNames; + try { + subjectAlternativeNames = certs[0].getSubjectAlternativeNames(); + if (subjectAlternativeNames == null) { + return null; + } + for (List sbjAltName : subjectAlternativeNames) { + if (sbjAltName == null) + continue; + + Integer nameType = (Integer) sbjAltName.get(0); + + if (nameType == generalName) { + logger.info("sbjAltName Type " + nameType); + logger.info("sbjAltName[1]: " + sbjAltName.get(1)); + + Object sbjObj = sbjAltName.get(1); + + switch (nameType) { + case GeneralName.RFC_822_NAME: + case GeneralName.DNS_NAME: + case GeneralName.DIRECTORY_NAME: + subjectName = (String) sbjObj; + break; + case GeneralName.OTHER_NAME: + DERDecoder derDecoder = new DERDecoder((byte[])sbjObj); + derDecoder.startSequence(); + boolean upnOidFound = false; + while (derDecoder.hasNextElement() && !upnOidFound) { + int asn1Type = derDecoder.peekType(); + logger.info("ASN.1 Type: " + derDecoder.peekType()); + + switch (asn1Type) { + case ASN1.OBJECT_IDENTIFIER_TYPE: + String oid = derDecoder.decodeObjectIdentifier(); + logger.info("OID: " + oid); + if(UPN_OID.equals(oid)) { + derDecoder.decodeImplicit(160); + byte[] sb = derDecoder.drainElementValue(); + while(!Character.isLetterOrDigit(sb[0])) { + sb = Arrays.copyOfRange(sb, 1, sb.length); + } + subjectName = new String(sb, "UTF-8"); + upnOidFound = true; + } + break; + case ASN1.UTF8_STRING_TYPE: + subjectName = derDecoder.decodeUtf8String(); + break; + case ASN1.PRINTABLE_STRING_TYPE: + subjectName = derDecoder.decodePrintableString(); + break; + case ASN1.UNIVERSAL_STRING_TYPE: + subjectName = derDecoder.decodeUniversalString(); + break; + case ASN1.OCTET_STRING_TYPE: + subjectName = derDecoder.decodeOctetStringAsString(); + break; + + } + + + } + logger.info("Subject Alt Name: " + subjectName); + } + + } + + } + } catch (CertificateParsingException | UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return subjectName; + } + } + + + + @Override + public UserIdentityExtractor getX500NameExtractor(String identifier, Function x500Name) { + return new X500NameRDNExtractorElytronProvider(identifier, x500Name); + } + + @Override + public SubjectAltNameExtractor getSubjectAltNameExtractor(int generalName) { + return new SubjectAltNameExtractorEltronProvider(generalName); + } + +} diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java new file mode 100644 index 00000000000..243a1ef06dd --- /dev/null +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java @@ -0,0 +1,170 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Signature; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; + +import org.keycloak.common.crypto.CertificateUtilsProvider; +import org.keycloak.common.crypto.CryptoConstants; +import org.keycloak.common.crypto.CryptoProvider; +import org.keycloak.common.crypto.ECDSACryptoProvider; +import org.keycloak.common.crypto.PemUtilsProvider; +import org.keycloak.common.crypto.UserIdentityExtractorProvider; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; +import org.keycloak.crypto.JavaAlgorithm; + +public class WildFlyElytronProvider implements CryptoProvider { + + private Map providers = new ConcurrentHashMap<>(); + + public WildFlyElytronProvider() { + providers.put(CryptoConstants.A128KW, new AesKeyWrapAlgorithmProvider()); + providers.put(CryptoConstants.RSA1_5, new ElytronRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding")); + providers.put(CryptoConstants.RSA_OAEP, new ElytronRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding")); + providers.put(CryptoConstants.RSA_OAEP_256, new ElytronRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")); + } + + @Override + public Provider getBouncyCastleProvider() { + return null; + } + + @Override + public T getAlgorithmProvider(Class clazz, String algorithm) { + Object o = providers.get(algorithm); + if (o == null) { + throw new IllegalArgumentException("Not found provider of algorithm type: " + algorithm); + } + return clazz.cast(o); + } + + @Override + public CertificateUtilsProvider getCertificateUtils() { + return new ElytronCertificateUtils(); + } + + @Override + public PemUtilsProvider getPemUtils() { + return new ElytronPEMUtilsProvider(); + } + + @Override + public T getOCSPProver(Class clazz) { + return clazz.cast(new ElytronOCSPProvider()); + } + + @Override + public UserIdentityExtractorProvider getIdentityExtractorProvider() { + return new ElytronUserIdentityExtractorProvider(); + } + + @Override + public ECDSACryptoProvider getEcdsaCryptoProvider() { + return new ElytronECDSACryptoProvider(); + } + + @Override + public ECParameterSpec createECParams(String curveName) { + AlgorithmParameters params; + try { + params = AlgorithmParameters.getInstance("EC"); + params.init(new ECGenParameterSpec(curveName)); + return params.getParameterSpec(ECParameterSpec.class); + } catch (Exception e) { + throw new RuntimeException("Failed to generate EC parameter spec", e); + } + } + + @Override + public KeyPairGenerator getKeyPairGen(String algorithm) throws NoSuchAlgorithmException { + return KeyPairGenerator.getInstance(algorithm); + } + + @Override + public KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException { + if("ECDSA".equals(algorithm)) { + // ECDSA is not a listed JavaSE KeyFactory algorithm + // see https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#cipher-algorithm-names + algorithm = "EC"; + } + return KeyFactory.getInstance(algorithm); + } + + @Override + public Cipher getAesCbcCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { + return Cipher.getInstance("AES/CBC/PKCS5Padding"); + } + + @Override + public Cipher getAesGcmCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { + return Cipher.getInstance("AES/GCM/NoPadding"); + } + + @Override + public SecretKeyFactory getSecretKeyFact(String keyAlgorithm) throws NoSuchAlgorithmException { + return SecretKeyFactory.getInstance(keyAlgorithm); + } + + @Override + public KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException { + return KeyStore.getInstance(format.toString()); + } + + @Override + public CertificateFactory getX509CertFactory() throws CertificateException { + return CertificateFactory.getInstance("X.509"); + } + + @Override + public CertStore getCertStore(CollectionCertStoreParameters certStoreParams) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { + + return CertStore.getInstance("Collection", certStoreParams); + + } + + @Override + public CertPathBuilder getCertPathBuilder() throws NoSuchAlgorithmException { + return CertPathBuilder.getInstance("PKIX"); + } + + @Override + public Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException { + return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName)); + + } +} diff --git a/crypto/elytron/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider b/crypto/elytron/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider new file mode 100644 index 00000000000..cc85e879c71 --- /dev/null +++ b/crypto/elytron/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider @@ -0,0 +1,18 @@ +# +# Copyright 2022 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# 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. +# + +org.keycloak.crypto.elytron.WildFlyElytronProvider \ No newline at end of file diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java new file mode 100644 index 00000000000..d42e60483c4 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/CRLDistributionPointTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.junit.Test; +import org.keycloak.crypto.elytron.ElytronCertificateUtils; +import org.wildfly.security.x500.GeneralName; +import org.wildfly.security.x500.cert.CRLDistributionPoint; +import org.wildfly.security.x500.cert.CRLDistributionPoint.DistributionPointName; +import org.wildfly.security.x500.cert.CRLDistributionPoint.FullNameDistributionPointName; +import org.wildfly.security.x500.cert.CRLDistributionPointsExtension; +import org.wildfly.security.x500.cert.X509CertificateBuilder; + +/** + * @author David Anderson + */ +public class CRLDistributionPointTest { + + @Test + public void getCrlDistPoint() throws CertificateException, NoSuchAlgorithmException, IOException { + + X509Certificate cert = createCRLcert(); + List expect = new ArrayList<>(); + expect.add("http://crl.test.com"); + + + ElytronCertificateUtils bcutil = new ElytronCertificateUtils(); + List crldp = bcutil.getCRLDistributionPoints(cert); + + assertArrayEquals(expect.toArray(), crldp.toArray()); + + } + + private X509Certificate createCRLcert() throws CertificateException, NoSuchAlgorithmException { + + X500Principal dn = new X500Principal("CN=testuser,OU=UNIT,O=TST"); + List distributionPoints = new ArrayList<>(); + + List fullName = new ArrayList<>(); + fullName.add(new GeneralName.URIName("http://crl.test.com")); + DistributionPointName distributionPoint = new FullNameDistributionPointName(fullName); + CRLDistributionPoint arg0 = new CRLDistributionPoint(distributionPoint, null, null); + distributionPoints.add(arg0); + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair(); + X509CertificateBuilder cbuilder = new X509CertificateBuilder() + .setSubjectDn(dn) + .setIssuerDn(dn) + + .setSigningKey(keyPair.getPrivate()) + .setPublicKey(keyPair.getPublic()) + + .addExtension(new CRLDistributionPointsExtension(false, distributionPoints)) + + .setSignatureAlgorithmName("SHA256withRSA"); + + return cbuilder.build(); + } + + + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWETest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWETest.java new file mode 100644 index 00000000000..f3e5aa3333e --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWETest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.junit.Test; +import org.keycloak.jose.JWETest; +import org.keycloak.jose.jwe.JWEConstants; + +public class ElytronCryptoJWETest extends JWETest { + + @Test + public void testDirect_Aes256CbcHmacSha512() throws Exception { + final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES"); + final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2"); + + testDirectEncryptAndDecrypt(aesKey, hmacKey, JWEConstants.A256CBC_HS512, PAYLOAD, true); + } + + @Override + public void testAesKW_Aes128CbcHmacSha256() throws Exception { + // Skipping this test, this test fails when not using BC provider. + + } + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWKTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWKTest.java new file mode 100644 index 00000000000..98883392cf1 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoJWKTest.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import org.keycloak.jose.jwk.JWKTest; + + +public class ElytronCryptoJWKTest extends JWKTest { + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoRSAVerifierTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoRSAVerifierTest.java new file mode 100644 index 00000000000..aefabfb24d0 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronCryptoRSAVerifierTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import org.keycloak.RSAVerifierTest; + +public class ElytronCryptoRSAVerifierTest extends RSAVerifierTest { + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronHmacTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronHmacTest.java new file mode 100644 index 00000000000..a83722971df --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronHmacTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import java.security.SecureRandom; +import java.util.UUID; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.jose.HmacTest; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.HMACProvider; + + +/** + * @author David Anderson + */ +public class ElytronHmacTest extends HmacTest { + + @Test + public void testHmacSignaturesUsingKeyGen() throws Exception { + + KeyGenerator keygen = KeyGenerator.getInstance("HmacSHA256"); + SecureRandom random = SecureRandom.getInstance("NativePRNG"); + random.setSeed(UUID.randomUUID().toString().getBytes()); + keygen.init(random); + SecretKey secret = keygen.generateKey(); + + String encoded = new JWSBuilder().content("12345678901234567890".getBytes()) + .hmac256(secret); + System.out.println("length: " + encoded.length()); + JWSInput input = new JWSInput(encoded); + Assert.assertTrue(HMACProvider.verify(input, secret)); + } +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronJWKSUtilsTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronJWKSUtilsTest.java new file mode 100644 index 00000000000..0a375b064e2 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronJWKSUtilsTest.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import org.keycloak.util.JWKSUtilsTest; + + +public class ElytronJWKSUtilsTest extends JWKSUtilsTest { + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronKeyPairVerifierTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronKeyPairVerifierTest.java new file mode 100644 index 00000000000..5d9ee6d468d --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronKeyPairVerifierTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import org.junit.Before; +import org.keycloak.KeyPairVerifierTest; + +/** + * Test with default security provider and non-fips bouncycastle + * + * @author Marek Posolda + */ +public class ElytronKeyPairVerifierTest extends KeyPairVerifierTest { + + @Before + public void initPrivKeys() { + + // The parent private key is not in a supported format, using a PKCS#8 formatted key + privateKey1 = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKtWsK5O0CtuBpnM" + + "vWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfd" + + "Q2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5" + + "SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAECgYB+Y7yBWHIHF2qXGYi6CVvPxtyN" + + "BuFcktHYShLyeBNeY3VujYv3QzSZQpJ1zuoXXQuARMHOovyNiVAhu357pMfx9wSk" + + "oKNSXKrQx/+9Vt9lI1pXJxjXedPOjbuI/JZAcrk0u4nOfXG/HGtR5cjoDZYWkYQE" + + "tsePCnHlZAb0D7axwQJBAO92f00Tvkc9NU/EGqwR3bPXRMqSX0JnG7XRBvLeJBCZ" + + "YsQn0s2bLdpy8qsTeAyJg1ZvrEc8qIio5HVqzsvbhpMCQQC3K9A6UK+vmQCNWqsQ" + + "pdqWPRPN7CPB67FzSmyS8CtMjY6jTvSHrkamggotz2N/5QDr1xG2q7A/3dpkq1bT" + + "pTx1AkAXZjjiSz+Yrn57IOqKTeSgIjTypoLwdirbBWXsbZCQnqxsBogu1y8P3ZOg" + + "6/IbJ4TR+W+YNnExiW9pmdpDSVxJAkEAplTq6YmLf/F4RuQmox94tyUPbtcYQWg9" + + "42uZ3HSrXQDOng18kBj5nwpHJAJHYEQb6g2K0E5n5hcX0oKkfdx2YQJAcSKAmFiD" + + "7KQ6+vVqJlQwVPvYdTSOeZB7YVV6S4b4slS3ZObsa0yNMWgal/QnCtW5k3f185gC" + + "Wj6dOLGB5btfxg=="; + + // The parent private key is not in a supported format, using a PKCS#8 formatted key + privateKey2048 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDhXcyk6e4qx0Ft" + + "HVTM2Mr2jmZ4QxDizlWnKSG/UOmpOKUo6IQftVD9e2M3HDTKOcUGKUKekrrI32YM" + + "QdsETNpGO12uBWGQh6OJpcUE/kwGFRDmX27wTchkLcTynAONUXRn27RHiUZ5SDaT" + + "o740q6aoi/GgsJO4bTN/ndty0axF3nsu4WKmAKQ3LG+xw6G6WxsQxisvCVvlswaD" + + "H2/5EKfC+6c0RvtEr7mnM/6IfUxF6NGxf5TuH3MdGfaBCEiXtLMb0Zd5u49avgor" + + "B8wdbLQ8S5Cux9ZFeb391MRq9t82S/kTnfNZeNbJ/3d7ORWEFfRiheZ/pQ7s8CW+" + + "yuOejdO7AgMBAAECggEBALmIIA5wK0t6aGls6UAPBeA+0SsWg1NE7IzGNusqsIJI" + + "iOeJrCPygC9+IerfxLHrJ0FwPFERmMX/7CIRIT6ECnohK3k1IuH6WG7cUrtOosWr" + + "GBOf41PfpSab63STbfUsZrmNzPfLkoIMKioXdmIkIfrF4vEYDTSaafgYu+3loX6O" + + "I7zQgIaziJSD30iheFzm79VTSEHknvwGKdaKeQIAG4E2QMuAipz0Ggfgvkw7HfMO" + + "rOYd996r37ZXhfs2IPlDKLJa0AFpCkQhjmRHjxFOejrE3eG8bjz8PCQ7aAAFItD8" + + "4l3ce6m/jCWaZJzXGj3cJpXjiGraLYaxTWKbp3fENbkCgYEA8J+S8+SqvzzGD7wK" + + "7cb/cYWlSxDRUSZ77x0iNcxMkdrXcrvFpGEYcJWDhrygcn8/+81LC8/JHvWJFfhy" + + "yqQpJqmu8mTy/FtTnf26eYdYqR9QevLBCXOrg65c6M528gss5Oy7f/6Tq8AgTpJk" + + "mIOZ/Z4bGL1BubmuXETeHcdEAp8CgYEA78SiAdXzouaclMlvHWE/ch9EeTSpqJKP" + + "fmWOUDP7e/oY38pJRgJZO2nYaNEgpjepDwjuX49VMWDdJjtw+rYL1MT7rGuiJaRR" + + "3YmV08thLGlakU1iWjvT1LOYuq4OGj5/AkKcDGjEqCGxclqvPtNF83IWoNexxLqh" + + "Au6tT0/mVWUCgYEAmHVC8u1Lkme7RnTqp8WSTCdVl75MIZK0q8hVyKhtS2zRXYzD" + + "qWcryQmykEgrkOA3dh+ZER7SW59PAHCuqt5ghHK2ujZkDqj+zffZku7CqkWBBKWS" + + "0Z5Mad6sV4WZr7qM829bTbnLbuMIlUAEJO4dP6hRmtcvMbIIW8X2xf9fhBkCgYEA" + + "gJqnivSHSckIE4Y34zpWHZBH2fs1RQXXkaRHQR2gtk7fKKoHw1VfJ08OlKoXKRCR" + + "zU6tDPSEbYfXFrqrTs52ahl+JG1W+3m3r2wswP1Fkdywh19KcbvFU0FBml/hkJIU" + + "7dFsgftv//6SfxPFC52m131KRdtrrmmsEzaSHwhsM0ECgYBWwq+Su4ftErsKKYNJ" + + "Zx0Aq8cBGqSOz8+h4oQRjxi7rN/Rn1NAzmW4L0TAFqZhxK6xZFx1zP70dgbnxcWe" + + "Zer7cRS4Vsn4uNvxhYGB4+NIcOhL/r7/7OoHVvm5Cn+NgVthCXnRQ9E9MX66XV5C" + + "jLsXjc2CPf/lwNFqsVl7dlPNmg=="; + + } + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronOCSPPoviderTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronOCSPPoviderTest.java new file mode 100644 index 00000000000..631dcd8e1e4 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronOCSPPoviderTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.junit.Test; +import org.keycloak.crypto.elytron.ElytronOCSPProvider; +import org.wildfly.security.x500.GeneralName; +import org.wildfly.security.x500.cert.AccessDescription; +import org.wildfly.security.x500.cert.AuthorityInformationAccessExtension; +import org.wildfly.security.x500.cert.X509CertificateBuilder; + +/** + * @author David Anderson + */ +public class ElytronOCSPPoviderTest extends ElytronOCSPProvider { + + @Test + public void responderURITest() throws NoSuchAlgorithmException, CertificateException, URISyntaxException, KeyStoreException, IOException, CertPathValidatorException, InvalidAlgorithmParameterException { + X509Certificate cert = createCert(); + + List luri = getResponderURIs(cert); + + assertEquals(1, luri.size()); + assertEquals("http://test.localhost/check", luri.get(0)); + + } + + private X509Certificate createCert() throws NoSuchAlgorithmException, CertificateException { + X500Principal dn = new X500Principal("CN=testuser,OU=UNIT,O=TST"); + + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair(); + List accessDescriptions = new ArrayList<>(); + String accessMethodId = "1.3.6.1.5.5.7.48.1"; + GeneralName accessLocation = new GeneralName.URIName("http://test.localhost/check"); + AccessDescription adesc = new AccessDescription(accessMethodId, accessLocation); + accessDescriptions.add(adesc); + + + X509CertificateBuilder cbuilder = new X509CertificateBuilder() + .setSubjectDn(dn) + .setIssuerDn(dn) + + .setSigningKey(keyPair.getPrivate()) + .setPublicKey(keyPair.getPublic()) + + .addExtension(new AuthorityInformationAccessExtension(accessDescriptions)) + + .setSignatureAlgorithmName("SHA256withRSA"); + + return cbuilder.build(); + } + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java new file mode 100644 index 00000000000..4653cbc78e1 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronPemUtilsTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import static org.junit.Assert.assertEquals; + +import java.security.NoSuchAlgorithmException; + +import org.junit.ClassRule; +import org.junit.Test; +import org.keycloak.rule.CryptoInitRule; + +public class ElytronPemUtilsTest { + + @ClassRule + public static CryptoInitRule cinit = new CryptoInitRule(); + + @Test + public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException { + String[] test = new String[] {"abcdefg"}; + String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-1"); + assertEquals(27, encoded.length()); + } + + @Test + public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException { + String[] test = new String[] {"abcdefg"}; + String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-256"); + assertEquals(43, encoded.length()); + } +} + diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronRSAVerifierTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronRSAVerifierTest.java new file mode 100644 index 00000000000..dbc534a4434 --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronRSAVerifierTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import org.keycloak.RSAVerifierTest; + +public class ElytronRSAVerifierTest extends RSAVerifierTest { + +} diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSecureRandomTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSecureRandomTest.java new file mode 100644 index 00000000000..eddb5a40f6b --- /dev/null +++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronSecureRandomTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.keycloak.crypto.elytron.test; + +import java.security.SecureRandom; + +import org.jboss.logging.Logger; +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.keycloak.common.crypto.CryptoIntegration; +import org.keycloak.rule.CryptoInitRule; + +/** + * @author Marek Posolda + */ +public class ElytronSecureRandomTest { + + @ClassRule + public static CryptoInitRule cryptoInitRule = new CryptoInitRule(); + + protected static final Logger logger = Logger.getLogger(ElytronSecureRandomTest.class); + + @Test + public void testSecureRandom() throws Exception { + logger.info(CryptoIntegration.dumpJavaSecurityProviders()); + + SecureRandom sc1 = new SecureRandom(); + logger.infof(dumpSecureRandom("new SecureRandom()", sc1)); + + SecureRandom sc3 = SecureRandom.getInstance("SHA1PRNG"); + logger.infof(dumpSecureRandom("SecureRandom.getInstance(\"SHA1PRNG\")", sc3)); + Assert.assertEquals("SHA1PRNG", sc3.getAlgorithm()); + } + + + private String dumpSecureRandom(String prefix, SecureRandom secureRandom) { + StringBuilder builder = new StringBuilder(prefix + ": algorithm: " + secureRandom.getAlgorithm() + ", provider: " + secureRandom.getProvider() + ", random numbers: "); + for (int i=0; i < 5; i++) { + builder.append(secureRandom.nextInt(1000) + ", "); + } + return builder.toString(); + } +} diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java index 1dd04127aaa..477780d5a9b 100644 --- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java +++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java @@ -1,5 +1,12 @@ package org.keycloak.crypto.fips; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.Provider; import java.security.spec.ECField; import java.security.spec.ECFieldF2m; @@ -8,9 +15,19 @@ import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; import java.security.Security; +import java.security.Signature; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; + import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.fips.FipsRSA; @@ -23,6 +40,9 @@ import org.keycloak.common.crypto.CryptoConstants; import org.keycloak.common.crypto.CertificateUtilsProvider; import org.keycloak.common.crypto.PemUtilsProvider; import org.keycloak.common.crypto.UserIdentityExtractorProvider; +import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; +import org.keycloak.crypto.JavaAlgorithm; /** @@ -118,4 +138,62 @@ public class FIPS1402Provider implements CryptoProvider { public T getOCSPProver(Class clazz) { return clazz.cast(new BCFIPSOCSPProvider()); } + + + @Override + public KeyPairGenerator getKeyPairGen(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return KeyPairGenerator.getInstance(algorithm, BouncyIntegration.PROVIDER); + } + + @Override + public KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return KeyFactory.getInstance(algorithm , BouncyIntegration.PROVIDER); + } + + @Override + public Cipher getAesCbcCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + return Cipher.getInstance("AES/CBC/PKCS7Padding", BouncyIntegration.PROVIDER); + } + + @Override + public Cipher getAesGcmCipher() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + return Cipher.getInstance("AES/GCM/NoPadding", BouncyIntegration.PROVIDER); + } + + @Override + public SecretKeyFactory getSecretKeyFact(String keyAlgorithm) throws NoSuchAlgorithmException, NoSuchProviderException { + return SecretKeyFactory.getInstance(keyAlgorithm, BouncyIntegration.PROVIDER); + } + + @Override + public KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException { + if (format == KeystoreFormat.JKS) { + return KeyStore.getInstance(format.toString()); + } else { + return KeyStore.getInstance(format.toString(), BouncyIntegration.PROVIDER); + } + } + + @Override + public CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException { + return CertificateFactory.getInstance("X.509", BouncyIntegration.PROVIDER); + } + + @Override + public CertStore getCertStore(CollectionCertStoreParameters certStoreParams) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + + return CertStore.getInstance("Collection", certStoreParams, BouncyIntegration.PROVIDER); + + } + + @Override + public CertPathBuilder getCertPathBuilder() throws NoSuchAlgorithmException, NoSuchProviderException { + return CertPathBuilder.getInstance("PKIX", BouncyIntegration.PROVIDER); + } + + @Override + public Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException, NoSuchProviderException { + return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName), BouncyIntegration.PROVIDER); + + } } diff --git a/crypto/pom.xml b/crypto/pom.xml index ab4f21bc107..90ddb551227 100644 --- a/crypto/pom.xml +++ b/crypto/pom.xml @@ -33,5 +33,6 @@ default fips1402 + elytron \ No newline at end of file diff --git a/integration/client-cli/admin-cli/pom.xml b/integration/client-cli/admin-cli/pom.xml index d5d11a2287e..b4777ac2276 100755 --- a/integration/client-cli/admin-cli/pom.xml +++ b/integration/client-cli/admin-cli/pom.xml @@ -38,10 +38,18 @@ org.keycloak keycloak-core + + org.keycloak + ${keycloak.crypto.artifactId} + org.apache.httpcomponents httpclient + + org.jboss.logging + jboss-logging + junit @@ -67,9 +75,9 @@ org.keycloak:keycloak-core org/keycloak/util/** + org/keycloak/crypto/** org/keycloak/json/** - org/keycloak/jose/jws/** - org/keycloak/jose/jwk/** + org/keycloak/jose/** org/keycloak/representations/adapters/config/** org/keycloak/representations/adapters/action/** org/keycloak/representations/AccessTokenResponse.class @@ -92,24 +100,7 @@ org/keycloak/TokenCategory.class - - org.keycloak:keycloak-common - - org/keycloak/common/util/** - - - - org.bouncycastle:bcprov-jdk15on - - **/** - - - - org.bouncycastle:bcpkix-jdk15on - - **/** - - + com.fasterxml.jackson.core:jackson-core diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/KcAdmMain.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/KcAdmMain.java index 948007701fb..4afa122b574 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/KcAdmMain.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/KcAdmMain.java @@ -27,6 +27,7 @@ import org.keycloak.client.admin.cli.aesh.AeshEnhancer; import org.keycloak.client.admin.cli.aesh.Globals; import org.keycloak.client.admin.cli.aesh.ValveInputStream; import org.keycloak.client.admin.cli.commands.KcAdmCmd; +import org.keycloak.common.crypto.CryptoIntegration; import java.util.ArrayList; import java.util.Arrays; @@ -38,57 +39,59 @@ public class KcAdmMain { public static void main(String [] args) { + CryptoIntegration.init(KcAdmMain.class.getClassLoader()); + Globals.stdin = new ValveInputStream(); Settings settings = new SettingsBuilder() - .logging(false) - .readInputrc(false) - .disableCompletion(true) - .disableHistory(true) - .enableAlias(false) - .enableExport(false) - .inputStream(Globals.stdin) - .create(); - + .logging(false) + .readInputrc(false) + .disableCompletion(true) + .disableHistory(true) + .enableAlias(false) + .enableExport(false) + .inputStream(Globals.stdin) + .create(); + CommandRegistry registry = new AeshCommandRegistryBuilder() - .command(KcAdmCmd.class) + .command(KcAdmCmd.class) .create(); - + AeshConsoleImpl console = (AeshConsoleImpl) new AeshConsoleBuilder() - .settings(settings) - .commandRegistry(registry) - .prompt(new Prompt("")) -// .commandInvocationProvider(new CommandInvocationServices() { -// -// }) - .create(); - - AeshEnhancer.enhance(console); - - // work around parser issues with quotes and brackets - ArrayList arguments = new ArrayList<>(); - arguments.add("kcadm"); - arguments.addAll(Arrays.asList(args)); - Globals.args = arguments; - - StringBuilder b = new StringBuilder(); - for (String s : args) { - // quote if necessary - boolean needQuote = false; - needQuote = s.indexOf(' ') != -1 || s.indexOf('\"') != -1 || s.indexOf('\'') != -1; - b.append(' '); - if (needQuote) { - b.append('\''); - } - b.append(s); - if (needQuote) { - b.append('\''); + .settings(settings) + .commandRegistry(registry) + .prompt(new Prompt("")) + // .commandInvocationProvider(new CommandInvocationServices() { + // + // }) + .create(); + + AeshEnhancer.enhance(console); + + // work around parser issues with quotes and brackets + ArrayList arguments = new ArrayList<>(); + arguments.add("kcadm"); + arguments.addAll(Arrays.asList(args)); + Globals.args = arguments; + + StringBuilder b = new StringBuilder(); + for (String s : args) { + // quote if necessary + boolean needQuote = false; + needQuote = s.indexOf(' ') != -1 || s.indexOf('\"') != -1 || s.indexOf('\'') != -1; + b.append(' '); + if (needQuote) { + b.append('\''); + } + b.append(s); + if (needQuote) { + b.append('\''); + } } + console.setEcho(false); + + console.execute("kcadm" + b.toString()); + + console.start(); } - console.setEcho(false); - - console.execute("kcadm" + b.toString()); - - console.start(); } -} diff --git a/integration/client-cli/client-registration-cli/pom.xml b/integration/client-cli/client-registration-cli/pom.xml index c1b08b5f920..c0299337f34 100755 --- a/integration/client-cli/client-registration-cli/pom.xml +++ b/integration/client-cli/client-registration-cli/pom.xml @@ -38,6 +38,14 @@ org.keycloak keycloak-core + + org.keycloak + ${keycloak.crypto.artifactId} + + + org.jboss.logging + jboss-logging + org.apache.httpcomponents httpclient @@ -66,9 +74,9 @@ org.keycloak:keycloak-core org/keycloak/util/** + org/keycloak/crypto/** org/keycloak/json/** - org/keycloak/jose/jws/** - org/keycloak/jose/jwk/** + org/keycloak/jose/** org/keycloak/representations/adapters/config/** org/keycloak/representations/AccessTokenResponse.class org/keycloak/representations/idm/ClientRepresentation.class @@ -84,6 +92,7 @@ org.keycloak:keycloak-common org/keycloak/common/util/** + org/keycloak/common/crypto/** diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/KcRegMain.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/KcRegMain.java index 57fe0f6550b..7036d3dbac6 100644 --- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/KcRegMain.java +++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/KcRegMain.java @@ -11,6 +11,7 @@ import org.keycloak.client.registration.cli.aesh.AeshEnhancer; import org.keycloak.client.registration.cli.aesh.ValveInputStream; import org.keycloak.client.registration.cli.aesh.Globals; import org.keycloak.client.registration.cli.commands.KcRegCmd; +import org.keycloak.common.crypto.CryptoIntegration; import java.util.ArrayList; import java.util.Arrays; @@ -22,6 +23,8 @@ public class KcRegMain { public static void main(String [] args) { + CryptoIntegration.init(KcRegMain.class.getClassLoader()); + Globals.stdin = new ValveInputStream(); Settings settings = new SettingsBuilder() diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml index 0994f262698..5b209bfc0b4 100755 --- a/integration/client-registration/pom.xml +++ b/integration/client-registration/pom.xml @@ -35,8 +35,8 @@ keycloak-core - org.apache.httpcomponents - httpclient + org.keycloak + ${keycloak.crypto.artifactId} diff --git a/pom.xml b/pom.xml index 4bbfd908e24..b5fcf937486 100644 --- a/pom.xml +++ b/pom.xml @@ -1610,6 +1610,11 @@ keycloak-crypto-fips1402 ${project.version} + + org.keycloak + keycloak-crypto-elytron + ${project.version} + org.keycloak keycloak-admin-cli diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java index a4310bf7c73..dea29500fff 100644 --- a/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java +++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java @@ -10,23 +10,18 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.StringReader; -import java.security.KeyPair; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Scanner; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.util.Arrays; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.crypto.CryptoProvider; import org.keycloak.common.util.Base64; import org.keycloak.common.util.DerUtils; +import org.keycloak.common.util.PemUtils; import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType; import org.keycloak.dom.saml.v2.protocol.ResponseType; @@ -66,7 +61,7 @@ public class AssertionUtilTest { byte[] validSignature = Base64.decode(signatureElement.getTextContent()); // change the signature value slightly - byte[] invalidSignature = Arrays.clone(validSignature); + byte[] invalidSignature = Arrays.copyOf(validSignature, validSignature.length); invalidSignature[0] ^= invalidSignature[0]; signatureElement.setTextContent(Base64.encodeBytes(invalidSignature)); @@ -107,6 +102,7 @@ public class AssertionUtilTest { } private PrivateKey extractPrivateKey() throws IOException { + StringBuilder sb = new StringBuilder(); try (Scanner sc = new Scanner(getEncryptedIdTestFileInputStream())) { while (sc.hasNextLine()) { @@ -124,11 +120,7 @@ public class AssertionUtilTest { } } assertNotEquals("PEM certificate not found in test data", 0, sb.length()); - PEMParser pp = new PEMParser(new StringReader(sb.toString())); - PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject(); - KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair); - pp.close(); - return kp.getPrivate(); + return PemUtils.decodePrivateKey(sb.toString()); } } diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java index b49c2e6f32d..1e6b25cd2ad 100644 --- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java @@ -17,8 +17,8 @@ package org.keycloak.credential.hash; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.Base64; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.credential.PasswordCredentialModel; @@ -126,7 +126,8 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider { private SecretKeyFactory getSecretKeyFactory() { try { - return SecretKeyFactory.getInstance(pbkdf2Algorithm, BouncyIntegration.PROVIDER); + return CryptoIntegration.getProvider().getSecretKeyFact(pbkdf2Algorithm); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException("PBKDF2 algorithm not found", e); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java index ae8788508a6..9db6fbe8396 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java @@ -18,28 +18,8 @@ package org.keycloak.authentication.authenticators.x509; -import org.apache.http.client.methods.HttpGet; -import org.keycloak.common.crypto.CryptoConstants; -import org.keycloak.common.crypto.CryptoIntegration; -import org.keycloak.utils.OCSPProvider; -import org.keycloak.common.util.BouncyIntegration; -import org.keycloak.common.util.Time; -import org.keycloak.connections.httpclient.HttpClientProvider; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.saml.common.exceptions.ProcessingException; -import org.keycloak.saml.processing.core.util.XMLSignatureUtil; -import org.keycloak.services.ServicesLogger; -import org.keycloak.truststore.TruststoreProvider; -import org.keycloak.utils.CRLUtils; +import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY; -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; -import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; @@ -49,19 +29,19 @@ import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; +import java.security.cert.CRLException; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathValidatorException; import java.security.cert.CertStore; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; -import java.security.cert.CRLException; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.TrustAnchor; -import java.security.cert.X509Certificate; -import java.security.cert.X509CertSelector; import java.security.cert.X509CRL; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -73,11 +53,30 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.security.auth.x500.X500Principal; + import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; - -import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY; +import org.keycloak.common.crypto.CryptoIntegration; +import org.keycloak.common.util.Time; +import org.keycloak.connections.httpclient.HttpClientProvider; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.saml.common.exceptions.ProcessingException; +import org.keycloak.saml.processing.core.util.XMLSignatureUtil; +import org.keycloak.services.ServicesLogger; +import org.keycloak.truststore.TruststoreProvider; +import org.keycloak.utils.CRLUtils; +import org.keycloak.utils.OCSPProvider; /** * @author Peter Nalyvayko @@ -643,12 +642,11 @@ public class CertificateValidator { for (X509Certificate clientCert : certChain) { intermediateCerts.add(clientCert); } - CertStore intermediateCertStore = CertStore.getInstance("Collection", - new CollectionCertStoreParameters(intermediateCerts), BouncyIntegration.PROVIDER); + CertStore intermediateCertStore = CryptoIntegration.getProvider().getCertStore(new CollectionCertStoreParameters(intermediateCerts)); pkixParams.addCertStore(intermediateCertStore); // Build and verify the certification chain - CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BouncyIntegration.PROVIDER); + CertPathBuilder builder = CryptoIntegration.getProvider().getCertPathBuilder(); PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) builder.build(pkixParams); return result; diff --git a/services/src/main/java/org/keycloak/protocol/docker/installation/compose/DockerComposeCertsDirectory.java b/services/src/main/java/org/keycloak/protocol/docker/installation/compose/DockerComposeCertsDirectory.java index 01ffa9516a9..2d5746c42de 100644 --- a/services/src/main/java/org/keycloak/protocol/docker/installation/compose/DockerComposeCertsDirectory.java +++ b/services/src/main/java/org/keycloak/protocol/docker/installation/compose/DockerComposeCertsDirectory.java @@ -1,7 +1,8 @@ package org.keycloak.protocol.docker.installation.compose; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.CertificateUtils; +import org.keycloak.crypto.KeyType; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -24,9 +25,8 @@ public class DockerComposeCertsDirectory { public DockerComposeCertsDirectory(final String directoryName, final Certificate realmCert, final String registryCertFilename, final String registryKeyFilename, final String idpCertTrustChainFilename, final String realmName) { this.directoryName = directoryName; - final KeyPairGenerator keyGen; try { - keyGen = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + final KeyPairGenerator keyGen = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); keyGen.initialize(2048, new SecureRandom()); final KeyPair keypair = keyGen.generateKeyPair(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index 22cf0a40c33..02944c5c181 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -23,9 +23,10 @@ import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; import javax.ws.rs.NotAcceptableException; import javax.ws.rs.NotFoundException; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.StreamUtil; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.jose.jwk.JSONWebKeySet; @@ -228,9 +229,7 @@ public class ClientAttributeCertificateResource { PrivateKey privateKey = null; X509Certificate certificate = null; try { - KeyStore keyStore = null; - if (keystoreFormat.equals("JKS")) keyStore = KeyStore.getInstance("JKS"); - else keyStore = KeyStore.getInstance(keystoreFormat, BouncyIntegration.PROVIDER); + KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(keystoreFormat)); keyStore.load(inputParts.get(0).getBody(InputStream.class, null), storePassword); try { privateKey = (PrivateKey)keyStore.getKey(keyAlias, keyPassword); @@ -332,9 +331,7 @@ public class ClientAttributeCertificateResource { private byte[] getKeystore(KeyStoreConfig config, String privatePem, String certPem) { try { String format = config.getFormat(); - KeyStore keyStore; - if (format.equals("JKS")) keyStore = KeyStore.getInstance("JKS"); - else keyStore = KeyStore.getInstance(format, BouncyIntegration.PROVIDER); + KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(format)); keyStore.load(null, null); String keyAlias = config.getKeyAlias(); if (keyAlias == null) keyAlias = client.getClientId(); diff --git a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java index b4a117fdcab..54093bc6f14 100644 --- a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java +++ b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java @@ -24,7 +24,7 @@ import java.util.Set; import org.jboss.logging.Logger; import org.jboss.logging.Logger.Level; import org.jboss.resteasy.spi.HttpRequest; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.PemException; import org.keycloak.common.util.PemUtils; import org.keycloak.models.KeycloakSession; @@ -186,11 +186,11 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific // Adding the list of intermediate certificates + end user certificate intermediateCerts.add(end_user_auth_cert); CollectionCertStoreParameters intermediateCA_userCert = new CollectionCertStoreParameters(intermediateCerts); - CertStore intermediateCertStore = CertStore.getInstance("Collection", intermediateCA_userCert, BouncyIntegration.PROVIDER); + CertStore intermediateCertStore = CryptoIntegration.getProvider().getCertStore(intermediateCA_userCert); pkixParams.addCertStore(intermediateCertStore); // Build and verify the certification chain (revocation status excluded) - CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX",BouncyIntegration.PROVIDER); + CertPathBuilder certPathBuilder = CryptoIntegration.getProvider().getCertPathBuilder(); CertPath certPath = certPathBuilder.build(pkixParams).getCertPath(); log.debug("Certification path building OK, and contains " + certPath.getCertificates().size() + " X509 Certificates"); diff --git a/services/src/test/java/org/keycloak/authentication/authenticators/x509/CertificateValidatorTest.java b/services/src/test/java/org/keycloak/authentication/authenticators/x509/CertificateValidatorTest.java index 80b1c0ef40a..696878c0194 100644 --- a/services/src/test/java/org/keycloak/authentication/authenticators/x509/CertificateValidatorTest.java +++ b/services/src/test/java/org/keycloak/authentication/authenticators/x509/CertificateValidatorTest.java @@ -6,7 +6,7 @@ import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; import org.keycloak.common.crypto.CryptoIntegration; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.crypto.KeyType; import org.keycloak.rule.CryptoInitRule; import java.security.GeneralSecurityException; @@ -34,7 +34,7 @@ public class CertificateValidatorTest { */ @Test public void testValidityOfCertificatesSuccess() throws GeneralSecurityException { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); kpg.initialize(512); KeyPair keyPair = kpg.generateKeyPair(); X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils() @@ -60,7 +60,7 @@ public class CertificateValidatorTest { */ @Test public void testValidityOfCertificatesNotValidYet() throws GeneralSecurityException { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); kpg.initialize(512); KeyPair keyPair = kpg.generateKeyPair(); X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils() @@ -87,7 +87,7 @@ public class CertificateValidatorTest { */ @Test public void testValidityOfCertificatesHasExpired() throws GeneralSecurityException { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); kpg.initialize(512); KeyPair keyPair = kpg.generateKeyPair(); X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils() @@ -330,9 +330,7 @@ public class CertificateValidatorTest { private void testCertificatePolicyValidation(String expectedPolicy, String mode, String... certificatePolicyOid) throws GeneralSecurityException { - - - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); kpg.initialize(512); KeyPair keyPair = kpg.generateKeyPair(); X509Certificate certificate = CryptoIntegration.getProvider().getCertificateUtils() diff --git a/services/src/test/java/org/keycloak/procotol/docker/installation/DockerComposeYamlInstallationProviderTest.java b/services/src/test/java/org/keycloak/procotol/docker/installation/DockerComposeYamlInstallationProviderTest.java index d704a927f1f..3bcff1ba420 100644 --- a/services/src/test/java/org/keycloak/procotol/docker/installation/DockerComposeYamlInstallationProviderTest.java +++ b/services/src/test/java/org/keycloak/procotol/docker/installation/DockerComposeYamlInstallationProviderTest.java @@ -33,9 +33,10 @@ import org.junit.Before; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.PemUtils; +import org.keycloak.crypto.KeyType; import org.keycloak.protocol.docker.installation.DockerComposeYamlInstallationProvider; import org.keycloak.rule.CryptoInitRule; @@ -49,8 +50,7 @@ public class DockerComposeYamlInstallationProviderTest { @Before public void setUp() throws Exception { - final KeyPairGenerator keyGen; - keyGen = KeyPairGenerator.getInstance("RSA", BouncyIntegration.PROVIDER); + final KeyPairGenerator keyGen = CryptoIntegration.getProvider().getKeyPairGen(KeyType.RSA); keyGen.initialize(2048, new SecureRandom()); final KeyPair keypair = keyGen.generateKeyPair(); diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 788d9680178..45873f872e1 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -61,6 +61,12 @@ + + org.keycloak + keycloak-core + test + test-jar + commons-configuration commons-configuration diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java index d1a4f596b83..c451634db33 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java @@ -1,7 +1,8 @@ package org.keycloak.testsuite.util; -import org.keycloak.common.util.BouncyIntegration; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.crypto.KeyStatus; +import org.keycloak.crypto.KeyType; import org.keycloak.crypto.KeyUse; import org.keycloak.representations.idm.KeysMetadataRepresentation; @@ -22,7 +23,7 @@ public class KeyUtils { public static PublicKey publicKeyFromString(String key) { try { - KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(KeyType.RSA); byte[] encoded = Base64.getDecoder().decode(key); return kf.generatePublic(new X509EncodedKeySpec(encoded)); } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchProviderException e) { @@ -32,7 +33,7 @@ public class KeyUtils { public static PrivateKey privateKeyFromString(String key) { try { - KeyFactory kf = KeyFactory.getInstance("RSA", BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(KeyType.RSA); byte[] encoded = Base64.getDecoder().decode(key); return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded)); } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchProviderException e) { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java index 3fd8307c8ee..5c5fb3cd176 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java @@ -29,8 +29,8 @@ import javax.ws.rs.core.Response; import org.jboss.logging.Logger; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.Base64; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.JavaAlgorithm; @@ -188,8 +188,7 @@ public class TokenSignatureUtil { private static Signature getSignature(String sigAlgName) { try { - // use Bouncy Castle for signature verification intentionally - Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName), BouncyIntegration.PROVIDER); + Signature signature = CryptoIntegration.getProvider().getSignature(JavaAlgorithm.getJavaAlgorithm(sigAlgName)); return signature; } catch (Exception e) { throw new RuntimeException(e); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java index d5e784b86c7..1bbaf3de6c4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java @@ -61,7 +61,6 @@ import org.hamcrest.Matchers; import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; @@ -71,9 +70,9 @@ import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.client.registration.Auth; import org.keycloak.client.registration.ClientRegistration; import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.Time; @@ -400,14 +399,14 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { private PrivateKey decodePrivateKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(keyAlg); return kf.generatePrivate(spec); } private PublicKey decodePublicKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { X509EncodedKeySpec spec = new X509EncodedKeySpec(der); String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(keyAlg); return kf.generatePublic(spec); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java index 8a22142fa78..4588321d6dd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java @@ -43,15 +43,16 @@ import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.Time; import org.keycloak.common.util.UriUtils; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.ECDSASignatureProvider; @@ -1382,7 +1383,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { } private static KeyStore getKeystore(InputStream is, String storePassword, String format) throws Exception { - KeyStore keyStore = format.equals("JKS") ? KeyStore.getInstance(format) : KeyStore.getInstance(format, BouncyIntegration.PROVIDER); + KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(format)); keyStore.load(is, storePassword.toCharArray()); return keyStore; } @@ -1455,14 +1456,14 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { private static PrivateKey decodePrivateKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(keyAlg); return kf.generatePrivate(spec); } private static PublicKey decodePublicKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { X509EncodedKeySpec spec = new X509EncodedKeySpec(der); String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, BouncyIntegration.PROVIDER); + KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory(keyAlg); return kf.generatePublic(spec); }