Unit tests in "crypto/fips1402" passing on RHEL 8.6 with BC FIPS approved mode. Cleanup (#13406)

Closes #13128
This commit is contained in:
Marek Posolda 2022-07-29 18:03:56 +02:00 committed by GitHub
parent 6f7d20f752
commit 7e925bfbff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 323 additions and 280 deletions

View file

@ -0,0 +1,19 @@
package org.keycloak.common.crypto;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CryptoConstants {
// JWE algorithms
public static final String A128KW = "A128KW";
public static final String RSA1_5 = "RSA1_5";
public static final String RSA_OAEP = "RSA-OAEP";
public static final String RSA_OAEP_256 = "RSA-OAEP-256";
/** Name of Java security provider used with non-fips BouncyCastle. Should be used in non-FIPS environment */
public static final String BC_PROVIDER_ID = "BC";
/** Name of Java security provider used with fips BouncyCastle. Should be used in FIPS environment */
public static final String BCFIPS_PROVIDER_ID = "BCFIPS";
}

View file

@ -1,5 +1,6 @@
package org.keycloak.common.crypto;
import java.security.Provider;
import java.security.spec.ECParameterSpec;
/**
@ -9,6 +10,11 @@ import java.security.spec.ECParameterSpec;
*/
public interface CryptoProvider {
/**
* @return BouncyCastle security provider. Can be either non-FIPS or FIPS based provider
*/
Provider getBouncyCastleProvider();
/**
* Get some algorithm provider implementation. Returned implementation can be dependent according to if we have
* non-fips bouncycastle or fips bouncycastle on the classpath.
@ -25,7 +31,7 @@ public interface CryptoProvider {
*
* @return
*/
public CertificateUtilsProvider getCertificateUtils();
CertificateUtilsProvider getCertificateUtils();
/**
@ -34,7 +40,7 @@ public interface CryptoProvider {
*
* @return
*/
public PemUtilsProvider getPemUtils();
PemUtilsProvider getPemUtils();
/**
@ -43,6 +49,6 @@ public interface CryptoProvider {
* @param curveName
* @return
*/
public ECParameterSpec createECParams(String curveName);
ECParameterSpec createECParams(String curveName);
}

View file

@ -1,11 +0,0 @@
package org.keycloak.common.crypto;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CryptoProviderTypes {
public static final String BC_SECURITY_PROVIDER = "bc-provider";
public static final String AES_KEY_WRAP_ALGORITHM_PROVIDER = "aes-keywrap-alg";
}

View file

@ -19,7 +19,7 @@ package org.keycloak.common.util;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.common.crypto.CryptoConstants;
import java.security.Provider;
import java.security.Security;
@ -35,7 +35,7 @@ public class BouncyIntegration {
public static final String PROVIDER = loadProvider();
private static String loadProvider() {
Provider provider = CryptoIntegration.getProvider().getAlgorithmProvider(Provider.class, CryptoProviderTypes.BC_SECURITY_PROVIDER);
Provider provider = CryptoIntegration.getProvider().getBouncyCastleProvider();
if (provider == null) {
throw new RuntimeException("Failed to load required security provider: BouncyCastleProvider or BouncyCastleFipsProvider");
}

View file

@ -30,10 +30,6 @@ import org.keycloak.common.crypto.CryptoIntegration;
*/
public class CertificateUtils {
static {
CryptoIntegration.init(ClassLoader.getSystemClassLoader());
}
/**
* Generates version 3 {@link java.security.cert.X509Certificate}.

View file

@ -43,6 +43,13 @@ public class Environment {
* @return true if java is FIPS mode
*/
public static boolean isJavaInFipsMode() {
// Check if FIPS explicitly enabled by system property
String property = System.getProperty("com.redhat.fips");
if (property != null) {
return Boolean.parseBoolean(property);
}
// Otherwise try to auto-detect
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;

View file

@ -37,10 +37,6 @@ public class PemUtils {
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
public static final String END_CERT = "-----END CERTIFICATE-----";
static {
CryptoIntegration.init(ClassLoader.getSystemClassLoader());
}
/**
* Decode a X509 Certificate from a PEM string
*

View file

@ -17,16 +17,18 @@
package org.keycloak.jose.jwe;
import org.keycloak.common.crypto.CryptoConstants;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWEConstants {
public static final String DIR = "dir";
public static final String A128KW = "A128KW";
public static final String RSA1_5 = "RSA1_5";
public static final String RSA_OAEP = "RSA-OAEP";
public static final String RSA_OAEP_256 = "RSA-OAEP-256";
public static final String DIRECT = "dir";
public static final String A128KW = CryptoConstants.A128KW;
public static final String RSA1_5 = CryptoConstants.RSA1_5;
public static final String RSA_OAEP = CryptoConstants.RSA_OAEP;
public static final String RSA_OAEP_256 = CryptoConstants.RSA_OAEP_256;
public static final String A128CBC_HS256 = "A128CBC-HS256";
public static final String A192CBC_HS384 = "A192CBC-HS384";

View file

@ -21,11 +21,8 @@ import java.util.HashMap;
import java.util.Map;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryption256JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -36,23 +33,11 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
*/
class JWERegistry {
// https://tools.ietf.org/html/rfc7518#page-12
// https://tools.ietf.org/html/rfc7518#page-22
// Registry not pluggable for now. Just supported algorithms included
private static final Map<String, JWEEncryptionProvider> ENC_PROVIDERS = new HashMap<>();
// https://tools.ietf.org/html/rfc7518#page-22
// Registry not pluggable for now. Just supported algorithms included
private static final Map<String, JWEAlgorithmProvider> ALG_PROVIDERS = new HashMap<>();
static {
// Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
ALG_PROVIDERS.put(JWEConstants.A128KW, CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER));
ALG_PROVIDERS.put(JWEConstants.RSA_OAEP, new RsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
ALG_PROVIDERS.put(JWEConstants.RSA_OAEP_256, new RsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
ENC_PROVIDERS.put(JWEConstants.A256GCM, new AesGcmJWEEncryptionProvider(JWEConstants.A256GCM));
ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
@ -61,7 +46,12 @@ class JWERegistry {
static JWEAlgorithmProvider getAlgProvider(String alg) {
return ALG_PROVIDERS.get(alg);
// https://tools.ietf.org/html/rfc7518#page-12
if (JWEConstants.DIRECT.equals(alg)) {
return new DirectAlgorithmProvider();
} else {
return CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, alg);
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright 2018 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.jose.jwe.alg;
import java.security.Key;
import javax.crypto.Cipher;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public abstract class KeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
Cipher cipher = getCipherProvider();
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encodedCek);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
Cipher cipher = getCipherProvider();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cekBytes = keyStorage.getCekBytes();
return cipher.doFinal(cekBytes);
}
protected abstract Cipher getCipherProvider() throws Exception;
}

View file

@ -1,67 +0,0 @@
/*
* Copyright 2018 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.jose.jwe.alg;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
public class RsaKeyEncryption256JWEAlgorithmProvider extends KeyEncryptionJWEAlgorithmProvider {
private final String jcaAlgorithmName;
public RsaKeyEncryption256JWEAlgorithmProvider(String jcaAlgorithmName) {
this.jcaAlgorithmName = jcaAlgorithmName;
}
@Override
protected Cipher getCipherProvider() throws Exception {
return Cipher.getInstance(jcaAlgorithmName);
}
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
AlgorithmParameters algp = AlgorithmParameters.getInstance("OAEP");
AlgorithmParameterSpec paramSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT);
algp.init(paramSpec);
Cipher cipher = getCipherProvider();
cipher.init(Cipher.DECRYPT_MODE, privateKey, algp);
return cipher.doFinal(encodedCek);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey)
throws Exception {
AlgorithmParameters algp = AlgorithmParameters.getInstance("OAEP");
AlgorithmParameterSpec paramSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT);
algp.init(paramSpec);
Cipher cipher = getCipherProvider();
cipher.init(Cipher.ENCRYPT_MODE, publicKey, algp);
byte[] cekBytes = keyStorage.getCekBytes();
return cipher.doFinal(cekBytes);
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright 2018 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.jose.jwe.alg;
import javax.crypto.Cipher;
public class RsaKeyEncryptionJWEAlgorithmProvider extends KeyEncryptionJWEAlgorithmProvider {
private final String jcaAlgorithmName;
public RsaKeyEncryptionJWEAlgorithmProvider(String jcaAlgorithmName) {
this.jcaAlgorithmName = jcaAlgorithmName;
}
@Override
protected Cipher getCipherProvider() throws Exception {
return Cipher.getInstance(jcaAlgorithmName);
}
}

View file

@ -203,7 +203,7 @@ public class TokenUtil {
default: throw new IllegalArgumentException("Bad size for Encryption key: " + aesKey + ". Valid sizes are 16, 24, 32.");
}
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIRECT, encAlgorithm, null);
JWE jwe = new JWE()
.header(jweHeader)
.content(contentBytes);

View file

@ -21,6 +21,7 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jwe.JWE;
@ -30,7 +31,6 @@ import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEUtils;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.AesCbcHmacShaJWEEncryptionProvider;
import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -80,7 +80,7 @@ public abstract class JWETest {
private void testDirectEncryptAndDecrypt(Key aesKey, Key hmacKey, String encAlgorithm, String payload, boolean sysout) throws Exception {
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, encAlgorithm, null);
JWEHeader jweHeader = new JWEHeader(JWEConstants.DIRECT, encAlgorithm, null);
JWE jwe = new JWE()
.header(jweHeader)
.content(payload.getBytes(StandardCharsets.UTF_8));
@ -273,7 +273,7 @@ public abstract class JWETest {
private void testKeyEncryption_ContentEncryptionAesGcm(String jweAlgorithmName, String jweEncryptionName) throws Exception {
// generate key pair for KEK
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
JWEAlgorithmProvider jweAlgorithmProvider = new RsaKeyEncryptionJWEAlgorithmProvider(getJcaAlgorithmName(jweAlgorithmName));
JWEAlgorithmProvider jweAlgorithmProvider = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, jweAlgorithmName);
JWEEncryptionProvider jweEncryptionProvider = new AesGcmJWEEncryptionProvider(jweEncryptionName);
JWEHeader jweHeader = new JWEHeader(jweAlgorithmName, jweEncryptionName, null);
@ -306,7 +306,7 @@ public abstract class JWETest {
final SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
JWEAlgorithmProvider jweAlgorithmProvider = new RsaKeyEncryptionJWEAlgorithmProvider(getJcaAlgorithmName(jweAlgorithmName));
JWEAlgorithmProvider jweAlgorithmProvider = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, jweAlgorithmName);
JWEEncryptionProvider jweEncryptionProvider = new AesCbcHmacShaJWEEncryptionProvider(jweEncryptionName);
JWEHeader jweHeader = new JWEHeader(jweAlgorithmName, jweEncryptionName, null);
@ -339,15 +339,4 @@ public abstract class JWETest {
Assert.assertEquals(PAYLOAD, decodedContent);
}
private String getJcaAlgorithmName(String jweAlgorithmName) {
String jcaAlgorithmName = null;
if (JWEConstants.RSA1_5.equals(jweAlgorithmName)) {
jcaAlgorithmName = "RSA/ECB/PKCS1Padding";
} else if (JWEConstants.RSA_OAEP.equals(jweAlgorithmName)) {
jcaAlgorithmName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
} else if (JWEConstants.RSA_OAEP_256.equals(jweAlgorithmName)) {
jcaAlgorithmName = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
}
return jcaAlgorithmName;
}
}

View file

@ -1,5 +1,7 @@
package org.keycloak.crypto.def;
import java.security.Provider;
import java.security.Security;
import java.security.spec.ECParameterSpec;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -9,7 +11,7 @@ import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import org.keycloak.common.crypto.PemUtilsProvider;
@ -18,18 +20,33 @@ import org.keycloak.common.crypto.PemUtilsProvider;
*/
public class DefaultCryptoProvider implements CryptoProvider {
private final BouncyCastleProvider bcProvider;
private Map<String, Object> providers = new ConcurrentHashMap<>();
public DefaultCryptoProvider() {
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, new BouncyCastleProvider());
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, new AesKeyWrapAlgorithmProvider());
// Make sure to instantiate this only once due it is expensive. And skip registration if already available in Java security providers (EG. due explicitly configured in java security file)
BouncyCastleProvider existingBc = (BouncyCastleProvider) Security.getProvider(CryptoConstants.BC_PROVIDER_ID);
this.bcProvider = existingBc == null ? new BouncyCastleProvider() : existingBc;
providers.put(CryptoConstants.A128KW, new AesKeyWrapAlgorithmProvider());
providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"));
providers.put(CryptoConstants.RSA_OAEP, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
providers.put(CryptoConstants.RSA_OAEP_256, new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
}
@Override
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
Object o = providers.get(algorithm);
public Provider getBouncyCastleProvider() {
return bcProvider;
}
@Override
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithmType) {
Object o = providers.get(algorithmType);
if (o == null) {
throw new IllegalArgumentException("Not found provider of algorithm: " + algorithm);
throw new IllegalArgumentException("Not found provider of algorithm type: " + algorithmType);
}
return clazz.cast(o);
}

View file

@ -0,0 +1,27 @@
package org.keycloak.crypto.def;
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;
public class DefaultRsaKeyEncryption256JWEAlgorithmProvider extends DefaultRsaKeyEncryptionJWEAlgorithmProvider {
public DefaultRsaKeyEncryption256JWEAlgorithmProvider(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);
}
}

View file

@ -0,0 +1,40 @@
package org.keycloak.crypto.def;
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;
public class DefaultRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmProvider {
private final String jcaAlgorithmName;
public DefaultRsaKeyEncryptionJWEAlgorithmProvider(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);
}
}

View file

@ -1,5 +1,8 @@
package org.keycloak.crypto.def.test;
import org.junit.Assume;
import org.junit.Before;
import org.keycloak.common.util.Environment;
import org.keycloak.jose.HmacTest;
@ -8,4 +11,10 @@ import org.keycloak.jose.HmacTest;
*
*/
public class DefaultCryptoHmacTest extends HmacTest {
@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());
}
}

View file

@ -1,5 +1,8 @@
package org.keycloak.crypto.def.test;
import org.junit.Assume;
import org.junit.Before;
import org.keycloak.common.util.Environment;
import org.keycloak.util.JWKSUtilsTest;
/**
@ -7,4 +10,10 @@ import org.keycloak.util.JWKSUtilsTest;
*
*/
public class DefaultCryptoJWKSUtilsTest extends JWKSUtilsTest {
@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());
}
}

View file

@ -1,10 +1,19 @@
package org.keycloak.crypto.def.test;
import org.junit.Assume;
import org.junit.Before;
import org.keycloak.RSAVerifierTest;
import org.keycloak.common.util.Environment;
/**
* Test with bouncycastle security provider
*
*/
public class DefaultCryptoRSAVerifierTest extends RSAVerifierTest {
@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());
}
}

