[java-idp-plugin-oidc-rp] branch main updated: Cleanup RequestObject signature signing config. Add tests
Phil Smart
philip.smart at jisc.ac.uk
Wed Jul 13 13:33:06 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=2ad45476a60132a389d878bb38564cdaf93d1274
The following commit(s) were added to refs/heads/main by this push:
new 2ad4547 Cleanup RequestObject signature signing config. Add tests
2ad4547 is described below
commit 2ad45476a60132a389d878bb38564cdaf93d1274
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jul 13 14:32:59 2022 +0100
Cleanup RequestObject signature signing config. Add tests
---
.../oidc/rp/config/CredentialsListFactory.java | 66 ++++++++++
.../MapBackedMemoryStorageServiceFactoryBean.java | 1 +
.../rp/impl/PopulateJWTEncryptionParameters.java | 2 +-
...RelyingPartyProxySigningParametersResolver.java | 24 +++-
.../oidc-relying-party-authn-beans.xml | 15 ++-
.../idp/service/relying-party/postconfig.xml | 6 +-
.../oidc/rp/config/CredentialsListFactoryTest.java | 65 ++++++++++
.../authn/oidc/rp/impl/AbstractOIDCTest.java | 1 +
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 116 ++++++++++++++++-
.../authn/oidc/rp/impl/TestCredentialHelper.java | 25 +++-
.../rp/messaging/impl/SignRequestObjectTest.java | 138 +++++++++++++++++++++
.../test/resources/conf/authn/rp-credentials.xml | 6 +
.../conf/credentials/idp-encryption-rsa.jwk | 14 +++
.../resources/conf/credentials/idp-signing-rsa.jwk | 14 +++
.../resources/conf/test-relying-party-system.xml | 9 ++
15 files changed, 489 insertions(+), 13 deletions(-)
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactory.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactory.java
new file mode 100644
index 0000000..ec7d214
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.idp.plugin.authn.oidc.rp.config;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
+import org.springframework.beans.factory.config.AbstractFactoryBean;
+
+/**
+ * A factory that returns a list of credentials which does not contain any {@literal null} elements.
+ *
+ * <p>Primarily created to support signature credential injection into the {@link BasicSignatureSigningConfiguration}
+ * when the credential list may contain, in its raw state, {@literal null} elements.</p>
+ */
+public class CredentialsListFactory extends AbstractFactoryBean<List<Credential>> {
+
+ /** The credentials which may contain {@literal null} elements. */
+ @Nonnull private List<Credential> credentials;
+
+ /**
+ * Constructor.
+ *
+ * @param creds the credentials, which can be {@literal null} and may contain {@literal null} elements.
+ */
+ public CredentialsListFactory(@Nullable final List<Credential> creds){
+ if (creds == null) {
+ credentials = Collections.emptyList();
+ } else {
+ credentials = creds;
+ }
+ }
+
+ @Override
+ public Class<?> getObjectType() {
+ return List.class;
+ }
+
+ @Override
+ protected List<Credential> createInstance() throws Exception {
+ return credentials.stream().filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/MapBackedMemoryStorageServiceFactoryBean.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/MapBackedMemoryStorageServiceFactoryBean.java
index 469c9d3..6cd60a9 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/MapBackedMemoryStorageServiceFactoryBean.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/MapBackedMemoryStorageServiceFactoryBean.java
@@ -43,6 +43,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* to the storage service. Values which are already Strings are just passed in without modification.
*/
@ThreadSafe
+ at Deprecated
public class MapBackedMemoryStorageServiceFactoryBean extends AbstractFactoryBean<StorageService> {
/** Class logger. */
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTEncryptionParameters.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTEncryptionParameters.java
index e8be51e..6b87308 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTEncryptionParameters.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTEncryptionParameters.java
@@ -195,7 +195,7 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
if (!super.doPreExecute(profileRequestContext)) {
- log.debug("{} Encryption disabled", getLogPrefix());
+ log.debug("{} Encryption disabled for {}", getLogPrefix(), forFriendlyName);
return false;
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/RelyingPartyProxySigningParametersResolver.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/RelyingPartyProxySigningParametersResolver.java
index 9b2f191..5c02095 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/RelyingPartyProxySigningParametersResolver.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/RelyingPartyProxySigningParametersResolver.java
@@ -21,6 +21,7 @@ import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.List;
+import java.util.StringJoiner;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -39,10 +40,12 @@ import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
import net.shibboleth.oidc.security.criterion.StaticCredentialCriterion;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
/**
@@ -119,8 +122,10 @@ public class RelyingPartyProxySigningParametersResolver extends BasicSignatureSi
log.trace("Resolved effective signature algorithms: {}", supportedAlgorithms);
// Pick the first credential that matches one of the supported algorithms
- for (final Credential credential : allCredentials) {
- log.trace("Evaluating signing credential '{}'", credential.getKeyNames());
+ for (final Credential credential : allCredentials) {
+ if (log.isTraceEnabled()) {
+ log.trace("Evaluating signing credential '{}'", extractKeyName(credential));
+ }
final JWSAlgorithm foundSupportedAlgorithm =
credentialSupportsSigningAlgorithm(credential, supportedAlgorithms);
if (foundSupportedAlgorithm != null) {
@@ -134,6 +139,21 @@ public class RelyingPartyProxySigningParametersResolver extends BasicSignatureSi
}
}
+ /**
+ * Extract the credentials name for display. Favouring the keyId if there is one, over the keyNames.
+ *
+ * @param credential the credential to extract the name from
+ *
+ * @return the key name.
+ */
+ private String extractKeyName(final Credential credential) {
+ if (credential instanceof JWKCredential &&
+ StringSupport.trimOrNull(((JWKCredential)credential).getKid()) != null) {
+ return ((JWKCredential)credential).getKid();
+ }
+ return credential.getKeyNames().stream().collect(Collectors.joining(","));
+ }
+
/** {@inheritDoc}
*
* <p>Does not include validation of the SignatureCanonicalizationAlgorithm or the
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index dcce53d..2c0e36b 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -146,22 +146,25 @@
- <bean id="shibboleth.authn.oidc.rp.DefaultSignRequestObjectCondition"
+ <bean id="SignRequestObjectProxyCondition"
class="net.shibboleth.idp.plugin.authn.oidc.rp.config.logic.SignRequestObjectPredicate"
p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.Proxy.RelyingPartyContext"/>
+ <bean id="SignRequestObjectCondition"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.logic.SignRequestObjectPredicate"
+ p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty"/>
- <bean id="shibboleth.authn.oidc.rp.DefaultEncryptRequestObjectCondition"
+ <bean id="EncryptRequestObjectCondition"
class="net.shibboleth.idp.plugin.authn.oidc.rp.config.logic.EncryptRequestObjectPredicate"
p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty"/>
- <bean id="PopulateRequestObjectSignatureSigningParameters"
+ <bean id="PopulateRequestObjectSignatureSigningParameters" scope="prototype"
class="net.shibboleth.oidc.security.impl.PopulateJWTSignatureSigningParameters"
c:strategy-ref="shibboleth.ChildLookup.Proxy.MessageContextLookup.Inbound"
p:noResultIsError="true"
p:configurationLookupStrategy-ref="RequestObjectSignatureSigningConfigurationLookup"
p:providerMetadataContextLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
p:signatureSigningParametersResolver-ref="shibboleth.authn.oidc.rp.RequestObjectSignatureSigningParametersResolver"
- p:activationCondition="#{getObject('shibboleth.authn.oidc.rp.SignRequestObjectCondition') ?: getObject('shibboleth.authn.oidc.rp.DefaultSignRequestObjectCondition')}"/>
+ p:activationCondition-ref="SignRequestObjectProxyCondition"/>
<bean id="shibboleth.authn.oidc.rp.RequestObjectSignatureSigningParametersResolver"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.RelyingPartyProxySigningParametersResolver"
@@ -182,7 +185,7 @@
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:providerMetadataContextLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
p:encryptionParametersResolver-ref="shibboleth.authn.oidc.rp.EncryptionParametersResolver"
- p:activationCondition="#{getObject('shibboleth.authn.oidc.rp.EncryptRequestObjectCondition') ?: getObject('shibboleth.authn.oidc.rp.DefaultEncryptRequestObjectCondition')}"/>
+ p:activationCondition-ref="EncryptRequestObjectCondition"/>
<bean id="RequestObjectEncryptionConfigurationLookup" lazy-init="true"
class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RequestObjectEncryptionConfigurationLookupFunction"
@@ -196,7 +199,7 @@
scope="prototype" p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
p:claimsSetIsValidPredicate="#{getObject('shibboleth.authn.oidc.rp.RequestObjectClaimsSetIsValidPredicate')}"
- p:requestObjectToBeSignedPredicate="#{getObject('shibboleth.authn.oidc.rp.SignRequestObjectCondition') ?: getObject('shibboleth.authn.oidc.rp.DefaultSignRequestObjectCondition')}"/>
+ p:requestObjectToBeSignedPredicate-ref="SignRequestObjectCondition"/>
<!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index fbad510..e876339 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -141,7 +141,7 @@
-->
<bean id="shibboleth.authn.oidc.rp.DefaultRequestObjectSigningConfiguration"
parent="shibboleth.BasicSignatureSigningConfiguration"
- p:signingCredentials="#{getObject('shibboleth.authn.oidc.rp.SigningCredentialss')}">
+ p:signingCredentials="#{getObject('shibboleth.authn.oidc.rp.SigningCredentials')}">
<property name="signatureAlgorithms">
<list>
<util:constant
@@ -159,6 +159,10 @@
</list>
</property>
</bean>
+
+ <bean id="shibboleth.authn.oidc.rp.SigningCredentials"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.CredentialsListFactory"
+ c:_0-ref="shibboleth.authn.oidc.rp.DefaultSigningCredentials"/>
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactoryTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactoryTest.java
new file mode 100644
index 0000000..8dcdebc
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/CredentialsListFactoryTest.java
@@ -0,0 +1,65 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.config;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opensaml.security.credential.Credential;
+import org.testng.annotations.Test;
+
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+
+/** Tests for the {@link CredentialsListFactory}.*/
+public class CredentialsListFactoryTest {
+
+ /**
+ * Test with a null input list.
+ *
+ * @throws Exception on error
+ */
+ @Test
+ public void testNullInputList() throws Exception {
+ final CredentialsListFactory factory = new CredentialsListFactory(null);
+ factory.afterPropertiesSet();
+ final List<Credential> creds = factory.createInstance();
+ assertNotNull(creds);
+ assertTrue(creds.isEmpty());
+ }
+
+
+ /**
+ * Test with an input list with a null element.
+ *
+ * @throws Exception on error
+ */
+ @Test
+ public void testInputListWithNullElement() throws Exception {
+ final var credsIn = new ArrayList<Credential>();
+ credsIn.add(null);
+ final CredentialsListFactory factory = new CredentialsListFactory(credsIn);
+ factory.afterPropertiesSet();
+ final List<Credential> creds = factory.createInstance();
+ assertNotNull(creds);
+ assertTrue(creds.isEmpty());
+ }
+
+ /**
+ * Test with an input list with one null element.
+ *
+ * @throws Exception on error
+ */
+ @Test
+ public void testInputListWithOneNullElement() throws Exception {
+ final var credsIn = new ArrayList<Credential>();
+ credsIn.add(null);
+ credsIn.add(new BasicJWKCredential());
+ final CredentialsListFactory factory = new CredentialsListFactory(credsIn);
+ factory.afterPropertiesSet();
+ final List<Credential> creds = factory.createInstance();
+ assertNotNull(creds);
+ assertTrue(creds.size()==1);
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index 6c5b8e5..69e084d 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -164,6 +164,7 @@ public abstract class AbstractOIDCTest {
outMsgCtx.setMessage(request);
prc.setOutboundMessageContext(outMsgCtx);
+ // FIXME we should no longer need this context
final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
metadataContext.setClientInformation(
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index 986e995..6cc3852 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -115,6 +115,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
/** The OP Issuer to use with an override in the config to use the request object authn param.*/
private static final String OP_ISSUER_ID_REQUESTOBJECT_TRUE = "https://localhost:9919";
+ /** The OP Issuer to use with an override in the config to use the request object authn param
+ * signed using RS256.*/
+ private static final String OP_ISSUER_ID_REQUESTOBJECT_TRUE_RSA256_SIG = "https://localhost:9920";
+
private final String RP_ALLOWED_ORIGINS = "https://localhost";
private static final String REDIRECT_URI_OVERRIDE = "https://localhost/callback";
@@ -127,6 +131,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
* Example of good provider metadata. Endpoints are localhost to support the
* mock server that is started.
*/
+ //TODO simplify these
private final static String GOOD_PROVIDER_CONFIGURATION_INFO = "{\n"
+ "\"issuer\": \"https://localhost:9918\",\n"
+ "\"authorization_endpoint\": \"https://localhost:9918/o/oauth2/v2/auth\",\n"
@@ -197,7 +202,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+ "\"token_endpoint\": \"https://localhost:9919/token\",\n"
+ "\"userinfo_endpoint\": \"https://localhost:9919/v1/userinfo\",\n"
+ "\"revocation_endpoint\": \"https://localhost:9919/revoke\",\n"
- + "\"jwks_uri\": \"https://localhost:9918/oauth2/v3/certs\",\n"
+ + "\"jwks_uri\": \"https://localhost:9919/oauth2/v3/certs\",\n"
+ "\"request_parameter_supported\":true,\n"
+ "\"response_types_supported\": [\n"
+ "\"code\",\n"
@@ -252,6 +257,73 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+ "\"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n"
+ "]\n"
+ "}";
+
+ /**
+ * Example of good provider metadata. Endpoints are localhost to support the
+ * mock server that is started. This OP supports the use of the request object.
+ */
+ private final static String GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_RSA256_SIG = "{\n"
+ + "\"issuer\": \"https://localhost:9920\",\n"
+ + "\"authorization_endpoint\": \"https://localhost:9920/o/oauth2/v2/auth\",\n"
+ + "\"device_authorization_endpoint\": \"https://localhost:9920/device/code\",\n"
+ + "\"token_endpoint\": \"https://localhost:9920/token\",\n"
+ + "\"userinfo_endpoint\": \"https://localhost:9920/v1/userinfo\",\n"
+ + "\"revocation_endpoint\": \"https://localhost:9920/revoke\",\n"
+ + "\"jwks_uri\": \"https://localhost:9920/oauth2/v3/certs\",\n"
+ + "\"request_parameter_supported\":true,\n"
+ + "\"response_types_supported\": [\n"
+ + "\"code\",\n"
+ + "\"token\",\n"
+ + "\"id_token\",\n"
+ + "\"code token\",\n"
+ + "\"code id_token\",\n"
+ + "\"token id_token\",\n"
+ + "\"code token id_token\",\n"
+ + "\"none\"\n"
+ + "],\n"
+ + "\"subject_types_supported\": [\n"
+ + "\"public\"\n"
+ + "],\n"
+ + "\"id_token_signing_alg_values_supported\": [\n"
+ + "\"RS256\"\n"
+ + "],\n"
+ + "\"request_object_signing_alg_values_supported\": [\n"
+ + "\"RS256\"\n"
+ + "],\n"
+ + "\"scopes_supported\": [\n"
+ + "\"openid\",\n"
+ + "\"email\",\n"
+ + "\"profile\"\n"
+ + "],\n"
+ + "\"token_endpoint_auth_methods_supported\": [\n"
+ + "\"client_secret_post\",\n"
+ + "\"client_secret_basic\"\n"
+ + "],\n"
+ + "\"claims_supported\": [\n"
+ + "\"aud\",\n"
+ + "\"email\",\n"
+ + "\"email_verified\",\n"
+ + "\"exp\",\n"
+ + "\"family_name\",\n"
+ + "\"given_name\",\n"
+ + "\"iat\",\n"
+ + "\"iss\",\n"
+ + "\"locale\",\n"
+ + "\"name\",\n"
+ + "\"picture\",\n"
+ + "\"sub\"\n"
+ + "],\n"
+ + "\"code_challenge_methods_supported\": [\n"
+ + "\"plain\",\n"
+ + "\"S256\"\n"
+ + "],\n"
+ + "\"grant_types_supported\": [\n"
+ + "\"authorization_code\",\n"
+ + "\"refresh_token\",\n"
+ + "\"urn:ietf:params:oauth:grant-type:device_code\",\n"
+ + "\"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n"
+ + "]\n"
+ + "}";
/** Mock JSON Object response from the UserInfo endpoint.*/
@@ -405,8 +477,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
loadBeanDefinitionsFromXmlFile(builderContext,
new ClassPathResource("attribute/filter/attribute-filter-system.xml"), null);
+ // Add a signing key incase it is used
loadBeanDefinitionsFromXmlFile(builderContext,
- new ClassPathResource("conf/authn/rp-credentials.xml"), null);
+ new ClassPathResource("conf/authn/rp-credentials.xml"), Map.of(
+ "idp.authn.oidc.rp.client.sig.key","conf/credentials/idp-signing-rsa.jwk"));
}
@@ -565,6 +639,44 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
assertCurrentStateEquals("AuthnRequest");
}
+ /**
+ * Test to the authorization redirect using an OP who's RP config in XML is set to use a request object.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testFlowToAuthorizationRedirect_UsingRequestObject_RSA256Signature() throws Exception {
+ setFlowPath(FLOW);
+ setFlowModelResources(flowResources);
+ setSubflows(subflows);
+
+ final Map<String,String> mockProperties = Map.of(
+ "idp.service.clientinfo.failFast","false",
+ "idp.entityID", "http://idp.example.com/",
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID_REQUESTOBJECT_TRUE_RSA256_SIG);
+
+ setMockProperties(mockProperties);
+
+ final MockWebServer mockOPServer = createSimpleServer();
+ // First is metadata exchange
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_RSA256_SIG));
+
+ mockOPServer.start(9920);
+
+ final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+ inputMap.put("calledAsSubflow", true);
+
+ final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty",false,true);
+
+ final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());
+ flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+ updateFlowExecution(flowExecution);
+ flowExecution.start(inputMap, externalContext);
+ assertCurrentStateEquals("AuthnRequest");
+ }
+
@Test
public void testFlowToAuthorizationRedirect_WithACRs() throws Exception {
setFlowPath(FLOW);
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestCredentialHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestCredentialHelper.java
index 0569db9..74803ec 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestCredentialHelper.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestCredentialHelper.java
@@ -9,6 +9,8 @@ import org.opensaml.security.credential.UsageType;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import net.shibboleth.oidc.security.credential.BasicExpiringJWKCredential;
@@ -61,6 +63,27 @@ public final class TestCredentialHelper {
return jwkCredential;
}
+ /**
+ * Create an asymmetric signing credential.
+ *
+ * @param key the key to convert to a {@link JWKCredential}.
+ *
+ * @return the credential
+ * @throws JOSEException on error
+ */
+ public static JWKCredential createAsymmetricSigningCredential(final AsymmetricJWK key) throws JOSEException {
+ final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+ jwkCredential.setPrivateKey(key.toPrivateKey());
+ jwkCredential.setPublicKey(key.toPublicKey());
+ jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+ jwkCredential.setUsageType(UsageType.SIGNING);
+
+ jwkCredential.setKid(((JWK)key).getKeyID());
+ jwkCredential.getKeyNames().add("mockKey");
+ jwkCredential.setAlgorithm(((JWK)key).getAlgorithm());
+ return jwkCredential;
+ }
+
/**
* Create a direct encryption {@link JWKCredential} from the given shared secret.
@@ -68,7 +91,7 @@ public final class TestCredentialHelper {
* @param secret the secret to convert to a {@link JWKCredential}.
*
* @return the credential
- * @throws JOSEException
+ * @throws JOSEException on error
*/
public static JWKCredential createKeyEncryptionCredential(final RSAKey secret) throws JOSEException {
final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObjectTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObjectTest.java
new file mode 100644
index 0000000..71b80c6
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObjectTest.java
@@ -0,0 +1,138 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.TestCredentialHelper;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+
+/** Tests for the SignRequestObject message handler.*/
+public class SignRequestObjectTest extends AbstractOIDCTest {
+
+ /** A client_secret to use.*/
+ @Nonnull private final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+
+ /** The signer to test.*/
+ private SignRequestObject signer;
+
+ /** The authn request.*/
+ private OIDCAuthenticationRequest request;
+
+ @Override
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ signer = new SignRequestObject();
+
+ request = new OIDCAuthenticationRequest(new ClientID("test-client"));
+ final JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .issuer("test-client")
+ .audience("test-op")
+ .issueTime(new Date())
+ .build();
+ request.setRequestObject(new PlainJWT(claims));
+
+ prc.getOutboundMessageContext().setMessage(request);
+ }
+
+ @Test
+ public void testSignHMAC_Success() throws MessageHandlerException {
+
+ final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
+ final var params = new SignatureSigningParameters();
+ params.setSigningCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
+ params.setSignatureAlgorithm("HS256");
+ secParamCtx.setSignatureSigningParameters(params);
+ prc.getOutboundMessageContext().addSubcontext(secParamCtx);
+
+ signer.invoke(prc.getOutboundMessageContext());
+ final JWT jwt = request.getRequestObject();
+ assertTrue(jwt instanceof SignedJWT);
+ final var signedJWT = (SignedJWT)jwt;
+ assertTrue(JWSAlgorithm.Family.HMAC_SHA.contains(signedJWT.getHeader().getAlgorithm()));
+ }
+
+ @Test(expectedExceptions = Exception.class)
+ public void testSignHMAC_WrongCredentialType() throws MessageHandlerException {
+
+ final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
+ final var params = new SignatureSigningParameters();
+ params.setSigningCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
+ params.setSignatureAlgorithm("RS256");
+ secParamCtx.setSignatureSigningParameters(params);
+ prc.getOutboundMessageContext().addSubcontext(secParamCtx);
+
+ signer.invoke(prc.getOutboundMessageContext());
+ final JWT jwt = request.getRequestObject();
+ assertTrue(jwt instanceof SignedJWT);
+ final var signedJWT = (SignedJWT)jwt;
+ assertTrue(JWSAlgorithm.Family.HMAC_SHA.contains(signedJWT.getHeader().getAlgorithm()));
+ }
+
+ @Test
+ public void testSignRS256_Success() throws MessageHandlerException, JOSEException {
+
+ final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
+ final var params = new SignatureSigningParameters();
+ final RSAKey rsaKey = new RSAKeyGenerator(2048)
+ .keyID("1")
+ .keyUse(KeyUse.SIGNATURE)
+ .generate();
+ params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+ params.setSignatureAlgorithm("RS256");
+ secParamCtx.setSignatureSigningParameters(params);
+ prc.getOutboundMessageContext().addSubcontext(secParamCtx);
+
+ signer.invoke(prc.getOutboundMessageContext());
+ final JWT jwt = request.getRequestObject();
+ assertTrue(jwt instanceof SignedJWT);
+ final var signedJWT = (SignedJWT)jwt;
+ assertTrue(JWSAlgorithm.Family.RSA.contains(signedJWT.getHeader().getAlgorithm()));
+ }
+
+ @Test
+ public void testSignES256_Success() throws MessageHandlerException, JOSEException {
+
+ final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
+ final var params = new SignatureSigningParameters();
+ final ECKey ecKey = new ECKeyGenerator(Curve.P_256)
+ .keyID("1")
+ .keyUse(KeyUse.SIGNATURE)
+ .generate();
+ params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(ecKey));
+ params.setSignatureAlgorithm("ES256");
+ secParamCtx.setSignatureSigningParameters(params);
+ prc.getOutboundMessageContext().addSubcontext(secParamCtx);
+
+ signer.invoke(prc.getOutboundMessageContext());
+ final JWT jwt = request.getRequestObject();
+ assertTrue(jwt instanceof SignedJWT);
+ final var signedJWT = (SignedJWT)jwt;
+ assertTrue(JWSAlgorithm.Family.EC.contains(signedJWT.getHeader().getAlgorithm()));
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/rp-credentials.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/rp-credentials.xml
index 7dc5a6f..d660e13 100644
--- a/idp-oidc-rp-impl/src/test/resources/conf/authn/rp-credentials.xml
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/rp-credentials.xml
@@ -15,4 +15,10 @@
p:resource="%{idp.authn.oidc.rp.client.enc.key:#{null}}" />
</util:list>
+ <!-- Default signing credentials -->
+ <util:list id="shibboleth.authn.oidc.rp.SigningCredentials">
+ <bean parent="shibboleth.authn.oidc.rp.JWKCredential" p:failIfResourceIsNull="false"
+ p:resource="%{idp.authn.oidc.rp.client.sig.key:#{null}}" />
+ </util:list>
+
</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-encryption-rsa.jwk b/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-encryption-rsa.jwk
new file mode 100644
index 0000000..11640fb
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-encryption-rsa.jwk
@@ -0,0 +1,14 @@
+{
+ "p": "5wVHO2-yeyReB-w_pUr-xzrY6tMS54rGlyUjOPiuUJxJYEHHOr1CqzuW1dUMkZDuec8ggF_rNd51hu6UChKsW6WLfLjSwVPx_ompBMmYVShv8PgtnwYL1d9PMqk2ibB_GaRLlM16e9ynqFaZXBNnbwl3qHv6WYu6nABPI54zoGs",
+ "kty": "RSA",
+ "q": "yzwGoBHkLWLqIQ_DdY2jimOEqgKrzPau41wHAMQXcyzGavayCgsc6r6o9_RDKkgwFnpXk-mmcZr-osqapRwL8LaYtoBBMXTRCFd02N1IqkxmXRjq3pu7WFSNsHWy0GkSdE626fpJklUjh4TLFfOSBRkzKy7E6cj-vyhzqmrgZAU",
+ "d": "bPsmY1pD0Wr5nj_Optd6hkoM5ANXKVeM2rMKQ2_n7qg6qA4Li-nb_jgyAaiomB2TYAjtJvY804Cc9lhsoXyN0o8NJh8YpI4_59oKJA-L_CupmeZxI9Jo7D4WCrh2HVIjCokqyDjd30aYdb_R9x1ACmE6cfwTxY0TVAhFaT9rhCVZHc6I8niw9kbevmpMZbLwR6WDvdivPBto6BGLXzInxf2s22lGcetP1m2Trj15hW5oOsUDTKXosKWZrs6-9qGO9Uq4JEzhdVdUOQvkoujrT-G9-hbscvDO2-KXJ6a3qz4SDYFCGoWB0QhsLmHGLtBOUvJRiEuztjAy-L_eyigLWQ",
+ "e": "AQAB",
+ "use": "enc",
+ "alg": "RSA-OAEP-256",
+ "kid": "defaultRSAEnc",
+ "qi": "X8a2QwIr5q94V9QyAsArVijyICSrEsdT5Zfpyoz7Eyhd2VoAyA74WiUbcFElbHNbJOKmvHzp9les4o3BCpsTYwUyRdlB-npL_tEpp7fdIj8I3EhWfspJwT1EfLtJakGwoa6v0KpOmEzzR9mCwKmSnKfhF3aA1S-Hch1eEiV8qm8",
+ "dp": "vJNaafHrRwmQl_cInOxyvD4VAtn4_HgTUx1FeyPDZpmsa55F-nSzDwM9RJ77-3bKszOX3DJv1TEZzmLBBNfpSYYALnbP0m-rgZLtHLNSXXD8rz5mPwC4eIQoKbpmgk5H8a5i47w21xRu5sKJvNc5_zFDM4y_9ohQczbtYq2ohrE",
+ "dq": "fyfzTTtkdDErI7xlIquX4wIZAvXg38CH86CkkQofUeR10H7BLh993DGmLl0ZmN7Jl4a8PMM3bGT5ZIk28I73uYfFTIo6P-NC1eFCLl1lFYfvk9f_O4BcWwusCvfZBSR4c2S8dPAwD-pM7IFdP0LB3YbCyQXeFhe3q4p3s-xisLE",
+ "n": "t2das2ad4qJFs9irOR6s4xYF6rCGZb1KkTZqu-C0enTFWDr6CZFCN645esS2n20-wbPzMZTcOxFTJN4vRwzEpz2t4DKwNmxMX8CXBmujY0EO-oY9888zoKy4M17KtJuWxcBw3djmcuy3srHsExx3Fj9IsYh2SO8vBBEFsj0MajeYi9xhZJv1pqg3HPrEptclIAEcjuIV2QtwJ3MtSPrmXuLV0WfGbJOVEZNS1JsqYLwMOpgnIBp2P2B_Iba4GwI_9FBpQJ486Szmcnf-8khzJwLmawDvIfrwyEspVF48EHgGLfwLOejAivYJKbEIaUtHDEm8fEx_zUJOp9sk7UvuFw"
+}
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-signing-rsa.jwk b/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-signing-rsa.jwk
new file mode 100644
index 0000000..11640fb
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/credentials/idp-signing-rsa.jwk
@@ -0,0 +1,14 @@
+{
+ "p": "5wVHO2-yeyReB-w_pUr-xzrY6tMS54rGlyUjOPiuUJxJYEHHOr1CqzuW1dUMkZDuec8ggF_rNd51hu6UChKsW6WLfLjSwVPx_ompBMmYVShv8PgtnwYL1d9PMqk2ibB_GaRLlM16e9ynqFaZXBNnbwl3qHv6WYu6nABPI54zoGs",
+ "kty": "RSA",
+ "q": "yzwGoBHkLWLqIQ_DdY2jimOEqgKrzPau41wHAMQXcyzGavayCgsc6r6o9_RDKkgwFnpXk-mmcZr-osqapRwL8LaYtoBBMXTRCFd02N1IqkxmXRjq3pu7WFSNsHWy0GkSdE626fpJklUjh4TLFfOSBRkzKy7E6cj-vyhzqmrgZAU",
+ "d": "bPsmY1pD0Wr5nj_Optd6hkoM5ANXKVeM2rMKQ2_n7qg6qA4Li-nb_jgyAaiomB2TYAjtJvY804Cc9lhsoXyN0o8NJh8YpI4_59oKJA-L_CupmeZxI9Jo7D4WCrh2HVIjCokqyDjd30aYdb_R9x1ACmE6cfwTxY0TVAhFaT9rhCVZHc6I8niw9kbevmpMZbLwR6WDvdivPBto6BGLXzInxf2s22lGcetP1m2Trj15hW5oOsUDTKXosKWZrs6-9qGO9Uq4JEzhdVdUOQvkoujrT-G9-hbscvDO2-KXJ6a3qz4SDYFCGoWB0QhsLmHGLtBOUvJRiEuztjAy-L_eyigLWQ",
+ "e": "AQAB",
+ "use": "enc",
+ "alg": "RSA-OAEP-256",
+ "kid": "defaultRSAEnc",
+ "qi": "X8a2QwIr5q94V9QyAsArVijyICSrEsdT5Zfpyoz7Eyhd2VoAyA74WiUbcFElbHNbJOKmvHzp9les4o3BCpsTYwUyRdlB-npL_tEpp7fdIj8I3EhWfspJwT1EfLtJakGwoa6v0KpOmEzzR9mCwKmSnKfhF3aA1S-Hch1eEiV8qm8",
+ "dp": "vJNaafHrRwmQl_cInOxyvD4VAtn4_HgTUx1FeyPDZpmsa55F-nSzDwM9RJ77-3bKszOX3DJv1TEZzmLBBNfpSYYALnbP0m-rgZLtHLNSXXD8rz5mPwC4eIQoKbpmgk5H8a5i47w21xRu5sKJvNc5_zFDM4y_9ohQczbtYq2ohrE",
+ "dq": "fyfzTTtkdDErI7xlIquX4wIZAvXg38CH86CkkQofUeR10H7BLh993DGmLl0ZmN7Jl4a8PMM3bGT5ZIk28I73uYfFTIo6P-NC1eFCLl1lFYfvk9f_O4BcWwusCvfZBSR4c2S8dPAwD-pM7IFdP0LB3YbCyQXeFhe3q4p3s-xisLE",
+ "n": "t2das2ad4qJFs9irOR6s4xYF6rCGZb1KkTZqu-C0enTFWDr6CZFCN645esS2n20-wbPzMZTcOxFTJN4vRwzEpz2t4DKwNmxMX8CXBmujY0EO-oY9888zoKy4M17KtJuWxcBw3djmcuy3srHsExx3Fj9IsYh2SO8vBBEFsj0MajeYi9xhZJv1pqg3HPrEptclIAEcjuIV2QtwJ3MtSPrmXuLV0WfGbJOVEZNS1JsqYLwMOpgnIBp2P2B_Iba4GwI_9FBpQJ486Szmcnf-8khzJwLmawDvIfrwyEspVF48EHgGLfwLOejAivYJKbEIaUtHDEm8fEx_zUJOp9sk7UvuFw"
+}
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml b/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
index 877092d..d5045fb 100644
--- a/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
+++ b/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
@@ -58,6 +58,15 @@
</list>
</property>
</bean>
+ <!-- This override is used in the OIDCRPFlowTest#testFlowToAuthorizationRedirect_UsingRequestObject_RSA256_Signature test -->
+ <bean id="TestRequestObject" parent="RelyingPartyByName" c:relyingPartyIds="https://localhost:9920">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO" p:useRequestObject="true" p:signRequestObject="true"
+ p:encryptRequestObject="false"/>
+ </list>
+ </property>
+ </bean>
</util:list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list