[java-idp-plugin-oidc-rp] branch main updated: Add access token hash validation

Phil Smart philip.smart at jisc.ac.uk
Thu Dec 1 10:57:54 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=2832ca64987e49d8a104866bbe14178c2d7af001

The following commit(s) were added to refs/heads/main by this push:
     new 2832ca6  Add access token hash validation
2832ca6 is described below

commit 2832ca64987e49d8a104866bbe14178c2d7af001
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Dec 1 10:57:51 2022 +0000

    Add access token hash validation
---
 .../navigate/IDTokenJOSEHeaderLookupStrategy.java  | 76 ++++++++++++++++++++++
 .../navigate/RawTokenResponseLookupStrategy.java   | 70 ++++++++++++++++++++
 .../oidc-relying-party-authn-beans.xml             | 17 ++++-
 .../authn/oidc/rp/conf/authn/oidc-rp.properties    |  7 +-
 4 files changed, 166 insertions(+), 4 deletions(-)

diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenJOSEHeaderLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenJOSEHeaderLookupStrategy.java
new file mode 100644
index 0000000..4f129fb
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/IDTokenJOSEHeaderLookupStrategy.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+
+import com.nimbusds.jose.Header;
+import com.nimbusds.jose.JWSHeader;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** Function that extracts the JWS JOSE header from the id_token inside the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
+public class IDTokenJOSEHeaderLookupStrategy implements Function<ProfileRequestContext, JWSHeader> {
+    
+    /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    public IDTokenJOSEHeaderLookupStrategy() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup()); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public IDTokenJOSEHeaderLookupStrategy(
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       tokenResponseContextLookupStrategy = 
+               Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+   }
+
+    @Override
+    @Nullable public JWSHeader apply(@Nonnull final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
+        if (tokenContext == null || tokenContext.getIdToken() == null) {
+            return null;
+        }
+        final Header header = tokenContext.getIdToken().getHeader();
+        if (header instanceof JWSHeader) {
+            return (JWSHeader) header;
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RawTokenResponseLookupStrategy.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RawTokenResponseLookupStrategy.java
new file mode 100644
index 0000000..b62c8bd
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RawTokenResponseLookupStrategy.java
@@ -0,0 +1,70 @@
+/*
+ * 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.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** Function that extracts the raw token response from the {@link AccessTokenResponseContext}.*/
+ at ThreadSafe
+public class RawTokenResponseLookupStrategy implements Function<ProfileRequestContext, Map<String, Object>> {
+    
+    /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from.*/
+    @Nonnull 
+    private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
+    
+    /** Constructor.*/
+    public RawTokenResponseLookupStrategy() {
+        tokenResponseContextLookupStrategy =
+                new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+                        new InboundMessageContextLookup()); 
+    }
+    
+   /**
+    * 
+    * Constructor.
+    *
+    * @param strategy the AccessTokenResponseContext lookup strategy to use.
+    */
+   public RawTokenResponseLookupStrategy(
+           @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+       tokenResponseContextLookupStrategy = 
+               Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+   }
+
+    @Override
+    @Nullable public  Map<String, Object> apply(@Nonnull final ProfileRequestContext prc) {
+        final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
+        if (tokenContext == null) {
+            return null;
+        }
+        return tokenContext.getRawTokenResponse();
+    }
+
+}
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 9c21aae..45fda2e 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
@@ -648,6 +648,19 @@
 
     <bean id="OIDCPeerEntityContextChildLookup" class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext) }" />
+        
+    <bean id="AtHashValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.AccessTokenHashValidator"
+        p:allowMissing="%{idp.authn.oidc.rp.client.tokenresponse.allowMissingAtHash:true}"
+        p:tokenResponseLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.TokenResponseLookupStrategy') ?: 
+                                getObject('shibboleth.authn.oidc.rp.jwt.DefaultTokenResponseLookupStrategy')}"
+        p:joseHeaderLookupStrategy="#{getObject('shibboleth.authn.oidc.rp.jwt.IDTokenJOSEHeaderLookupStrategy') ?: 
+                                getObject('shibboleth.authn.oidc.rp.jwt.DefaultIDTokenJOSEHeaderLookupStrategy')}"/>
+                                
+    <bean id="shibboleth.authn.oidc.rp.jwt.DefaultTokenResponseLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RawTokenResponseLookupStrategy"/>
+    
+    <bean id="shibboleth.authn.oidc.rp.jwt.DefaultIDTokenJOSEHeaderLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.IDTokenJOSEHeaderLookupStrategy"/>
 
     <util:list id="IDTokenClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="IDTokenRequiredClaimsValidator" />
@@ -659,8 +672,8 @@
         <ref bean="IssuedAtClaimsValidator" />
         <ref bean="NotBeforeClaimsValidator" />
         <ref bean="NonceClaimValidator" />
-        <!-- missing ACR? and auth_time, access_token at_hash. -->
-        <!-- TODO at_hash if contained in id_token should exact match (via some steps) a derivation of the access_token -->
+        <ref bean="AtHashValidator"/>
+        <!-- missing ACR? and auth_time -->
     </util:list>
 
 
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
index 188f1b7..62786c7 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
@@ -20,9 +20,12 @@ idp.authn.oidc.rp.client.redirecturl.allowedOrigins = https://localhost:8443
 #idp.authn.oidc.rp.client.idtoken.tlsServerValidationOnly = false
 
 #idp.authn.oidc.rp.client.userinfo.enabled = true
-#idp.authn.oidc.rp.client.userinfo.requireJWTResponse = false
 
-## Use small fetch interval so we can re-run tests against the certification OP
+## Should validation be skipped if the at_hash is not present in the id_token response. Defaults to 'true' as 
+## access token at_hash validation is optional in the Authorization Code Flow. 
+#idp.authn.oidc.rp.client.tokenresponse.allowMissingAtHash = true
+
+## The OP's keyset document fetch interval i.e. how long should keys from the OP be cached.
 #idp.authn.oidc.rp.provider.keyfetch.interval = PT30M
 
 ## Override the default response_mode for the given response_type

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list