View file

@ -2,25 +2,25 @@ package org.keycloak.crypto.def.test;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.crypto.def.AesKeyWrapAlgorithmProvider;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.rule.CryptoInitRule;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultCryptoUnitTest {
@Before
public void init() {
CryptoIntegration.init(ClassLoader.getSystemClassLoader());
}
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@Test
public void testDefaultCrypto() throws Exception {
JWEAlgorithmProvider jweAlg = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER);
JWEAlgorithmProvider jweAlg = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, CryptoConstants.A128KW);
Assert.assertEquals(jweAlg.getClass(), AesKeyWrapAlgorithmProvider.class);
}
}

View file

@ -4,9 +4,12 @@ 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;
/**
@ -17,6 +20,12 @@ public class DefaultSecureRandomTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@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());
}
protected static final Logger logger = Logger.getLogger(DefaultSecureRandomTest.class);
@Test

View file

@ -1,6 +1,9 @@
package org.keycloak.crypto.def.test;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.util.Environment;
import java.security.NoSuchAlgorithmException;
@ -8,6 +11,11 @@ import static org.junit.Assert.assertEquals;
public class PemUtilsBCTest {
@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());
}
@Test
public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException {

View file

@ -34,34 +34,12 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<!-- TODO: These exclusions can be removed once we remove dependency of keycloak-core on bouncycastle -->
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>test</scope>
<type>test-jar</type>
<!-- TODO: These exclusions can be removed once we remove dependency of keycloak-core on bouncycastle -->
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View file

@ -1,5 +1,6 @@
package org.keycloak.crypto.fips;
import java.security.Provider;
import java.security.spec.ECField;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECFieldFp;
@ -12,10 +13,12 @@ import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.fips.FipsRSA;
import org.bouncycastle.crypto.fips.FipsSHS;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.CertificateUtilsProvider;
import org.keycloak.common.crypto.PemUtilsProvider;
@ -27,16 +30,28 @@ import org.keycloak.common.crypto.PemUtilsProvider;
*/
public class FIPS1402Provider implements CryptoProvider {
private final BouncyCastleFipsProvider bcFipsProvider;
private final Map<String, Object> providers = new ConcurrentHashMap<>();
public FIPS1402Provider() {
BouncyCastleFipsProvider bcFipsProvider = new BouncyCastleFipsProvider();
providers.put(CryptoProviderTypes.BC_SECURITY_PROVIDER, bcFipsProvider);
providers.put(CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER, new FIPSAesKeyWrapAlgorithmProvider());
// Case when BCFIPS provider already registered in Java security file
BouncyCastleFipsProvider existingBcFipsProvider = (BouncyCastleFipsProvider) Security.getProvider(CryptoConstants.BCFIPS_PROVIDER_ID);
this.bcFipsProvider = existingBcFipsProvider == null ? new BouncyCastleFipsProvider() : existingBcFipsProvider;
providers.put(CryptoConstants.A128KW, new FIPSAesKeyWrapAlgorithmProvider());
providers.put(CryptoConstants.RSA1_5, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_PKCS1v1_5));
providers.put(CryptoConstants.RSA_OAEP, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP));
providers.put(CryptoConstants.RSA_OAEP_256, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP.withDigest(FipsSHS.Algorithm.SHA256)));
Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1);
}
@Override
public Provider getBouncyCastleProvider() {
return bcFipsProvider;
}
@Override
public <T> T getAlgorithmProvider(Class<T> clazz, String algorithm) {
Object o = providers.get(algorithm);

View file

@ -0,0 +1,56 @@
package org.keycloak.crypto.fips;
import java.security.Key;
import java.security.SecureRandom;
import org.bouncycastle.crypto.KeyUnwrapperUsingSecureRandom;
import org.bouncycastle.crypto.KeyWrapperUsingSecureRandom;
import org.bouncycastle.crypto.asymmetric.AsymmetricRSAPrivateKey;
import org.bouncycastle.crypto.asymmetric.AsymmetricRSAPublicKey;
import org.bouncycastle.crypto.fips.FipsRSA;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
/**
* Fips note: Based on https://downloads.bouncycastle.org/fips-java/BC-FJA-UserGuide-1.0.2.pdf, Section 4
* There are no direct public/private key ciphers available in approved mode. Available ciphers are
* restricted to use for key wrapping and key transport, see section 7 and section 8 for details.
* Our solution is to pull out the CEK signature and encryption keys , encode them separately , and then
*/
public class FIPSRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmProvider {
private final FipsRSA.WrapParameters wrapParameters;
public FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WrapParameters wrapParameters) {
this.wrapParameters = wrapParameters;
}
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
AsymmetricRSAPrivateKey rsaPrivateKey =
new AsymmetricRSAPrivateKey(FipsRSA.ALGORITHM, privateKey.getEncoded());
FipsRSA.KeyWrapOperatorFactory wrapFact =
new FipsRSA.KeyWrapOperatorFactory();
KeyUnwrapperUsingSecureRandom<FipsRSA.WrapParameters> unwrapper =
wrapFact.createKeyUnwrapper(rsaPrivateKey, wrapParameters)
.withSecureRandom(SecureRandom.getInstance("DEFAULT"));
return unwrapper.unwrap(encodedCek, 0, encodedCek.length);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
AsymmetricRSAPublicKey rsaPubKey =
new AsymmetricRSAPublicKey(FipsRSA.ALGORITHM, publicKey.getEncoded());
byte[] inputKeyBytes = keyStorage.getCekBytes();
FipsRSA.KeyWrapOperatorFactory wrapFact =
new FipsRSA.KeyWrapOperatorFactory();
KeyWrapperUsingSecureRandom<FipsRSA.WrapParameters> wrapper =
wrapFact.createKeyWrapper(rsaPubKey, wrapParameters).withSecureRandom( SecureRandom.getInstance("DEFAULT"));
return wrapper.wrap(inputKeyBytes, 0, inputKeyBytes.length);
}
}

