[java-oidc-common] branch dev/JCOMOIDC-87 updated: JCOMOIDC-87 - Profile configuration for OIDC logout

Henri Mikkonen henri.mikkonen at iki.fi
Tue Oct 24 13:32:14 UTC 2023


This is an automated email from the git hooks/post-receive script.

hjmikkon pushed a commit to branch dev/JCOMOIDC-87
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=6e52b78f0390159cf04432536c9669d951d6cba0

The following commit(s) were added to refs/heads/dev/JCOMOIDC-87 by this push:
     new 6e52b78  JCOMOIDC-87 - Profile configuration for OIDC logout
6e52b78 is described below

commit 6e52b78f0390159cf04432536c9669d951d6cba0
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Oct 24 12:31:36 2023 +0300

    JCOMOIDC-87 - Profile configuration for OIDC logout
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-87
    
    New ClientInformationClientSecretCredentialResolver can be used for
    resolving only client secret from OIDCClientInformation. This is
    useful for signature validation configuration for id_token_hints.
---
 ...bstractClientInformationCredentialResolver.java |  96 ++++++++++++++
 ...tInformationClientSecretCredentialResolver.java |  44 +++++++
 .../impl/ClientInformationCredentialResolver.java  |  36 +----
 ...ormationClientSecretCredentialResolverTest.java | 145 +++++++++++++++++++++
 .../shibboleth/oidc/profile/core/OidcEventIds.java |   7 +-
 5 files changed, 293 insertions(+), 35 deletions(-)

diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/AbstractClientInformationCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/AbstractClientInformationCredentialResolver.java
new file mode 100644
index 0000000..c93a1e1
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/AbstractClientInformationCredentialResolver.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed 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.oidc.security.credential.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.oidc.security.credential.ClientSecretCredential;
+import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
+import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.component.InitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Base class for resolving credentials from {@link OIDCClientInformation}.
+ */
+public abstract class AbstractClientInformationCredentialResolver extends BasicJOSEObjectCredentialResolver 
+        implements InitializableComponent {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractClientInformationCredentialResolver.class);
+
+    /** Initialization flag. */
+    private boolean isInitialized;
+    
+    /** {@inheritDoc} */
+    @Override
+    public boolean isInitialized() {
+        return isInitialized;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public void initialize() throws ComponentInitializationException {
+        isInitialized = true;
+    }
+
+    /**
+     * Resolves client secret credential.
+     * @param criteriaSet the criteria set containing {@link ClientInformationCriterion}.
+     * @return the set containing {@link ClientSecretCredential} if secret was found from the metadata.
+     */
+    @Nonnull protected Collection<Credential> resolveSecretCredentials(@Nonnull final CriteriaSet criteriaSet) {
+        
+        Constraint.isNotNull(criteriaSet, "CriteriaSet was null");
+
+        if (criteriaSet.contains(ClientInformationCriterion.class)) {
+            final OIDCClientInformation information =
+                    criteriaSet.get(ClientInformationCriterion.class).getOidcClientInformation();
+
+            if (information.getSecret() != null) {
+                final LinkedHashSet<Credential> credentials = new LinkedHashSet<>(1);
+                try {
+                    final ClientSecretCredential secretCred = 
+                            new DefaultClientSecretCredential(information.getSecret().getValue());
+                    final Credential derivedCredential = deriveClientSecretCredential(secretCred, criteriaSet);
+                    if (derivedCredential != null) {
+                        credentials.add(derivedCredential);
+                    }
+                } catch (final ResolverException e) {
+                    log.warn("Unable to derive a client_secret based credential", e);
+                }
+                return credentials;
+            }            
+            
+        } else {
+            log.debug("Criteria did not contain a ClientInformationCriterion could not perform resolution");
+        }
+        return Collections.emptySet();
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolver.java
new file mode 100644
index 0000000..0f129f9
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed 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.oidc.security.credential.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.security.credential.ClientSecretCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * A {@link JOSEObjectCredentialResolver} that resolves client secret from of a ClientInformation. If the information
+ * contains client secret, it's converted into a {@link ClientSecretCredential}. Further filtering of credentials is
+ * provided by the {@link AbstractCriteriaFilteringCredentialResolver} parent class.
+ */
+public class ClientInformationClientSecretCredentialResolver extends AbstractClientInformationCredentialResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ClientInformationClientSecretCredentialResolver.class);
+        
+    /** {@inheritDoc} */
+    @Override
+    protected Iterable<Credential> resolveFromSource(@Nonnull final CriteriaSet criteriaSet) throws ResolverException {
+        return resolveSecretCredentials(criteriaSet);
+    }
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
index 538b70c..f2d03ab 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
@@ -33,14 +33,10 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
-import net.shibboleth.oidc.security.credential.ClientSecretCredential;
-import net.shibboleth.oidc.security.credential.DefaultClientSecretCredential;
 import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
 import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.Positive;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.component.InitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.CriteriaSet;
@@ -55,15 +51,11 @@ import net.shibboleth.shared.resolver.ResolverException;
  * <p>Note, only RSA or EC keys are resolved as these are the only key types which should be exposed in 
  * public JWKS documents.</p>
  */
-public class ClientInformationCredentialResolver extends BasicJOSEObjectCredentialResolver 
-        implements InitializableComponent {
+public class ClientInformationCredentialResolver extends AbstractClientInformationCredentialResolver {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ClientInformationCredentialResolver.class);
     
-    /** Initialization flag. */
-    private boolean isInitialized;
-    
     /** The cache for remote JWK key sets. */
     @Nonnull private final RemoteJwkSetCache remoteJwkSetCache;
     
@@ -95,18 +87,6 @@ public class ClientInformationCredentialResolver extends BasicJOSEObjectCredenti
         keyFetchInterval = interval;
     }
     
-    /** {@inheritDoc} */
-    @Override
-    public boolean isInitialized() {
-        return isInitialized;
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    public void initialize() throws ComponentInitializationException {
-        isInitialized = true;
-    }
-    
     /** {@inheritDoc} */
     @Override
     protected Iterable<Credential> resolveFromSource(@Nonnull final CriteriaSet criteriaSet) throws ResolverException {
@@ -140,19 +120,7 @@ public class ClientInformationCredentialResolver extends BasicJOSEObjectCredenti
         final LinkedHashSet<Credential> credentials = new LinkedHashSet<>(1);
         final OIDCClientMetadata metadata = information.getOIDCMetadata();
 
-        if (information.getSecret() != null) {
-            try {
-                final ClientSecretCredential secretCred = 
-                        new DefaultClientSecretCredential(information.getSecret().getValue());
-                final Credential derivedCredential = deriveClientSecretCredential(secretCred, criteriaSet);
-                if (derivedCredential != null) {
-                    credentials.add(derivedCredential);
-                }
-            } catch (final ResolverException e) {
-                log.warn("Unable to derive a client_secret based credential", e);
-            }            
-        }            
-
+        credentials.addAll(resolveSecretCredentials(criteriaSet));
         final JWKSet keySet;
 
         if (metadata.getJWKSetURI() != null) {
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolverTest.java
new file mode 100644
index 0000000..79af86b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolverTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed 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.oidc.security.credential.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.mockito.Mockito;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.util.FileCopyUtils;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
+import net.shibboleth.oidc.security.jose.criterion.ProviderMetadataCriterion;
+import net.shibboleth.shared.component.InitializableComponent;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Unit tests for {@link ClientInformationClientSecretCredentialResolver}.
+ */
+public class ClientInformationClientSecretCredentialResolverTest {
+    
+    private static final ClassPathResource CLIENT_INFORMATION_REMOTE_KEYS = 
+            new ClassPathResource("/metadata/test-resolver-client-information-remote-jwks.json");
+
+    private static final ClassPathResource CLIENT_INFORMATION_SECRET = 
+            new ClassPathResource("/metadata/test-resolver-client-information-secret.json");
+
+    private ClientInformationClientSecretCredentialResolver resolver =
+            new ClientInformationClientSecretCredentialResolver();
+
+    private CriteriaSet criteria;
+    
+    @BeforeMethod
+    protected CriteriaSet buildInitialCriteriaSet() throws Exception {
+        criteria = new CriteriaSet();
+        
+        criteria.add(new ClientInformationCriterion(
+                OIDCClientInformation.parse(JSONObjectUtils.parse(readJsonFromFile(CLIENT_INFORMATION_REMOTE_KEYS)))));
+        return criteria;
+    }
+        
+    @Test
+    public void testFail_IncorrectCriteria() throws Exception {
+        ((InitializableComponent) resolver).initialize();
+        // Needs ProviderMetadataCriterion or ClientInformationCriterion
+        criteria = new CriteriaSet();
+        criteria.add(new ProviderMetadataCriterion(Mockito.mock(OIDCProviderMetadata.class)));
+        final Iterable<Credential> creds = resolver.resolve(criteria);
+        
+        assertNotNull(creds);
+        final List<Credential> credsList = new ArrayList<>();
+        creds.forEach(credsList::add);        
+        // No keys
+        assertEquals(credsList.size(), 0);
+    }
+
+    @Test
+    public void testSuccess_Secret() throws Exception {
+        ((InitializableComponent) resolver).initialize();
+        criteria = new CriteriaSet();
+        
+        criteria.add(new ClientInformationCriterion(
+                OIDCClientInformation.parse(JSONObjectUtils.parse(readJsonFromFile(CLIENT_INFORMATION_SECRET)))));
+        
+        criteria.add(new UsageCriterion(UsageType.SIGNING));
+
+        final Iterable<Credential> creds = resolver.resolve(criteria);
+        
+        assertNotNull(creds);
+        final List<Credential> credsList = new ArrayList<>();
+        creds.forEach(credsList::add);        
+        // No keys
+        assertEquals(credsList.size(), 1);
+
+    }
+
+    @Test
+    public void testSuccess_Secret_noSecretCredentialWhenAsymmetricEncryption() throws Exception {
+        ((InitializableComponent) resolver).initialize();
+        criteria = new CriteriaSet();
+
+        criteria.add(new ClientInformationCriterion(
+                OIDCClientInformation.parse(JSONObjectUtils.parse(readJsonFromFile(CLIENT_INFORMATION_SECRET)))));
+
+        criteria.add(new UsageCriterion(UsageType.ENCRYPTION));
+        criteria.add(new KeyManagmentAlgorithmCriterion("RSA1_5"));
+        criteria.add(new DataEncryptionAlgorithmCriterion("A128GCW"));
+
+        final Iterable<Credential> creds = resolver.resolve(criteria);
+
+        assertNotNull(creds);
+        final List<Credential> credsList = new ArrayList<>();
+        creds.forEach(credsList::add);
+        // No keys nor secret
+        assertEquals(credsList.size(), 0);
+
+    }
+
+    /**
+     * 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) {
+            fail();
+            return null;
+        }
+    }
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OidcEventIds.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OidcEventIds.java
index 6b8821d..d5ddba8 100644
--- a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OidcEventIds.java
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OidcEventIds.java
@@ -98,7 +98,12 @@ public final class OidcEventIds {
      * The id_token is invalid.
      */
     @Nonnull @NotEmpty public static final String INVALID_ID_TOKEN = "InvalidIdToken";
-    
+
+    /**
+     * The id_token_hint is invalid.
+     */
+    @Nonnull @NotEmpty public static final String INVALID_ID_TOKEN_HINT = "InvalidIdTokenHint";
+
     /**
      * The token is invalid.
      */

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


More information about the commits mailing list