[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