Possibility to switch between FIPS and non-FIPS during keycloak+quarkus seerver build (#12513)

* Possibility to switch between FIPS and non-FIPS during keycloak+quarkus server build

Closes #12522
This commit is contained in:
Marek Posolda 2022-06-21 11:17:45 +02:00 committed by GitHub
parent ae7c01b719
commit 3f5741e988
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 425 additions and 24 deletions

View file

@ -0,0 +1,43 @@
package org.keycloak.crypto.integration;
import java.util.ServiceLoader;
import org.jboss.logging.Logger;
import org.keycloak.common.util.BouncyIntegration;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CryptoIntegration {
protected static final Logger logger = Logger.getLogger(CryptoIntegration.class);
private static volatile CryptoProvider securityProvider;
public static CryptoProvider getProvider() {
if (securityProvider == null) {
logger.debugf("Using BouncyCastle provider: %s", BouncyIntegration.PROVIDER);
securityProvider = detectProvider();
logger.infof("Detected security provider: %s", securityProvider);
}
return securityProvider;
}
// This can be possibly set by the configuration (SPI) to override the "detected" instance
public static void setProvider(CryptoProvider provider) {
securityProvider = provider;
}
// Try to auto-detect provider
private static CryptoProvider detectProvider() {
// TODO This may not work on Wildfly (assuming FIPS module will be different Wildfly module than keycloak-core). May need to be improved (EG. with usage of org.keycloak.platform.Platform)
for (CryptoProvider cryptoProvider : ServiceLoader.load(CryptoProvider.class, CryptoIntegration.class.getClassLoader())) {
return cryptoProvider;
}
// Fallback. This should not be needed once DefaultCryptoProvider is moved into separate module like "crypto/default" and provided via ServiceLoader
return new DefaultCryptoProvider();
}
}

View file

@ -0,0 +1,22 @@
package org.keycloak.crypto.integration;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
/**
* Abstraction to handle differences between the APIs for non-fips and fips mode
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface CryptoProvider {
/**
* @return secureRandom implementation based on the available security algorithms according to environment (FIPS non-fips)
*/
SecureRandom getSecureRandom() throws NoSuchAlgorithmException, NoSuchProviderException;
JWEAlgorithmProvider getAesKeyWrapAlgorithmProvider();
}

View file

@ -0,0 +1,23 @@
package org.keycloak.crypto.integration;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultCryptoProvider implements CryptoProvider {
@Override
public SecureRandom getSecureRandom() throws NoSuchAlgorithmException {
return SecureRandom.getInstance("SHA1PRNG");
}
@Override
public JWEAlgorithmProvider getAesKeyWrapAlgorithmProvider() {
return new AesKeyWrapAlgorithmProvider();
}
}

View file

@ -20,7 +20,7 @@ package org.keycloak.jose.jwe;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
import org.keycloak.crypto.integration.CryptoIntegration;
import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.alg.RsaKeyEncryption256JWEAlgorithmProvider;
@ -47,7 +47,7 @@ class JWERegistry {
static {
// Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
ALG_PROVIDERS.put(JWEConstants.A128KW, CryptoIntegration.getProvider().getAesKeyWrapAlgorithmProvider());
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"));

View file

@ -25,6 +25,7 @@ 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.integration.CryptoIntegration;
import org.keycloak.util.JsonSerialization;
import java.nio.charset.StandardCharsets;
@ -33,7 +34,6 @@ import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
@ -130,7 +130,7 @@ public class JWKTest {
@Test
public void publicEs256() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", BouncyIntegration.PROVIDER);
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
SecureRandom randomGen = CryptoIntegration.getProvider().getSecureRandom();
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
keyGen.initialize(ecSpec, randomGen);
KeyPair keyPair = keyGen.generateKeyPair();

79
crypto/fips1402/pom.xml Normal file
View file

@ -0,0 +1,79 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-crypto-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-fips1402</artifactId>
<name>Keycloak FIPS 140-2 Integration</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<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.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-fips</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-fips</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,27 @@
package org.keycloak.crypto.fips;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import org.keycloak.crypto.integration.CryptoProvider;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
/**
* Integration based on FIPS 140-2
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FIPS1402Provider implements CryptoProvider {
@Override
public SecureRandom getSecureRandom() throws NoSuchAlgorithmException, NoSuchProviderException {
return SecureRandom.getInstance("DEFAULT","BCFIPS");
}
@Override
public JWEAlgorithmProvider getAesKeyWrapAlgorithmProvider() {
return new FIPSAesKeyWrapAlgorithmProvider();
}
}

View file

@ -0,0 +1,37 @@
package org.keycloak.crypto.fips;
import java.security.Key;
import org.bouncycastle.crypto.KeyUnwrapper;
import org.bouncycastle.crypto.KeyWrapper;
import org.bouncycastle.crypto.SymmetricKey;
import org.bouncycastle.crypto.SymmetricSecretKey;
import org.bouncycastle.crypto.fips.FipsAES;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
/**
* Variant of {@link org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider} based on FIPS
*/
public class FIPSAesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
byte[] keyBytes = encryptionKey.getEncoded(); // bytes making up AES key doing the wrapping
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, keyBytes);
FipsAES.KeyWrapOperatorFactory factory = new FipsAES.KeyWrapOperatorFactory();
KeyUnwrapper unwrapper = factory.createKeyUnwrapper(aesKey, FipsAES.KW);
return unwrapper.unwrap(encodedCek, 0, encodedCek.length);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
byte[] inputKeyBytes = keyStorage.getCekBytes(); // bytes making up the key to be wrapped
byte[] keyBytes = encryptionKey.getEncoded(); // bytes making up AES key doing the wrapping
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, keyBytes);
FipsAES.KeyWrapOperatorFactory factory = new FipsAES.KeyWrapOperatorFactory();
KeyWrapper wrapper = factory.createKeyWrapper(aesKey, FipsAES.KW);
return wrapper.wrap(inputKeyBytes, 0, inputKeyBytes.length);
}
}

