8000 feat(fips): Project Brisbane prototype by slaskawi · Pull Request #53711 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ public enum Feature {

FIPS("FIPS 140-2 mode", Type.DISABLED_BY_DEFAULT),

BRISBANE("Brisbane cryptographic provider", Type.EXPERIMENTAL, Feature.FIPS),

DPOP("OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer", Type.DEFAULT),

DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
Expand All @@ -27,10 +28,18 @@ public class CryptoIntegration {
private static volatile CryptoProvider cryptoProvider;

public static void init(ClassLoader classLoader) {
init(() -> detectProvider(classLoader));
}

public static void init(ClassLoader classLoader, String providerName, FipsMode fipsMode) {
init(() -> detectProvider(classLoader, providerName, fipsMode));
}

private static void init(Supplier<CryptoProvider> providerSupplier) {
if (cryptoProvider == null) {
synchronized (lock) {
if (cryptoProvider == null) {
cryptoProvider = detectProvider(classLoader);
cryptoProvider = providerSupplier.get();
logger.debugv("java security provider: {0}", BouncyIntegration.PROVIDER);

}
Expand All @@ -43,6 +52,27 @@ public static void init(ClassLoader classLoader) {
}
}

static CryptoProvider detectProvider(Iterable<CryptoProviderFactory> factories, String providerName, FipsMode fipsMode) {
List<CryptoProviderFactory> matchingFactories = StreamSupport.stream(factories.spliterator(), false)
.filter(factory -> providerName.equals(factory.getName()))
.collect(Collectors.toList());

if (matchingFactories.size() != 1) {
throw new IllegalStateException("Expected one crypto provider named '" + providerName + "', found " + matchingFactories.size());
}

CryptoProvider provider = matchingFactories.get(0).create(fipsMode);
if (provider == null) {
throw new IllegalStateException("Crypto provider factory '" + providerName + "' returned null");
}
logger.debugf("Detected crypto provider: %s", provider.getClass().getName());
return provider;
}

private static CryptoProvider detectProvider(ClassLoader classLoader, String providerName, FipsMode fipsMode) {
return detectProvider(ServiceLoader.load(CryptoProviderFactory.class, classLoader), providerName, fipsMode);
}

public static boolean isInitialised() {
return cryptoProvider != null;
}
Expand Down
< 10BC8 td class="blob-num blob-num-addition empty-cell">
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2026 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.common.crypto;

public interface CryptoProviderFactory {

String getName();

CryptoProvider create(FipsMode fipsMode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2026 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.common.crypto;

public enum FipsProvider {
BOUNCY_CASTLE("bouncycastle"),
BRISBANE("brisbane");

private final String optionName;

FipsProvider(String optionName) {
this.optionName = optionName;
}

@Override
public String toString() {
return optionName;
}
}
15 changes: 15 additions & 0 deletions common/src/test/java/org/keycloak/common/ProfileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ public void checkFailureIfDependencyDisabled() {
() -> Profile.configure(new PropertiesProfileConfigResolver(properties))).getMessage());
}

@Test
public void brisbaneRequiresFipsFeature() {
Properties properties = new Properties();
properties.setProperty("keycloak.profile.feature.brisbane", "enabled");

Assertions.assertEquals("Feature brisbane depends on disabled feature fips",
assertThrows(ProfileException.class,
() -> Profile.configure(new PropertiesProfileConfigResolver(properties))).getMessage());

properties.setProperty("keycloak.profile.feature.fips", "enabled");
Profile.configure(new PropertiesProfileConfigResolver(properties));
Assertions.assertTrue(Profile.isFeatureEnabled(Profile.Feature.BRISBANE));
Assertions.assertTrue(Profile.isFeatureEnabled(Profile.Feature.FIPS));
}

@Test
public void checkSuccessIfFeatureDisabledWithDisabledDependencies() {
Properties properties = new Properties();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2026 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.common.crypto;

import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class CryptoProviderFactoryTest {

@Test
public void shouldSelectFactoryByNameAndPassFipsMode() {
CryptoProvider expected = provider();
AtomicReference<FipsMode> selectedMode = new AtomicReference<>();
List<CryptoProviderFactory> factories = Arrays.asList(
factory("default", provider(), new AtomicReference<>()),
factory("brisbane", expected, selectedMode));

CryptoProvider actual = CryptoIntegration.detectProvider(factories, "brisbane", FipsMode.STRICT);

assertSame(expected, actual);
assertEquals(FipsMode.STRICT, selectedMode.get());
}

@Test
public void shouldRejectMissingFactory() {
IllegalStateException cause = assertThrows(IllegalStateException.class,
() -> CryptoIntegration.detectProvider(Collections.emptyList(), "brisbane", FipsMode.STRICT));

assertEquals("Expected one crypto provider named 'brisbane', found 0", cause.getMessage());
}

@Test
public void shouldRejectDuplicateFactories() {
List<CryptoProviderFactory> factories = Arrays.asList(
factory("brisbane", provider(), new AtomicReference<>()),
factory("brisbane", provider(), new AtomicReference<>()));

IllegalStateException cause = assertThrows(IllegalStateException.class,
() -> CryptoIntegration.detectProvider(factories, "brisbane", FipsMode.STRICT));

assertEquals("Expected one crypto provider named 'brisbane', found 2", cause.getMessage());
}

@Test
public void shouldRejectNullProvider() {
IllegalStateException cause = assertThrows(IllegalStateException.class,
() -> CryptoIntegration.detectProvider(
Collections.singletonList(factory("brisbane", null, new AtomicReference<>())), "brisbane", FipsMode.STRICT));

assertEquals("Crypto provider factory 'brisbane' returned null", cause.getMessage());
}

private static CryptoProviderFactory factory(String name, CryptoProvider provider, AtomicReference<FipsMode> selectedMode) {
return new CryptoProviderFactory() {
@Override
public String getName() {
return name;
}

@Override
public CryptoProvider create(FipsMode fipsMode) {
selectedMode.set(fipsMode);
return provider;
}
};
}

private static CryptoProvider provider() {
return (CryptoProvider) Proxy.newProxyInstance(CryptoProvider.class.getClassLoader(),
new Class<?>[] { CryptoProvider.class }, (proxy, method, args) -> null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 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.common.crypto;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class FipsProviderTest {

@Test
public void shouldUseProviderFactoryNames() {
assertEquals("bouncycastle", FipsProvider.BOUNCY_CASTLE.toString());
assertEquals("brisbane", FipsProvider.BRISBANE.toString());
}

@Test
public void shouldKeepExistingProviderClassNames() {
assertEquals("org.keycloak.crypto.fips.FIPS1402Provider", FipsMode.NON_STRICT.getProviderClassName());
assertEquals("org.keycloak.crypto.fips.Fips1402StrictCryptoProvider", FipsMode.STRICT.getProviderClassName());
assertEquals("org.keycloak.crypto.def.DefaultCryptoProvider", FipsMode.DISABLED.getProviderClassName());
}
}
59 changes: 59 additions & 0 deletions crypto/brisbane/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<!--
~ Copyright 2026 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.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>keycloak-crypto-brisbane</artifactId>
<name>Keycloak Crypto Brisbane Integration</name>
<description>Keycloak Crypto Brisbane Integration</description>

<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-crypto-elytron</artifactId>
</dependency>
<dependency>
<groupId>com.github.slaskawi</groupId>
<artifactId>brisbane</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>
Loading
Loading
0