[java-idp-plugin-oidc-rp] branch main updated: Add basic JWT encryption support
Phil Smart
philip.smart at jisc.ac.uk
Wed Jul 20 10:55:49 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=2fde5cc348cb6752df13ac86e9f70b5b43f3e7dc
The following commit(s) were added to refs/heads/main by this push:
new 2fde5cc Add basic JWT encryption support
2fde5cc is described below
commit 2fde5cc348cb6752df13ac86e9f70b5b43f3e7dc
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jul 20 11:55:44 2022 +0100
Add basic JWT encryption support
The OpenID Provider encryption parameter resolver needs work. It
currently only supports RSA or EC encryption based on an arbitrary key
in the OP's JWKSet.
Some other cleanup
---
.../navigate/RequestObjectTokenUpdateStrategy.java | 68 +++++
...WTClaimsSetFromRequestObjectLookupFunction.java | 88 +++++++
.../PayloadFromRequestObjectLookupFunction.java | 89 +++++++
.../logic/UserInfoPlainResponseTypeCondition.java | 2 +-
.../oidc/rp/messaging/JWTUserInfoResponseTest.java | 18 ++
.../impl/AbstractRequestEncoderFunction.java | 8 +-
.../impl/DefaultUserInfoRequestEncoder.java | 3 +-
.../impl/NimbusAuthCodeTokenRequestEncoder.java | 2 +-
.../AbstractOIDCAuthenticationRequestAction.java | 41 +--
.../plugin/authn/oidc/rp/impl/AddEndpointURI.java | 3 +-
.../plugin/authn/oidc/rp/impl/AddRedirectURI.java | 2 +-
.../authn/oidc/rp/impl/BuildRequestObject.java | 12 +-
.../rp/impl/PopulateJWTEncryptionParameters.java | 60 +++--
...oviderMetadataEncryptionParametersResolver.java | 251 ++++++++++++++++++
.../authn/oidc/rp/messaging/impl/EncryptJWT.java | 213 ++++++++++++++++
.../oidc/rp/messaging/impl/SignRequestObject.java | 100 ++++----
.../oidc-relying-party-authn-beans.xml | 36 ++-
.../oidc-relying-party-authn-flow.xml | 30 +--
.../idp/service/relying-party/postconfig.xml | 15 +-
.../oidc/rp/impl/AuthorizationControllerTest.java | 32 ++-
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 281 +++++++--------------
.../authn/oidc/rp/impl/TestCredentialHelper.java | 39 ++-
.../plugin/authn/oidc/rp/impl/TestTokenHelper.java | 1 +
.../rp/messaging/impl/SignRequestObjectTest.java | 4 +-
.../flow/AbstractAuthnXmlFlowExecutionTests.java | 21 ++
.../resources/conf/credentials/idp-signing-rsa.jwk | 2 +-
.../conf/credentials/remote-jwkset-response.jwk | 66 +++++
.../resources/conf/test-relying-party-system.xml | 9 +
.../src/test/resources/logback-test.xml | 1 +
.../test-provider-requestobject-encrypt.json | 88 +++++++
.../test-provider-requestobject-rs256-sig.json | 62 +++++
.../metadata/test-provider-requestobject.json | 62 +++++
.../resources/metadata/test-provider-standard.json | 58 +++++
33 files changed, 1431 insertions(+), 336 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RequestObjectTokenUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RequestObjectTokenUpdateStrategy.java
new file mode 100644
index 0000000..779012f
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RequestObjectTokenUpdateStrategy.java
@@ -0,0 +1,68 @@
+/*
+ * 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.navigate;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** Add the {@link JWT} back to the Request Object in the {@link OIDCAuthenticationRequest}.*/
+public class RequestObjectTokenUpdateStrategy implements BiConsumer<JWT, MessageContext> {
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public RequestObjectTokenUpdateStrategy() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof OIDCAuthenticationRequest) {
+ return (OIDCAuthenticationRequest)mc.getMessage();
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ public void accept(final JWT jwt, final MessageContext messageContext) {
+ if (messageContext == null) {
+ return;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+ authnRequest.setRequestObject(jwt);
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTClaimsSetFromRequestObjectLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTClaimsSetFromRequestObjectLookupFunction.java
new file mode 100644
index 0000000..86bcd3f
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/JWTClaimsSetFromRequestObjectLookupFunction.java
@@ -0,0 +1,88 @@
+/*
+ * 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.messaging.context.logic;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Extract the {@link Payload} from the Request Object inside the {@link OIDCAuthenticationRequest}.
+ * The Payload can either be signed, or plain.
+ */
+public class JWTClaimsSetFromRequestObjectLookupFunction implements Function<MessageContext, JWTClaimsSet>{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JWTClaimsSetFromRequestObjectLookupFunction.class);
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public JWTClaimsSetFromRequestObjectLookupFunction() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof OIDCAuthenticationRequest) {
+ return (OIDCAuthenticationRequest)mc.getMessage();
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ @Nullable public JWTClaimsSet apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+
+ if (authnRequest == null || authnRequest.getRequestObject() == null) {
+ return null;
+ }
+
+ try {
+ return authnRequest.getRequestObject().getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.debug("Error parsing JWT Claims Set of the RequestObject", e);
+ return null;
+ }
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/PayloadFromRequestObjectLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/PayloadFromRequestObjectLookupFunction.java
new file mode 100644
index 0000000..afe7a10
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/PayloadFromRequestObjectLookupFunction.java
@@ -0,0 +1,89 @@
+/*
+ * 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.messaging.context.logic;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Extract the {@link Payload} from the Request Object inside the {@link OIDCAuthenticationRequest}.
+ * The Payload can either be signed, or plain.
+ */
+public class PayloadFromRequestObjectLookupFunction implements Function<MessageContext, Payload>{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PayloadFromRequestObjectLookupFunction.class);
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public PayloadFromRequestObjectLookupFunction() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof OIDCAuthenticationRequest) {
+ return (OIDCAuthenticationRequest)mc.getMessage();
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ @Nullable public Payload apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+ if (authnRequest.getRequestObject() instanceof SignedJWT) {
+ return new Payload((SignedJWT) authnRequest.getRequestObject());
+ } else if (authnRequest.getRequestObject() instanceof PlainJWT) {
+ try {
+ return new Payload(authnRequest.getRequestObject().getJWTClaimsSet().getClaims());
+ } catch (final ParseException e) {
+ log.error("Unable to convert plaintext JWT to claims set", e);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
index 7262d67..fb3b7ea 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
@@ -25,7 +25,7 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse.UserInfoResponseType;
/**
- * Return true if the UserInfo response was an encrypted JWT type.
+ * Condition that returns true if the UserInfo response was an plain JWT type i.e. not signed and or encrypted.
*/
public class UserInfoPlainResponseTypeCondition extends AbstractUserInfoResponseTypeCondition {
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
index 63f23fe..a059a72 100644
--- a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponseTest.java
@@ -1,3 +1,21 @@
+/*
+ * 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.messaging;
import static org.testng.Assert.assertEquals;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
index d1045b5..3b82e32 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
@@ -32,18 +32,16 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
//TODO is the same as the AbstractOIDCAuthenticationAction.
-/** Abstract request encoder function that pulls out various contexts and request/reponse messages.*/
+/** Abstract request encoder function that pulls out various contexts and request/response messages.*/
public abstract class AbstractRequestEncoderFunction extends AbstractInitializableComponent
implements Function<ProfileRequestContext, HttpUriRequest> {
@@ -199,13 +197,13 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
}
/**
- * Encode a HttpUriRequest from the given context. Implementations should override this mehtod.
+ * Encode a HttpUriRequest from the given context. Implementations should override this method.
*
* @param profileRequestContext the profile request context.
*
* @return the request to execute.
*/
- protected abstract HttpUriRequest doApply(ProfileRequestContext profileRequestContext);
+ @Nullable protected abstract HttpUriRequest doApply(@Nonnull ProfileRequestContext profileRequestContext);
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
index d0fdb01..a38e8a0 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
@@ -39,9 +39,10 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContex
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
/**
- * Default encoder for create UserInfo requests.
+ * Default encoder for UserInfo requests.
*/
//TODO This could be GET or POST - how to signal that? profile config, client metadata etc.
+//TODO move to commons?
public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunction {
/** The HTTPS scheme.*/
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
index 6f65a3d..c2d8f94 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
@@ -74,7 +74,7 @@ public class NimbusAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFun
*
* @return the convert HTTP request
*/
- private HttpUriRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
+ @Nullable private HttpUriRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
if (request.getMethod() != HTTPRequest.Method.POST) {
// Should never happen as Nimbus should always use POST
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationRequestAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationRequestAction.java
index fd7e34d..6cd97a8 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationRequestAction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationRequestAction.java
@@ -31,7 +31,7 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
@@ -46,30 +46,33 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
/**
*
- * Abstract class for actions performing actions on {@link AuthenticationResponse} located under
- * {@link ProfileRequestContext#getOutboundMessageContext()#getM}.
+ * Abstract class for actions performing operations on a {@link OIDCAuthenticationRequest} located under
+ * the outbound message context.
+ *
+ * <p>Makes available the OpenID Provider metadata context, the applicable profile configuration, and the
+ * in-building authentication request.</p>
*
*/
-abstract class AbstractOIDCAuthenticationRequestAction extends AbstractAuthenticationAction {
+public abstract class AbstractOIDCAuthenticationRequestAction extends AbstractAuthenticationAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationRequestAction.class);
- /** Lookup strategy to locate the OP metadata to use.*/
+ /** Lookup strategy to locate the OpenID Provider metadata to use.*/
@Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+
+ /** Lookup function for relying party context. */
+ @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
/** OIDC authentication request built by the IdP. */
@Nullable private OIDCAuthenticationRequest authnRequest;
- /** OIDC Metadata context. */
- @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+ /** OpenID Provider metadata .*/
+ @Nullable private OIDCProviderMetadata providerMetadata;
/** Applicable profile configuration. */
@Nullable private OIDCAuthorizationConfiguration profileConfiguration;
- /** Lookup function for relying party context. */
- @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
-
/** Constructor.*/
protected AbstractOIDCAuthenticationRequestAction() {
providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
@@ -127,12 +130,12 @@ abstract class AbstractOIDCAuthenticationRequestAction extends AbstractAuthentic
/**
- * Returns the OIDC provider metadata context.
+ * Returns the OpenID Provider metadata.
*
* @return The provider metadata context.
*/
- @Nullable protected OIDCProviderMetadataContext getProviderMetadataContext() {
- return providerMetadataContext;
+ @Nullable protected OIDCProviderMetadata getProviderMetadata() {
+ return providerMetadata;
}
@Override
@@ -160,10 +163,18 @@ abstract class AbstractOIDCAuthenticationRequestAction extends AbstractAuthentic
}
authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
- providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+ final OIDCProviderMetadataContext providerMetadataContext =
+ providerMetadataLookupStrategy.apply(profileRequestContext);
if (providerMetadataContext == null) {
+ log.error("{} No provider metadata context found for peer", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ providerMetadata = providerMetadataContext.getProviderInformation();
+ if (providerMetadata == null) {
log.error("{} No provider metadata found for peer", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddEndpointURI.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddEndpointURI.java
index eb61079..1caaa3e 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddEndpointURI.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddEndpointURI.java
@@ -38,8 +38,7 @@ public class AddEndpointURI extends AbstractOIDCAuthenticationRequestAction {
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
- getAuthenticationRequest().setEndpointURI(getProviderMetadataContext().getProviderInformation()
- .getAuthorizationEndpointURI());
+ getAuthenticationRequest().setEndpointURI(getProviderMetadata().getAuthorizationEndpointURI());
log.trace("{} Added authorization endpoint '{}' to authentication request for client '{}'",getLogPrefix(),
getAuthenticationRequest().getEndpointURI(), getAuthenticationRequest().getClientID());
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddRedirectURI.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddRedirectURI.java
index 84c35fb..99be73c 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddRedirectURI.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddRedirectURI.java
@@ -40,7 +40,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
/**
- * Adds a redirect_uri to the authentication request
+ * Action that adds a redirect_uri to the authentication request.
*
* TODO Events.*/
public class AddRedirectURI extends AbstractOIDCAuthenticationRequestAction {
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/BuildRequestObject.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/BuildRequestObject.java
index 4aaa48b..1e3f666 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/BuildRequestObject.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/BuildRequestObject.java
@@ -112,8 +112,16 @@ public class BuildRequestObject extends AbstractOIDCAuthenticationRequestAction
final ClaimsSet requestObjectClaims = new ClaimsSet();
if (requestObjectToBeSignedPredicate.test(profileRequestContext)) {
- requestObjectClaims.setAudience(
- new Audience(getProviderMetadataContext().getProviderInformation().getIssuer().getValue()));
+ if (getProviderMetadata().getIssuer() != null) {
+ requestObjectClaims.setAudience(
+ new Audience(getProviderMetadata().getIssuer().getValue()));
+ } else {
+ // Should never happen
+ log.error("{} Signed RequestObject requires 'iss' claim, which is currently null",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return;
+ }
requestObjectClaims.setIssuer(new Issuer(getAuthenticationRequest().getClientID().getValue()));
}
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 6b87308..1c78a66 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
@@ -25,11 +25,10 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.saml.saml2.profile.context.EncryptionContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.opensaml.xmlsec.EncryptionConfiguration;
import org.opensaml.xmlsec.EncryptionParameters;
import org.opensaml.xmlsec.EncryptionParametersResolver;
@@ -43,6 +42,7 @@ import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
import net.shibboleth.oidc.security.criterion.ClientInformationCriterion;
import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
import net.shibboleth.oidc.security.criterion.StaticCredentialCriterion;
@@ -55,7 +55,21 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
-
+/**
+ * Action that resolves and populates {@link EncryptionParameters} on an {@link JWTSecurityParametersContext}
+ * created/accessed via a lookup function, by default on a child of the outbound message context.
+ *
+ * <p>The resolution process is contingent on the active profile configuration requesting encryption.</p>
+ *
+ * <p>The default, per-RelyingParty, and default per-profile {@link EncryptionConfiguration}
+ * objects are input to the resolution process, along with the relying party's client metadata, any static
+ * credentials configured on the relying party, and the OpenID Provider metadata (which in most cases
+ * will be the source of the eventual encryption key)</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_SEC_CFG}
+ */
//TODO similar to PopulateOIDCEncryptionParameters? shall we merge into commons, adds the OP metadata from downstream
// If exists, useful for proxy.
// TODO move to commons?
@@ -67,8 +81,9 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
/** A friendly name to log as the subject of encryption parameter resolution.*/
@Nonnull private String forFriendlyName;
- /** Strategy used to look up the {@link EncryptionContext} to store parameters in. */
- @Nonnull private final Function<ProfileRequestContext,EncryptionContext> encryptionContextLookupStrategy;
+ /** Strategy used to look up the {@link JWTSecurityParametersContext} to extract parameters from. */
+ @Nonnull
+ private Function<ProfileRequestContext,JWTSecurityParametersContext> securityParametersContextLookupStrategy;
/** Strategy used to look up a per-request {@link EncryptionConfiguration} list. */
@NonnullAfterInit private Function<ProfileRequestContext,List<EncryptionConfiguration>> configurationLookupStrategy;
@@ -90,22 +105,23 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
@Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
/** Context to populate. */
- private EncryptionContext encryptionContext;
+ private JWTSecurityParametersContext encryptionContext;
/** Constructor. */
public PopulateJWTEncryptionParameters() {
forFriendlyName = "not-specified";
- encryptionContextLookupStrategy = new ChildContextLookup<>(EncryptionContext.class, true);
+ securityParametersContextLookupStrategy =
+ new ChildContextLookup<>(JWTSecurityParametersContext.class, true).compose(
+ new OutboundMessageContextLookup());
oidcClientMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class);
oidcProviderMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class);
- relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class)
- .compose(new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class));
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
}
/**
* Set lookup strategy for relying party context.
*
- * @param strategy lookup strategy
+ * @param strategy lookup strategy
*/
public void setRelyingPartyContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
@@ -115,6 +131,20 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
}
+ /**
+ * Set the lookup strategy to locate the security parameters context.
+ *
+ * @param strategy the lookup strategy
+ */
+ public void setSecurityParametersContextLookupStrategy(
+ final Function<ProfileRequestContext, JWTSecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ securityParametersContextLookupStrategy = Constraint.isNotNull(strategy,
+ "securityParametersContextLookupStrategy can not be null");
+ }
+
/**
* Set lookup strategy for {@link OIDCMetadataContext} for input to resolution.
*
@@ -195,11 +225,11 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
if (!super.doPreExecute(profileRequestContext)) {
- log.debug("{} Encryption disabled for {}", getLogPrefix(), forFriendlyName);
+ log.debug("{} Encryption disabled for '{}'", getLogPrefix(), forFriendlyName);
return false;
}
- encryptionContext = encryptionContextLookupStrategy.apply(profileRequestContext);
+ encryptionContext = securityParametersContextLookupStrategy.apply(profileRequestContext);
if (encryptionContext == null) {
log.debug("{} No EncryptionContext returned by lookup strategy", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
@@ -213,7 +243,7 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- log.debug("{} Resolving EncryptionParameters for {}", getLogPrefix(),forFriendlyName);
+ log.debug("{} Resolving EncryptionParameters for '{}'", getLogPrefix(),forFriendlyName);
try {
encryptionConfigurations = configurationLookupStrategy.apply(profileRequestContext);
@@ -225,7 +255,7 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
if (params != null) {
log.debug("{} Resolved EncryptionParameters for {}", getLogPrefix(),forFriendlyName);
- encryptionContext.setAssertionEncryptionParameters(params);
+ encryptionContext.setEncryptionParameters(params);
} else {
log.warn("{} Resolver returned no EncryptionParameters", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
@@ -249,7 +279,7 @@ public class PopulateJWTEncryptionParameters extends AbstractProfileAction {
final CriteriaSet criteria = new CriteriaSet(new EncryptionConfigurationCriterion(encryptionConfigurations));
- // Add client metadata criterion
+ // Add client metadata criterion
final OIDCMetadataContext oidcMetadataCtx =
oidcClientMetadataContextLookupStrategy.apply(profileRequestContext);
if (oidcMetadataCtx != null && oidcMetadataCtx.getClientInformation() != null) {
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProviderMetadataEncryptionParametersResolver.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProviderMetadataEncryptionParametersResolver.java
new file mode 100644
index 0000000..ddce8f0
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProviderMetadataEncryptionParametersResolver.java
@@ -0,0 +1,251 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.EncryptionParameters;
+import org.opensaml.xmlsec.impl.BasicEncryptionParametersResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyType;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.oidc.security.criterion.StaticCredentialCriterion;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+//TODO so far, this is only pulling out request object algs. Use a strategy to make it generic
+public class ProviderMetadataEncryptionParametersResolver extends BasicEncryptionParametersResolver {
+
+ /** Logger. */
+ private final Logger log = LoggerFactory.getLogger(ProviderMetadataEncryptionParametersResolver.class);
+
+ /** The cache for remote JWK key sets. */
+ @Nullable private RemoteJwkSetCache remoteJwkSetCache;
+
+ /** The remote key refresh interval. Default value: 30 minutes. */
+ @Positive
+ private Duration keyFetchInterval = Duration.ofMinutes(30);
+
+ /** Constructor.*/
+ public ProviderMetadataEncryptionParametersResolver() {
+ super();
+ }
+
+
+ /**
+ * Set the cache for remote JWK key sets.
+ *
+ * @param jwkSetCache What to set.
+ */
+ public void setRemoteJwkSetCache(final RemoteJwkSetCache jwkSetCache) {
+ remoteJwkSetCache = Constraint.isNotNull(jwkSetCache, "The remote JWK set cache cannot be null");
+ }
+
+ /**
+ * Set the remote key refresh interval.
+ *
+ * @param interval What to set.
+ */
+ public void setKeyFetchInterval(@Positive final Duration interval) {
+ Constraint.isFalse(interval == null || interval.isNegative(), "Remote key refresh must be greater than 0");
+ keyFetchInterval = interval;
+ }
+
+ @Override
+ protected void resolveAndPopulateCredentialsAndAlgorithms(@Nonnull final EncryptionParameters params,
+ @Nonnull final CriteriaSet criteria, @Nonnull final Predicate<String> includeExcludePredicate) {
+
+ if (remoteJwkSetCache == null) {
+ log.error("OIDC Provider metadata encryption parameters resolver does not have a remote JWKSet cache set");
+ }
+
+ if (!criteria.contains(ProviderMetadataCriterion.class)) {
+ log.debug("No provider metadata criterion, falling back to local configuration");
+ super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, includeExcludePredicate);
+ return;
+ }
+
+ final OIDCProviderMetadata metadata = criteria.get(ProviderMetadataCriterion.class).getMetadata();
+
+ // We populate the parameters for the algorithm the provider has registered
+ final List<JWEAlgorithm> keyTransportAlgorithms = metadata.getRequestObjectJWEAlgs() !=null
+ ? metadata.getRequestObjectJWEAlgs(): Collections.emptyList();
+ log.trace("Resolved effective key transport algorithms from provider metadata: {}", keyTransportAlgorithms);
+ if (keyTransportAlgorithms.isEmpty()) {
+ log.debug("No algorithm information in provider metadata, falling back to default configuration");
+ super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, includeExcludePredicate);
+ return;
+ }
+
+ //TODO use strategy to pull these out
+ final List<EncryptionMethod> dataEncryptionMethods = metadata.getRequestObjectJWEEncs() != null ?
+ metadata.getRequestObjectJWEEncs() : Collections.emptyList();
+
+ log.trace("Resolved effective data encryption algorithms from provider metadata: {}", dataEncryptionMethods);
+
+ final List<String> keyTransportAlgorithmSupported =
+ getEffectiveKeyTransportAlgorithms(criteria, includeExcludePredicate);
+ log.trace("Resolved supported key transport algorithms from config: {}",
+ keyTransportAlgorithmSupported);
+
+ final List<String> dataEncryptionAlgorithmsSupported =
+ getEffectiveDataEncryptionAlgorithms(criteria, includeExcludePredicate);
+ log.trace("Resolved supported data encryption algorithms from config: {}", dataEncryptionAlgorithmsSupported);
+
+
+ final List<String> supportedAndConfiguredKeyTransportAlgorithms =
+ findAlgorithmIntersection(keyTransportAlgorithms.stream().map(JWEAlgorithm::getName)
+ .collect(Collectors.toList()),keyTransportAlgorithmSupported);
+
+ final List<String> supportedAndConfiguredDataEncryptionAlgorithms =
+ findAlgorithmIntersection(dataEncryptionMethods.stream().map(EncryptionMethod::getName)
+ .collect(Collectors.toList()),dataEncryptionAlgorithmsSupported);
+
+ log.debug("Supported and configured key transport algorithms: {}",
+ supportedAndConfiguredKeyTransportAlgorithms);
+ log.debug("Supported and configured data encryption algorithms: {}",
+ supportedAndConfiguredDataEncryptionAlgorithms);
+
+
+ JWKSet providerKeySet = getProviderKeys(metadata);
+ if (providerKeySet == null) {
+ providerKeySet = new JWKSet();
+ }
+ log.trace("Has '{}' keys from provider's JWKSet", providerKeySet.getKeys().size());
+
+ // Add any static credentials from the criteria. Add as a data encryption credential e.g for Direct Encryption.
+ //TODO we need to consider this for the 'dir' alg.
+ if (criteria.contains(StaticCredentialCriterion.class)) {
+ final Credential staticCred = criteria.get(StaticCredentialCriterion.class).getCredential();
+ log.trace("Signing credential found in criterion '{}'", staticCred.getKeyNames());
+ if (staticCred.getSecretKey() != null) {
+ //dataEncryptionCredentials.add(staticCred);
+ }
+ }
+
+ final List<JWEAlgorithm> supportedJWEKeyTransportAlgorithms =
+ convertSupportAlgorithmsToJwkAlgorithms(supportedAndConfiguredKeyTransportAlgorithms);
+
+ // Default encEnc value
+ // TODO we need to 'chose' this.
+ final EncryptionMethod encryptionMethod = EncryptionMethod.A128CBC_HS256;
+
+ // Keys in the remote keys file are keytransport algorithms?
+ for (final JWK key : providerKeySet.getKeys()) {
+ if (KeyUse.SIGNATURE.equals(key.getKeyUse())) {
+ continue;
+ }
+ final JWEAlgorithm keyTransportAlgorithm = findSupportedAlgorithm(key, supportedJWEKeyTransportAlgorithms);
+ if (keyTransportAlgorithm != null) {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(keyTransportAlgorithm);
+ jwkCredential.setKid(key.getKeyID());
+ try {
+ if (key.getKeyType().equals(KeyType.RSA)) {
+ jwkCredential.setPublicKey(((RSAKey) key).toPublicKey());
+ } else {
+ jwkCredential.setPublicKey(((ECKey) key).toPublicKey());
+ }
+ } catch (final JOSEException e) {
+ log.warn("Unable to parse keyset", e);
+ continue;
+ }
+ log.debug("Selected key {} for alg {} and enc {}", key.getKeyID(), keyTransportAlgorithm.getName(),
+ encryptionMethod.getName());
+ params.setKeyTransportEncryptionCredential(jwkCredential);
+ params.setKeyTransportEncryptionAlgorithm(keyTransportAlgorithm.getName());
+ params.setDataEncryptionAlgorithm(encryptionMethod.getName());
+ return;
+ }
+
+ }
+ //TODO THIS IS NOT CORRECT, needs much review, e.g. key wrapping, key aggreement.
+
+ }
+
+ /**
+ * Convert the algorithms represented as strings, into Nimbus {@link Algorithm}s for later comparison.
+ *
+ * @param algos the algorithms to convert
+ *
+ * @return the converted algorithms
+ */
+ @Nonnull private List<JWEAlgorithm> convertSupportAlgorithmsToJwkAlgorithms(@Nonnull final List<String> algos) {
+ return algos.stream().map(JWEAlgorithm::parse).collect(Collectors.toList());
+ }
+
+ /**
+ * Does the key support any one of the given algorithms.
+ *
+ * @param key the key to check
+ * @param algorithms the algorithms to check against
+ *
+ * @return the supported algorithm, or null if none are supported
+ */
+ @Nullable private JWEAlgorithm findSupportedAlgorithm(@Nonnull final JWK key,
+ @Nonnull final List<JWEAlgorithm> algorithms) {
+ final JWEAlgorithm algorithm =
+ algorithms.stream().filter(alg -> alg.equals(key.getAlgorithm())).findFirst().orElse(null);
+
+ if ((JWEAlgorithm.Family.RSA.contains(algorithm) &&
+ key.getKeyType().equals(KeyType.RSA))
+ || (JWEAlgorithm.Family.ECDH_ES.contains(algorithm) &&
+ key.getKeyType().equals(KeyType.EC))) {
+ return algorithm;
+ }
+ // No support
+ return null;
+ }
+
+ /**
+ * Fetch the OpenID Provider's remote JWKSet.
+ *
+ * @param metadata the OpenID Provider's metadata
+ *
+ * @return the JSON Web Keys set.
+ */
+ @Nullable private JWKSet getProviderKeys(@Nonnull final OIDCProviderMetadata metadata) {
+ return remoteJwkSetCache.fetch(metadata.getJWKSetURI(),
+ Instant.now().plus(keyFetchInterval));
+ }
+
+ /**
+ * Return a new list of algorithms that represents the set intersection of the two input algorithm lists.
+ *
+ * @param providerAlgorithms the set of algorithms specified by the OpenID Provider
+ * @param configAlgorithms the set of algorithms specified by the IdP's configuration
+ *
+ * @return the intersection of both lists
+ */
+ @Nonnull private List<String> findAlgorithmIntersection(@Nonnull final List<String> providerAlgorithms,
+ @Nonnull final List<String> configAlgorithms){
+ return providerAlgorithms.stream().filter(configAlgorithms::contains).collect(Collectors.toList());
+
+ }
+
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/EncryptJWT.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/EncryptJWT.java
new file mode 100644
index 0000000..e27fd36
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/EncryptJWT.java
@@ -0,0 +1,213 @@
+/*
+ * 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.messaging.impl;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.EncryptionParameters;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.AESEncrypter;
+import com.nimbusds.jose.crypto.ECDHEncrypter;
+import com.nimbusds.jose.crypto.RSAEncrypter;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A {@link MessageHandler} that encrypts a JWT using the {@link EncryptionParameters} found in the
+ * {@link JWTSecurityParametersContext}. The {@link Payload} to encrypt is determined by lookup strategy.
+ * A consumer takes the {@link EncryptedJWT} and updates the correct object in the {@link MessageContext}.
+ */
+public class EncryptJWT extends AbstractMessageHandler {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(EncryptJWT.class);
+
+ /** Strategy used to locate the {@link SecurityParametersContext} to use for signing. */
+ @Nonnull private Function<MessageContext, JWTSecurityParametersContext> securityParametersLookupStrategy;
+
+ /** Strategy used to locate the payload to encrypt.*/
+ @NonnullAfterInit private Function<MessageContext, Payload> payloadToEncryptLookupStrategy;
+
+ /** A consumer that takes the EncryptedJWT and updates the correct object inside the MessageContext.*/
+ @NonnullAfterInit private BiConsumer<JWT, MessageContext> jwtUpdateConsumer;
+
+ /** The signature signing parameters. */
+ @Nullable private EncryptionParameters encryptionParameters;
+
+
+ /** Constructor.*/
+ public EncryptJWT() {
+ securityParametersLookupStrategy = new ChildContextLookup<>(JWTSecurityParametersContext.class);
+ }
+
+ /**
+ * Set the consumer used to update the MessageContext with the supplied EncryptedJWT.
+ *
+ * @param consumer the consumer
+ */
+ public void setJwtUpdateConsumer(final BiConsumer<JWT, MessageContext> consumer) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ jwtUpdateConsumer = Constraint.isNotNull(consumer, "JwtUpdateConsumer can not be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link Payload} to encrypt.
+ *
+ * @param strategy the strategy
+ */
+ public void setPayloadToEncryptLookupStrategy(@Nonnull final Function<MessageContext, Payload> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ payloadToEncryptLookupStrategy =
+ Constraint.isNotNull(strategy, "payloadToEncryptLookupStrategy can not be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link SecurityParametersContext} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityParametersLookupStrategy(
+ @Nonnull final Function<MessageContext, JWTSecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ securityParametersLookupStrategy =
+ Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ if (payloadToEncryptLookupStrategy == null) {
+ throw new ComponentInitializationException("payloadToEncryptLookupStrategy can not be null");
+ }
+ if (jwtUpdateConsumer == null) {
+ throw new ComponentInitializationException("jwtUpdateConsumer can not be null");
+ }
+ super.doInitialize();
+ }
+
+
+ @Override
+ protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+ if (!super.doPreInvoke(messageContext)) {
+ return false;
+ }
+
+ final JWTSecurityParametersContext secParamCtx =
+ securityParametersLookupStrategy.apply(messageContext);
+ if (secParamCtx == null) {
+ log.debug("{} Message context did not contain encryption parameters context, "
+ + "request object will not be encrypted", getLogPrefix());
+ return false;
+ }
+
+ encryptionParameters = secParamCtx.getEncryptionParameters();
+ if (encryptionParameters == null) {
+ log.debug("{} Message context did not contain encryption parameters, "
+ + "request object will not be encrypted", getLogPrefix());
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+ final Payload payload = payloadToEncryptLookupStrategy.apply(messageContext);
+ if (payload == null) {
+ log.debug("{} No plain text source provided to encrypt", getLogPrefix());
+ return;
+ }
+
+ final JWEAlgorithm encAlg = JWEAlgorithm.parse(encryptionParameters.getKeyTransportEncryptionAlgorithm());
+ final Credential credential = encryptionParameters.getKeyTransportEncryptionCredential();
+ final EncryptionMethod encEnc = EncryptionMethod.parse(encryptionParameters.getDataEncryptionAlgorithm());
+ final String kid = CredentialConversionUtil.resolveKid(credential);
+
+ log.debug("{} Encrypting with kid {} and params alg: {} enc: {}", getLogPrefix(), kid, encAlg.getName(),
+ encEnc.getName());
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(encAlg, encEnc).contentType("JWT").keyID(kid).build(), payload);
+ try {
+ //TODO does not support 'dir'?
+ if (JWEAlgorithm.Family.RSA.contains(encAlg)) {
+ jweObject.encrypt(new RSAEncrypter((RSAPublicKey) credential.getPublicKey()));
+ } else if (JWEAlgorithm.Family.ECDH_ES.contains(encAlg)) {
+ jweObject.encrypt(new ECDHEncrypter((ECPublicKey) credential.getPublicKey()));
+ } else if (JWEAlgorithm.Family.SYMMETRIC.contains(encAlg)) {
+ jweObject.encrypt(new AESEncrypter(credential.getSecretKey()));
+ } else {
+ log.error("{} Unsupported algorithm {}", getLogPrefix(), encAlg.getName());
+ throw new MessageHandlerException("Unsupported algorithm "+encAlg.getName());
+ }
+
+ final EncryptedJWT encryptedJWT = EncryptedJWT.parse(jweObject.serialize());
+ jwtUpdateConsumer.accept(encryptedJWT, messageContext);
+
+ if (log.isDebugEnabled() && !log.isTraceEnabled()) {
+ log.debug("{} Encrypted RequestObject", getLogPrefix());
+ } else if (log.isTraceEnabled()) {
+ log.debug("{} Encrypted RequestObject: {}", getLogPrefix(), encryptedJWT.serialize());
+ }
+
+ } catch (final JOSEException | ParseException e) {
+ log.error("{} Encryption failed {}", getLogPrefix(), e);
+ throw new MessageHandlerException("Encryption failed", e);
+ }
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObject.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObject.java
index 0e6c98e..d15b3c1 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObject.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/impl/SignRequestObject.java
@@ -18,7 +18,7 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl;
import java.security.interfaces.ECPrivateKey;
-import java.text.ParseException;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -41,6 +41,7 @@ import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject.State;
import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
@@ -48,11 +49,12 @@ import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
-import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -60,6 +62,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
* Action that signs a request object and sets it as the request object to the authentication request.
*/
+//TODO move to commons?
public class SignRequestObject extends AbstractMessageHandler {
/** Class logger. */
@@ -68,9 +71,12 @@ public class SignRequestObject extends AbstractMessageHandler {
/** Strategy used to locate the {@link SecurityParametersContext} to use for signing. */
@Nonnull private Function<MessageContext, JWTSecurityParametersContext> securityParametersLookupStrategy;
- /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
- @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+ /** A consumer that takes the EncryptedJWT and updates the correct object inside the MessageContext.*/
+ @NonnullAfterInit private BiConsumer<JWT, MessageContext> jwtUpdateConsumer;
+ /** Strategy used to locate the payload to encrypt.*/
+ @NonnullAfterInit private Function<MessageContext, JWTClaimsSet> claimsToSignLookupStrategy;
+
/** The signature signing parameters. */
@Nullable private SignatureSigningParameters signatureSigningParameters;
@@ -83,18 +89,45 @@ public class SignRequestObject extends AbstractMessageHandler {
/** "typ" header to insert while signing. */
@Nullable @NotEmpty private String typeHeader;
- /** The stashed authentication request.*/
- @Nullable private OIDCAuthenticationRequest authnRequest;
-
/** Constructor.*/
public SignRequestObject() {
- securityParametersLookupStrategy = new ChildContextLookup<>(JWTSecurityParametersContext.class);
- authenticationRequestLookupStrategy = mc -> {
- if (mc.getMessage() instanceof OIDCAuthenticationRequest) {
- return (OIDCAuthenticationRequest)mc.getMessage();
- }
- return null;
- };
+ securityParametersLookupStrategy = new ChildContextLookup<>(JWTSecurityParametersContext.class);
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ if (claimsToSignLookupStrategy == null) {
+ throw new ComponentInitializationException("claimsToSignLookupStrategy can not be null");
+ }
+ if (jwtUpdateConsumer == null) {
+ throw new ComponentInitializationException("jwtUpdateConsumer can not be null");
+ }
+ super.doInitialize();
+ }
+
+ /**
+ * Set the strategy used to locate the {@link JWTClaimsSet} to sign.
+ *
+ * @param strategy the strategy
+ */
+ public void setClaimsToSignLookupStrategy(@Nonnull final Function<MessageContext, JWTClaimsSet> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ claimsToSignLookupStrategy =
+ Constraint.isNotNull(strategy, "claimsToSignLookupStrategy can not be null");
+ }
+
+ /**
+ * Set the consumer used to update the MessageContext with the supplied EncryptedJWT.
+ *
+ * @param consumer the consumer
+ */
+ public void setJwtUpdateConsumer(final BiConsumer<JWT, MessageContext> consumer) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ jwtUpdateConsumer = Constraint.isNotNull(consumer, "JwtUpdateConsumer can not be null");
}
/**
@@ -121,20 +154,6 @@ public class SignRequestObject extends AbstractMessageHandler {
Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
}
- /**
- * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
- *
- * @param strategy lookup strategy
- */
- public void setAuthenticationRequestLookupStrategy(
- @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- authenticationRequestLookupStrategy =
- Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
- }
-
-
/** {@inheritDoc} */
@Override
protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
@@ -142,19 +161,7 @@ public class SignRequestObject extends AbstractMessageHandler {
if (!super.doPreInvoke(messageContext)) {
return false;
}
-
- authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
- if (authnRequest == null) {
- log.debug("{} No authentication request available", getLogPrefix());
- return false;
- }
-
- final JWT requestObject = authnRequest.getRequestObject();
- if (requestObject == null) {
- log.debug("{} No JWT RequestObject found, nothing to sign", getLogPrefix());
- return false;
- }
-
+
final JWTSecurityParametersContext secParamCtx =
securityParametersLookupStrategy.apply(messageContext);
if (secParamCtx == null) {
@@ -169,10 +176,9 @@ public class SignRequestObject extends AbstractMessageHandler {
return false;
}
- try {
- jwtClaimSetToSign = requestObject.getJWTClaimsSet();
- } catch (final ParseException e) {
- log.debug("{} No JWT RequestObject found, nothing to sign", getLogPrefix(), e);
+ jwtClaimSetToSign = claimsToSignLookupStrategy.apply(messageContext);
+ if (jwtClaimSetToSign == null) {
+ log.debug("{} No JWT ClaimsSet for RequestObject, nothing to sign", getLogPrefix());
return false;
}
@@ -208,8 +214,8 @@ public class SignRequestObject extends AbstractMessageHandler {
throw new MessageHandlerException("RequestObject was not signed, unknown cause");
}
- // Add the signed JWT over the unsigned JWT
- authnRequest.setRequestObject(jwt);
+ // Update to the signed JWT
+ jwtUpdateConsumer.accept(jwt, messageContext);
} catch (final JOSEException e) {
log.error("{} Error signing claim set: {}", getLogPrefix(), e.getMessage());
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 2c0e36b..4fa23fa 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
@@ -192,7 +192,8 @@
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
<bean id="shibboleth.authn.oidc.rp.EncryptionParametersResolver"
- class="org.opensaml.xmlsec.impl.BasicEncryptionParametersResolver"/>
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ProviderMetadataEncryptionParametersResolver"
+ p:remoteJwkSetCache-ref="shibboleth.authn.oidc.rp.RemoteJwkSetCache"/>
<bean id="BuildRequestObject" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.BuildRequestObject"
@@ -240,7 +241,24 @@
scope="prototype" />
<bean id="SignRequestObject"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.SignRequestObject" scope="prototype" />
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.SignRequestObject" scope="prototype">
+ <property name="claimsToSignLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.JWTClaimsSetFromRequestObjectLookupFunction"/>
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RequestObjectTokenUpdateStrategy"/>
+ </property>
+ </bean>
+
+ <bean id="EncryptRequestObject"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.EncryptJWT" scope="prototype">
+ <property name="payloadToEncryptLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.PayloadFromRequestObjectLookupFunction"/>
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RequestObjectTokenUpdateStrategy"/>
+ </property>
+ </bean>
</list>
</property>
</bean>
@@ -368,6 +386,8 @@
<!-- ID_TOKEN Decryption -->
+ <!-- TODO should we use an activation condition to decide if decryption params are needed e.g. JWT is not a JWE
+ to stop redundent resolution of credentials -->
<bean id="PopulateIDTokenDecryptionParameters"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateJWTDecryptionParameters" scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
@@ -630,7 +650,7 @@
<!-- UserInfo decryption and signature check if JWT type -->
- <!-- FIXME: (might not be an issue) Will populate the same security params context as the id_token, but overright the
+ <!-- FIXME: (might not be an issue) Will populate the same security params context as the id_token, but overwrite the
decryption config -->
<bean id="PopulateUserInfoDecryptionParameters"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateJWTDecryptionParameters" scope="prototype"
@@ -699,15 +719,13 @@
<property name="providerMetadataLookupStrategy">
<ref bean="shibboleth.ChildLookup.OIDCProviderMetadataFromPeerEntityContext" />
</property>
- </bean>
- <!-- TODO WE NEED TO CHECK JWT CLAIMS HERE see spec -->
+ </bean>
</list>
</property>
</bean>
</constructor-arg>
</bean>
- <!-- UserInfo response JWT validation -->
<bean id="ValidateUserInfoTokenClaims" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
@@ -749,12 +767,6 @@
<ref bean="AudienceClaimsValidator" />
</util:list>
-
-
-
-
- <!-- UserInfo Decryption and Signature Validation Done -->
-
<!-- This is a very simplified and hard coded version of the claims verification used for a JWT. Maybe look to replace -->
<bean id="ValidateUserInfoPlainResponseClaims" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoJSONObjectClaims"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 99eb402..9354e25 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -60,7 +60,7 @@
<evaluate expression="PopulateRequestObjectSignatureSigningParameters" />
<evaluate expression="PopulateRequestObjectEncryptionParameters" />
<evaluate expression="BuildRequestObject" />
- <!-- We can not sign and encrypt the RO here, it has to be done as part of the controller so we can add state etc. -->
+ <!-- We can not sign and encrypt the RO here. That is left to the preEncodeMessageHandlers. -->
<evaluate expression="'proceed'" />
<transition on="proceed" to="AuthnRequest" />
</action-state>
@@ -84,20 +84,24 @@
<evaluate expression="ValidateExternalAuthenticationContext" />
<evaluate expression="ValidateAuthenticationResponseResult" />
<evaluate expression="ValidateResponseStateMatchesRequest" />
- <!-- Add a new OIDCPeerEntityContext to inbound authentication response context using the original authenticating
- authority. The OIDC response does not contain an issuer (this is later tested in the id_token) matched against the original
- provider metadata -->
+ <!--
+ Add a new OIDCPeerEntityContext to inbound authentication response context using the original
+ authenticating authority. The OIDC response does not contain an issuer (this is later tested in the
+ id_token matched against the original provider metadata).
+ -->
<evaluate expression="AddPeerEntityContextToInboundMessage" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="SwitchOnGrantType" />
</action-state>
- <!-- Switch flow path based on OIDC grant_type used. TODO possible places for an NPE, use strategy? -->
+ <!-- Switch flow path based on OIDC grant_type used -->
<decision-state id="SwitchOnGrantType">
- <if test="IsCodeFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))" then="CodeFlow" />
- <if test="IsHybridFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))" then="HybridFlow" />
- <!-- final IF has an else if an unsupported flow is used (should not happen) -->
- <if test="IsImplicitFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))" then="ImplicitFlow" else="UnsupportedFlow" />
+ <if test="IsCodeFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))"
+ then="CodeFlow" />
+ <if test="IsHybridFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))"
+ then="HybridFlow" />
+ <if test="IsImplicitFlow.test(OutboundMessageContextFromRootPRC.apply(opensamlProfileRequestContext))"
+ then="ImplicitFlow" else="UnsupportedFlow" />
</decision-state>
<action-state id="CodeFlow">
@@ -105,13 +109,6 @@
<evaluate expression="ExchangeCodeForAccessToken" />
<evaluate expression="ValidateOAuthAccessTokenResponse" />
<evaluate expression="ExtractIDTokenFromTokenResponse" />
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateToken" />
- </action-state>
-
-
- <!-- TODO claim validation will differ per grant_type -->
- <action-state id="ValidateToken">
<evaluate expression="PopulateIDTokenDecryptionParameters" />
<evaluate expression="DecryptJWT" />
<evaluate expression="PopulateIDTokenSignatureValidationParameters" />
@@ -122,6 +119,7 @@
<transition on="proceed" to="CheckUserInfoClaimsRequired" />
</action-state>
+
<decision-state id="CheckUserInfoClaimsRequired">
<if test="CheckUserInfoRequiredCondition.test(ProxyProfileRequestContextLookup.apply(opensamlProfileRequestContext))"
then="UserInfoRequest" else="FinalizeResponse" />
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 e876339..10ef5fe 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
@@ -170,7 +170,6 @@
parent="shibboleth.authn.oidc.rp.ExpiringJWKCredential"
p:secret="%{idp.authn.oidc.rp.client.clientSecret:#{null}}"
p:keyNames="defaultPropertiesClientSecret"
- p:encMethod="%{idp.authn.oidc.rp.client.clientSecret.encMethods:A256GCM}"
p:alg="dir" />
<bean id="shibboleth.authn.oidc.rp.DefaultJWTDecryptionConfiguration"
@@ -180,10 +179,10 @@
<!-- A resolver to public/private keys global to the RP -->
<bean id="defaultOIDCRPKeyEncryptionCredentialResolver"
- class="org.opensaml.security.credential.impl.ChainingCredentialResolver">
+ class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
<constructor-arg>
<list>
- <bean class="org.opensaml.security.credential.impl.StaticCredentialResolver"
+ <bean class="net.shibboleth.oidc.security.credential.impl.StaticJOSEObjectCredentialResolver"
c:credentials-ref="shibboleth.authn.oidc.rp.DefaultKeyEncryptionCredentials" />
</list>
</constructor-arg>
@@ -191,11 +190,11 @@
<!-- A pre-shared Direct Encryption key e.g. a pairwise client_secret from the input criterion -->
<bean id="defaultOIDCRPContentEncryptionKeyCredentialResolver"
- class="org.opensaml.security.credential.impl.ChainingCredentialResolver">
+ class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
<constructor-arg>
<list>
<bean id="CriterionCredentialResolver"
- class="net.shibboleth.oidc.security.impl.CriterionCredentialResolver" />
+ class="net.shibboleth.oidc.security.credential.impl.CriterionCredentialResolver" />
</list>
</constructor-arg>
</bean>
@@ -211,14 +210,14 @@
<!-- A resolver for resolving trusted credentials to match against those used -->
<bean id="defaultSignedJWTTrustedCredentialResolver"
- class="org.opensaml.security.credential.impl.ChainingCredentialResolver">
+ class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
<constructor-arg>
<list>
<bean id="OIDCProviderMetadataCredentialResolver"
- class="net.shibboleth.oidc.security.impl.ProviderMetadataCredentialResolver"
+ class="net.shibboleth.oidc.security.credential.impl.ProviderMetadataCredentialResolver"
p:remoteJwkSetCache-ref="shibboleth.authn.oidc.rp.RemoteJwkSetCache" />
<bean id="CriterionCredentialResolver"
- class="net.shibboleth.oidc.security.impl.CriterionCredentialResolver" />
+ class="net.shibboleth.oidc.security.credential.impl.CriterionCredentialResolver" />
</list>
</constructor-arg>
</bean>
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
index 6d48544..a52124b 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
@@ -28,6 +28,7 @@ import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
+import java.security.KeyException;
import java.util.ArrayList;
import javax.annotation.Nonnull;
@@ -48,6 +49,7 @@ import org.opensaml.profile.action.AbstractProfileAction;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.xmlsec.EncryptionParameters;
import org.opensaml.xmlsec.SignatureSigningParameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
@@ -68,6 +70,11 @@ import org.springframework.webflow.executor.FlowExecutorImpl;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.id.Audience;
@@ -81,11 +88,15 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RequestObjectTokenUpdateStrategy;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.JWTClaimsSetFromRequestObjectLookupFunction;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.PayloadFromRequestObjectLookupFunction;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.AddState;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.BuildPlainRequestObjectJWT;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.EncryptJWT;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.impl.SignRequestObject;
import net.shibboleth.idp.plugin.authn.test.flow.mock.IdPPropertyConfigurer;
import net.shibboleth.idp.session.IdPSession;
@@ -276,14 +287,21 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
final var addState = new AddState();
addState.initialize();
final var signer = new SignRequestObject();
+ signer.setClaimsToSignLookupStrategy(new JWTClaimsSetFromRequestObjectLookupFunction());
+ signer.setJwtUpdateConsumer(new RequestObjectTokenUpdateStrategy());
signer.initialize();
final var buildRequestObjectJwt = new BuildPlainRequestObjectJWT();
buildRequestObjectJwt.initialize();
+ final var encrypter = new EncryptJWT();
+ encrypter.setPayloadToEncryptLookupStrategy(new PayloadFromRequestObjectLookupFunction());
+ encrypter.setJwtUpdateConsumer(new RequestObjectTokenUpdateStrategy());
+ encrypter.initialize();
handlers.add(addState);
handlers.add(buildRequestObjectJwt);
handlers.add(signer);
+ handlers.add(encrypter);
chainingMsgHandler.setHandlers(handlers);
authnContext.setOutboundMessageHandler(chainingMsgHandler);
@@ -293,8 +311,9 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
* A a security parameters context, almost always for request object signing/encryption by this point.
*
* @param prc the profile request context
+ * @throws Exception on error
*/
- private void addSecurityParametersContext(@Nonnull final ProfileRequestContext prc) {
+ private void addSecurityParametersContext(@Nonnull final ProfileRequestContext prc) throws Exception {
// Create a sec context under the nested prc outbound msg context
final var secContext = prc.getOutboundMessageContext().getSubcontext(JWTSecurityParametersContext.class, true);
@@ -302,6 +321,17 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
sigParams.setSignatureAlgorithm("HS256");
sigParams.setSigningCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
secContext.setSignatureSigningParameters(sigParams);
+
+ final var encParams = new EncryptionParameters();
+ encParams.setDataEncryptionAlgorithm("A128CBC-HS256");
+ encParams.setKeyTransportEncryptionAlgorithm("RSA-OAEP-256");
+ final RSAKey key = new RSAKeyGenerator(2048)
+ .algorithm(JWEAlgorithm.RSA_OAEP_256)
+ .keyUse(KeyUse.ENCRYPTION)
+ .keyID("mock-key")
+ .generate();
+ encParams.setKeyTransportEncryptionCredential(TestCredentialHelper.createKeyEncryptionCredential(key));
+ secContext.setEncryptionParameters(encParams);
}
/**
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 6cc3852..f7e75b3 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
@@ -83,10 +83,11 @@ import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.oidc.profile.config.OIDCSecurityConfiguration;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.credential.impl.CriterionCredentialResolver;
import net.shibboleth.oidc.security.impl.BasicJWTDecryptionConfiguration;
import net.shibboleth.oidc.security.impl.BasicSignatureValidationConfiguration;
-import net.shibboleth.oidc.security.impl.CriterionCredentialResolver;
import net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -110,6 +111,7 @@ import okhttp3.tls.HeldCertificate;
* */
public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+ /** The OP Issuer to use.*/
private static final String OP_ISSUER_ID = "https://localhost:9918";
/** The OP Issuer to use with an override in the config to use the request object authn param.*/
@@ -119,216 +121,56 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
* 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";
+ /** The OP Issuer to use with an override in the config to use the request object authn param
+ * which is to be encrypted.*/
+ private static final String OP_ISSUER_ID_REQUESTOBJECT_TRUE_ENCRYPT= "https://localhost:9921";
+ /** A redirect_uri override.*/
private static final String REDIRECT_URI_OVERRIDE = "https://localhost/callback";
+ /** The client_id.*/
private static final String CLIENT_ID = "demo_rp";
+ /** The client_secret.*/
private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+
+ /** A JWKSet resource.*/
+ private static final ClassPathResource REMOTE_JWKSET_RESPONSE =
+ new ClassPathResource("/conf/credentials/remote-jwkset-response.jwk");
/**
* 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"
- + "\"device_authorization_endpoint\": \"https://localhost:9918/device/code\",\n"
- + "\"token_endpoint\": \"https://localhost:9918/token\",\n"
- + "\"userinfo_endpoint\": \"https://localhost:9918/v1/userinfo\",\n"
- + "\"revocation_endpoint\": \"https://localhost:9918/revoke\",\n"
- + "\"jwks_uri\": \"https://localhost:9918/oauth2/v3/certs\",\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"
- + "\"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"
- + "}";
+ private static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO =
+ new ClassPathResource("/metadata/test-provider-standard.json");
+
/**
* 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 = "{\n"
- + "\"issuer\": \"https://localhost:9919\",\n"
- + "\"authorization_endpoint\": \"https://localhost:9919/o/oauth2/v2/auth\",\n"
- + "\"device_authorization_endpoint\": \"https://localhost:9919/device/code\",\n"
- + "\"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:9919/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"
- + "\"HS256\"\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"
- + "}";
+ private static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT =
+ new ClassPathResource("/metadata/test-provider-requestobject.json");
/**
* 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"
- + "}";
+ private static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_ENCRYPT =
+ new ClassPathResource("/metadata/test-provider-requestobject-encrypt.json");;
+
+ /**
+ * 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 static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_RSA256_SIG =
+ new ClassPathResource("/metadata/test-provider-requestobject-rs256-sig.json");
/** Mock JSON Object response from the UserInfo endpoint.*/
@Nonnull @NotEmpty
- protected final String USERINFO_RESPONSE ="{\n"
+ private static final String USERINFO_RESPONSE ="{\n"
+ " \"sub\": \"jdoe\",\n"
+ " \"website\": \"https://openid.net/\",\n"
+ " \"zoneinfo\": \"America/Los_Angeles\",\n"
@@ -346,7 +188,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
/** Mock JSON Object response from the UserInfo endpoint.*/
@Nonnull @NotEmpty
- protected final String USERINFO_RESPONSE_NO_SUB ="{\n"
+ private static final String USERINFO_RESPONSE_NO_SUB ="{\n"
+ " \"website\": \"https://openid.net/\",\n"
+ " \"zoneinfo\": \"America/Los_Angeles\",\n"
+ " \"birthdate\": \"2000-02-03\",\n"
@@ -421,7 +263,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
genericBeanDefinition(org.opensaml.storage.ReplayCache.class).getBeanDefinition());
addBeanDefinition(builderContext, "shibboleth.StorageService",BeanDefinitionBuilder.
- genericBeanDefinition(org.opensaml.storage.impl.MemoryStorageService.class).getBeanDefinition());
+ genericBeanDefinition(org.opensaml.storage.impl.MemoryStorageService.class)
+ .setInitMethodName("initialize")
+ .getBeanDefinition());
addBeanDefinition(builderContext, "shibboleth.SAML2AuthnContextClassRef",BeanDefinitionBuilder.
@@ -517,7 +361,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
*/
private OIDCPeerEntityContext createPeerContext() throws ParseException {
final OIDCPeerEntityContext peerCtx = new OIDCPeerEntityContext();
- final OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO);
+ final OIDCProviderMetadata providerMetadata =
+ OIDCProviderMetadata.parse(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO));
final OIDCProviderMetadataContext providerMetadataCtx = new OIDCProviderMetadataContext();
providerMetadataCtx.setProviderInformation(providerMetadata);
peerCtx.addSubcontext(providerMetadataCtx);
@@ -585,7 +430,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// First is metadata exchange
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/json")
- .setBody(GOOD_PROVIDER_CONFIGURATION_INFO));
+ .setBody(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO)));
mockOPServer.start(9918);
@@ -623,7 +468,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// First is metadata exchange
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/json")
- .setBody(GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT));
+ .setBody(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT)));
mockOPServer.start(9919);
@@ -639,6 +484,50 @@ 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.
+ * And encryption is enabled
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testFlowToAuthorizationRedirect_UsingRequestObject_WithEncryption() 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_ENCRYPT);
+
+ setMockProperties(mockProperties);
+
+ final MockWebServer mockOPServer = createSimpleServer();
+ // First is metadata exchange
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_ENCRYPT)));
+
+ // Second is JWKSet lookup
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(readJsonFromFile(REMOTE_JWKSET_RESPONSE)));
+
+ mockOPServer.start(9921);
+
+ 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 to the authorization redirect using an OP who's RP config in XML is set to use a request object.
*
@@ -661,7 +550,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// 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));
+ .setBody(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO_USE_REQUEST_OBJECT_RSA256_SIG)));
mockOPServer.start(9920);
@@ -694,7 +583,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// First is metadata exchange
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/json")
- .setBody(GOOD_PROVIDER_CONFIGURATION_INFO));
+ .setBody(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO)));
mockOPServer.start(9918);
@@ -920,7 +809,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
@Test
- public void testAuthnFlowFromAuthorizationCallback_Using_AsymetricSignedAndEncrypted_IDTokenAndUserInfoResponse()
+ public void testAuthnFlowFromAuthorizationCallback_Using_SymetricSignedIDToken_And_AsymetricSignedAndEncryptedUserInfoResponse()
throws Exception {
setFlowPath(FLOW);
@@ -979,7 +868,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final var userInfoDecryptConfig = new BasicJWTDecryptionConfiguration();
userInfoDecryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
- userInfoDecryptConfig.setKEKCredentialResolver(new CredentialResolver() {
+ userInfoDecryptConfig.setKEKCredentialResolver(new JOSEObjectCredentialResolver() {
@Override
public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
@@ -1100,7 +989,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
secConfig.setIdTokenJwtSignatureValidationConfiguration(sigValidation);
final var idTokenDecryptConfig = new BasicJWTDecryptionConfiguration();
- idTokenDecryptConfig.setKEKCredentialResolver(new CredentialResolver() {
+ idTokenDecryptConfig.setKEKCredentialResolver(new JOSEObjectCredentialResolver() {
@Override
public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
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 74803ec..8f8460e 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
@@ -1,12 +1,29 @@
+/*
+ * 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.impl;
+import java.security.KeyException;
import java.time.Duration;
-import javax.crypto.spec.SecretKeySpec;
-
import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.crypto.KeySupport;
-import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.jwk.AsymmetricJWK;
@@ -15,7 +32,6 @@ import com.nimbusds.jose.jwk.RSAKey;
import net.shibboleth.oidc.security.credential.BasicExpiringJWKCredential;
import net.shibboleth.oidc.security.credential.JWKCredential;
-import net.shibboleth.oidc.security.credential.impl.JWKEncryptionCredentialContext;
import net.shibboleth.oidc.security.impl.JWSAssemblyUtils;
/** Helper that creates different credentials.*/
@@ -31,11 +47,12 @@ public final class TestCredentialHelper {
* @param secret the secret to convert to a {@link JWKCredential}.
*
* @return the credential
+ * @throws KeyException on error creating the key
*/
//TODO used for both signing and encryption, so alg needs to reflect this
- public static JWKCredential createClientSecretCredential(final String secret) {
+ public static JWKCredential createClientSecretCredential(final String secret) throws KeyException {
final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
- jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(JWSAssemblyUtils.getSecretBytes(secret), "AES"));
jwkCredential.setCredentialExpiresAt(Duration.ZERO);
jwkCredential.setUsageType(UsageType.UNSPECIFIED);
jwkCredential.setKid("mockKey");
@@ -49,14 +66,16 @@ public final class TestCredentialHelper {
* @param secret the secret to convert to a {@link JWKCredential}.
*
* @return the credential
+ * @throws KeyException on error creating the key
*/
- public static JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret) {
+ public static JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret)
+ throws KeyException {
final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
- jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(secret), "AES"));
jwkCredential.setCredentialExpiresAt(Duration.ZERO);
jwkCredential.setUsageType(UsageType.UNSPECIFIED);
- jwkCredential.getCredentialContextSet().add(
- new JWKEncryptionCredentialContext(EncryptionMethod.A256GCM));
+
jwkCredential.setKid("mockKey");
jwkCredential.getKeyNames().add("mockKey");
jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
index e79dd48..774ceed 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
@@ -81,6 +81,7 @@ public final class TestTokenHelper {
.claim("nonce", nonce)
.claim("azp", clientId)
.claim("name",name)
+ .issueTime(new Date())
.expirationTime(Date.from(Instant.now().plusSeconds(120)))
.build();
}
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
index 71b80c6..7ed4488 100644
--- 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
@@ -60,7 +60,7 @@ public class SignRequestObjectTest extends AbstractOIDCTest {
}
@Test
- public void testSignHMAC_Success() throws MessageHandlerException {
+ public void testSignHMAC_Success() throws Exception {
final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
final var params = new SignatureSigningParameters();
@@ -77,7 +77,7 @@ public class SignRequestObjectTest extends AbstractOIDCTest {
}
@Test(expectedExceptions = Exception.class)
- public void testSignHMAC_WrongCredentialType() throws MessageHandlerException {
+ public void testSignHMAC_WrongCredentialType() throws Exception {
final JWTSecurityParametersContext secParamCtx = new JWTSecurityParametersContext();
final var params = new SignatureSigningParameters();
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/test/flow/AbstractAuthnXmlFlowExecutionTests.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/test/flow/AbstractAuthnXmlFlowExecutionTests.java
index 1f8ac43..7003e42 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/test/flow/AbstractAuthnXmlFlowExecutionTests.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/test/flow/AbstractAuthnXmlFlowExecutionTests.java
@@ -17,6 +17,9 @@
package net.shibboleth.idp.plugin.authn.test.flow;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Collections;
@@ -52,6 +55,7 @@ import org.springframework.mock.env.MockPropertySource;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.util.FileCopyUtils;
import org.springframework.webflow.config.FlowDefinitionResource;
import org.springframework.webflow.config.FlowDefinitionResourceFactory;
import org.springframework.webflow.engine.Flow;
@@ -579,6 +583,23 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
prc.setBrowserProfile(true);
return prc;
}
+
+ /**
+ * Read a file into a string.
+ *
+ * @param location the location of the file to read
+ *
+ * @return the file as a string
+ */
+ protected String readJsonFromFile(@Nonnull final Resource location) {
+ try (Reader reader = new InputStreamReader(location.getInputStream(), StandardCharsets.UTF_8)) {
+ return FileCopyUtils.copyToString(reader);
+ } catch (final Exception ex) {
+ log.error("Error reading file",ex);
+ fail();
+ return null;
+ }
+ }
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
index 11640fb..0ae2443 100644
--- 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
@@ -5,7 +5,7 @@
"d": "bPsmY1pD0Wr5nj_Optd6hkoM5ANXKVeM2rMKQ2_n7qg6qA4Li-nb_jgyAaiomB2TYAjtJvY804Cc9lhsoXyN0o8NJh8YpI4_59oKJA-L_CupmeZxI9Jo7D4WCrh2HVIjCokqyDjd30aYdb_R9x1ACmE6cfwTxY0TVAhFaT9rhCVZHc6I8niw9kbevmpMZbLwR6WDvdivPBto6BGLXzInxf2s22lGcetP1m2Trj15hW5oOsUDTKXosKWZrs6-9qGO9Uq4JEzhdVdUOQvkoujrT-G9-hbscvDO2-KXJ6a3qz4SDYFCGoWB0QhsLmHGLtBOUvJRiEuztjAy-L_eyigLWQ",
"e": "AQAB",
"use": "enc",
- "alg": "RSA-OAEP-256",
+ "alg": "RS256",
"kid": "defaultRSAEnc",
"qi": "X8a2QwIr5q94V9QyAsArVijyICSrEsdT5Zfpyoz7Eyhd2VoAyA74WiUbcFElbHNbJOKmvHzp9les4o3BCpsTYwUyRdlB-npL_tEpp7fdIj8I3EhWfspJwT1EfLtJakGwoa6v0KpOmEzzR9mCwKmSnKfhF3aA1S-Hch1eEiV8qm8",
"dp": "vJNaafHrRwmQl_cInOxyvD4VAtn4_HgTUx1FeyPDZpmsa55F-nSzDwM9RJ77-3bKszOX3DJv1TEZzmLBBNfpSYYALnbP0m-rgZLtHLNSXXD8rz5mPwC4eIQoKbpmgk5H8a5i47w21xRu5sKJvNc5_zFDM4y_9ohQczbtYq2ohrE",
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/credentials/remote-jwkset-response.jwk b/idp-oidc-rp-impl/src/test/resources/conf/credentials/remote-jwkset-response.jwk
new file mode 100644
index 0000000..5473bee
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/credentials/remote-jwkset-response.jwk
@@ -0,0 +1,66 @@
+{
+"keys": [
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "7da9fea4-7a38-4398-aaef-8226b26776a2",
+"n": "kk-3jeBmUPbpMk0fEdIn-APAdNOoOckA0e-SiALLxy5dWfG-GyF51g31zuM_iNiSiMSsmG2ZAVi48iItFpd-JW9IIT40TC147I6aKrel0Rf39Mwp-1tCzME6VYEgOmgI9qDg2e4edt1cvjQfiw3IZlXakwgYQn2BuoknoCBVjETVLHrnsvEqXhPffzML9O5Ze_nBOX6-pCAzVsimr-ljoln2GQz-ID5fGzlflXJV78v7QzlyyAAQovYQMxiEBgecHu44S0Iu_esLEOOobQkZyHc-OgcwEazfJUEUhKEnevVTJFlQF3Odxp1I6W9zd-zLUceqIMKF5Xs10AfmkPhboQ"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "9ae4e77c-a0c7-4c52-982f-b8e5e6b62ab8",
+"n": "lBh4Ujl1k_H9CAfJe-SD-ngZnllWh5lShhv2FF_OlSlDEwr5wbf4WimeQhqLtfeT-dJXALpLSncaG_5y8pHHh0Pflnx_pZfCoOOc4Fba7wZgpHzfSQePwIDH8ygmzMLNzLaECa5m1LxnDD0oVHsABOab-_6_Uvuvam5xo2pKfJHoxkVsEDxQ2R0T_GfqC2bmCNJCdadeqw43yF_ILBRX-9sosA_7GPwyBWKAyiHX-DTUKwWrpR2bwCGE2Bxfgj3cDa97prSX8Vwpj_DEPOH8hbMAjO-N4EBcvcJZ0O0CD3X3IrquC__wqc9aOMEh2xbRxnTHdrNNG1KqzS8-L2yS0w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "7b1a7c28-df25-4d54-b111-11903db56d52",
+"x": "XRlwH72XaSlYjybpA6q4DTHsOphTuSWNPULNKwQ38wo",
+"y": "xWIoYZAyQxZM7RJCL-k14PdIHkCPo4m2tKRiCWyXU9w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "00cfe876-ed35-4052-8045-be1088c3212f",
+"x": "SwSHs_Df-Qxl83Mibu_lWzxn0mBn9hGts9gougQlrqs",
+"y": "kKQfhy4jDV_cxpC3iptQTFODkgENp-HC4XK7NIDqt5s"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "secp256k1",
+"kid": "4d1d9b37-3acc-46b9-9426-ee209db8541f",
+"x": "cZb41D8qgFxbpxnqOcp-kc78M8EdtSYqotje0IhWk_o",
+"y": "viJ95PgOZdYFPHRqdO4NOhRQgkejVDv8RmDprbLE31g"
+},
+{
+"kty": "OKP",
+"use": "sig",
+"crv": "Ed25519",
+"kid": "0c54869d-7d20-4faf-b607-3b040d1e1f27",
+"x": "gi3CalT0xmz8V52rgfdvYyM-rUwKnf8gUUUqB87Gycw"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "enc",
+"kid": "87ff206d-15f9-4b8c-ba88-a8c17014da13",
+"alg": "RSA-OAEP",
+"n": "uAVnVD3cMEbrAsDg1c3n6GfzR3sSg9C9pbjTw39_jgWk5YQCHPPOt4zYyZZL2JCnm9TFjnndCCW5ZPWHPJjumiNB2r-vC0CmI-T66JSRX3YYw0h2Odiusr_74FNe_mYyEuClFa4hwo-RMgrp8L1sbrAWcgGOc84rD6-fZXVrWFMkOb0jg6tqF1EwBSxZFG1cfvUmatNuBXs6njPHvvqhd7Bz6adK4YkpzCUbD-jSjpvAvU-Q4TZT_bXq4WRFOPqXv2NX4ch7ErjEm5tJEk7BIqOh7Byg0pWB4WAwsMcZKnHlp7JjtB2T1s_45_iqD2xipxpF-NxoHUlz67qHt7-W4Q"
+},
+{
+"kty": "EC",
+"use": "enc",
+"crv": "P-256",
+"kid": "c689ce91-8d82-45f2-b671-38ee38e7599f",
+"x": "redOUw802EuKJRoS8kQx6_RjuCypx0dcMBhv4IAALvQ",
+"y": "oaypduaS_wPLGCDQfJ0SKtQu0urJzhr4ZRn5wYMhiyk",
+"alg": "ECDH-ES"
+}
+]
+}
\ No newline at end of file
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 d5045fb..fc28498 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
@@ -67,6 +67,15 @@
</list>
</property>
</bean>
+ <!-- This override is used in the OIDCRPFlowTest#testFlowToAuthorizationRedirect_UsingRequestObject_WithEncryption test -->
+ <bean id="TestRequestObject" parent="RelyingPartyByName" c:relyingPartyIds="https://localhost:9921">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO" p:useRequestObject="true" p:signRequestObject="true"
+ p:encryptRequestObject="true"/>
+ </list>
+ </property>
+ </bean>
</util:list>
diff --git a/idp-oidc-rp-impl/src/test/resources/logback-test.xml b/idp-oidc-rp-impl/src/test/resources/logback-test.xml
index 5a9618d..cabbb3c 100644
--- a/idp-oidc-rp-impl/src/test/resources/logback-test.xml
+++ b/idp-oidc-rp-impl/src/test/resources/logback-test.xml
@@ -2,6 +2,7 @@
<configuration>
<logger name="net.shibboleth" level="DEBUG"/>
+ <logger name="org.opensaml.xmlsec.impl" level="DEBUG"/>
<logger name="net.shibboleth.idp.plugin.authn" level="TRACE"/>
<logger name="org.springframework" level="INFO"/>
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-encrypt.json b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-encrypt.json
new file mode 100644
index 0000000..17c2156
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-encrypt.json
@@ -0,0 +1,88 @@
+{
+ "issuer":"https://localhost:9921",
+ "authorization_endpoint":"https://localhost:9921/o/oauth2/v2/auth",
+ "device_authorization_endpoint":"https://localhost:9921/device/code",
+ "token_endpoint":"https://localhost:9921/token",
+ "userinfo_endpoint":"https://localhost:9921/v1/userinfo",
+ "revocation_endpoint":"https://localhost:9921/revoke",
+ "jwks_uri":"https://localhost:9921/oauth2/v3/certs",
+ "request_parameter_supported":true,
+ "response_types_supported":[
+ "code",
+ "token",
+ "id_token",
+ "code token",
+ "code id_token",
+ "token id_token",
+ "code token id_token",
+ "none"
+ ],
+ "subject_types_supported":[
+ "public"
+ ],
+ "id_token_signing_alg_values_supported":[
+ "RS256"
+ ],
+ "request_object_signing_alg_values_supported":[
+ "HS256"
+ ],
+ "request_object_encryption_alg_values_supported":[
+ "RSA1_5",
+ "RSA-OAEP",
+ "RSA-OAEP-256",
+ "RSA-OAEP-384",
+ "RSA-OAEP-512",
+ "ECDH-ES",
+ "ECDH-ES+A128KW",
+ "ECDH-ES+A192KW",
+ "ECDH-ES+A256KW",
+ "A128KW",
+ "A192KW",
+ "A256KW",
+ "A128GCMKW",
+ "A192GCMKW",
+ "A256GCMKW",
+ "dir"
+ ],
+ "request_object_encryption_enc_values_supported":[
+ "A128CBC-HS256",
+ "A192CBC-HS384",
+ "A256CBC-HS512",
+ "A128GCM",
+ "A192GCM",
+ "A256GCM"
+ ],
+ "scopes_supported":[
+ "openid",
+ "email",
+ "profile"
+ ],
+ "token_endpoint_auth_methods_supported":[
+ "client_secret_post",
+ "client_secret_basic"
+ ],
+ "claims_supported":[
+ "aud",
+ "email",
+ "email_verified",
+ "exp",
+ "family_name",
+ "given_name",
+ "iat",
+ "iss",
+ "locale",
+ "name",
+ "picture",
+ "sub"
+ ],
+ "code_challenge_methods_supported":[
+ "plain",
+ "S256"
+ ],
+ "grant_types_supported":[
+ "authorization_code",
+ "refresh_token",
+ "urn:ietf:params:oauth:grant-type:device_code",
+ "urn:ietf:params:oauth:grant-type:jwt-bearer"
+ ]
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-rs256-sig.json b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-rs256-sig.json
new file mode 100644
index 0000000..8125e9f
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject-rs256-sig.json
@@ -0,0 +1,62 @@
+{
+ "issuer":"https://localhost:9920",
+ "authorization_endpoint":"https://localhost:9920/o/oauth2/v2/auth",
+ "device_authorization_endpoint":"https://localhost:9920/device/code",
+ "token_endpoint":"https://localhost:9920/token",
+ "userinfo_endpoint":"https://localhost:9920/v1/userinfo",
+ "revocation_endpoint":"https://localhost:9920/revoke",
+ "jwks_uri":"https://localhost:9920/oauth2/v3/certs",
+ "request_parameter_supported":true,
+ "response_types_supported":[
+ "code",
+ "token",
+ "id_token",
+ "code token",
+ "code id_token",
+ "token id_token",
+ "code token id_token",
+ "none"
+ ],
+ "subject_types_supported":[
+ "public"
+ ],
+ "id_token_signing_alg_values_supported":[
+ "RS256"
+ ],
+ "request_object_signing_alg_values_supported":[
+ "RS256"
+ ],
+ "scopes_supported":[
+ "openid",
+ "email",
+ "profile"
+ ],
+ "token_endpoint_auth_methods_supported":[
+ "client_secret_post",
+ "client_secret_basic"
+ ],
+ "claims_supported":[
+ "aud",
+ "email",
+ "email_verified",
+ "exp",
+ "family_name",
+ "given_name",
+ "iat",
+ "iss",
+ "locale",
+ "name",
+ "picture",
+ "sub"
+ ],
+ "code_challenge_methods_supported":[
+ "plain",
+ "S256"
+ ],
+ "grant_types_supported":[
+ "authorization_code",
+ "refresh_token",
+ "urn:ietf:params:oauth:grant-type:device_code",
+ "urn:ietf:params:oauth:grant-type:jwt-bearer"
+ ]
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject.json b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject.json
new file mode 100644
index 0000000..7e64106
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-requestobject.json
@@ -0,0 +1,62 @@
+{
+ "issuer":"https://localhost:9919",
+ "authorization_endpoint":"https://localhost:9919/o/oauth2/v2/auth",
+ "device_authorization_endpoint":"https://localhost:9919/device/code",
+ "token_endpoint":"https://localhost:9919/token",
+ "userinfo_endpoint":"https://localhost:9919/v1/userinfo",
+ "revocation_endpoint":"https://localhost:9919/revoke",
+ "jwks_uri":"https://localhost:9919/oauth2/v3/certs",
+ "request_parameter_supported":true,
+ "response_types_supported":[
+ "code",
+ "token",
+ "id_token",
+ "code token",
+ "code id_token",
+ "token id_token",
+ "code token id_token",
+ "none"
+ ],
+ "subject_types_supported":[
+ "public"
+ ],
+ "id_token_signing_alg_values_supported":[
+ "RS256"
+ ],
+ "request_object_signing_alg_values_supported":[
+ "HS256"
+ ],
+ "scopes_supported":[
+ "openid",
+ "email",
+ "profile"
+ ],
+ "token_endpoint_auth_methods_supported":[
+ "client_secret_post",
+ "client_secret_basic"
+ ],
+ "claims_supported":[
+ "aud",
+ "email",
+ "email_verified",
+ "exp",
+ "family_name",
+ "given_name",
+ "iat",
+ "iss",
+ "locale",
+ "name",
+ "picture",
+ "sub"
+ ],
+ "code_challenge_methods_supported":[
+ "plain",
+ "S256"
+ ],
+ "grant_types_supported":[
+ "authorization_code",
+ "refresh_token",
+ "urn:ietf:params:oauth:grant-type:device_code",
+ "urn:ietf:params:oauth:grant-type:jwt-bearer"
+ ]
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json
new file mode 100644
index 0000000..41404ac
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/test-provider-standard.json
@@ -0,0 +1,58 @@
+{
+ "issuer":"https://localhost:9918",
+ "authorization_endpoint":"https://localhost:9918/o/oauth2/v2/auth",
+ "device_authorization_endpoint":"https://localhost:9918/device/code",
+ "token_endpoint":"https://localhost:9918/token",
+ "userinfo_endpoint":"https://localhost:9918/v1/userinfo",
+ "revocation_endpoint":"https://localhost:9918/revoke",
+ "jwks_uri":"https://localhost:9918/oauth2/v3/certs",
+ "response_types_supported":[
+ "code",
+ "token",
+ "id_token",
+ "code token",
+ "code id_token",
+ "token id_token",
+ "code token id_token",
+ "none"
+ ],
+ "subject_types_supported":[
+ "public"
+ ],
+ "id_token_signing_alg_values_supported":[
+ "RS256"
+ ],
+ "scopes_supported":[
+ "openid",
+ "email",
+ "profile"
+ ],
+ "token_endpoint_auth_methods_supported":[
+ "client_secret_post",
+ "client_secret_basic"
+ ],
+ "claims_supported":[
+ "aud",
+ "email",
+ "email_verified",
+ "exp",
+ "family_name",
+ "given_name",
+ "iat",
+ "iss",
+ "locale",
+ "name",
+ "picture",
+ "sub"
+ ],
+ "code_challenge_methods_supported":[
+ "plain",
+ "S256"
+ ],
+ "grant_types_supported":[
+ "authorization_code",
+ "refresh_token",
+ "urn:ietf:params:oauth:grant-type:device_code",
+ "urn:ietf:params:oauth:grant-type:jwt-bearer"
+ ]
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list