View file

@ -7,8 +7,11 @@ import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.Environment;
import org.keycloak.jose.HmacTest;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
@ -21,6 +24,12 @@ import org.keycloak.jose.jws.crypto.HMACProvider;
*/
public class FIPS1402HmacTest extends HmacTest {
@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());
}
@Test
public void testHmacSignaturesFIPS() throws Exception {
//

View file

@ -60,6 +60,8 @@ public class FIPS1402KeyPairVerifierTest extends KeyPairVerifierTest {
"jLsXjc2CPf/lwNFqsVl7dlPNmg==";
}
@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());

View file

@ -2,6 +2,8 @@ package org.keycloak.crypto.fips.test;
import java.security.SecureRandom;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.fips.FipsStatus;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Assume;
@ -32,6 +34,8 @@ public class FIPS1402SecureRandomTest {
public void testSecureRandom() throws Exception {
logger.info(CryptoIntegration.dumpJavaSecurityProviders());
logger.infof("BC FIPS approved mode: %b, FIPS Status: %s", CryptoServicesRegistrar.isInApprovedOnlyMode(), FipsStatus.getStatusMessage());
SecureRandom sc1 = new SecureRandom();
logger.infof(dumpSecureRandom("new SecureRandom()", sc1));

View file

@ -2,9 +2,8 @@ package org.keycloak.crypto.fips.test;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoProviderTypes;
import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.crypto.fips.FIPSAesKeyWrapAlgorithmProvider;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
@ -20,7 +19,7 @@ public class FIPS1402UnitTest {
@Test
public void testFips() throws Exception {
JWEAlgorithmProvider jweAlg = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, CryptoProviderTypes.AES_KEY_WRAP_ALGORITHM_PROVIDER);
JWEAlgorithmProvider jweAlg = CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, CryptoConstants.A128KW);
Assert.assertEquals(jweAlg.getClass(), FIPSAesKeyWrapAlgorithmProvider.class);
}
}

