[java-oidc-common] 04/04: JCOMOIDC-87 - Profile configuration for OIDC logout

Henri Mikkonen henri.mikkonen at iki.fi
Wed Jan 3 10:57:27 UTC 2024


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

hjmikkon pushed a commit to branch main
in repository java-oidc-common.

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

commit 9663dbeac0fef0f05b0c8d38477a0fcefd16c6e6
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 |  95 ++++++++++++++
 ...tInformationClientSecretCredentialResolver.java |  45 +++++++
 .../impl/ClientInformationCredentialResolver.java  |  38 +-----
 ...ormationClientSecretCredentialResolverTest.java | 145 +++++++++++++++++++++
 .../shibboleth/oidc/profile/core/OidcEventIds.java |   7 +-
 5 files changed, 295 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..2b484ff
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/AbstractClientInformationCredentialResolver.java
@@ -0,0 +1,95 @@
+/*
+ * 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 javax.annotation.Nullable;
+
+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.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(@Nullable final CriteriaSet criteriaSet) {
+        
+        if (criteriaSet != null) {
+            final ClientInformationCriterion clientCrit = criteriaSet.get(ClientInformationCriterion.class);
+            if (clientCrit != null) {
+                final OIDCClientInformation information = clientCrit.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..0b993b7
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationClientSecretCredentialResolver.java
@@ -0,0 +1,45 @@
+/*
+ * 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 javax.annotation.Nullable;
+
+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 @Nonnull
+    protected Iterable<Credential> resolveFromSource(@Nullable 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 c089dec..542e322 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
@@ -32,15 +32,11 @@ 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.collection.CollectionSupport;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.component.InitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
@@ -56,15 +52,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;
     
@@ -96,22 +88,12 @@ 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
     @Nonnull protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet)
             throws ResolverException {
+        
+        Constraint.isNotNull(criteriaSet, "CriteriaSet was null");
 
         if (criteriaSet != null) {
             final ClientInformationCriterion clientCrit = criteriaSet.get(ClientInformationCriterion.class);
@@ -140,19 +122,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