diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140.java b/src/main/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140.java new file mode 100644 index 00000000..9b6f212d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140.java @@ -0,0 +1,115 @@ +package org.jenkinsci.plugins.oic; + +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.impl.AESCryptoProvider; +import com.nimbusds.jose.crypto.impl.ContentCryptoProvider; +import com.nimbusds.jose.crypto.impl.ECDHCryptoProvider; +import com.nimbusds.jose.crypto.impl.PasswordBasedCryptoProvider; +import com.nimbusds.jose.crypto.impl.RSACryptoProvider; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * This class helps in validating algorithms for FIPS-140 compliance and filtering the non-compliant algorithms when in + * FIPS mode. + */ +public class OicAlgorithmValidatorFIPS140 { + + private static final Set JWSSupportedAlgorithms = new LinkedHashSet<>(); + private static final Set JWESupportedAlgorithms = new LinkedHashSet<>(); + private static final Set supportedEncryptionMethod = new LinkedHashSet<>(); + + // Below list of compliant algorithms will be used to block the FIPS non-compliant algorithms. + static { + // Init compliant JWS algorithms + JWSSupportedAlgorithms.addAll(MACSigner.SUPPORTED_ALGORITHMS); + JWSSupportedAlgorithms.addAll(RSASSASigner.SUPPORTED_ALGORITHMS); + JWSSupportedAlgorithms.addAll(ECDSASigner.SUPPORTED_ALGORITHMS); + + // Init compliant JWE algorithms + JWESupportedAlgorithms.addAll(AESCryptoProvider.SUPPORTED_ALGORITHMS); + JWESupportedAlgorithms.addAll(RSACryptoProvider.SUPPORTED_ALGORITHMS); + // RSA1_5 is deprecated and not a compliant algorithm. + JWESupportedAlgorithms.remove(JWEAlgorithm.RSA1_5); + JWESupportedAlgorithms.addAll(ECDHCryptoProvider.SUPPORTED_ALGORITHMS); + JWESupportedAlgorithms.addAll(PasswordBasedCryptoProvider.SUPPORTED_ALGORITHMS); + + // Init complaint EncryptionMethods + supportedEncryptionMethod.addAll(ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS); + supportedEncryptionMethod.remove(EncryptionMethod.XC20P); + } + + /** + * Checks if the JWS signing algorithm used for OIC configuration is FIPS-140 compliant. + */ + public static boolean isJWSAlgorithmFipsCompliant(@NonNull JWSAlgorithm algorithm) { + return JWSSupportedAlgorithms.contains(algorithm); + } + + /** + * Checks if the JWE encryption algorithm used for OIC configuration is FIPS-140 compliant. + */ + public static boolean isJWEAlgorithmFipsCompliant(@NonNull JWEAlgorithm algorithm) { + return JWESupportedAlgorithms.contains(algorithm); + } + + /** + * Checks if the encryption method used for OIC configuration is FIPS-140 compliant. + */ + public static boolean isEncryptionMethodFipsCompliant(@NonNull EncryptionMethod encryptionMethod) { + return supportedEncryptionMethod.contains(encryptionMethod); + } + + /** + * Filter the list of JWE encryption lists used in OIC configuration and return only the FIPS-140 compliant + * algorithms + * @return immutable list of FIPS-140 JWE encryption algorithms + */ + @NonNull + public static List getFipsCompliantJWEAlgorithm(@NonNull List algorithms) { + return filterAlgorithms(algorithms, OicAlgorithmValidatorFIPS140::isJWEAlgorithmFipsCompliant); + } + + /** + * Filter the list of JWS encryption lists used in OIC configuration and return only the FIPS-140 compliant + * algorithms + * @return immutable list of FIPS-140 JWS encryption algorithms + */ + @NonNull + public static List getFipsCompliantJWSAlgorithm(@NonNull List algorithms) { + return filterAlgorithms(algorithms, OicAlgorithmValidatorFIPS140::isJWSAlgorithmFipsCompliant); + } + + /** + * Filter the list of encryption method lists used in OIC configuration and return only the FIPS-140 compliant + * algorithms + * @return immutable list of FIPS-140 encryption methods + */ + public static List getFipsCompliantEncryptionMethod(@NonNull List algorithms) { + return filterAlgorithms(algorithms, OicAlgorithmValidatorFIPS140::isEncryptionMethodFipsCompliant); + } + + /** + * Filters out FIPS non-compliant algorithms from the provided list. + * + * @param the type of the algorithm + * @param algorithms the list of algorithms to filter + * @param criteria that checks if an algorithm should be filtered or not + * @return immutable filtered list with elements matching the criteria + */ + @NonNull + private static List filterAlgorithms( + @NonNull List algorithms, @NonNull Function criteria) { + return algorithms.stream().filter(criteria::apply).collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java b/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java index ffb1df7c..bd11f766 100644 --- a/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java +++ b/src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java @@ -25,6 +25,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; import com.nimbusds.oauth2.sdk.GrantType; @@ -35,6 +38,7 @@ import com.nimbusds.oauth2.sdk.token.RefreshToken; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Util; @@ -504,6 +508,7 @@ private OidcConfiguration buildOidcConfiguration() { // set many more as needed... OIDCProviderMetadata oidcProviderMetadata = serverConfiguration.toProviderMetadata(); + filterNonFIPS140CompliantAlgorithms(oidcProviderMetadata); if (this.isDisableTokenVerification()) { conf.setAllowUnsignedIdTokens(true); conf.setTokenValidator(new AnythingGoesTokenValidator()); @@ -524,6 +529,144 @@ private OidcConfiguration buildOidcConfiguration() { return conf; } + // Visible for testing + @Restricted(NoExternalUse.class) + protected void filterNonFIPS140CompliantAlgorithms(@NonNull OIDCProviderMetadata oidcProviderMetadata) { + if (FIPS140.useCompliantAlgorithms()) { + // If FIPS is not enabled, then we don't have to filter the algorithms + filterJwsAlgorithms(oidcProviderMetadata); + filterJweAlgorithms(oidcProviderMetadata); + filterEncryptionMethods(oidcProviderMetadata); + } + } + + private void filterEncryptionMethods(@NonNull OIDCProviderMetadata oidcProviderMetadata) { + if (oidcProviderMetadata.getRequestObjectJWEEncs() != null) { + List requestObjectJWEEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getRequestObjectJWEEncs()); + oidcProviderMetadata.setRequestObjectJWEEncs(requestObjectJWEEncs); + } + + if (oidcProviderMetadata.getAuthorizationJWEEncs() != null) { + List authorizationJWEEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getAuthorizationJWEEncs()); + oidcProviderMetadata.setAuthorizationJWEEncs(authorizationJWEEncs); + } + + if (oidcProviderMetadata.getIDTokenJWEEncs() != null) { + List idTokenJWEEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getIDTokenJWEEncs()); + oidcProviderMetadata.setIDTokenJWEEncs(idTokenJWEEncs); + } + + if (oidcProviderMetadata.getUserInfoJWEEncs() != null) { + List userInfoJWEEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getUserInfoJWEEncs()); + oidcProviderMetadata.setUserInfoJWEEncs(userInfoJWEEncs); + } + + if (oidcProviderMetadata.getRequestObjectJWEEncs() != null) { + List requestObjectJweEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getRequestObjectJWEEncs()); + oidcProviderMetadata.setRequestObjectJWEEncs(requestObjectJweEncs); + } + + if (oidcProviderMetadata.getAuthorizationJWEEncs() != null) { + List authJweEncs = OicAlgorithmValidatorFIPS140.getFipsCompliantEncryptionMethod( + oidcProviderMetadata.getAuthorizationJWEEncs()); + oidcProviderMetadata.setAuthorizationJWEEncs(authJweEncs); + } + } + + private void filterJweAlgorithms(@NonNull OIDCProviderMetadata oidcProviderMetadata) { + if (oidcProviderMetadata.getIDTokenJWEAlgs() != null) { + List idTokenJWEAlgs = + OicAlgorithmValidatorFIPS140.getFipsCompliantJWEAlgorithm(oidcProviderMetadata.getIDTokenJWEAlgs()); + oidcProviderMetadata.setIDTokenJWEAlgs(idTokenJWEAlgs); + } + + if (oidcProviderMetadata.getUserInfoJWEAlgs() != null) { + List userTokenJWEAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWEAlgorithm( + oidcProviderMetadata.getUserInfoJWEAlgs()); + oidcProviderMetadata.setUserInfoJWEAlgs(userTokenJWEAlgs); + } + + if (oidcProviderMetadata.getRequestObjectJWEAlgs() != null) { + List requestObjectJWEAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWEAlgorithm( + oidcProviderMetadata.getRequestObjectJWEAlgs()); + oidcProviderMetadata.setRequestObjectJWEAlgs(requestObjectJWEAlgs); + } + + if (oidcProviderMetadata.getAuthorizationJWEAlgs() != null) { + List authorizationJWEAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWEAlgorithm( + oidcProviderMetadata.getAuthorizationJWEAlgs()); + oidcProviderMetadata.setAuthorizationJWEAlgs(authorizationJWEAlgs); + } + } + + private void filterJwsAlgorithms(@NonNull OIDCProviderMetadata oidcProviderMetadata) { + if (oidcProviderMetadata.getIDTokenJWSAlgs() != null) { + List idTokenJWSAlgs = + OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm(oidcProviderMetadata.getIDTokenJWSAlgs()); + oidcProviderMetadata.setIDTokenJWSAlgs(idTokenJWSAlgs); + } + + if (oidcProviderMetadata.getUserInfoJWSAlgs() != null) { + List userInfoJwsAlgo = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getUserInfoJWSAlgs()); + oidcProviderMetadata.setUserInfoJWSAlgs(userInfoJwsAlgo); + } + + if (oidcProviderMetadata.getTokenEndpointJWSAlgs() != null) { + List tokenEndpointJWSAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getTokenEndpointJWSAlgs()); + oidcProviderMetadata.setTokenEndpointJWSAlgs(tokenEndpointJWSAlgs); + } + + if (oidcProviderMetadata.getIntrospectionEndpointJWSAlgs() != null) { + List introspectionEndpointJWSAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getIntrospectionEndpointJWSAlgs()); + oidcProviderMetadata.setIntrospectionEndpointJWSAlgs(introspectionEndpointJWSAlgs); + } + + if (oidcProviderMetadata.getRevocationEndpointJWSAlgs() != null) { + List revocationEndpointJWSAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getRevocationEndpointJWSAlgs()); + oidcProviderMetadata.setRevocationEndpointJWSAlgs(revocationEndpointJWSAlgs); + } + + if (oidcProviderMetadata.getRequestObjectJWSAlgs() != null) { + List requestObjectJWSAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getRequestObjectJWSAlgs()); + oidcProviderMetadata.setRequestObjectJWSAlgs(requestObjectJWSAlgs); + } + + if (oidcProviderMetadata.getDPoPJWSAlgs() != null) { + List dPoPJWSAlgs = + OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm(oidcProviderMetadata.getDPoPJWSAlgs()); + oidcProviderMetadata.setDPoPJWSAlgs(dPoPJWSAlgs); + } + + if (oidcProviderMetadata.getAuthorizationJWSAlgs() != null) { + List authorizationJWSAlgs = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getAuthorizationJWSAlgs()); + oidcProviderMetadata.setAuthorizationJWSAlgs(authorizationJWSAlgs); + } + + if (oidcProviderMetadata.getBackChannelAuthenticationRequestJWSAlgs() != null) { + List backChannelAuthenticationRequestJWSAlgs = + OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getBackChannelAuthenticationRequestJWSAlgs()); + oidcProviderMetadata.setBackChannelAuthenticationRequestJWSAlgs(backChannelAuthenticationRequestJWSAlgs); + } + + if (oidcProviderMetadata.getClientRegistrationAuthnJWSAlgs() != null) { + List clientRegisterationAuth = OicAlgorithmValidatorFIPS140.getFipsCompliantJWSAlgorithm( + oidcProviderMetadata.getClientRegistrationAuthnJWSAlgs()); + oidcProviderMetadata.setClientRegistrationAuthnJWSAlgs(clientRegisterationAuth); + } + } + @Restricted(NoExternalUse.class) // exposed for testing only protected OidcClient buildOidcClient() { OidcConfiguration oidcConfiguration = buildOidcConfiguration(); diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicServerManualConfiguration.java b/src/main/java/org/jenkinsci/plugins/oic/OicServerManualConfiguration.java index 907dac0c..c11577f7 100644 --- a/src/main/java/org/jenkinsci/plugins/oic/OicServerManualConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/oic/OicServerManualConfiguration.java @@ -26,6 +26,7 @@ import java.util.Locale; import java.util.Objects; import jenkins.model.Jenkins; +import jenkins.security.FIPS140; import org.jenkinsci.Symbol; import org.jenkinsci.plugins.oic.OicSecurityRealm.TokenAuthMethod; import org.kohsuke.stapler.DataBoundConstructor; @@ -155,7 +156,13 @@ public OIDCProviderMetadata toProviderMetadata() { // rather we just say "I support anything, and let the check for the specific ones fail and fall through ArrayList allAlgorthms = new ArrayList<>(); allAlgorthms.addAll(JWSAlgorithm.Family.HMAC_SHA); - allAlgorthms.addAll(JWSAlgorithm.Family.SIGNATURE); + if (FIPS140.useCompliantAlgorithms()) { + // In FIPS-140 Family.ED is not supported + allAlgorthms.addAll(JWSAlgorithm.Family.RSA); + allAlgorthms.addAll(JWSAlgorithm.Family.EC); + } else { + allAlgorthms.addAll(JWSAlgorithm.Family.SIGNATURE); + } providerMetadata.setIDTokenJWSAlgs(allAlgorthms); return providerMetadata; } catch (URISyntaxException e) { diff --git a/src/main/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration.java b/src/main/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration.java index a3806424..bbb329b9 100644 --- a/src/main/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/oic/OicServerWellKnownConfiguration.java @@ -155,7 +155,7 @@ public OIDCProviderMetadata toProviderMetadata() { // hope. return oidcProviderMetadata; } - throw new IllegalStateException("Well known configuration could not be loaded, login can not preceed."); + throw new IllegalStateException("Well known configuration could not be loaded, login can not proceed."); } /** diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140Test.java b/src/test/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140Test.java new file mode 100644 index 00000000..43275d45 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/oic/OicAlgorithmValidatorFIPS140Test.java @@ -0,0 +1,34 @@ +package org.jenkinsci.plugins.oic; + +import com.nimbusds.jose.JWSAlgorithm; +import jenkins.security.FIPS140; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; + +public class OicAlgorithmValidatorFIPS140Test { + + @Test + void isJwsAlgorithmFipsCompliant() { + try (MockedStatic fips140Mock = mockStatic(FIPS140.class)) { + fips140Mock.when(FIPS140::useCompliantAlgorithms).thenReturn(true); + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm(""))); + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm(" "))); + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm("invalid-algo"))); + + String[] validAlgoArray = { + "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES256K", "ES384", "ES512", "PS256", + "PS384", "PS512" + }; + for (String algo : validAlgoArray) { + assertTrue(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm(algo))); + } + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm("EdDSA"))); + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm("Ed25519"))); + assertFalse(OicAlgorithmValidatorFIPS140.isJWSAlgorithmFipsCompliant(new JWSAlgorithm("Ed448"))); + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest.java b/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest.java new file mode 100644 index 00000000..08e17612 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest.java @@ -0,0 +1,167 @@ +package org.jenkinsci.plugins.oic; + +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; +import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import jenkins.security.FIPS140; +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.mockito.MockedStatic; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +public class OicSecurityRealmFIPSAlgoTest { + + @Test + public void doCheckAlgorithmFilteredNotInFipsMode() throws Exception { + try (MockedStatic fips140Mocked = mockStatic(FIPS140.class)) { + fips140Mocked.when(FIPS140::useCompliantAlgorithms).thenReturn(false); + + OIDCProviderMetadata oidcProviderMetadata = generateProviderMetadata(); + + OicSecurityRealm mocked = mock(OicSecurityRealm.class); + doCallRealMethod().when(mocked).filterNonFIPS140CompliantAlgorithms(any()); + + mocked.filterNonFIPS140CompliantAlgorithms(oidcProviderMetadata); + + /* + Original List: "PS384", "RS384", "EdDSA", "ES384", "HS256", "HS512", "ES256", "RS256", "HS384", "ES512", + "PS256", "PS512", "RS512" + Non FIPS: "EdDSA" + */ + assertThat( + "EdDSA is non compliant, but we are not in FIPS mode", + oidcProviderMetadata.getIDTokenJWSAlgs(), + containsInAnyOrder( + JWSAlgorithm.HS256, + JWSAlgorithm.HS384, + JWSAlgorithm.HS512, + JWSAlgorithm.RS256, + JWSAlgorithm.RS384, + JWSAlgorithm.RS512, + JWSAlgorithm.ES256, + JWSAlgorithm.ES384, + JWSAlgorithm.ES512, + JWSAlgorithm.PS256, + JWSAlgorithm.PS384, + JWSAlgorithm.PS512, + JWSAlgorithm.EdDSA)); + /* + Original List: "RSA-OAEP", "RSA-OAEP-256", "RSA1_5" + Non FIPS: "RSA1_5" + */ + assertThat( + "RSA1_5 is non compliant, but we are not in FIPS mode", + oidcProviderMetadata.getIDTokenJWEAlgs(), + containsInAnyOrder(JWEAlgorithm.RSA_OAEP, JWEAlgorithm.RSA_OAEP_256, JWEAlgorithm.RSA1_5)); + + /* + Original List: "A256GCM", "A192GCM", "A128GCM", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512", "XC20P" + Non FIPS: "XC20P" + */ + assertThat( + "XC20P is non compliant, but we are not in FIPS mode", + oidcProviderMetadata.getIDTokenJWEEncs(), + containsInAnyOrder( + EncryptionMethod.A256GCM, + EncryptionMethod.A192GCM, + EncryptionMethod.A128GCM, + EncryptionMethod.A128CBC_HS256, + EncryptionMethod.A192CBC_HS384, + EncryptionMethod.A256CBC_HS512, + EncryptionMethod.XC20P)); + } + } + + @Test + public void doCheckAlgorithmFilteredInFipsMode() throws Exception { + try (MockedStatic fips140Mocked = mockStatic(FIPS140.class)) { + fips140Mocked.when(FIPS140::useCompliantAlgorithms).thenReturn(true); + + OIDCProviderMetadata oidcProviderMetadata = generateProviderMetadata(); + + OicSecurityRealm mocked = mock(OicSecurityRealm.class); + doCallRealMethod().when(mocked).filterNonFIPS140CompliantAlgorithms(any()); + + mocked.filterNonFIPS140CompliantAlgorithms(oidcProviderMetadata); + + /* + Original List: "PS384", "RS384", "EdDSA", "ES384", "HS256", "HS512", "ES256", "RS256", "HS384", "ES512", + "PS256", "PS512", "RS512" + Non FIPS: "EdDSA" + */ + assertThat( + "EdDSA is non compliant", + oidcProviderMetadata.getIDTokenJWSAlgs(), + not(contains(JWSAlgorithm.EdDSA))); + assertThat( + "Rest of algorithms are compliant", + oidcProviderMetadata.getIDTokenJWSAlgs(), + containsInAnyOrder( + JWSAlgorithm.HS256, + JWSAlgorithm.HS384, + JWSAlgorithm.HS512, + JWSAlgorithm.RS256, + JWSAlgorithm.RS384, + JWSAlgorithm.RS512, + JWSAlgorithm.ES256, + JWSAlgorithm.ES384, + JWSAlgorithm.ES512, + JWSAlgorithm.PS256, + JWSAlgorithm.PS384, + JWSAlgorithm.PS512)); + /* + Original List: "RSA-OAEP", "RSA-OAEP-256", "RSA1_5" + Non FIPS: "RSA1_5" + */ + assertThat( + "RSA1_5 is non compliant", + oidcProviderMetadata.getIDTokenJWEAlgs(), + not(contains(JWEAlgorithm.RSA1_5))); + assertThat( + "Rest of algorithms are compliant", + oidcProviderMetadata.getIDTokenJWEAlgs(), + containsInAnyOrder(JWEAlgorithm.RSA_OAEP, JWEAlgorithm.RSA_OAEP_256)); + + /* + Original List: "A256GCM", "A192GCM", "A128GCM", "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512", "XC20P" + Non FIPS: "XC20P" + */ + assertThat( + "XC20P is non compliant", + oidcProviderMetadata.getIDTokenJWEEncs(), + not(contains(EncryptionMethod.XC20P))); + assertThat( + "Rest of algorithms are compliant", + oidcProviderMetadata.getIDTokenJWEEncs(), + containsInAnyOrder( + EncryptionMethod.A256GCM, + EncryptionMethod.A192GCM, + EncryptionMethod.A128GCM, + EncryptionMethod.A128CBC_HS256, + EncryptionMethod.A192CBC_HS384, + EncryptionMethod.A256CBC_HS512)); + } + } + + private OIDCProviderMetadata generateProviderMetadata() throws Exception { + File json = Paths.get( + "src/test/resources/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest/metadata" + ".json") + .toFile(); + String metadata = FileUtils.readFileToString(json, StandardCharsets.UTF_8); + + return OIDCProviderMetadata.parse(JSONObjectUtils.parse(metadata)); + } +} diff --git a/src/test/resources/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest/metadata.json b/src/test/resources/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest/metadata.json new file mode 100644 index 00000000..813cd254 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/oic/OicSecurityRealmFIPSAlgoTest/metadata.json @@ -0,0 +1,301 @@ +{ + "issuer": "http://localhost:9090/realms/master", + "authorization_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/auth", + "token_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/token", + "introspection_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/token/introspect", + "userinfo_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/userinfo", + "end_session_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/logout", + "frontchannel_logout_session_supported": true, + "frontchannel_logout_supported": true, + "jwks_uri": "http://localhost:9090/realms/master/protocol/openid-connect/certs", + "check_session_iframe": "http://localhost:9090/realms/master/protocol/openid-connect/login-status-iframe.html", + "grant_types_supported": [ + "authorization_code", + "implicit", + "refresh_token", + "password", + "client_credentials", + "urn:openid:params:grant-type:ciba", + "urn:ietf:params:oauth:grant-type:device_code" + ], + "acr_values_supported": [ + "0", + "1" + ], + "response_types_supported": [ + "code", + "none", + "id_token", + "token", + "id_token token", + "code id_token", + "code token", + "code id_token token" + ], + "subject_types_supported": [ + "public", + "pairwise" + ], + "id_token_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "id_token_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "id_token_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512", + "XC20P" + ], + "userinfo_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512", + "none" + ], + "userinfo_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "userinfo_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "request_object_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512", + "none" + ], + "request_object_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "request_object_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post", + "query.jwt", + "fragment.jwt", + "form_post.jwt", + "jwt" + ], + "registration_endpoint": "http://localhost:9090/realms/master/clients-registrations/openid-connect", + "token_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "token_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "introspection_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "introspection_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "authorization_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "authorization_encryption_alg_values_supported": [ + "RSA-OAEP", + "RSA-OAEP-256", + "RSA1_5" + ], + "authorization_encryption_enc_values_supported": [ + "A256GCM", + "A192GCM", + "A128GCM", + "A128CBC-HS256", + "A192CBC-HS384", + "A256CBC-HS512" + ], + "claims_supported": [ + "aud", + "sub", + "iss", + "auth_time", + "name", + "given_name", + "family_name", + "preferred_username", + "email", + "acr" + ], + "claim_types_supported": [ + "normal" + ], + "claims_parameter_supported": true, + "scopes_supported": [ + "openid", + "basic", + "phone", + "acr", + "address", + "roles", + "microprofile-jwt", + "profile", + "email", + "web-origins", + "offline_access" + ], + "request_parameter_supported": true, + "request_uri_parameter_supported": true, + "require_request_uri_registration": true, + "code_challenge_methods_supported": [ + "plain", + "S256" + ], + "tls_client_certificate_bound_access_tokens": true, + "revocation_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/revoke", + "revocation_endpoint_auth_methods_supported": [ + "private_key_jwt", + "client_secret_basic", + "client_secret_post", + "tls_client_auth", + "client_secret_jwt" + ], + "revocation_endpoint_auth_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "HS256", + "HS512", + "ES256", + "RS256", + "HS384", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "backchannel_logout_supported": true, + "backchannel_logout_session_supported": true, + "device_authorization_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/auth/device", + "backchannel_token_delivery_modes_supported": [ + "poll", + "ping" + ], + "backchannel_authentication_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/ext/ciba/auth", + "backchannel_authentication_request_signing_alg_values_supported": [ + "PS384", + "RS384", + "EdDSA", + "ES384", + "ES256", + "RS256", + "ES512", + "PS256", + "PS512", + "RS512" + ], + "require_pushed_authorization_requests": false, + "pushed_authorization_request_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/ext/par/request", + "mtls_endpoint_aliases": { + "token_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/token", + "revocation_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/revoke", + "introspection_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/token/introspect", + "device_authorization_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/auth/device", + "registration_endpoint": "http://localhost:9090/realms/master/clients-registrations/openid-connect", + "userinfo_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/userinfo", + "pushed_authorization_request_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/ext/par/request", + "backchannel_authentication_endpoint": "http://localhost:9090/realms/master/protocol/openid-connect/ext/ciba/auth" + }, + "authorization_response_iss_parameter_supported": true +}