View file

@ -1,6 +1,9 @@
package org.keycloak.crypto.fips.test;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.util.Environment;
import org.keycloak.common.util.PemUtils;
import java.security.NoSuchAlgorithmException;
@ -9,6 +12,12 @@ import static org.junit.Assert.assertEquals;
public class PemUtilsBCFIPSTest {
@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());
}
@Test
public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException {
String[] test = new String[] {"abcdefg"};

View file

@ -38,4 +38,20 @@ cd keycloak-999-SNAPSHOT/bin
```
NOTE: Right now, server should start, and I am able to create admin user on `http://localhost:8080`, but I am not able to finish
login to the admin console. However the Keycloak uses bouncycastle-fips libraries and the `CryptoIntegration` uses `FIPS1402Provider`. More fixes are required to have Keycloak server working...
login to the admin console. However the Keycloak uses bouncycastle-fips libraries and the `CryptoIntegration` uses `FIPS1402Provider`. More fixes are required to have Keycloak server working...
Run the tests in the FIPS environment
-------------------------------------
This instruction is about running automated tests on the FIPS enabled RHEL 8.6 system with the FIPS enabled OpenJDK 11.
So far only the unit tests inside the `crypto` module are supported. More effort is needed to have whole testsuite passing.
First it is needed to build the project (See above). Then run the tests in the `crypto` module.
```
mvn clean install -f crypto
```
The tests should work also with the BouncyCastle approved mode, which is more strict in the used crypto algorithms
```
mvn clean install -f crypto -Dorg.bouncycastle.fips.approved_only=true
```

View file

@ -17,10 +17,9 @@
package org.keycloak.crypto;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryption256JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
import org.keycloak.models.KeycloakSession;
public class RsaCekManagementProvider implements CekManagementProvider {
@ -35,15 +34,12 @@ public class RsaCekManagementProvider implements CekManagementProvider {
@Override
public JWEAlgorithmProvider jweAlgorithmProvider() {
String jcaAlgorithmName = null;
if (JWEConstants.RSA1_5.equals(jweAlgorithmName)) {
jcaAlgorithmName = "RSA/ECB/PKCS1Padding";
} else if (JWEConstants.RSA_OAEP.equals(jweAlgorithmName)) {
jcaAlgorithmName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
} else if (JWEConstants.RSA_OAEP_256.equals(jweAlgorithmName)) {
return new RsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
if (JWEConstants.RSA1_5.equals(jweAlgorithmName) || JWEConstants.RSA_OAEP.equals(jweAlgorithmName) ||
JWEConstants.RSA_OAEP_256.equals(jweAlgorithmName)) {
return CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, jweAlgorithmName);
} else {
return null;
}
return new RsaKeyEncryptionJWEAlgorithmProvider(jcaAlgorithmName);
}
}

View file

@ -230,12 +230,7 @@ public class AuthorizationTokenEncryptionTest extends AbstractTestRealmKeycloakT
}
private JWEAlgorithmProvider getJweAlgorithmProvider(String algAlgorithm) {
JWEAlgorithmProvider jweAlgorithmProvider = null;
if (JWEConstants.RSA1_5.equals(algAlgorithm) || JWEConstants.RSA_OAEP.equals(algAlgorithm) ||
JWEConstants.RSA_OAEP_256.equals(algAlgorithm)) {
jweAlgorithmProvider = new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
return jweAlgorithmProvider;
return new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
private JWEEncryptionProvider getJweEncryptionProvider(String encAlgorithm) {
JWEEncryptionProvider jweEncryptionProvider = null;

View file

@ -262,12 +262,7 @@ public class IdTokenEncryptionTest extends AbstractTestRealmKeycloakTest {
}
private JWEAlgorithmProvider getJweAlgorithmProvider(String algAlgorithm) {
JWEAlgorithmProvider jweAlgorithmProvider = null;
if (JWEConstants.RSA1_5.equals(algAlgorithm) || JWEConstants.RSA_OAEP.equals(algAlgorithm) ||
JWEConstants.RSA_OAEP_256.equals(algAlgorithm)) {
jweAlgorithmProvider = new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
return jweAlgorithmProvider;
return new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
private JWEEncryptionProvider getJweEncryptionProvider(String encAlgorithm) {
JWEEncryptionProvider jweEncryptionProvider = null;

View file

@ -386,12 +386,7 @@ public class UserInfoTest extends AbstractKeycloakTest {
}
private JWEAlgorithmProvider getJweAlgorithmProvider(String algAlgorithm) {
JWEAlgorithmProvider jweAlgorithmProvider = null;
if (JWEConstants.RSA1_5.equals(algAlgorithm) || JWEConstants.RSA_OAEP.equals(algAlgorithm) ||
JWEConstants.RSA_OAEP_256.equals(algAlgorithm)) {
jweAlgorithmProvider = new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
return jweAlgorithmProvider;
return new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
}
private JWEEncryptionProvider getJweEncryptionProvider(String encAlgorithm) {
JWEEncryptionProvider jweEncryptionProvider = null;