[java-idp-plugin-oidc-rp] branch main updated: Add UserInfo JWT decryption. Clean UserInfo response flow.
Phil Smart
philip.smart at jisc.ac.uk
Tue Jun 7 10:13:08 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=289c499fca80213a031afa9c529080eb4350cb31
The following commit(s) were added to refs/heads/main by this push:
new 289c499 Add UserInfo JWT decryption. Clean UserInfo response flow.
289c499 is described below
commit 289c499fca80213a031afa9c529080eb4350cb31
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Jun 7 11:13:02 2022 +0100
Add UserInfo JWT decryption. Clean UserInfo response flow.
---
...okenDecryptionConfigurationLookupFunction.java} | 2 +-
.../IDTokenInAccessTokenUpdateStrategy.java | 2 +-
...InfoDecryptionConfigurationLookupFunction.java} | 10 ++--
...nfoInUserInfoResponseContextUpdateStrategy.java | 43 +++++++++-------
.../oidc/rp/messaging/JWTUserInfoResponse.java | 5 ++
.../oidc/rp/messaging/PlainUserInfoResponse.java | 5 ++
.../authn/oidc/rp/messaging/UserInfoResponse.java | 16 ++++++
.../logic/UserInfoPlainResponseTypeCondition.java | 39 +++++++++++++++
.../impl/DefaultUserInfoResponseDecoder.java | 1 +
.../rp/impl/PopulateJWTDecryptionParameters.java | 1 -
.../oidc-relying-party-authn-beans.xml | 57 +++++++++++++++++-----
.../oidc-relying-party-authn-flow.xml | 34 ++++---------
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 48 +++++++++++++-----
13 files changed, 188 insertions(+), 75 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenDecryptionConfigurationLookupFunction.java
similarity index 98%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenDecryptionConfigurationLookupFunction.java
index daf4c06..e6ff572 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenDecryptionConfigurationLookupFunction.java
@@ -40,7 +40,7 @@ import net.shibboleth.oidc.security.DecryptionConfiguration;
* If a specific setting is unavailable, a null value is returned.
* </p>
*/
-public class JWTDecryptionConfigurationLookupFunction
+public class IDTokenDecryptionConfigurationLookupFunction
extends AbstractRelyingPartyLookupFunction<List<DecryptionConfiguration>> {
/** A resolver for default security configurations. */
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategy.java
similarity index 98%
copy from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategy.java
index 8db68b7..7803de7 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenInAccessTokenUpdateStrategy.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
import java.util.function.BiConsumer;
import java.util.function.Function;
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoDecryptionConfigurationLookupFunction.java
similarity index 92%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoDecryptionConfigurationLookupFunction.java
index daf4c06..273d5f6 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/JWTDecryptionConfigurationLookupFunction.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoDecryptionConfigurationLookupFunction.java
@@ -40,7 +40,7 @@ import net.shibboleth.oidc.security.DecryptionConfiguration;
* If a specific setting is unavailable, a null value is returned.
* </p>
*/
-public class JWTDecryptionConfigurationLookupFunction
+public class UserInfoDecryptionConfigurationLookupFunction
extends AbstractRelyingPartyLookupFunction<List<DecryptionConfiguration>> {
/** A resolver for default security configurations. */
@@ -68,9 +68,9 @@ public class JWTDecryptionConfigurationLookupFunction
final ProfileConfiguration pc = rpc.getProfileConfig();
if (pc != null && pc.getSecurityConfiguration(input) instanceof OIDCSecurityConfiguration
&& ((OIDCSecurityConfiguration) pc.getSecurityConfiguration(input))
- .getIdtokenJwtDecryptionConfig() != null) {
+ .getUserInfoJwtDecryptionConfig() != null) {
configs.add(((OIDCSecurityConfiguration) pc.getSecurityConfiguration(input))
- .getIdtokenJwtDecryptionConfig());
+ .getUserInfoJwtDecryptionConfig());
}
}
@@ -80,9 +80,9 @@ public class JWTDecryptionConfigurationLookupFunction
rpResolver.getDefaultSecurityConfiguration(input.getProfileId());
if (defaultConfig instanceof OIDCSecurityConfiguration
&& ((OIDCSecurityConfiguration) defaultConfig)
- .getIdtokenJwtDecryptionConfig() != null) {
+ .getUserInfoJwtDecryptionConfig() != null) {
configs.add(
- ((OIDCSecurityConfiguration) defaultConfig).getIdtokenJwtDecryptionConfig());
+ ((OIDCSecurityConfiguration) defaultConfig).getUserInfoJwtDecryptionConfig());
}
}
// TODO: Support for Global Default configuration?
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
similarity index 56%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
index 8db68b7..3541557 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/IDTokenInAccessTokenUpdateStrategy.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/UserInfoInUserInfoResponseContextUpdateStrategy.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
import java.util.function.BiConsumer;
import java.util.function.Function;
@@ -31,18 +31,24 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.JWT;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
import net.shibboleth.utilities.java.support.annotation.ParameterName;
import net.shibboleth.utilities.java.support.logic.Constraint;
-/** Strategy to update the id_token in the {@link AccessTokenResponseContext}.*/
-public class IDTokenInAccessTokenUpdateStrategy implements BiConsumer<ProfileRequestContext, JWT> {
+/**
+ * Strategy to update the UserInfo JWT in the {@link UserInfoResponseContext}.
+ * Note, replaces the entire {@link UserInfoResponse} object.
+ */
+public class UserInfoInUserInfoResponseContextUpdateStrategy implements BiConsumer<ProfileRequestContext, JWT> {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(IDTokenInAccessTokenUpdateStrategy.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(UserInfoInUserInfoResponseContextUpdateStrategy.class);
/** Strategy used to look up the {@link AccessTokenResponseContext} to set id_token on. */
- @Nonnull private final Function<ProfileRequestContext, AccessTokenResponseContext>
- tokenResponseContextLookupStrategy;
+ @Nonnull private final Function<ProfileRequestContext, UserInfoResponseContext>
+ userInfoResponseContextLookupStrategy;
/**
*
@@ -50,29 +56,30 @@ public class IDTokenInAccessTokenUpdateStrategy implements BiConsumer<ProfileRe
*
* @param strategy the strategy used look up the {@link AccessTokenResponseContext}.
*/
- public IDTokenInAccessTokenUpdateStrategy(@ParameterName(name="accessTokenContextLookupStrategy") final
- Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+ public UserInfoInUserInfoResponseContextUpdateStrategy(
+ @ParameterName(name="userInfoResponseContextLookupStrategy") final
+ Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
- tokenResponseContextLookupStrategy =
- Constraint.isNotNull(strategy, "accessTokenContextLookupStrategy can not be null");
+ userInfoResponseContextLookupStrategy =
+ Constraint.isNotNull(strategy, "userInfoResponseContextLookupStrategy can not be null");
}
/** Constructor.*/
- public IDTokenInAccessTokenUpdateStrategy() {
- tokenResponseContextLookupStrategy =
- new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+ public UserInfoInUserInfoResponseContextUpdateStrategy() {
+ userInfoResponseContextLookupStrategy =
+ new ChildContextLookup<>(UserInfoResponseContext.class, true).compose(
new InboundMessageContextLookup());
}
@Override
- public void accept(final ProfileRequestContext profileRequestContext, final JWT idToken) {
+ public void accept(final ProfileRequestContext profileRequestContext, final JWT token) {
- final AccessTokenResponseContext context =
- tokenResponseContextLookupStrategy.apply(profileRequestContext);
+ final UserInfoResponseContext context =
+ userInfoResponseContextLookupStrategy.apply(profileRequestContext);
if (context != null) {
- context.setIdToken(idToken);
+ context.setUserInfo(new JWTUserInfoResponse(token));
} else {
- log.warn("Unable to set id_token back onto access token response context");
+ log.warn("Unable to set UserInfo back onto response context");
}
}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
index 82a0423..baba023 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/JWTUserInfoResponse.java
@@ -104,4 +104,9 @@ public class JWTUserInfoResponse implements UserInfoResponse {
return responseJwt instanceof EncryptedJWT;
}
+ @Override
+ public UserInfoResponseType getType() {
+ return UserInfoResponseType.JWT;
+ }
+
}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
index 25587d0..9e50da3 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/PlainUserInfoResponse.java
@@ -72,5 +72,10 @@ public class PlainUserInfoResponse implements UserInfoResponse {
public boolean isEncrypted() {
return false;
}
+
+ @Override
+ public UserInfoResponseType getType() {
+ return UserInfoResponseType.PLAIN;
+ }
}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
index a7dc0ca..9cb9927 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
@@ -27,6 +27,15 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
*/
public interface UserInfoResponse {
+ /** Which type of response is this.*/
+ enum UserInfoResponseType {
+ /** A plain JSON Object type.*/
+ PLAIN,
+
+ /** A signed and possibly encrypted JWT type.*/
+ JWT
+ }
+
/**
* Get the claims for the authenticated end-user.
*
@@ -66,6 +75,13 @@ public interface UserInfoResponse {
* @return true iff the response is encrypted.
*/
boolean isEncrypted();
+
+ /**
+ * Get the UserInfo response type.
+ *
+ * @return the response type, e.g. plain or JWT.
+ */
+ UserInfoResponseType getType();
}
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
new file mode 100644
index 0000000..7262d67
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/logic/UserInfoPlainResponseTypeCondition.java
@@ -0,0 +1,39 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+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.
+ */
+public class UserInfoPlainResponseTypeCondition extends AbstractUserInfoResponseTypeCondition {
+
+ @Override
+ protected boolean doTest(@Nonnull final ProfileRequestContext prc,
+ @Nonnull final UserInfoResponseContext context) {
+ if (context.getUserInfo() == null) {
+ return false;
+ }
+ return context.getUserInfo().getType() == UserInfoResponseType.PLAIN;
+ }}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
index 3b2cd1f..2cf6b8b 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
@@ -42,6 +42,7 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
/** Response decoder for UserInfo responses. Supports both plain JSON Object and JWT responses.*/
+//TODO check no bypass for plain to be recorded for a JWT type, as no signature check would then be performed
public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction<UserInfoResponse> {
/** The application/jwt media type.*/
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTDecryptionParameters.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTDecryptionParameters.java
index e9bf192..822b6be 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTDecryptionParameters.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateJWTDecryptionParameters.java
@@ -27,7 +27,6 @@ import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
-import org.opensaml.xmlsec.EncryptionParametersResolver;
import org.opensaml.xmlsec.context.SecurityParametersContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 0d1009a..bcc7a5f 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
@@ -256,20 +256,20 @@
<bean id="PopulateIDTokenDecryptionParameters" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
<constructor-arg>
<bean class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateJWTDecryptionParameters"
- p:configurationLookupStrategy-ref="shibboleth.authn.oidc.rp.DecryptionConfigurationLookup"
- p:decryptionParametersResolver-ref="shibboleth.authn.oidc.rp.IDTokenDecryptionParametersResolver" />
+ p:configurationLookupStrategy-ref="IDTokenDecryptionConfigurationLookup"
+ p:decryptionParametersResolver-ref="JWTDecryptionParametersResolver" />
</constructor-arg>
</bean>
- <bean id="shibboleth.authn.oidc.rp.IDTokenDecryptionParametersResolver"
+ <bean id="JWTDecryptionParametersResolver"
class="net.shibboleth.oidc.security.impl.DefaultJWTDecryptionParametersResolver"/>
- <bean id="shibboleth.authn.oidc.rp.DecryptionConfigurationLookup" lazy-init="true"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.JWTDecryptionConfigurationLookupFunction"
+ <bean id="IDTokenDecryptionConfigurationLookup" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.IDTokenDecryptionConfigurationLookupFunction"
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
<bean id="IDTokenInAccessTokenUpdateStrategy"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.IDTokenInAccessTokenUpdateStrategy"/>
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.IDTokenInAccessTokenUpdateStrategy"/>
<bean id="DecryptJWT" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
<constructor-arg>
@@ -306,7 +306,7 @@
class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.IDTokenSignatureValidationConfigurationLookupFunction"
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
- <!-- TODO decryption as well? -->
+ <!-- TODO should we handle claims validation here as well? -->
<bean id="HandleIDTokenValidation" parent="NestedWebFlowMessageHandlerAdaptor" scope="prototype"
c:executionDirection="INBOUND">
<constructor-arg>
@@ -501,6 +501,42 @@
<bean id="shibboleth.authn.oidc.rp.DefaultUserInfoRequestEncoder" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.DefaultUserInfoRequestEncoder" />
+
+ <!-- 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 decryption config -->
+ <bean id="PopulateUserInfoDecryptionParameters" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateJWTDecryptionParameters"
+ p:configurationLookupStrategy-ref="UserInfoTokenDecryptionConfigurationLookup"
+ p:decryptionParametersResolver-ref="JWTDecryptionParametersResolver" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="UserInfoTokenDecryptionConfigurationLookup" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.UserInfoDecryptionConfigurationLookupFunction"
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+
+ <bean id="UserInfoInUserInfoResponseContextUpdateStrategy"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.UserInfoInUserInfoResponseContextUpdateStrategy"/>
+
+ <bean id="DecryptUserInfoJWT" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.DecryptJWT">
+ <property name="jwtTokenLookupStrategy">
+ <bean class="net.shibboleth.idp.profile.context.navigate.SpringExpressionContextLookupFunction"
+ c:_0="#{ T(org.opensaml.profile.context.ProfileRequestContext) }"
+ c:outputType="#{T(com.nimbusds.jwt.EncryptedJWT)}"
+ c:expression="#input.getInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext)).getUserInfo().getResponseJwt()" />
+ </property>
+ <property name="jwtUpdateStrategy">
+ <ref bean="UserInfoInUserInfoResponseContextUpdateStrategy"/>
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+ <!-- Done UserInfo decryption -->
<bean id="ValidateUserInfoClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
@@ -512,12 +548,9 @@
scope="prototype" p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
- <bean id="CheckUserInfoSignedJWTResponseTypeCondition"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.UserInfoSignedJWTResponseTypeCondition" />
-
- <bean id="CheckUserInfoEncryptedJWTResponseTypeCondition"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.UserInfoEncryptedJWTResponseTypeCondition" />
+ <bean id="CheckUserInfoPlainResponseTypeCondition"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.UserInfoPlainResponseTypeCondition"/>
<!-- UserInfo response JWT validation -->
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 367a719..75638c2 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
@@ -132,36 +132,22 @@
<transition on="proceed" to="CheckUserInfoResponseType" />
</action-state>
- <!-- A plain JWT will skip token validation and go straight to claim validation -->
+ <!-- A plain JWT will skip token validation and go straight to claims validation -->
<decision-state id="CheckUserInfoResponseType">
<if
- test="CheckUserInfoSignedJWTResponseTypeCondition.test(opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext').getSubcontext('org.opensaml.profile.context.ProfileRequestContext'))"
- then="ValidateSignedUserInfoJWT" />
- <if
- test="CheckUserInfoEncryptedJWTResponseTypeCondition.test(opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext').getSubcontext('org.opensaml.profile.context.ProfileRequestContext'))"
- then="DecryptUserInfoJWT" else="ValidateUserInfoClaimsSet" />
+ test="CheckUserInfoPlainResponseTypeCondition.test(opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext').getSubcontext('org.opensaml.profile.context.ProfileRequestContext'))"
+ then="ValidateUserInfoClaimsSet" else="ValidateUserInfoJWT"/>
</decision-state>
-
- <action-state id="ValidateSignedUserInfoJWT">
- <!-- <evaluate expression="PopulateUserInfoTokenSignatureValidationParameters" /> -->
- <!-- SIGNATURE CHECK! -->
- <evaluate expression="ValidateUserInfoToken" />
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateUserInfoClaimsSet" />
- </action-state>
-
- <!-- TODO decrypt then check signature if signed, then check claims if signed! see 5.3.2 -->
- <action-state id="DecryptUserInfoJWT">
- <!-- <evaluate expression="PopulateTokenEncryptionParameters" /> -->
- <evaluate expression="ValidateUserInfoToken" /> <!-- Will die if not decrypted properly first -->
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateSignedUserInfoJWT" />
- </action-state>
-
- <action-state id="ValidateUserInfoClaimsSet">
+
+ <action-state id="ValidateUserInfoJWT">
+ <evaluate expression="PopulateUserInfoDecryptionParameters" />
+ <evaluate expression="DecryptUserInfoJWT"/>
+ <!-- <evaluate expression="PopulateUserInfoSignatureValidationParameters" />
+ <evaluate expression="HandleUserInfoTokenValidation" /> -->
<evaluate expression="ValidateUserInfoClaims" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="FinalizeResponse" />
+
</action-state>
<action-state id="FinalizeResponse">
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 7a4ee64..5589143 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
@@ -503,8 +503,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
* @return the signed JWT
* @throws JOSEException on error
*/
- private EncryptedJWT createSignedAndEncryptedUserInfoJWTResponseJSON(final String issuer, final String audience)
- throws Exception {
+ private Pair<RSAKey, EncryptedJWT> createSignedAndAssymetricEncryptedUserInfoJWTResponse(
+ final String issuer, final String audience) throws Exception {
final RSAKey keyRecipient = new RSAKeyGenerator(2048)
.keyID("2")
@@ -517,7 +517,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
.build(),
new Payload(createSignedUserInfoJWTResponseJSON(issuer, audience)));
jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
- return EncryptedJWT.parse(jweObject.serialize());
+ return new Pair<RSAKey, EncryptedJWT>(keyRecipient, EncryptedJWT.parse(jweObject.serialize()));
}
/**
@@ -898,10 +898,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
.setHeader("content-type", "application/json")
.setBody(createAccessTokenResponseJSONSignedAndEncrypted()));
// Second is userInfo
+ final var userInfoTokenAndKey = createSignedAndAssymetricEncryptedUserInfoJWTResponse(OP_ISSUER_ID,CLIENT_ID);
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/jwt")
- .setBody(createSignedAndEncryptedUserInfoJWTResponseJSON(OP_ISSUER_ID,CLIENT_ID)
- .serialize()));
+ .setBody(userInfoTokenAndKey.getSecond().serialize()));
mockOPServer.start(9918);
@@ -924,7 +924,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// Set a default security config for the profile config
final OIDCSecurityConfiguration secConfig = new OIDCSecurityConfiguration();
- //Use a mocked signature config
+ //Signature config for id_token
final BasicSignatureValidationConfiguration<SignedJWT> sigValidation =
new BasicSignatureValidationConfiguration<>();
sigValidation.setSignatureTrustEngine(new ExplicitKeySignedJWTTrustEngine(
@@ -932,12 +932,34 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
secConfig.setIdTokenJwtSignatureValidationConfig(sigValidation);
- final var decryptConfig = new BasicJWTDecryptionConfiguration();
+ final var idTokenDecryptConfig = new BasicJWTDecryptionConfiguration();
+ idTokenDecryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
- // Use a real resolver.
- decryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
+ final var userInfoDecryptConfig = new BasicJWTDecryptionConfiguration();
+ userInfoDecryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
+ userInfoDecryptConfig.setKEKCredentialResolver(new CredentialResolver() {
+
+ @Override
+ public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
+ jwkCredential.setKid(userInfoTokenAndKey.getFirst().getKeyID());
+ try {
+ jwkCredential.setPrivateKey(userInfoTokenAndKey.getFirst().toPrivateKey());
+ jwkCredential.setPublicKey(userInfoTokenAndKey.getFirst().toPublicKey());
+ } catch (final JOSEException e) {
+ fail();
+ }
+ return jwkCredential;
+ }
+ @Override
+ public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+ return List.of(resolveSingle(criteria));
+ }
+ });
- secConfig.setIdTokenJwtDecryptionConfig(decryptConfig);
+ secConfig.setIdTokenJwtDecryptionConfig(idTokenDecryptConfig);
+ secConfig.setUserInfoJwtDecryptionConfig(userInfoDecryptConfig);
partyConfig.setSecurityConfiguration(secConfig);
@@ -1043,8 +1065,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
secConfig.setIdTokenJwtSignatureValidationConfig(sigValidation);
- final var decryptConfig = new BasicJWTDecryptionConfiguration();
- decryptConfig.setKEKCredentialResolver(new CredentialResolver() {
+ final var idTokenDecryptConfig = new BasicJWTDecryptionConfiguration();
+ idTokenDecryptConfig.setKEKCredentialResolver(new CredentialResolver() {
@Override
public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
@@ -1065,7 +1087,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
});
- secConfig.setIdTokenJwtDecryptionConfig(decryptConfig);
+ secConfig.setIdTokenJwtDecryptionConfig(idTokenDecryptConfig);
partyConfig.setSecurityConfiguration(secConfig);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list