View file

@ -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.fips.FIPS1402Provider

View file

@ -0,0 +1,25 @@
package org.keycloak.crypto.fips.test;
import java.security.SecureRandom;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.crypto.fips.FIPSAesKeyWrapAlgorithmProvider;
import org.keycloak.crypto.integration.CryptoIntegration;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FIPS1402UnitTest {
@Test
public void testFips() throws Exception {
JWEAlgorithmProvider jweAlg = CryptoIntegration.getProvider().getAesKeyWrapAlgorithmProvider();
Assert.assertEquals(jweAlg.getClass(), FIPSAesKeyWrapAlgorithmProvider.class);
SecureRandom scr = CryptoIntegration.getProvider().getSecureRandom();
Assert.assertEquals("BCFIPS", scr.getProvider().getName());
}
}

36
crypto/pom.xml Normal file
View file

@ -0,0 +1,36 @@
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Crypto Parent</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-crypto-parent</artifactId>
<packaging>pom</packaging>
<modules>
<module>fips1402</module>
</modules>
</project>

38
docs/fips.md Normal file
View file

@ -0,0 +1,38 @@
FIPS 140-2 Integration
======================
Build with FIPS
---------------
With OpenJDK 11 on the classpath, run this from the project root directory:
```
mvn clean install -DskipTests=true -Dfips140-2 -Pquarkus
```
The property `fips140-2` is used to trigger maven profile to build keycloak+quarkus distribution with `bouncycastle-fips` dependencies instead of plain `bouncycastle`
and also with `keycloak-fips140-2` module containing some security code dependent on bouncycastle-fips APIs.
Then unzip and check only bouncycastle-fips libraries are inside "lib" directory:
```
tar xf $KEYCLOAK_SOURCES/quarkus/dist/target/keycloak-999-SNAPSHOT.tar.gz
ls keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bc*
```
Output should be something like:
```
keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bc-fips-1.0.2.jar keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bctls-fips-1.0.11.jar
keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bcpkix-fips-1.0.3.jar
```
Similarly the JAR keycloak-fips-integration should be available:
```
ls keycloak-999-SNAPSHOT/lib/lib/main/org.keycloak.keycloak-fips-integration-999-SNAPSHOT.jar
```
Now run the server on the FIPS enabled machine with FIPS-enabled OpenJDK (Tested on RHEL 8.6):
```
cd keycloak-999-SNAPSHOT/bin
./kc.sh start-dev
```
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...

27
pom.xml
View file

@ -75,6 +75,12 @@
<apache.mime4j.version>0.6</apache.mime4j.version>
<jboss.dmr.version>1.5.1.Final</jboss.dmr.version>
<bouncycastle.version>1.68</bouncycastle.version>
<!-- TODO Are these correct versions? -->
<bouncycastle.fips.version>1.0.2.3</bouncycastle.fips.version>
<bouncycastle.pkixfips.version>1.0.5</bouncycastle.pkixfips.version>
<bouncycastle.tlsfips.version>1.0.12.2</bouncycastle.tlsfips.version>
<cxf.version>3.3.10</cxf.version>
<cxf.jetty.version>3.3.10</cxf.jetty.version>
<cxf.jaxrs.version>3.3.10</cxf.jaxrs.version>
@ -269,6 +275,7 @@
<module>boms</module>
<module>common</module>
<module>core</module>
<module>crypto</module>
<module>dependencies</module>
<module>docs</module>
<module>server-spi</module>
@ -361,6 +368,21 @@
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
<version>${bouncycastle.fips.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-fips</artifactId>
<version>${bouncycastle.tlsfips.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-fips</artifactId>
<version>${bouncycastle.pkixfips.version}</version>
</dependency>
<dependency>
<groupId>com.github.ua-parser</groupId>
<artifactId>uap-java</artifactId>
@ -1581,6 +1603,11 @@
<artifactId>keycloak-client-registration-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-fips1402</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-cli</artifactId>

View file

@ -392,26 +392,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-hotrod</artifactId>
@ -613,6 +593,52 @@
</dependency>
</dependencies>
</profile>
<profile>
<id>crypto-default</id>
<activation>
<property>
<name>!fips140-2</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</profile>
<profile>
<id>fips140-2</id>
<activation>
<property>
<name>fips140-2</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-fips1402</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
<build>