[java-idp-plugin-oidc-op-oidfed] branch dev/CACHE-REFACTOR updated: Initial implementation for the signed keyset flow (to be used as OP's signed_jwks_uri)
Codeberg
noreply at shibboleth.net
Thu Mar 19 16:51:50 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/293855a3ce05a9a63fbe7d2666e08c4be4a7a629
The following commit(s) were added to refs/heads/dev/CACHE-REFACTOR by this push:
new 293855a Initial implementation for the signed keyset flow (to be used as OP's signed_jwks_uri)
293855a is described below
commit 293855a3ce05a9a63fbe7d2666e08c4be4a7a629
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Mar 19 18:49:54 2026 +0200
Initial implementation for the signed keyset flow (to be used as OP's signed_jwks_uri)
- OIDFED.Keyset profile needs to be enabled for shibboleth.UnverifiedRelyingParty
- Response (a signed JWT) is cached in the same way as entity configuration
---
.../oidc/op/oidfed/metadata/SignedKeyset.java | 25 +++
.../metadata/payload/SignedKeysetPayload.java | 31 +++
...tityStatementProducingProfileConfiguration.java | 30 +++
...FederationSignedKeysetProfileConfiguration.java | 32 +++
.../EntityStatementLifetimeLookupFunction.java | 7 +-
...tionalClaimsLookupStrategiesLookupFunction.java | 6 +-
idp-oidfed-op-impl/pom.xml | 5 +
.../messaging/impl/SignedKeysetResponse.java | 55 ++++++
.../op/oidfed/metadata/impl/SignedKeysetImpl.java | 63 ++++++
.../payload/impl/SignedKeysetPayloadImpl.java | 80 ++++++++
...onExplicitRegistrationProfileConfiguration.java | 78 ++++++++
...FederationSignedKeysetProfileConfiguration.java | 199 +++++++++++++++++++
.../op/oidfed/profile/impl/BuildSignedKeyset.java | 140 ++++++++++++++
.../profile/impl/EntityStatementContext.java | 25 +++
.../impl/FormOutboundSignedKeysetResponse.java | 214 +++++++++++++++++++++
.../InitializeEntityStatementContextForKeyset.java | 189 ++++++++++++++++++
...sponse.java => LookupCachedNimbusResponse.java} | 14 +-
.../impl/LookupCachedResolveEntityResponse.java | 6 +-
.../oidfed/profile/impl/OidFederationEventIds.java | 10 +-
.../entity-configuration-beans.xml | 4 +-
.../entity-configuration-flow.xml | 4 +-
.../oidfed/resolve-entity/resolve-entity-flow.xml | 2 +-
.../signed-keyset-beans.xml} | 47 ++---
.../signed-keyset-flow.xml} | 12 +-
.../idp/service/relying-party/postconfig.xml | 8 +
.../profile/flow/oidfed/SignedKeysetFlowTest.java | 146 ++++++++++++++
.../shibboleth/idp/module/conf/relying-party.xml | 1 +
pom.xml | 7 +
28 files changed, 1383 insertions(+), 57 deletions(-)
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SignedKeyset.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SignedKeyset.java
new file mode 100644
index 0000000..926d381
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/SignedKeyset.java
@@ -0,0 +1,25 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.metadata;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.SignedKeysetPayload;
+
+/**
+ * A wrapper inteface containing the {@link SignedJWT} and parsed claims related to signed keyset.
+ */
+public interface SignedKeyset extends BaseJWTWrapper<SignedKeysetPayload> {
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/SignedKeysetPayload.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/SignedKeysetPayload.java
new file mode 100644
index 0000000..e158d63
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/SignedKeysetPayload.java
@@ -0,0 +1,31 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.metadata.payload;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+/**
+ * Signed keyset payload claims as defined by the OpenID Federation 1.0 Section 5.2.1.
+ */
+public interface SignedKeysetPayload extends BaseExpirableSubjectPayload {
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public JWKSet getKeys();
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java
index 986291a..30b379a 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationEntityStatementProducingProfileConfiguration.java
@@ -14,14 +14,20 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config;
+import java.time.Duration;
import java.util.Map;
import java.util.function.BiFunction;
+import java.util.function.Function;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
/**
* Configuration common to OpenID Federation Entity Statement producing profiles.
@@ -40,4 +46,28 @@ public interface OIDFederationEntityStatementProducingProfileConfiguration exten
getEntityStatementClaimsSetManipulationStrategy(
@Nullable final ProfileRequestContext profileRequestContext);
+ /**
+ * Get entity statement lifetime.
+ *
+ * <p>Defaults to 24 hours.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return entity statement lifetime
+ */
+ @ConfigurationSetting(name="entityStatementLifetime")
+ @Nonnull Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return optional claims to be included to the entity statement
+ */
+ @ConfigurationSetting(name="optionalClaimsLookupStrategies")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable
+ Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java
new file mode 100644
index 0000000..eb93b46
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationSignedKeysetProfileConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.profile.config;
+
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+
+/**
+ * Profile configuration for an OpenID Federation Entity Configuration.
+ */
+public interface OIDFederationSignedKeysetProfileConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDFederationProfileConfiguration, OIDFederationEntityStatementProducingProfileConfiguration,
+ OIDFederationResponseCachingProfileConfiguration{
+
+ /** OIDC base protocol URI. Section 4 is relevant. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/keyset";
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java
index 8e36ff2..c2504ab 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/EntityStatementLifetimeLookupFunction.java
@@ -20,13 +20,14 @@ import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationEntityStatementProducingProfileConfiguration;
import net.shibboleth.profile.config.ProfileConfiguration;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
/**
- * A function that returns {@link OIDFederationEntityConfigurationProfileConfiguration#getEntityStatementLifetime(ProfileRequestContext)}
+ * A function that returns
+ * {@link OIDFederationEntityStatementProducingProfileConfiguration#getEntityStatementLifetime(ProfileRequestContext)}
* if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child
* of the {@link ProfileRequestContext}.
*
@@ -40,7 +41,7 @@ public class EntityStatementLifetimeLookupFunction extends AbstractRelyingPartyL
final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
if (rpc != null) {
final ProfileConfiguration pc = rpc.getProfileConfig();
- if (pc instanceof OIDFederationEntityConfigurationProfileConfiguration ofec) {
+ if (pc instanceof OIDFederationEntityStatementProducingProfileConfiguration ofec) {
return ofec.getEntityStatementLifetime(input);
}
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java
index a973cd0..f28141c 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/OptionalClaimsLookupStrategiesLookupFunction.java
@@ -21,14 +21,14 @@ import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationEntityStatementProducingProfileConfiguration;
import net.shibboleth.profile.config.ProfileConfiguration;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
/**
* A function that returns optional entity configuration claims lookup strategies from
- * {@link OIDFederationEntityConfigurationProfileConfiguration} if such a profile is available from a
+ * {@link OIDFederationEntityStatementProducingProfileConfiguration} if such a profile is available from a
* {@link RelyingPartyContext} obtained via a lookup function, by default a child of the {@link ProfileRequestContext}.
*
* <p>If a specific setting is unavailable, a null value is returned.</p>
@@ -43,7 +43,7 @@ public class OptionalClaimsLookupStrategiesLookupFunction extends
final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
if (rpc != null) {
final ProfileConfiguration pc = rpc.getProfileConfig();
- if (pc instanceof OIDFederationEntityConfigurationProfileConfiguration ofecpc) {
+ if (pc instanceof OIDFederationEntityStatementProducingProfileConfiguration ofecpc) {
return ofecpc.getOptionalClaimsLookupStrategies(input);
}
}
diff --git a/idp-oidfed-op-impl/pom.xml b/idp-oidfed-op-impl/pom.xml
index 0e8b2ed..a9d1a94 100644
--- a/idp-oidfed-op-impl/pom.xml
+++ b/idp-oidfed-op-impl/pom.xml
@@ -486,6 +486,11 @@
<artifactId>nashorn-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/SignedKeysetResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/SignedKeysetResponse.java
new file mode 100644
index 0000000..8fb4788
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/SignedKeysetResponse.java
@@ -0,0 +1,55 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.messaging.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+
+/**
+ * Response message to the OpenID federation signed keyset endpoint.
+ */
+public class SignedKeysetResponse extends AbstractSignedJWTResponse {
+
+ /** The JWT type header. */
+ @Nonnull
+ public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("jwk-set+jwt");
+
+ /** The content type. */
+ @Nonnull public static final ContentType HTTP_RESPONSE_CONTENT_TYPE =
+ new ContentType("application", JWT_TYPE_HEADER.toString());
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param statement entity statement
+ */
+ public SignedKeysetResponse(@Nonnull final SignedJWT statement) {
+ super(statement);
+ }
+
+ /** {@inheritDoc} */
+ protected ContentType getHttpResponseContentType() {
+ return HTTP_RESPONSE_CONTENT_TYPE;
+ }
+
+ /** {@inheritDoc} */
+ protected JOSEObjectType getJWTTypeHeader() {
+ return JWT_TYPE_HEADER;
+ }
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/impl/SignedKeysetImpl.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/impl/SignedKeysetImpl.java
new file mode 100644
index 0000000..6113649
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/impl/SignedKeysetImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.metadata.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SignedKeyset;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.SignedKeysetPayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.impl.SignedKeysetPayloadImpl;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+
+/**
+ * A wrapper class extending {@link EntityStatementImpl} with parsed claims related to signed keyset.
+ */
+public class SignedKeysetImpl extends AbstractJWTWrapperImpl<SignedKeysetPayload> implements SignedKeyset {
+
+ /**
+ * Constructor.
+ *
+ * @param signedJwt entity configuration
+ * @param payload entity configuration payload
+ * @throws ConstraintViolationException if the payload syntax/content is not expected
+ */
+ public SignedKeysetImpl(@Nonnull final SignedJWT signedJwt,
+ @Nonnull final SignedKeysetPayload payload)
+ throws ConstraintViolationException {
+ super(signedJwt, payload);
+ }
+
+ /**
+ * Parse an {@link SignedKeysetImpl} from the given JWT by using the given object mapper.
+ *
+ * @param jwt entity statement JWT
+ * @param objectMapper object mapper capable of parsing JWT payload
+ * @return entity configuration
+ * @throws JsonProcessingException if the payload could not be parsed
+ */
+ @Nonnull public static SignedKeysetImpl parse(@Nonnull final SignedJWT jwt,
+ @Nonnull final ObjectMapper objectMapper) throws JsonProcessingException {
+ final JavaType objectType = objectMapper.constructType(SignedKeysetPayloadImpl.class);
+ final SignedKeysetPayloadImpl result = objectMapper.readValue(jwt.getPayload().toString(), objectType);
+ assert result != null;
+ return new SignedKeysetImpl(jwt, result);
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java
new file mode 100644
index 0000000..c5d8c4c
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/payload/impl/SignedKeysetPayloadImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.metadata.payload.impl;
+
+import javax.annotation.Nonnull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.SignedKeysetPayload;
+
+/**
+ * Signed keyset payload claims as defined by the OpenID Federation 1.0 Section 5.2.1.
+ */
+public class SignedKeysetPayloadImpl extends BaseExpirableSubjectPayloadImpl implements SignedKeysetPayload {
+
+ /** A JWK set. */
+ @JsonProperty("keys") private JWKSet keys;
+
+ /**
+ * Constructor.
+ */
+ public SignedKeysetPayloadImpl() {
+ // no op
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param payload content
+ */
+ public SignedKeysetPayloadImpl(@Nonnull final SignedKeysetPayload payload) {
+ super(payload);
+ setKeys(payload.getKeys());
+ }
+
+ /**
+ * Get the JWK set.
+ *
+ * @return JWK set
+ */
+ public JWKSet getKeys() {
+ return keys;
+ }
+
+ /**
+ * Set the JWK set.
+ *
+ * @param jwks JWK set
+ */
+ public void setKeys(final JWKSet jwks) {
+ keys = jwks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("iss", getIssuer())
+ .add("sub", getSubject())
+ .add("iat", getIssuedAt())
+ .add("exp", getExpiration())
+ .add("keys", keys)
+ .add("customClaims", getCustomClaims()).toString();
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java
index 5679621..33f6b6b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationExplicitRegistrationProfileConfiguration.java
@@ -14,6 +14,7 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl;
+import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
@@ -32,6 +33,7 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationExpl
import net.shibboleth.shared.annotation.constraint.NonnullElements;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
import net.shibboleth.shared.annotation.constraint.Unmodifiable;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
@@ -65,11 +67,19 @@ public class DefaultOIDFederationExplicitRegistrationProfileConfiguration
/** Enabled token endpoint authentication methods. */
@Nonnull private Function<ProfileRequestContext,Set<String>> tokenEndpointAuthMethodsLookupStrategy;
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
/** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
@Nonnull
private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
entityStatementClaimsSetManipulationStrategyLookupStrategy;
+ /** Lookup function to supply map of strategies for optional claims to be included in the entity statement. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
/**
* Constructor.
*/
@@ -95,6 +105,8 @@ public class DefaultOIDFederationExplicitRegistrationProfileConfiguration
ClientAuthenticationMethod.CLIENT_SECRET_JWT.toString(),
ClientAuthenticationMethod.PRIVATE_KEY_JWT.toString()));
entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(null);
}
/** {@inheritDoc} */
@@ -260,4 +272,70 @@ public class DefaultOIDFederationExplicitRegistrationProfileConfiguration
entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
"Lookup strategy cannot be null");
}
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String,Function<ProfileRequestContext,Object>> strategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ return strategies != null ? strategies : CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategies lookup strategies for optional claims to be included to the entity statement
+ */
+ public void setOptionalClaimsLookupStrategies(
+ @Nullable final Map<String,Function<ProfileRequestContext,Object>> strategies) {
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(strategies);
+ }
+
+ /**
+ * Set a lookup strategy for the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ optionalClaimsLookupStrategiesLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java
new file mode 100644
index 0000000..3df320c
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationSignedKeysetProfileConfiguration.java
@@ -0,0 +1,199 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.profile.config.impl;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationSignedKeysetProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Signed keyset.
+ */
+public class DefaultOIDFederationSignedKeysetProfileConfiguration
+ extends AbstractOIDFederationResponseCachingProfileConfiguration
+ implements OIDFederationSignedKeysetProfileConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.keyset";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /** Lookup function to supply map of strategies for optional claims to be included in the entity statement. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationSignedKeysetProfileConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationSignedKeysetProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isNegative(),
+ "Entity statement lifetime must be equal to or greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isNegative(),
+ "Entity statement lifetime must be equal or greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy bi-function for manipulating entity statement claims set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+ }
+
+ /**
+ * Set a lookup strategy for the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Map<String,Function<ProfileRequestContext,Object>> getOptionalClaimsLookupStrategies(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final Map<String,Function<ProfileRequestContext,Object>> strategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ return strategies != null ? strategies : CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategies lookup strategies for optional claims to be included to the entity statement
+ */
+ public void setOptionalClaimsLookupStrategies(
+ @Nullable final Map<String,Function<ProfileRequestContext,Object>> strategies) {
+ optionalClaimsLookupStrategiesLookupStrategy = FunctionSupport.constant(strategies);
+ }
+
+ /**
+ * Set a lookup strategy for the lookup strategies for optional claims to be included to the entity statement.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ optionalClaimsLookupStrategiesLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildSignedKeyset.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildSignedKeyset.java
new file mode 100644
index 0000000..2d08aa2
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildSignedKeyset.java
@@ -0,0 +1,140 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.EntityStatementLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.OptionalClaimsLookupStrategiesLookupFunction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Action that creates a keyset JWT, and stores it to an {@link EntityStatementContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#IO_ERROR}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ */
+public class BuildSignedKeyset extends AbstractBuildEntityStatementAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(BuildSignedKeyset.class);
+
+ /** Strategy used to obtain the entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Strategy used to locate strategies for optional claims. */
+ @Nonnull private Function<ProfileRequestContext,Map<String, Function<ProfileRequestContext,Object>>>
+ optionalClaimsLookupStrategiesLookupStrategy;
+
+ /** Constructor. */
+ public BuildSignedKeyset() {
+ entityStatementLifetimeLookupStrategy = new EntityStatementLifetimeLookupFunction();
+ optionalClaimsLookupStrategiesLookupStrategy = new OptionalClaimsLookupStrategiesLookupFunction();
+ }
+
+ /**
+ * Set the strategy used to obtain the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,Duration> strategy) {
+ checkSetterPreconditions();
+
+ entityStatementLifetimeLookupStrategy =
+ Constraint.isNotNull(strategy, "Entity statement lifetime lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate strategies for optional claims.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOptionalClaimsLookupStrategiesLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext, Map<String,Function<ProfileRequestContext,Object>>> strategy) {
+ checkSetterPreconditions();
+
+ optionalClaimsLookupStrategiesLookupStrategy =
+ Constraint.isNotNull(strategy, "Optional claims lookup strategies lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean populateClaimsSetBuilder(@Nonnull final JWTClaimsSet.Builder builder,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+ if (lifetime == null || Duration.ZERO.equals(lifetime)) {
+ log.debug("{} No lifetime supplied for entity statement", getLogPrefix());
+ } else {
+ final Instant now = Instant.now();
+ final Instant dateExp = now.plus(lifetime);
+ assert dateExp != null;
+
+ log.debug("{} Set expiration time of entity statement into {}", getLogPrefix(), dateExp);
+ builder.expirationTime(Date.from(dateExp));
+ }
+
+ final JWKSet jwks = entityStatementCtx.getKeys();
+ if (jwks == null || jwks.isEmpty()) {
+ log.error("{} No credentials to publish resolved for signed keyset entity statement", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return false;
+
+ }
+
+ log.trace("{} Resolved jwks to publish: {}", getLogPrefix(), jwks);
+
+ builder.claim("keys", jwks.toJSONObject(true));
+
+ final Map<String, Function<ProfileRequestContext, Object>> optionalClaimsLookupStrategies =
+ optionalClaimsLookupStrategiesLookupStrategy.apply(profileRequestContext);
+ if (optionalClaimsLookupStrategies != null) {
+ for (final String claim : optionalClaimsLookupStrategies.keySet()) {
+ log.trace("{} Looking up the value for clain {}", getLogPrefix(), claim);
+ final Function<ProfileRequestContext,Object> lookup = optionalClaimsLookupStrategies.get(claim);
+ final Object value = lookup.apply(profileRequestContext);
+ if (value != null) {
+ log.debug("{} Resolved value {} for clain {}", getLogPrefix(), value, claim);
+ builder.claim(claim, value);
+ } else {
+ log.debug("{} No value resolved for clain {}", getLogPrefix(), claim);
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java
index 2b155bf..7a1386f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java
@@ -21,6 +21,7 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
+import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.Metadata;
@@ -39,6 +40,9 @@ public final class EntityStatementContext extends BaseContext {
/** The entity statement. */
@Nullable private JWT jwt;
+ /** The keys claim for the entity statement. */
+ @Nullable private JWKSet keys;
+
/**
* Get the metadata.
*
@@ -105,4 +109,25 @@ public final class EntityStatementContext extends BaseContext {
lifetime = lt;
return this;
}
+
+ /**
+ * Get the keys claim for the entity statement.
+ *
+ * @return keys
+ */
+ @Nullable public JWKSet getKeys() {
+ return keys;
+ }
+
+ /**
+ * Set the keys claim for the entity statement.
+ *
+ * @param jwks keys
+ *
+ * @return this context
+ */
+ @Nonnull public EntityStatementContext setKeys(@Nullable final JWKSet jwks) {
+ keys = jwks;
+ return this;
+ }
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundSignedKeysetResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundSignedKeysetResponse.java
new file mode 100644
index 0000000..48b3ae9
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundSignedKeysetResponse.java
@@ -0,0 +1,214 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.Response;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.SignedKeysetResponse;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.ResponseContainerExpirationCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.NimbusResponseContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.NimbusResponseCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.CachedSuccessResponseLifetimeLookupFunction;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * This action builds a response for the OpenID federation configuration request. The response contains an
+ * {@link SignedJWT} obtained from {@link EntityStatementContext#getJWT()}.
+ */
+public class FormOutboundSignedKeysetResponse extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(FormOutboundSignedKeysetResponse.class);
+
+ /** Metadata cache for cached response containers. */
+ @NonnullAfterInit private MetadataCache<NimbusResponseContainer> responseCache;
+
+ /** Strategy used to locate the cached message context. */
+ @Nonnull
+ private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextLookupStrategy;
+
+ /** Strategy used to locate the subcontext to hold the statement. */
+ @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextLookupStrategy;
+
+ /** Strategy used to locate the lifetime for the cached response record. */
+ @Nonnull private Function<ProfileRequestContext,Duration> cachedResponseLifetimeLookupStrategy;
+
+ /** JWT used to build entity statement. */
+ @Nullable private SignedJWT jwt;
+
+ /** The resolve entity context to operate on. */
+ @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
+
+ /**
+ * Constructor.
+ */
+ public FormOutboundSignedKeysetResponse() {
+ final Function<ProfileRequestContext,EntityStatementContext> escls =
+ new ChildContextLookup<>(EntityStatementContext.class).compose(
+ new OutboundMessageContextLookup());
+ assert escls != null;
+ entityStatementContextLookupStrategy = escls;
+ cachedMessageContextLookupStrategy = new ChildContextLookup<>(RelyingPartyCachedMessageContext.class);
+ cachedResponseLifetimeLookupStrategy = new CachedSuccessResponseLifetimeLookupFunction();
+ }
+
+ /**
+ * Set the strategy used to locate the subcontext to hold the statement
+ *
+ * @param strategy What to set.
+ */
+ public void setEntityStatementContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+ checkSetterPreconditions();
+ entityStatementContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+ }
+
+ /**
+ * Set the strategy used to locate the cached message context
+ *
+ * @param strategy What to set.
+ */
+ public void setCachedMessageContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
+ checkSetterPreconditions();
+ cachedMessageContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+ }
+
+ /**
+ * Set the metadata cache for cached response containers.
+ *
+ * @param cache What to set.
+ */
+ public void setResponseCache(@Nonnull final MetadataCache<NimbusResponseContainer> cache) {
+ checkSetterPreconditions();
+ responseCache = Constraint.isNotNull(cache, "Response metadata cache cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the lifetime for the cached response record.
+ *
+ * @param strategy What to set.
+ */
+ public void setCachedResponseLifetimeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, Duration> strategy) {
+ checkSetterPreconditions();
+ cachedResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (responseCache == null) {
+ throw new ComponentInitializationException("Response metadata cache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+ cachedMessageContext = cachedMessageContextLookupStrategy.apply(profileRequestContext);
+ if (cachedMessageContext == null) {
+ log.error("{} Could not resolve cached message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final Response cachedResponse = cachedMessageContext.getCachedResponse();
+ if (cachedResponse != null) {
+ log.debug("{} Cached response found, storing in to the outbound message context", getLogPrefix());
+ profileRequestContext.ensureOutboundMessageContext().setMessage(cachedResponse);
+ return;
+ }
+ log.debug("{} No cached response found, resolving the response JWT from the context", getLogPrefix());
+ final EntityStatementContext entityStatementContext =
+ entityStatementContextLookupStrategy.apply(profileRequestContext);
+ if (entityStatementContext == null) {
+ log.error("{} Could not resolve entity statement context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ if (entityStatementContext.getJWT() instanceof SignedJWT signedJwt) {
+ jwt = signedJwt;
+ } else {
+ log.error("{} No signed JWT found from the entity statement context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ assert jwt != null;
+ final SignedKeysetResponse response = new SignedKeysetResponse(jwt);
+ final NimbusResponseCriterion responseCriterion = new NimbusResponseCriterion(response);
+ final Duration lifetime = cachedResponseLifetimeLookupStrategy.apply(profileRequestContext);
+ if (lifetime == null) {
+ log.error("{} Could not resolve lifetime for the cached response record", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ final Instant expiration = Instant.now().plus(lifetime);
+ assert expiration != null;
+ final ResponseContainerExpirationCriterion expirationCriterion =
+ new ResponseContainerExpirationCriterion(expiration);
+ final CriteriaSet criteria = new CriteriaSet(responseCriterion, expirationCriterion);
+ try {
+ final List<NimbusResponseContainer> result = responseCache.get(criteria);
+ if (result.size() != 1) {
+ log.error("{} Unexpected result (size={}) when storing response record into the metadata cache",
+ getLogPrefix(), result.size());
+ } else {
+ log.debug("{} Response stored into the cache", getLogPrefix());
+ }
+ } catch (final MetadataCacheException e) {
+ log.error("{} Could not store the response record into tht metadata cache", getLogPrefix(), e);
+ }
+
+ profileRequestContext.ensureOutboundMessageContext().setMessage(response);
+ }
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/InitializeEntityStatementContextForKeyset.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/InitializeEntityStatementContextForKeyset.java
new file mode 100644
index 0000000..61010a7
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/InitializeEntityStatementContextForKeyset.java
@@ -0,0 +1,189 @@
+/*
+ * 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.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.opensaml.security.config.SecurityConfiguration;
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.config.JSONSecurityConfiguration;
+import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Action that creates and initializes the {@link EntityStatementContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#IO_ERROR}
+ */
+public class InitializeEntityStatementContextForKeyset extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(InitializeEntityStatementContextForKeyset.class);
+
+ /** Strategy used to create the subcontext to hold the statement. */
+ @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextCreationStrategy;
+
+ /**
+ * Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link ProfileRequestContext}.
+ */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /**
+ * Strategy used to locate the list of credentials to publish.
+ */
+ @Nonnull private Function<JSONSecurityConfiguration, List<Credential>> credentialsToPublishLookupStrategy;
+
+ /** Security configuration we look for keys to publish. */
+ @Nullable private JSONSecurityConfiguration secConfiguration;
+
+ /** Entity statement context. */
+ @NonnullBeforeExec private EntityStatementContext entityStatementCtx;
+
+ /** Constructor. */
+ public InitializeEntityStatementContextForKeyset() {
+ final Function<ProfileRequestContext,EntityStatementContext> esccs =
+ new ChildContextLookup<>(EntityStatementContext.class, true).compose(
+ new OutboundMessageContextLookup());
+ assert esccs != null;
+ entityStatementContextCreationStrategy = esccs;
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+ credentialsToPublishLookupStrategy = secConfig -> Collections.emptyList();
+ }
+
+ /**
+ * Set the strategy used to create the {@link EntityStatementContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setEntityStatementContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+ checkSetterPreconditions();
+
+ entityStatementContextCreationStrategy =
+ Constraint.isNotNull(strategy, "EntityStatementContext creation strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link RelyingPartyContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy strategy used to locate the {@link RelyingPartyContext} associated with a given
+ * {@link ProfileRequestContext}
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
+ checkSetterPreconditions();
+
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the credentials to publish at the KeySet endpoint.
+ *
+ * @param strategy the strategy.
+ */
+ public void setCredentialsToPublishLookupStrategy(
+ @Nonnull final Function<JSONSecurityConfiguration, List<Credential>> strategy) {
+ checkSetterPreconditions();
+
+ credentialsToPublishLookupStrategy = Constraint.isNotNull(strategy,
+ "credentialsToPublishLookupStrategy can not be null");
+ }
+
+ // Checkstyle: CyclomaticComplexity|MethodLength OFF
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ entityStatementCtx = entityStatementContextCreationStrategy.apply(profileRequestContext);
+ if (entityStatementCtx == null) {
+ log.error("{} Unable to create EntityStatementContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+ if (rpCtx == null) {
+ log.debug("{} No relying party context associated with this profile request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ return false;
+ }
+
+ final ProfileConfiguration profileConfig = rpCtx.getProfileConfig();
+ if (profileConfig == null) {
+ log.debug("{} No profile configuration associated with this profile request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ return false;
+ }
+
+ final SecurityConfiguration securityConfig =
+ profileConfig.getSecurityConfiguration(profileRequestContext);
+
+ if (!(securityConfig instanceof JSONSecurityConfiguration)) {
+ log.debug("{} No security configuration associated with the profile configuration of the profile request",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
+ return false;
+ }
+
+ secConfiguration = (JSONSecurityConfiguration) securityConfig;
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final List<Credential> credentialsToPublish = credentialsToPublishLookupStrategy.apply(secConfiguration);
+ if (credentialsToPublish == null || credentialsToPublish.isEmpty()) {
+ log.error("{} No credentials to publish resolved for signed keyset entity statement", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+
+ final JWKSet jwks = new JWKSet(credentialsToPublish.stream()
+ .map(credential -> CredentialConversionUtil.credentialToKey(credential))
+ .toList());
+ log.trace("{} Resolved jwks to set in the contet: {}", getLogPrefix(), jwks);
+ entityStatementCtx.setKeys(jwks);
+ }
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedNimbusResponse.java
similarity index 91%
rename from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java
rename to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedNimbusResponse.java
index 9b1de1d..fd36f36 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedNimbusResponse.java
@@ -37,17 +37,17 @@ import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
/**
- * Lookup if a cached response already exists for entity configuration. If yes, the response is
+ * Lookup if a cached response already exists for entity statement. If yes, the response is
* stored into {@link RelyingPartyCachedMessageContext} and a corresponding event ID is published.
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link OidFederationEventIds#CACHED_ENTITY_CONFIGURATION_RESPONSE}
+ * @event {@link OidFederationEventIds#CACHED_RESPONSE_FOUND}
*/
-public class LookupCachedEntityConfigurationResponse extends AbstractProfileAction {
+public class LookupCachedNimbusResponse extends AbstractProfileAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedEntityConfigurationResponse.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedNimbusResponse.class);
/** Strategy used to create the cached message context. */
@Nonnull
@@ -62,7 +62,7 @@ public class LookupCachedEntityConfigurationResponse extends AbstractProfileActi
/**
* Constructor.
*/
- public LookupCachedEntityConfigurationResponse() {
+ public LookupCachedNimbusResponse() {
final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
new ChildContextLookup<>(RelyingPartyCachedMessageContext.class, true);
assert recls != null;
@@ -128,9 +128,9 @@ public class LookupCachedEntityConfigurationResponse extends AbstractProfileActi
final NimbusResponseContainer cachedResponse = result.get(0);
cachedMessageContext.setCachedResponse(cachedResponse.getResponse());
log.debug("{} Response found from the cache, publishing event {}", getLogPrefix(),
- OidFederationEventIds.CACHED_ENTITY_CONFIGURATION_RESPONSE);
+ OidFederationEventIds.CACHED_RESPONSE_FOUND);
ActionSupport.buildEvent(profileRequestContext,
- OidFederationEventIds.CACHED_ENTITY_CONFIGURATION_RESPONSE);
+ OidFederationEventIds.CACHED_RESPONSE_FOUND);
return;
}
} catch (final MetadataCacheException e) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
index f4b8e01..76d1f8e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
@@ -45,7 +45,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link OidFederationEventIds#CACHED_RESOLVE_ENTITY_RESPONSE}
+ * @event {@link OidFederationEventIds#CACHED_RESPONSE_FOUND}
*/
public class LookupCachedResolveEntityResponse extends AbstractProfileAction {
@@ -145,8 +145,8 @@ public class LookupCachedResolveEntityResponse extends AbstractProfileAction {
final ResolveEntityResponseContainer cachedResponse = result.get(0);
cachedMessageContext.setCachedResponse(cachedResponse.getResponse());
log.debug("{} Response found from the cache, publishing event {}", getLogPrefix(),
- OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
- ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
+ OidFederationEventIds.CACHED_RESPONSE_FOUND);
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.CACHED_RESPONSE_FOUND);
return;
}
} catch (final MetadataCacheException e) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
index 82837a4..9c5752c 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
@@ -30,15 +30,9 @@ public class OidFederationEventIds {
@Nonnull @NotEmpty public static final String RESELECT_TRUST_CHAIN = "ReselectTrustChain";
/**
- * ID of event returned if cached resolve entity response was found and set to the context.
+ * ID of event returned if cached response was found and set to the context.
*/
- @Nonnull @NotEmpty public static final String CACHED_RESOLVE_ENTITY_RESPONSE = "CachedResolveEntityResponseFound";
-
- /**
- * ID of event returned if cached entity configuration response was found and set to the context.
- */
- @Nonnull @NotEmpty public static final String CACHED_ENTITY_CONFIGURATION_RESPONSE =
- "CachedEntityConfigurationResponseFound";
+ @Nonnull @NotEmpty public static final String CACHED_RESPONSE_FOUND = "CachedResponseFound";
/**
* ID of event returned if no trust chains were resolved for the client.
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
index 5b21a3e..5434b68 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
@@ -43,8 +43,8 @@
</property>
</bean>
- <bean id="LookupCachedEntityConfigurationResponse"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedEntityConfigurationResponse"
+ <bean id="LookupCachedResponse"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedNimbusResponse"
scope="prototype"
p:responseCache-ref="shibboleth.oidfed.EntityConfigurationResponseMetadataCache" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
index 670f03d..f937952 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
@@ -10,9 +10,9 @@
</action-state>
<action-state id="LookupCachedResponse">
- <evaluate expression="LookupCachedEntityConfigurationResponse" />
+ <evaluate expression="LookupCachedResponse" />
<evaluate expression="'proceed'" />
- <transition on="CachedEntityConfigurationResponseFound" to="BuildResponseMessage" />
+ <transition on="CachedResponseFound" to="BuildResponseMessage" />
<transition on="proceed" to="InitializeEntityStatementContext" />
</action-state>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
index 81639af..e1a17f0 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
@@ -43,7 +43,7 @@
<evaluate expression="ValidateRequest" />
<evaluate expression="LookupCachedResolveEntityResponse" />
<evaluate expression="'proceed'" />
- <transition on="CachedResolveEntityResponseFound" to="BuildResponseMessage" />
+ <transition on="CachedResponseFound" to="BuildResponseMessage" />
<transition on="proceed" to="ResolveTrustChains" />
</action-state>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-beans.xml
similarity index 83%
copy from idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
copy to idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-beans.xml
index 5b21a3e..65791cc 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-beans.xml
@@ -8,24 +8,24 @@
default-init-method="initialize" default-destroy-method="destroy">
<bean id="shibboleth.oidc.profileId" class="java.lang.String"
- c:_0="#{T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationEntityConfigurationProfileConfiguration).PROFILE_ID}" />
+ c:_0="#{T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationSignedKeysetProfileConfiguration).PROFILE_ID}" />
- <bean id="shibboleth.oidc.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.oidfedconfig:OIDFED.Configuration}" />
+ <bean id="shibboleth.oidc.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.oidfedconfig:OIDFED.SignedKeyset}" />
<util:constant id="shibboleth.metrics.ProfileCounter"
- static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl.DefaultOIDFederationEntityConfigurationProfileConfiguration.PROFILE_COUNTER" />
+ static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl.DefaultOIDFederationSignedKeysetProfileConfiguration.PROFILE_COUNTER" />
- <bean id="shibboleth.oidfed.EntityConfigurationResponseMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <bean id="shibboleth.oidfed.SignedKeysetResponseMetadataCache" parent="shibboleth.oidc.CacheBuilder">
<constructor-arg>
- <bean p:cacheId="DefaultEntityConfigurationResponseMetadataCache" parent="shibboleth.oidfed.EntityConfigurationResponseMetadataCacheBuilderSpec"
+ <bean p:cacheId="DefaultSignedKeysetResponseMetadataCache" parent="shibboleth.oidfed.SignedKeysetResponseMetadataCacheBuilderSpec"
p:cleanupTaskInterval="PT30S"/>
</constructor-arg>
</bean>
- <bean id="shibboleth.oidfed.EntityConfigurationResponseMetadataCacheBuilderSpec"
+ <bean id="shibboleth.oidfed.SignedKeysetResponseMetadataCacheBuilderSpec"
class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
- p:minCacheDuration="%{idp.oidfed.entity-configuration.maxRefreshDelay:PT1S}"
- p:maxCacheDuration="%{idp.oidfed.entity-configuration.maxRefreshDelay:PT30S}">
+ p:minCacheDuration="%{idp.oidfed.signed-keyset.maxRefreshDelay:PT1S}"
+ p:maxCacheDuration="%{idp.oidfed.signed-keyset.maxRefreshDelay:PT30S}">
<property name="criteriaToIdentifierStrategy">
<bean parent="shibboleth.Functions.Constant" c:target-ref="shibboleth.oidc.issuer" />
</property>
@@ -43,15 +43,15 @@
</property>
</bean>
- <bean id="LookupCachedEntityConfigurationResponse"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedEntityConfigurationResponse"
+ <bean id="LookupCachedResponse"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedNimbusResponse"
scope="prototype"
- p:responseCache-ref="shibboleth.oidfed.EntityConfigurationResponseMetadataCache" />
+ p:responseCache-ref="shibboleth.oidfed.SignedKeysetResponseMetadataCache" />
<bean id="InitializeEntityStatementContext"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.InitializeEntityStatementContext"
- p:metadataResolver-ref="#{'%{idp.oidfed.configuration.resolver:shibboleth.oidfed.DefaultOpenIdConfigurationResolver}'.trim()}"
- p:metadataSkeletonLookupStrategy-ref="#{'%{idp.oidfed.configuration.EntityConfigurationMetadataSkeletonLookupStrategy:DefaultEntityConfigurationMetadataSkeletonLookupStrategy}'.trim()}"/>
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.InitializeEntityStatementContextForKeyset"
+ p:credentialsToPublishLookupStrategy="#{getObject('shibboleth.oidc.keyset.KeySetCredentialsToPublishLookupStrategy') ?:
+ getObject('shibboleth.oidc.keyset.DefaultKeySetCredentialsToPublishLookupStrategy')}"/>
<bean id="DefaultEntityConfigurationMetadataSkeletonLookupStrategy"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityConfigurationMetadataSkeletonLookupStrategy"
@@ -86,26 +86,29 @@
</constructor-arg>
</bean>
- <bean id="SelectOidcConfigurationProfileConfiguration"
+ <bean id="SelectOidcKeysetProfileConfiguration"
class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
- p:profileId="#{T(net.shibboleth.oidc.profile.config.OIDCProviderInformationConfiguration).PROFILE_ID}" />
+ p:profileId="#{T(net.shibboleth.oidc.profile.config.OIDCPublishKeySetConfiguration).PROFILE_ID}" />
<bean id="ConfigurationRelyingPartyCreationStrategy" parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.ChildLookupOrCreate.RelyingPartyContext"
c:f-ref="shibboleth.MessageContextLookup.Outbound" />
<bean id="BuildEntityStatement"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.BuildEntityConfiguration" scope="prototype"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.BuildSignedKeyset" scope="prototype"
p:identifierGeneratorLookupStrategy-ref="shibboleth.oidc.DefaultIdentifierGenerationStrategy"
- p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"/>
+ p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
+
+ <bean id="shibboleth.oidc.keyset.DefaultKeySetCredentialsToPublishLookupStrategy"
+ class="net.shibboleth.oidc.profile.config.navigate.JWKCredentialsToPublishLookupStrategy"/>
<bean id="SignEntityStatement" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="OUTBOUND ">
<constructor-arg name="messageHandler">
<bean id="SignEntityStatementHandler"
- class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Entity Statement"
+ class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Signed Keyset"
p:securityParametersLookupStrategy-ref="EntityStatementSecurityParametersCreationViaMessageContextStrategy"
- p:typeHeader="entity-statement+jwt">
+ p:typeHeader="jwk-set+jwt">
<property name="claimsToSignLookupStrategy">
<bean
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.JWTClaimsSetFromEntityStatementLookupFunction" />
@@ -118,8 +121,8 @@
</constructor-arg>
</bean>
- <bean id="FormOutboundMessage" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.FormOutboundFederationConfigurationResponse"
- scope="prototype" p:responseCache-ref="shibboleth.oidfed.EntityConfigurationResponseMetadataCache">
+ <bean id="FormOutboundMessage" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.FormOutboundSignedKeysetResponse"
+ scope="prototype" p:responseCache-ref="shibboleth.oidfed.SignedKeysetResponseMetadataCache">
</bean>
<bean id="shibboleth.oidfed.EntityConfigurationMetadataSkeletonMetadataCache" parent="shibboleth.oidc.CacheBuilder">
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-flow.xml
similarity index 75%
copy from idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
copy to idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-flow.xml
index 670f03d..d1b9ea8 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/signed-keyset/signed-keyset-flow.xml
@@ -10,22 +10,22 @@
</action-state>
<action-state id="LookupCachedResponse">
- <evaluate expression="LookupCachedEntityConfigurationResponse" />
+ <evaluate expression="LookupCachedResponse" />
<evaluate expression="'proceed'" />
- <transition on="CachedEntityConfigurationResponseFound" to="BuildResponseMessage" />
+ <transition on="CachedResponseFound" to="BuildResponseMessage" />
<transition on="proceed" to="InitializeEntityStatementContext" />
</action-state>
<action-state id="InitializeEntityStatementContext">
- <evaluate expression="SelectOidcConfigurationProfileConfiguration" />
+ <evaluate expression="SelectOidcKeysetProfileConfiguration" />
<evaluate expression="InitializeEntityStatementContext"/>
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="BuildEntityConfiguration" />
+ <transition on="proceed" to="BuildNewResponse" />
</action-state>
- <action-state id="BuildEntityConfiguration">
+ <action-state id="BuildNewResponse">
<evaluate expression="PopulateEntityStatementSignatureSigningParameters" />
<evaluate expression="BuildEntityStatement" />
<evaluate expression="SignEntityStatement" />
@@ -34,6 +34,6 @@
<transition on="proceed" to="BuildResponseMessage"/>
</action-state>
- <bean-import resource="entity-configuration-beans.xml" />
+ <bean-import resource="signed-keyset-beans.xml" />
</flow>
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index abc34ff..2e2b53b 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -18,6 +18,14 @@
p:authorityHints="%{idp.oidfed.entity.authorityHints:https://example.org}"
p:optionalClaimsLookupStrategies-ref="shibboleth.oidfed.EntityConfigurationClaimsLookupStrategies" />
+ <bean id="OIDFED.Keyset" parent="AbstractOIDFederationProfile" lazy-init="true"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl.DefaultOIDFederationSignedKeysetProfileConfiguration"
+ p:issuer-ref="shibboleth.oidc.issuer">
+ <property name="entityStatementLifetime">
+ <bean class="java.time.Duration" factory-method="parse" c:_0="PT0S" />
+ </property>
+ </bean>
+
<bean id="OIDFED.AutomaticRegistration" parent="AbstractOIDFederationProfile" lazy-init="true"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl.DefaultOIDFederationAutomaticRegistrationProfileConfiguration"
p:mandatoryTrustMarks="%{idp.oidfed.automaticRegistration.mandatoryTrustMarks:}"
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/SignedKeysetFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/SignedKeysetFlowTest.java
new file mode 100644
index 0000000..d5d4468
--- /dev/null
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/SignedKeysetFlowTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.idp.plugin.oidc.op.profile.flow.oidfed;
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyType;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Response;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SignedKeyset;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.SignedKeysetImpl;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.SignedKeysetPayload;
+
+/**
+ * Unit test for the signed keyset flow.
+ */
+ at SuppressWarnings("null")
+public class SignedKeysetFlowTest extends AbstractFederationFlowTest {
+
+ public static final String FLOW_ID = "oidfed/signed-keyset";
+
+ Resource rsaSigKey = new ClassPathResource("/credentials/idp-signing-rs.jwk");
+ Resource rsaEncKey = new ClassPathResource("/credentials/idp-encryption-rsa.jwk");
+ Resource ecSigKey = new ClassPathResource("/credentials/idp-signing-es.jwk");
+ Resource ec384SigKey = new ClassPathResource("/credentials/idp-signing-es384.jwk");
+ Resource ec512SigKey = new ClassPathResource("/credentials/idp-signing-es521.jwk");
+ Resource ecEncKey = new ClassPathResource("/credentials/idp-encryption-ec.jwk");
+
+ @Autowired
+ @Qualifier("shibboleth.oidfed.JWTPayloadJSONObjectMapper")
+ ObjectMapper payloadObjectMapper;
+
+ protected SignedKeysetFlowTest() {
+ super(FLOW_ID);
+ }
+
+ @Test
+ public void testOutputAndCaching()
+ throws ParseException, IOException, InterruptedException, java.text.ParseException {
+ request.setRequestURI("/idp/profile/oidfed/signed-keyset");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response = parseResponse(result);
+ Assert.assertTrue(response.indicatesSuccess());
+ assertEntityStatement(response);
+
+ final FlowExecutionResult result2 = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response2 = parseResponse(result2);
+ Assert.assertEquals(response2.toHTTPResponse().getContent(), response.toHTTPResponse().getContent());
+
+ Thread.sleep(2100);
+ final FlowExecutionResult result3 = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response3 = parseResponse(result3);
+ assertEntityStatement(response3);
+ Assert.assertNotEquals(response3.toHTTPResponse().getContent(), response.toHTTPResponse().getContent());
+
+ }
+
+ protected void assertEntityStatement(final Response response)
+ throws ParseException, IOException, java.text.ParseException {
+ final SignedKeyset entityStatement;
+ try {
+ entityStatement = SignedKeysetImpl.parse(
+ SignedJWT.parse(response.toHTTPResponse().getContent()), payloadObjectMapper);
+ } catch (JsonProcessingException | java.text.ParseException e) {
+ Assert.fail();
+ return;
+ }
+ final SignedKeysetPayload payload = entityStatement.getParsedPayload();
+ Assert.assertEquals(payload.getSubject(), issuer);
+ Assert.assertEquals(payload.getIssuer(), issuer);
+ Assert.assertNull(payload.getExpiration());
+ final JWKSet keys = payload.getKeys();
+ Assert.assertNotNull(keys);
+ Assert.assertEquals(keys.size(), 6);
+ final JWK rsaSigJwk = JWK.parse(IOUtils.toString(rsaSigKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, rsaSigJwk));
+ final JWK rsaEncJwk = JWK.parse(IOUtils.toString(rsaEncKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, rsaEncJwk));
+ final JWK ecSigJwk = JWK.parse(IOUtils.toString(ecSigKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, ecSigJwk));
+ final JWK ec384SigJwk = JWK.parse(IOUtils.toString(ec384SigKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, ec384SigJwk));
+ final JWK ec512SigJwk = JWK.parse(IOUtils.toString(ec512SigKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, ec512SigJwk));
+ final JWK ecEncJwk = JWK.parse(IOUtils.toString(ecEncKey.getInputStream(), "UTF-8"));
+ Assert.assertTrue(listContainsPublicJwk(keys, ecEncJwk));
+ }
+
+ protected boolean listContainsPublicJwk(final JWKSet jwkSet, final JWK jwk) {
+ for (final JWK item : jwkSet.getKeys()) {
+ Assert.assertEquals(item.toJSONString(), item.toPublicJWK().toJSONString());
+ if (jwk.getKeyType().equals(item.getKeyType())
+ && jwk.getKeyUse().equals(item.getKeyUse())
+ && jwk.getKeyID().equals(item.getKeyID())) {
+ if (jwk.getKeyType().equals(KeyType.EC)) {
+ Assert.assertNull(item.toJSONObject().get("d"));
+ if (jsonValueEquals(jwk, item, "crv")
+ && jsonValueEquals(jwk, item, "x")
+ && jsonValueEquals(jwk, item, "y")) {
+ return true;
+ }
+ } else if (jwk.getKeyType().equals(KeyType.RSA)) {
+ Assert.assertNull(item.toJSONObject().get("d"));
+ if (jsonValueEquals(jwk, item, "e")
+ && jsonValueEquals(jwk, item, "n")) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean jsonValueEquals(final JWK first, final JWK another, final String key) {
+ return first.toJSONObject().get(key).equals((another.toJSONObject().get(key)));
+ }
+
+
+}
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index 8466c66..60ce13d 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -40,6 +40,7 @@
<ref bean="OIDC.Registration" />
<ref bean="OIDC.Configuration" />
<bean parent="OIDFED.Configuration" p:cachedSuccessResponseLifetime="PT2S" />
+ <bean parent="OIDFED.Keyset" p:cachedSuccessResponseLifetime="PT2S" />
<bean parent="OIDFED.ResolveEntity" />
</list>
</property>
diff --git a/pom.xml b/pom.xml
index b84cf69..366a7cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@
<shib-attribute.version>5.0.0</shib-attribute.version>
<shib-metadata.groupId>net.shibboleth</shib-metadata.groupId>
<shib-metadata.version>5.0.0</shib-metadata.version>
+ <commons.io.version>2.6</commons.io.version>
<checkstyle.configLocation>${project.basedir}/resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
</properties>
@@ -179,6 +180,12 @@
<version>${nashorn.jdk.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons.io.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list