Skip adding xmlsec security provider. Adding KeycloakFipsSecurityProvider to workaround 'Security.getInstance("SHA1PRNG")' (#12786)
Closes #12425 #12853
This commit is contained in:
parent
bbda7526dd
commit
4e4fc16617
18 changed files with 279 additions and 387 deletions
|
@ -1,8 +1,9 @@
|
||||||
package org.keycloak.common.crypto;
|
package org.keycloak.common.crypto;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
@ -25,6 +26,10 @@ public class CryptoIntegration {
|
||||||
if (cryptoProvider == null) {
|
if (cryptoProvider == null) {
|
||||||
cryptoProvider = detectProvider(classLoader);
|
cryptoProvider = detectProvider(classLoader);
|
||||||
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
|
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef(dumpJavaSecurityProviders());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,9 +54,17 @@ public class CryptoIntegration {
|
||||||
throw new IllegalStateException("Multiple crypto providers loaded with the classLoader: " + classLoader +
|
throw new IllegalStateException("Multiple crypto providers loaded with the classLoader: " + classLoader +
|
||||||
". Make sure only one cryptoProvider available on the classpath. Available providers: " +foundProviders);
|
". Make sure only one cryptoProvider available on the classpath. Available providers: " +foundProviders);
|
||||||
} else {
|
} else {
|
||||||
logger.infof("Detected security provider: %s", foundProviders.get(0).getClass().getName());
|
logger.infof("Detected crypto provider: %s", foundProviders.get(0).getClass().getName());
|
||||||
return foundProviders.get(0);
|
return foundProviders.get(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String dumpJavaSecurityProviders() {
|
||||||
|
StringBuilder builder = new StringBuilder("Java security providers: [ \n");
|
||||||
|
for (Provider p : Security.getProviders()) {
|
||||||
|
builder.append(" " + p.toString() + " - " + p.getClass() + ", \n");
|
||||||
|
}
|
||||||
|
return builder.append("]").toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
package org.keycloak.common.crypto;
|
package org.keycloak.common.crypto;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,11 +9,6 @@ import java.security.spec.ECParameterSpec;
|
||||||
*/
|
*/
|
||||||
public interface CryptoProvider {
|
public interface CryptoProvider {
|
||||||
|
|
||||||
/**
|
|
||||||
* @return secureRandom implementation based on the available security algorithms according to environment (FIPS non-fips)
|
|
||||||
*/
|
|
||||||
SecureRandom getSecureRandom() throws NoSuchAlgorithmException, NoSuchProviderException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get some algorithm provider implementation. Returned implementation can be dependent according to if we have
|
* Get some algorithm provider implementation. Returned implementation can be dependent according to if we have
|
||||||
* non-fips bouncycastle or fips bouncycastle on the classpath.
|
* non-fips bouncycastle or fips bouncycastle on the classpath.
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
@ -35,4 +38,16 @@ public class Environment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to detect if Java platform is in the FIPS mode
|
||||||
|
* @return true if java is FIPS mode
|
||||||
|
*/
|
||||||
|
public static boolean isJavaInFipsMode() {
|
||||||
|
for (Provider provider : Security.getProviders()) {
|
||||||
|
if (provider.getName().equals("BCFIPS")) continue; // Ignore BCFIPS provider for the detection as we may register it programatically
|
||||||
|
if (provider.getName().toUpperCase().contains("FIPS")) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ public abstract class JWKTest {
|
||||||
@Test
|
@Test
|
||||||
public void publicEs256() throws Exception {
|
public void publicEs256() throws Exception {
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", BouncyIntegration.PROVIDER);
|
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", BouncyIntegration.PROVIDER);
|
||||||
SecureRandom randomGen = CryptoIntegration.getProvider().getSecureRandom();
|
SecureRandom randomGen = new SecureRandom();
|
||||||
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
|
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
|
||||||
keyGen.initialize(ecSpec, randomGen);
|
keyGen.initialize(ecSpec, randomGen);
|
||||||
KeyPair keyPair = keyGen.generateKeyPair();
|
KeyPair keyPair = keyGen.generateKeyPair();
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
package org.keycloak.crypto.def;
|
package org.keycloak.crypto.def;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||||
|
@ -21,21 +18,16 @@ import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
*/
|
*/
|
||||||
public class DefaultCryptoProvider implements CryptoProvider {
|
public class DefaultCryptoProvider implements CryptoProvider {
|
||||||
|
|
||||||
private Map<String, Supplier<?>> providers = new HashMap<>();
|
private Map<String, Object> providers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public DefaultCryptoProvider() {
|
public DefaultCryptoProvider() {
|
||||||
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, BouncyCastleProvider::new);
|
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, new BouncyCastleProvider());
|
||||||
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, AesKeyWrapAlgorithmProvider::new);
|
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, new AesKeyWrapAlgorithmProvider());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecureRandom getSecureRandom() throws NoSuchAlgorithmException {
|
|
||||||
return SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
|
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
|
||||||
Object o = providers.get(algorithm).get();
|
Object o = providers.get(algorithm);
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
throw new IllegalArgumentException("Not found provider of algorithm: " + algorithm);
|
throw new IllegalArgumentException("Not found provider of algorithm: " + algorithm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.keycloak.crypto.def.test;
|
package org.keycloak.crypto.def.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
import org.keycloak.jose.JWETest;
|
import org.keycloak.jose.JWETest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,4 +11,10 @@ import org.keycloak.jose.JWETest;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultCryptoJWETest extends JWETest {
|
public class DefaultCryptoJWETest extends JWETest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is not in FIPS mode
|
||||||
|
Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.keycloak.crypto.def.test;
|
package org.keycloak.crypto.def.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
import org.keycloak.jose.jwk.JWKTest;
|
import org.keycloak.jose.jwk.JWKTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,4 +11,10 @@ import org.keycloak.jose.jwk.JWKTest;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultCryptoJWKTest extends JWKTest {
|
public class DefaultCryptoJWKTest extends JWKTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is not in FIPS mode
|
||||||
|
Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.keycloak.crypto.def.test;
|
package org.keycloak.crypto.def.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
import org.keycloak.KeyPairVerifierTest;
|
import org.keycloak.KeyPairVerifierTest;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test with default security provider and non-fips bouncycastle
|
* Test with default security provider and non-fips bouncycastle
|
||||||
|
@ -8,4 +11,10 @@ import org.keycloak.KeyPairVerifierTest;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultCryptoKeyPairVerifierTest extends KeyPairVerifierTest {
|
public class DefaultCryptoKeyPairVerifierTest extends KeyPairVerifierTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is not in FIPS mode
|
||||||
|
Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.keycloak.crypto.def.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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultSecureRandomTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(DefaultSecureRandomTest.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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,14 @@
|
||||||
package org.keycloak.crypto.fips;
|
package org.keycloak.crypto.fips;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.ECField;
|
import java.security.spec.ECField;
|
||||||
import java.security.spec.ECFieldF2m;
|
import java.security.spec.ECFieldF2m;
|
||||||
import java.security.spec.ECFieldFp;
|
import java.security.spec.ECFieldFp;
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
import java.security.spec.ECPoint;
|
import java.security.spec.ECPoint;
|
||||||
import java.security.spec.EllipticCurve;
|
import java.security.spec.EllipticCurve;
|
||||||
import java.util.HashMap;
|
import java.security.Security;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
|
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
@ -30,21 +27,19 @@ import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
*/
|
*/
|
||||||
public class FIPS1402Provider implements CryptoProvider {
|
public class FIPS1402Provider implements CryptoProvider {
|
||||||
|
|
||||||
private Map<String, Supplier<?>> providers = new HashMap<>();
|
private final Map<String, Object> providers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public FIPS1402Provider() {
|
public FIPS1402Provider() {
|
||||||
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, BouncyCastleFipsProvider::new);
|
BouncyCastleFipsProvider bcFipsProvider = new BouncyCastleFipsProvider();
|
||||||
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, FIPSAesKeyWrapAlgorithmProvider::new);
|
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, bcFipsProvider);
|
||||||
}
|
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, new FIPSAesKeyWrapAlgorithmProvider());
|
||||||
|
|
||||||
@Override
|
Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1);
|
||||||
public SecureRandom getSecureRandom() throws NoSuchAlgorithmException, NoSuchProviderException {
|
|
||||||
return SecureRandom.getInstance("DEFAULT","BCFIPS");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
|
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
|
||||||
Object o = providers.get(algorithm).get();
|
Object o = providers.get(algorithm);
|
||||||
if (o == null) {
|
if (o == null) {
|
||||||
throw new IllegalArgumentException("Not found provider of algorithm: " + algorithm);
|
throw new IllegalArgumentException("Not found provider of algorithm: " + algorithm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.keycloak.crypto.fips;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
|
||||||
|
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security provider to workaround usage of potentially unsecured algorithms by 3rd party dependencies.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakFipsSecurityProvider extends Provider {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(KeycloakFipsSecurityProvider.class);
|
||||||
|
|
||||||
|
private final BouncyCastleFipsProvider bcFipsProvider;
|
||||||
|
|
||||||
|
public KeycloakFipsSecurityProvider(BouncyCastleFipsProvider bcFipsProvider) {
|
||||||
|
super("KC", 1, "Keycloak pseudo provider");
|
||||||
|
this.bcFipsProvider = bcFipsProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized final Service getService(String type, String algorithm) {
|
||||||
|
// Using 'SecureRandom.getInstance("SHA1PRNG")' will delegate to BCFIPS DEFAULT provider instead of returning SecureRandom based on potentially unsecure SHA1PRNG
|
||||||
|
if ("SHA1PRNG".equals(algorithm) && "SecureRandom".equals(type)) {
|
||||||
|
logger.debug("Returning DEFAULT algorithm of BCFIPS provider instead of SHA1PRNG");
|
||||||
|
return this.bcFipsProvider.getService("SecureRandom", "DEFAULT");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package org.keycloak.crypto.fips.test;
|
package org.keycloak.crypto.fips.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
import org.keycloak.jose.JWETest;
|
import org.keycloak.jose.JWETest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,4 +11,10 @@ import org.keycloak.jose.JWETest;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class FIPS1402JWETest extends JWETest {
|
public class FIPS1402JWETest extends JWETest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is in FIPS mode
|
||||||
|
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package org.keycloak.crypto.fips.test;
|
package org.keycloak.crypto.fips.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
import org.keycloak.jose.jwk.JWKTest;
|
import org.keycloak.jose.jwk.JWKTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,10 +14,15 @@ import org.keycloak.jose.jwk.JWKTest;
|
||||||
*/
|
*/
|
||||||
public class FIPS1402JWKTest extends JWKTest {
|
public class FIPS1402JWKTest extends JWKTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is in FIPS mode
|
||||||
|
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore("Test not supported by BC FIPS")
|
@Ignore("Test not supported by BC FIPS")
|
||||||
@Test
|
@Test
|
||||||
public void publicEs256() throws Exception {
|
public void publicEs256() throws Exception {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package org.keycloak.crypto.fips.test;
|
package org.keycloak.crypto.fips.test;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Assume;
|
||||||
import org.keycloak.KeyPairVerifierTest;
|
import org.keycloak.KeyPairVerifierTest;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test with fips1402 security provider and bouncycastle-fips
|
* Test with fips1402 security provider and bouncycastle-fips
|
||||||
|
@ -58,4 +60,8 @@ public class FIPS1402KeyPairVerifierTest extends KeyPairVerifierTest {
|
||||||
"jLsXjc2CPf/lwNFqsVl7dlPNmg==";
|
"jLsXjc2CPf/lwNFqsVl7dlPNmg==";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is in FIPS mode
|
||||||
|
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.keycloak.crypto.fips.test;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
|
import org.keycloak.rule.CryptoInitRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class FIPS1402SecureRandomTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is in FIPS mode
|
||||||
|
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(FIPS1402SecureRandomTest.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecureRandom() throws Exception {
|
||||||
|
logger.info(CryptoIntegration.dumpJavaSecurityProviders());
|
||||||
|
|
||||||
|
SecureRandom sc1 = new SecureRandom();
|
||||||
|
logger.infof(dumpSecureRandom("new SecureRandom()", sc1));
|
||||||
|
|
||||||
|
SecureRandom sc2 = SecureRandom.getInstance("DEFAULT", "BCFIPS");
|
||||||
|
logger.infof(dumpSecureRandom("SecureRandom.getInstance(\"DEFAULT\", \"BCFIPS\")", sc2));
|
||||||
|
Assert.assertEquals("DEFAULT", sc2.getAlgorithm());
|
||||||
|
Assert.assertEquals("BCFIPS", sc2.getProvider().getName());
|
||||||
|
|
||||||
|
SecureRandom sc3 = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
logger.infof(dumpSecureRandom("SecureRandom.getInstance(\"SHA1PRNG\")", sc3));
|
||||||
|
Assert.assertEquals("SHA1PRNG", sc3.getAlgorithm());
|
||||||
|
Assert.assertEquals("BCFIPS", sc3.getProvider().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,127 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.saml.processing.core.util;
|
|
||||||
|
|
||||||
import org.keycloak.saml.common.PicketLinkLogger;
|
|
||||||
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
|
||||||
|
|
||||||
import org.keycloak.saml.common.util.SecurityActions;
|
|
||||||
import java.security.AccessController;
|
|
||||||
import java.security.PrivilegedAction;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility dealing with the Santuario (XMLSec) providers registration for PicketLink
|
|
||||||
*
|
|
||||||
* @author alessio.soldano@jboss.com
|
|
||||||
* @since 07-May-2012
|
|
||||||
*/
|
|
||||||
public class ProvidersUtil {
|
|
||||||
|
|
||||||
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No-op call such that the default system properties are set
|
|
||||||
*/
|
|
||||||
public static synchronized void ensure() {
|
|
||||||
AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
|
|
||||||
public Boolean run() {
|
|
||||||
// register Apache Santuario 1.5.x XMLDSig version
|
|
||||||
addXMLDSigRI();
|
|
||||||
// register BC provider if available (to have additional encryption algorithms, etc.)
|
|
||||||
// addJceProvider("BC", "org.bouncycastle.jce.provider.BouncyCastleProvider");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addXMLDSigRI() {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = SecurityActions
|
|
||||||
.loadClass(XMLSignatureUtil.class, "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI");
|
|
||||||
if (clazz == null)
|
|
||||||
throw logger.classNotLoadedError("org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI");
|
|
||||||
addJceProvider("ApacheXMLDSig", (Provider) clazz.newInstance());
|
|
||||||
} catch (Throwable t) {
|
|
||||||
// ignore - may be a NoClassDefFound if XMLDSigRI isn't avail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new JCE security provider to use for PicketLink.
|
|
||||||
*
|
|
||||||
* @param name The name string of the provider (this may not be the real name of the provider)
|
|
||||||
* @param provider A subclass of <code>java.security.Provider</code>
|
|
||||||
*
|
|
||||||
* @return Returns the actual name of the provider that was loaded
|
|
||||||
*/
|
|
||||||
private static String addJceProvider(String name, Provider provider) {
|
|
||||||
Provider currentProvider = Security.getProvider(name);
|
|
||||||
if (currentProvider == null) {
|
|
||||||
try {
|
|
||||||
//
|
|
||||||
// Install the provider after the SUN provider (see WSS-99)
|
|
||||||
// Otherwise fall back to the old behaviour of inserting
|
|
||||||
// the provider in position 2. For AIX, install it after
|
|
||||||
// the IBMJCE provider.
|
|
||||||
//
|
|
||||||
int ret = 0;
|
|
||||||
Provider[] provs = Security.getProviders();
|
|
||||||
for (int i = 0; i < provs.length; i++) {
|
|
||||||
if ("SUN".equals(provs[i].getName()) || "IBMJCE".equals(provs[i].getName())) {
|
|
||||||
ret = Security.insertProviderAt(provider, i + 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret == 0) {
|
|
||||||
ret = Security.insertProviderAt(provider, 2);
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("The provider " + provider.getName() + " - " + provider.getVersion() + " was added at position: "
|
|
||||||
+ ret);
|
|
||||||
}
|
|
||||||
return provider.getName();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.jceProviderCouldNotBeLoaded(name, t);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentProvider.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String addJceProvider(String name, String className) {
|
|
||||||
Provider currentProvider = Security.getProvider(name);
|
|
||||||
if (currentProvider == null) {
|
|
||||||
try {
|
|
||||||
// Class<? extends Provider> clazz = Loader.loadClass(className, false, Provider.class);
|
|
||||||
Class<? extends Provider> clazz = Class.forName(className).asSubclass(Provider.class);
|
|
||||||
Provider provider = clazz.newInstance();
|
|
||||||
return addJceProvider(name, provider);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.jceProviderCouldNotBeLoaded(name, t);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentProvider.getName();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ import org.apache.xml.security.utils.EncryptionConstants;
|
||||||
|
|
||||||
import org.keycloak.saml.common.PicketLinkLogger;
|
import org.keycloak.saml.common.PicketLinkLogger;
|
||||||
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
import org.keycloak.saml.common.util.StringUtil;
|
import org.keycloak.saml.common.util.StringUtil;
|
||||||
|
@ -86,7 +85,7 @@ public class XMLEncryptionUtil {
|
||||||
*
|
*
|
||||||
* @throws org.keycloak.saml.common.exceptions.ProcessingException
|
* @throws org.keycloak.saml.common.exceptions.ProcessingException
|
||||||
*/
|
*/
|
||||||
public static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
|
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
|
||||||
int keySize) throws ProcessingException {
|
int keySize) throws ProcessingException {
|
||||||
XMLCipher keyCipher;
|
XMLCipher keyCipher;
|
||||||
String pubKeyAlg = keyUsedToEncryptSecretKey.getAlgorithm();
|
String pubKeyAlg = keyUsedToEncryptSecretKey.getAlgorithm();
|
||||||
|
@ -197,176 +196,6 @@ public class XMLEncryptionUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Encrypts an element in a XML document using the specified public key, secret key, and key size. This method
|
|
||||||
* doesn't wrap
|
|
||||||
* the encrypted element in a new element. Instead, it replaces the element with its encrypted version.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* For example, calling this method to encrypt the <tt><b>inner</b></tt> element in the following XML document
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* <root>
|
|
||||||
* <outer>
|
|
||||||
* <inner>
|
|
||||||
* ...
|
|
||||||
* </inner>
|
|
||||||
* </outer>
|
|
||||||
* </root>
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* would result in a document similar to
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* <root>
|
|
||||||
* <outer>
|
|
||||||
* <xenc:EncryptedData xmlns:xenc="...">
|
|
||||||
* ...
|
|
||||||
* </xenc:EncryptedData>
|
|
||||||
* </outer>
|
|
||||||
* </root>
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param document the {@code Document} that contains the element to be encrypted.
|
|
||||||
* @param element the {@code Element} to be encrypted.
|
|
||||||
* @param publicKey the {@code PublicKey} that must be used to encrypt the secret key.
|
|
||||||
* @param secretKey the {@code SecretKey} used to encrypt the specified element.
|
|
||||||
* @param keySize the size (in bits) of the secret key.
|
|
||||||
*
|
|
||||||
* @throws ProcessingException if an error occurs while encrypting the element with the specified params.
|
|
||||||
*/
|
|
||||||
public static void encryptElement(Document document, Element element, PublicKey publicKey, SecretKey secretKey, int keySize)
|
|
||||||
throws ProcessingException {
|
|
||||||
if (element == null)
|
|
||||||
throw logger.nullArgumentError("element");
|
|
||||||
if (document == null)
|
|
||||||
throw logger.nullArgumentError("document");
|
|
||||||
|
|
||||||
XMLCipher cipher = null;
|
|
||||||
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize);
|
|
||||||
String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
|
|
||||||
|
|
||||||
// Encrypt the Document
|
|
||||||
try {
|
|
||||||
cipher = XMLCipher.getInstance(encryptionAlgorithm);
|
|
||||||
cipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
|
|
||||||
} catch (XMLEncryptionException e1) {
|
|
||||||
throw logger.processingError(e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Document encryptedDoc;
|
|
||||||
try {
|
|
||||||
encryptedDoc = cipher.doFinal(document, element);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw logger.processingError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The EncryptedKey element is added
|
|
||||||
Element encryptedKeyElement = cipher.martial(document, encryptedKey);
|
|
||||||
|
|
||||||
// Outer ds:KeyInfo Element to hold the EncryptionKey
|
|
||||||
Element sigElement = encryptedDoc.createElementNS(XMLSignature.XMLNS, DS_KEY_INFO);
|
|
||||||
sigElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:ds", XMLSignature.XMLNS);
|
|
||||||
sigElement.appendChild(encryptedKeyElement);
|
|
||||||
|
|
||||||
// Insert the Encrypted key before the CipherData element
|
|
||||||
NodeList nodeList = encryptedDoc.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_CIPHERDATA);
|
|
||||||
if (nodeList == null || nodeList.getLength() == 0)
|
|
||||||
throw logger.domMissingElementError("xenc:CipherData");
|
|
||||||
Element cipherDataElement = (Element) nodeList.item(0);
|
|
||||||
Node cipherParent = cipherDataElement.getParentNode();
|
|
||||||
cipherParent.insertBefore(sigElement, cipherDataElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt the root document element inside a Document. <b>NOTE:</b> The document root element will be replaced by
|
|
||||||
* the
|
|
||||||
* wrapping element.
|
|
||||||
*
|
|
||||||
* @param document Document that contains an element to encrypt
|
|
||||||
* @param publicKey The Public Key used to encrypt the secret encryption key
|
|
||||||
* @param secretKey The secret encryption key
|
|
||||||
* @param keySize Length of key
|
|
||||||
* @param wrappingElementQName QName of the element to be used to wrap around the cipher data.
|
|
||||||
* @param addEncryptedKeyInKeyInfo Should the encrypted key be inside a KeyInfo or added as a peer of Cipher Data
|
|
||||||
*
|
|
||||||
* @return An element that has the wrappingElementQName
|
|
||||||
*
|
|
||||||
* @throws ProcessingException
|
|
||||||
* @throws org.keycloak.saml.common.exceptions.ConfigurationException
|
|
||||||
*/
|
|
||||||
public static Element encryptElementInDocument(Document document, PublicKey publicKey, SecretKey secretKey, int keySize,
|
|
||||||
QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException, ConfigurationException {
|
|
||||||
String wrappingElementPrefix = wrappingElementQName.getPrefix();
|
|
||||||
if (wrappingElementPrefix == null || "".equals(wrappingElementPrefix))
|
|
||||||
throw logger.wrongTypeError("Wrapping element prefix invalid");
|
|
||||||
|
|
||||||
XMLCipher cipher = null;
|
|
||||||
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize);
|
|
||||||
|
|
||||||
String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
|
|
||||||
// Encrypt the Document
|
|
||||||
try {
|
|
||||||
cipher = XMLCipher.getInstance(encryptionAlgorithm);
|
|
||||||
cipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
|
|
||||||
} catch (XMLEncryptionException e1) {
|
|
||||||
throw logger.configurationError(e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Document encryptedDoc;
|
|
||||||
try {
|
|
||||||
encryptedDoc = cipher.doFinal(document, document.getDocumentElement());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw logger.processingError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The EncryptedKey element is added
|
|
||||||
Element encryptedKeyElement = cipher.martial(document, encryptedKey);
|
|
||||||
|
|
||||||
final String wrappingElementName;
|
|
||||||
|
|
||||||
if (StringUtil.isNullOrEmpty(wrappingElementPrefix)) {
|
|
||||||
wrappingElementName = wrappingElementQName.getLocalPart();
|
|
||||||
} else {
|
|
||||||
wrappingElementName = wrappingElementPrefix + ":" + wrappingElementQName.getLocalPart();
|
|
||||||
}
|
|
||||||
// Create the wrapping element and set its attribute NS
|
|
||||||
Element wrappingElement = encryptedDoc.createElementNS(wrappingElementQName.getNamespaceURI(), wrappingElementName);
|
|
||||||
|
|
||||||
if (! StringUtil.isNullOrEmpty(wrappingElementPrefix)) {
|
|
||||||
wrappingElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + wrappingElementPrefix, wrappingElementQName.getNamespaceURI());
|
|
||||||
}
|
|
||||||
|
|
||||||
Element encryptedDocRootElement = encryptedDoc.getDocumentElement();
|
|
||||||
// Bring in the encrypted wrapping element to wrap the root node
|
|
||||||
encryptedDoc.replaceChild(wrappingElement, encryptedDocRootElement);
|
|
||||||
|
|
||||||
wrappingElement.appendChild(encryptedDocRootElement);
|
|
||||||
|
|
||||||
if (addEncryptedKeyInKeyInfo) {
|
|
||||||
// Outer ds:KeyInfo Element to hold the EncryptionKey
|
|
||||||
Element sigElement = encryptedDoc.createElementNS(XMLSignature.XMLNS, DS_KEY_INFO);
|
|
||||||
sigElement.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:ds", XMLSignature.XMLNS);
|
|
||||||
sigElement.appendChild(encryptedKeyElement);
|
|
||||||
|
|
||||||
// Insert the Encrypted key before the CipherData element
|
|
||||||
NodeList nodeList = encryptedDocRootElement.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_CIPHERDATA);
|
|
||||||
if (nodeList == null || nodeList.getLength() == 0)
|
|
||||||
throw logger.domMissingElementError("xenc:CipherData");
|
|
||||||
|
|
||||||
Element cipherDataElement = (Element) nodeList.item(0);
|
|
||||||
encryptedDocRootElement.insertBefore(sigElement, cipherDataElement);
|
|
||||||
} else {
|
|
||||||
// Add the encrypted key as a child of the wrapping element
|
|
||||||
wrappingElement.appendChild(encryptedKeyElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedDoc.getDocumentElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an encrypted element inside a document
|
* Decrypt an encrypted element inside a document
|
||||||
*
|
*
|
||||||
|
|
|
@ -107,7 +107,6 @@ public class XMLSignatureUtil {
|
||||||
|
|
||||||
// Set some system properties and Santuario providers. Run this block before any other class initialization.
|
// Set some system properties and Santuario providers. Run this block before any other class initialization.
|
||||||
static {
|
static {
|
||||||
ProvidersUtil.ensure();
|
|
||||||
SystemPropertiesUtil.ensure();
|
SystemPropertiesUtil.ensure();
|
||||||
String keyInfoProp = SecurityActions.getSystemProperty("picketlink.xmlsig.includeKeyInfo", null);
|
String keyInfoProp = SecurityActions.getSystemProperty("picketlink.xmlsig.includeKeyInfo", null);
|
||||||
if (StringUtil.isNotNull(keyInfoProp)) {
|
if (StringUtil.isNotNull(keyInfoProp)) {
|
||||||
|
|
Loading…
Reference in a new issue