[java-oidc-common] 02/35: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
Phil Smart
philip.smart at jisc.ac.uk
Tue Sep 20 14:19:02 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch dev/JCOMOIDC-41
in repository java-oidc-common.
View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=35d959706f0c0f978fbb5ecaf8a1c3ef8ca94697
commit 35d959706f0c0f978fbb5ecaf8a1c3ef8ca94697
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri May 6 14:18:01 2022 +0100
JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter
classes to commons
See https://shibboleth.atlassian.net/browse/JCOMOIDC-41
---
.../security/SignatureValidationConfiguration.java | 41 +++
.../security/SignatureValidationParameters.java | 36 +++
.../SignatureValidationParametersResolver.java | 13 +
.../context/JWTSecurityParametersContext.java | 137 ++++++++++
...TSignatureValidationConfigurationCriterion.java | 109 ++++++++
.../oidc/security/criterion/KeyIdCriterion.java | 82 ++++++
.../criterion/ProviderMetadataCriterion.java | 69 +++++
oidc-common-crypto-impl/pom.xml | 6 +
.../impl/EvaluableKeyIDCredentialCriterion.java | 98 +++++++
.../impl/BaseJWTSignatureSecurityHandler.java | 118 ++++++++
.../security/impl/BaseSignedJWTTrustEngine.java | 14 +-
.../BasicSignatureValidationConfiguration.java | 40 +++
.../impl/ExplicitKeySignedJWTTrustEngine.java | 25 ++
.../impl/JWTMessageSignatureSecurityHandler.java | 114 ++++++++
...ationSignatureValidationParametersResolver.java | 119 ++++++++
.../PopulateJWTSignatureValidationParameters.java | 142 ++++++++++
...ateJWTSignatureValidationParametersHandler.java | 164 +++++++++++
.../impl/ProviderMetadataCredentialResolver.java | 180 ++++++++++++
.../impl/ExplicitKeySignedJWTTrustEngineTest.java | 301 +++++++++++++++++++++
.../config/OIDCAuthorizationConfiguration.java | 283 ++++++++++++++-----
.../profile/config/OIDCSecurityConfiguration.java | 24 ++
pom.xml | 2 +-
22 files changed, 2040 insertions(+), 77 deletions(-)
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationConfiguration.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationConfiguration.java
new file mode 100644
index 0000000..b593989
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.opensaml.xmlsec.AlgorithmPolicyConfiguration;
+
+/**
+ * The configuration information to use when validating a signature.
+ *
+ * @param <T> the token type to validate
+ */
+//TODO We can not use the existing SignatureValidationConfiguration
+//as it implements the signaturetrustengine which is only compatible with XML.
+public interface SignatureValidationConfiguration<T> extends AlgorithmPolicyConfiguration {
+
+ /**
+ * Get the signature trust engine to use.
+ *
+ * @return the signature trust engine
+ */
+ @Nullable public TrustEngine<T> getSignatureTrustEngine();
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParameters.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParameters.java
new file mode 100644
index 0000000..e242b5f
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParameters.java
@@ -0,0 +1,36 @@
+package net.shibboleth.oidc.security;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.opensaml.xmlsec.AlgorithmPolicyParameters;
+
+/**
+ * The effective parameters to use when validating a signature of the given type.
+ *
+ * @param <T> the token type this validation parameter
+ */
+public class SignatureValidationParameters<T> extends AlgorithmPolicyParameters {
+
+ /** The signature trust engine to use. */
+ @Nullable private TrustEngine<T> signatureTrustEngine;
+
+ /**
+ * Get the signature trust engine to use.
+ *
+ * @return the signature trust engine
+ */
+ @Nullable public TrustEngine<T> getSignatureTrustEngine() {
+ return signatureTrustEngine;
+ }
+
+ /**
+ * Set the signature trust engine to use.
+ *
+ * @param engine the signature trust engine
+ */
+ public void setSignatureTrustEngine(@Nullable final TrustEngine<T> engine) {
+ signatureTrustEngine = engine;
+ }
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParametersResolver.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParametersResolver.java
new file mode 100644
index 0000000..2eb87f4
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/SignatureValidationParametersResolver.java
@@ -0,0 +1,13 @@
+package net.shibboleth.oidc.security;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.Resolver;
+
+/**
+ * An interface for components which resolve {@link SignatureValidationParameters} based on a {@link CriteriaSet}.
+ *
+ * @param <T> the token type.
+ */
+public interface SignatureValidationParametersResolver<T> extends Resolver<SignatureValidationParameters<T>, CriteriaSet> {
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/context/JWTSecurityParametersContext.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/context/JWTSecurityParametersContext.java
new file mode 100644
index 0000000..3c8ebc9
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/context/JWTSecurityParametersContext.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.xmlsec.DecryptionParameters;
+import org.opensaml.xmlsec.EncryptionParameters;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.SignatureValidationParameters;
+
+/**
+ * A context implementation for storing parameter instances for JWT signature signing and validation, and JWT
+ * encryption and decryption.
+ */
+//TODO some of this is generic enough to use in any context?
+public class JWTSecurityParametersContext extends BaseContext {
+
+ /** Signature signing parameters. */
+ @Nullable private SignatureSigningParameters signatureSigningParameters;
+
+ /** Signature validation parameters. */
+ @Nullable private SignatureValidationParameters<SignedJWT> signatureValidationParameters;
+
+ /** Encryption parameters. */
+ @Nullable private EncryptionParameters encryptionParameters;
+
+ /** Decryption parameters. */
+ @Nullable private DecryptionParameters decryptionParameters;
+
+ /**
+ * Get the parameters to use for XML signature signing operations.
+ *
+ * @return the parameters
+ */
+ @Nullable public SignatureSigningParameters getSignatureSigningParameters() {
+ return signatureSigningParameters;
+ }
+
+ /**
+ * Set the parameters to use for XML signature signing operations.
+ *
+ * @param params the parameters
+ *
+ * @return this context
+ */
+ @Nonnull public JWTSecurityParametersContext setSignatureSigningParameters(
+ @Nullable final SignatureSigningParameters params) {
+ signatureSigningParameters = params;
+ return this;
+ }
+
+ /**
+ * Get the parameters to use for XML signature validation operations.
+ *
+ * @return the parameters
+ */
+ @Nullable public SignatureValidationParameters<SignedJWT> getSignatureValidationParameters() {
+ return signatureValidationParameters;
+ }
+
+ /**
+ * Set the parameters to use for XML signature validation operations.
+ *
+ * @param params The signatureValidationParameters to set
+ *
+ * @return this context
+ */
+ @Nonnull public JWTSecurityParametersContext setSignatureValidationParameters(
+ @Nullable final SignatureValidationParameters<SignedJWT> params) {
+ signatureValidationParameters = params;
+ return this;
+ }
+
+ /**
+ * Get the parameters to use for XML encryption operations.
+ *
+ * @return the parameters
+ */
+ @Nullable public EncryptionParameters getEncryptionParameters() {
+ return encryptionParameters;
+ }
+
+ /**
+ * Set the parameters to use for XML encryption operations.
+ *
+ * @param params the parameters
+ *
+ * @return this context
+ */
+ @Nonnull public JWTSecurityParametersContext setEncryptionParameters(@Nullable final EncryptionParameters params) {
+ encryptionParameters = params;
+ return this;
+ }
+
+ /**
+ * Get the parameters to use for XML decryption operations.
+ *
+ * @return the parameters
+ */
+ @Nullable public DecryptionParameters getDecryptionParameters() {
+ return decryptionParameters;
+ }
+
+ /**
+ * Set the parameters to use for XML decryption operations.
+ *
+ * @param params the parameters
+ *
+ * @return this context
+ */
+ @Nonnull public JWTSecurityParametersContext setDecryptionParameters(@Nullable final DecryptionParameters params) {
+ decryptionParameters = params;
+ return this;
+ }
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JWTSignatureValidationConfigurationCriterion.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JWTSignatureValidationConfigurationCriterion.java
new file mode 100644
index 0000000..8e8c34a
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JWTSignatureValidationConfigurationCriterion.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.criterion;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.SignatureValidationConfiguration;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+
+/**
+ * Criterion which holds one or more instances of {@link SignatureValidationConfiguration}
+ * for {@link SignedJWT signed JWTs}.
+ */
+public class JWTSignatureValidationConfigurationCriterion implements Criterion {
+
+ /** The list of configuration instances. */
+ @Nonnull @NonnullElements private final List<SignatureValidationConfiguration<SignedJWT>> configs;
+
+ /**
+ * Constructor.
+ *
+ * @param configurations list of configuration instances
+ */
+ public JWTSignatureValidationConfigurationCriterion(@Nonnull @NonnullElements @NotEmpty final
+ List<SignatureValidationConfiguration<SignedJWT>> configurations) {
+ configs = List.copyOf(Constraint.isNotNull(configurations, "List of configurations cannot be null"));
+ Constraint.isNotEmpty(configs, "At least one configuration is required");
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param configurations varargs array of configuration instances
+ */
+ public JWTSignatureValidationConfigurationCriterion(@Nonnull @NonnullElements @NotEmpty final
+ SignatureValidationConfiguration<SignedJWT>... configurations) {
+ configs = List.of(Constraint.isNotNull(configurations, "List of configurations cannot be null"));
+ Constraint.isNotEmpty(configs, "At least one configuration is required");
+ }
+
+ /**
+ * Get the list of configuration instances.
+ * @return the list of configuration instances
+ */
+ @Nonnull @NonnullElements @NotLive @Unmodifiable @NotEmpty
+ public List<SignatureValidationConfiguration<SignedJWT>> getConfigurations() {
+ return configs;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("GenericSignatureValidationConfigurationCriterion [configs=");
+ builder.append(configs);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return configs.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof JWTSignatureValidationConfigurationCriterion) {
+ return configs.equals(((JWTSignatureValidationConfigurationCriterion) obj).getConfigurations());
+ }
+
+ return false;
+ }
+
+}
+
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/KeyIdCriterion.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/KeyIdCriterion.java
new file mode 100644
index 0000000..a071373
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/KeyIdCriterion.java
@@ -0,0 +1,82 @@
+package net.shibboleth.oidc.security.criterion;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+
+
+/**
+ * An implementation of {@link Criterion} which specifies key ID criteria.
+ */
+public class KeyIdCriterion implements Criterion {
+
+ /** Key ID of resolved credentials. */
+ private String keyId;
+
+ /**
+ * Constructor.
+ *
+ * @param id key identifier.
+ */
+ public KeyIdCriterion(@Nonnull final String id) {
+ setKeyId(id);
+ }
+
+ /**
+ * Get the key ID criteria.
+ *
+ * @return Returns the keyId.
+ */
+ @Nonnull public String getKeyId() {
+ return keyId;
+ }
+
+ /**
+ * Set the key name criteria.
+ *
+ * @param id The keyId to set.
+ */
+ public void setKeyId(@Nonnull final String id) {
+ final String trimmed = StringSupport.trimOrNull(id);
+ Constraint.isNotNull(trimmed, "Key id criteria value cannot be null or empty");
+
+ keyId = trimmed;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("KeyIdCriterion [keyId =");
+ builder.append(keyId);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return keyId.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof KeyIdCriterion) {
+ return keyId.equals(((KeyIdCriterion) obj).keyId);
+ }
+
+ return false;
+ }
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/ProviderMetadataCriterion.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/ProviderMetadataCriterion.java
new file mode 100644
index 0000000..be342bd
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/ProviderMetadataCriterion.java
@@ -0,0 +1,69 @@
+package net.shibboleth.oidc.security.criterion;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.saml.criterion.RoleDescriptorCriterion;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+
+public class ProviderMetadataCriterion implements Criterion {
+
+ /** The OIDC provider metadata. */
+ @Nonnull private final OIDCProviderMetadata metadata;
+
+ /**
+ * Constructor.
+ *
+ * @param providerMetadata the provider's metadata
+ */
+ public ProviderMetadataCriterion(@Nonnull final OIDCProviderMetadata providerMetadata) {
+ metadata = Constraint.isNotNull(providerMetadata, "Provider Metadata cannot be null");
+ }
+
+ /**
+ * Gets the OIDC provider metadata.
+ *
+ * @return the entity role
+ */
+ @Nonnull public OIDCProviderMetadata getMetadata() {
+ return metadata;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ProviderMetadataCriterion [metadata=");
+ builder.append(metadata);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return metadata.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof RoleDescriptorCriterion) {
+ return metadata.equals(((ProviderMetadataCriterion) obj).metadata);
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/pom.xml b/oidc-common-crypto-impl/pom.xml
index 8c1add8..097c258 100644
--- a/oidc-common-crypto-impl/pom.xml
+++ b/oidc-common-crypto-impl/pom.xml
@@ -19,6 +19,12 @@
</properties>
<dependencies>
+ <!-- Do we need this? should not!! -->
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-xmlsec-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/EvaluableKeyIDCredentialCriterion.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/EvaluableKeyIDCredentialCriterion.java
new file mode 100644
index 0000000..5b4854d
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/EvaluableKeyIDCredentialCriterion.java
@@ -0,0 +1,98 @@
+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.criteria.impl.EvaluableCredentialCriterion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.criterion.KeyIdCriterion;
+import net.shibboleth.utilities.java.support.logic.AbstractTriStatePredicate;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Instance of evaluable credential criteria for evaluating credential key IDs.
+ */
+public class EvaluableKeyIDCredentialCriterion extends AbstractTriStatePredicate<Credential>
+ implements EvaluableCredentialCriterion {
+
+ /** Logger. */
+ private final Logger log = LoggerFactory.getLogger(EvaluableKeyIDCredentialCriterion.class);
+
+ /** Base criteria. */
+ private final String keyId;
+
+ /**
+ * Constructor.
+ *
+ * @param criteria the criteria which is the basis for evaluation
+ */
+ public EvaluableKeyIDCredentialCriterion(@Nonnull final KeyIdCriterion criteria) {
+ keyId = Constraint.isNotNull(criteria, "Criterion instance cannot be null").getKeyId();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param newKeyId the criteria value which is the basis for evaluation
+ */
+ public EvaluableKeyIDCredentialCriterion(@Nonnull final String newKeyId) {
+ final String trimmed = StringSupport.trimOrNull(newKeyId);
+ Constraint.isNotNull(trimmed, "Key id cannot be null or empty");
+
+ keyId = trimmed;
+ }
+
+ @Override
+ public boolean test(@Nullable final Credential target) {
+ if (target == null) {
+ log.error("Credential target was null");
+ return isNullInputSatisfies();
+ }
+ if (!(target instanceof JWKCredential) ||
+ ((JWKCredential)target).getKid() == null || ((JWKCredential)target).getKid().isEmpty()) {
+ log.info("Could not evaluate criteria, credential contained no key ID");
+ return isUnevaluableSatisfies();
+ }
+ return ((JWKCredential)target).getKid().equals(keyId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("EvaluableKeyIDCredentialCriterion [keyId =");
+ builder.append(keyId);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return keyId.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof EvaluableKeyIDCredentialCriterion) {
+ return keyId.equals(((EvaluableKeyIDCredentialCriterion) obj).keyId);
+ }
+
+ return false;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java
new file mode 100644
index 0000000..cf9fa26
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.messaging.impl.BaseTrustEngineSecurityHandler;
+import org.opensaml.security.trust.TrustEngine;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public abstract class BaseJWTSignatureSecurityHandler extends BaseTrustEngineSecurityHandler<SignedJWT> {
+
+ /**
+ * Function that looks up provider metadata from the given message context. Can return {@literal null}
+ * if not used.
+ */
+ @Nonnull private Function<MessageContext, OIDCProviderMetadata> providerMetadataLookupStrategy;
+
+ /** The provider metadata found from the lookup strategy.*/
+ @Nullable private OIDCProviderMetadata providerMetadata;
+
+ /** Constructor.*/
+ protected BaseJWTSignatureSecurityHandler() {
+ providerMetadataLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID providers metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCProviderMetadata> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ }
+
+ @Override
+ @Nullable protected TrustEngine<SignedJWT> resolveTrustEngine(final MessageContext messageContext) {
+ final JWTSecurityParametersContext secParams =
+ messageContext.getSubcontext(JWTSecurityParametersContext.class);
+ if (secParams == null || secParams.getSignatureValidationParameters() == null) {
+ return null;
+ }
+ return secParams.getSignatureValidationParameters().getSignatureTrustEngine();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+ if (!super.doPreInvoke(messageContext)) {
+ return false;
+ }
+ providerMetadata = providerMetadataLookupStrategy.apply(messageContext);
+ return true;
+ }
+
+ @Override
+ protected CriteriaSet buildCriteriaSet(final String entityID, final MessageContext messageContext)
+ throws MessageHandlerException {
+
+ final CriteriaSet criteriaSet = new CriteriaSet();
+
+ if (providerMetadata != null) {
+ criteriaSet.add(new ProviderMetadataCriterion(providerMetadata));
+ }
+// if (!Strings.isNullOrEmpty(entityID)) {
+// criteriaSet.add(new EntityIdCriterion(entityID) );
+// }
+
+ criteriaSet.add( new UsageCriterion(UsageType.SIGNING) );
+
+// final SignedJWTSecurityParametersContext secParamsContext =
+// messageContext.getSubcontext(SignedJWTSecurityParametersContext.class);
+// if (secParamsContext != null && secParamsContext.getSignatureValidationParameters() != null) {
+// criteriaSet.add(
+// new SignatureValidationParametersCriterion(secParamsContext.getSignatureValidationParameters()));
+// }
+
+ return criteriaSet;
+ }
+
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
index 2e426ab..e372896 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
@@ -217,10 +217,11 @@ public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustE
//TODO handle JWK Key Set (jku)?
- List<Credential> credentials = new ArrayList<>();
+ final List<Credential> credentials = new ArrayList<>();
if (signedJWT.getHeader().getJWK() != null) {
- final Credential cred = buildCredential(signedJWT.getHeader().getJWK());
+ final Credential cred = buildCredential(signedJWT.getHeader().getJWK(),
+ signedJWT.getHeader().getKeyID());
if (cred != null) {
credentials.add(cred);
}
@@ -230,7 +231,9 @@ public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustE
}
//TODO maybe just temporary in favor of separate components?
- @Nullable protected BasicJWKCredential buildCredential(@Nonnull final JWK jwk) {
+ @Nullable protected BasicJWKCredential buildCredential(@Nonnull final JWK jwk,
+ @Nullable final String headerKid) {
+
final BasicJWKCredential credential = new BasicJWKCredential();
if (jwk.getKeyType() == KeyType.EC || jwk.getKeyType() == KeyType.RSA) {
try {
@@ -247,6 +250,11 @@ public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustE
credential.getKeyNames().add(jwk.getKeyID());
credential.setKid(jwk.getKeyID());
}
+ if (headerKid != null && !headerKid.equals(credential.getKid())) {
+ log.warn("Key ID in JOSE header does not match 'kid' in JWK");
+ return null;
+
+ }
if (jwk.getKeyUse() != null) {
credential.setUsageType(CredentialConversionUtil.getUsageType(jwk));
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicSignatureValidationConfiguration.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicSignatureValidationConfiguration.java
new file mode 100644
index 0000000..4f668a8
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicSignatureValidationConfiguration.java
@@ -0,0 +1,40 @@
+package net.shibboleth.oidc.security.impl;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.opensaml.xmlsec.impl.BasicWhitelistBlacklistConfiguration;
+
+import net.shibboleth.oidc.security.SignatureValidationConfiguration;
+
+
+//TODO same as BasicSignatureValidationConfiguration. Needs to not be generic, but for JWT Trust Engine?
+public class BasicSignatureValidationConfiguration<T> extends BasicWhitelistBlacklistConfiguration
+ implements SignatureValidationConfiguration<T> {
+
+
+ /** The signature trust engine to use. */
+ @Nullable private TrustEngine<T> signatureTrustEngine;
+
+ //TODO chaining to parent config instance on getters? or use a wrapping proxy, etc?
+
+ /**
+ * Get the signature trust engine to use.
+ *
+ * @return the signature trust engine
+ */
+ @Override
+ @Nullable public TrustEngine<T> getSignatureTrustEngine() {
+ return signatureTrustEngine;
+ }
+
+ /**
+ * Set the signature trust engine to use.
+ *
+ * @param engine the signature trust engine
+ */
+ public void setSignatureTrustEngine(@Nullable final TrustEngine<T> engine) {
+ signatureTrustEngine = engine;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
index 79d5366..0f8ca2f 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.oidc.security.impl;
import javax.annotation.Nonnull;
@@ -16,6 +33,8 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
import com.nimbusds.jwt.SignedJWT;
+import net.shibboleth.oidc.security.credential.impl.EvaluableKeyIDCredentialCriterion;
+import net.shibboleth.oidc.security.criterion.KeyIdCriterion;
import net.shibboleth.utilities.java.support.annotation.ParameterName;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
@@ -64,6 +83,12 @@ public class ExplicitKeySignedJWTTrustEngine extends BaseSignedJWTTrustEngine<It
if (!Strings.isNullOrEmpty(jcaAlgorithm)) {
criteriaSet.add(new KeyAlgorithmCriterion(jcaAlgorithm), true);
}
+
+ // Add the kid from the JOSE header if available
+ final String kid = signedJWT.getHeader().getKeyID();
+ if (!Strings.isNullOrEmpty(kid)) {
+ criteriaSet.add(new EvaluableKeyIDCredentialCriterion(new KeyIdCriterion(kid)));
+ }
final Iterable<Credential> trustedCredentials;
try {
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTMessageSignatureSecurityHandler.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTMessageSignatureSecurityHandler.java
new file mode 100644
index 0000000..b00e0e5
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTMessageSignatureSecurityHandler.java
@@ -0,0 +1,114 @@
+package net.shibboleth.oidc.security.impl;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.JWSObject.State;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.credential.impl.EvaluableKeyIDCredentialCriterion;
+import net.shibboleth.oidc.security.criterion.KeyIdCriterion;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public class JWTMessageSignatureSecurityHandler extends BaseJWTSignatureSecurityHandler {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JWTMessageSignatureSecurityHandler.class);
+
+ /** Function that looks up a signed JWT token from the given message context to validate.*/
+ @NonnullAfterInit private Function<MessageContext, SignedJWT> jwtTokenLookupStrategy;
+
+ /** The extracted signed JWT that is to be validated.*/
+ @Nullable private SignedJWT signedJwt;
+
+ /** The stashed claims set belonging to the JWT.*/
+ @Nullable private JWTClaimsSet claimsSet;
+
+ /**
+ * Set the strategy used to look up a {@link SignedJWT signed JWT token}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setJwtTokenLookupStrategy(
+ @Nonnull final Function<MessageContext, SignedJWT> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ jwtTokenLookupStrategy = Constraint.isNotNull(strategy,
+ "JwtToken lookup strategy cannot be null");
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (jwtTokenLookupStrategy == null) {
+ throw new ComponentInitializationException("JwtTokenLookupStrategy cannot be null");
+ }
+ }
+
+ @Override
+ protected boolean doPreInvoke(final MessageContext messageContext) throws MessageHandlerException {
+ if (!super.doPreInvoke(messageContext)) {
+ return false;
+ }
+
+ signedJwt = jwtTokenLookupStrategy.apply(messageContext);
+ if (signedJwt == null) {
+ log.debug("{} Extracted JWT was not a SignedJWT, cannot process signature",
+ getLogPrefix());
+ throw new MessageHandlerException("Signed JWT was missing or unpopulated");
+ }
+ try {
+ //TODO debatable we should fail here if there are no claims, as the signature can still be checked.
+ claimsSet = signedJwt.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ throw new MessageHandlerException("Signed JWT did not have any claims, signature check failed");
+ }
+
+ return true;
+
+ }
+
+ @Override
+ protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+
+
+ if (signedJwt.getState() != State.SIGNED && signedJwt.getState() != State.VERIFIED) {
+ log.debug("{} The JWS object must be in a signed or verified state, cannot process signature",
+ getLogPrefix());
+ throw new MessageHandlerException("Validation of JWS failed. JWT is not signed.");
+ }
+ if (signedJwt.getState() == State.VERIFIED) {
+ log.debug("{} The JWS object was already verified! validating again", getLogPrefix());
+ }
+
+ //TODO entityID could be issuerID?
+ if (evaluate(signedJwt, null, messageContext)) {
+ log.debug("{} Validation of JWS token signature succeeded",
+ getLogPrefix());
+ } else {
+ log.debug(
+ "{} Validation of JWS token signature failed for context issuer '{}'",
+ getLogPrefix(), claimsSet.getIssuer());
+ throw new MessageHandlerException("Validation of JWS failed");
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCProviderConfigurationSignatureValidationParametersResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCProviderConfigurationSignatureValidationParametersResolver.java
new file mode 100644
index 0000000..294cf86
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCProviderConfigurationSignatureValidationParametersResolver.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+
+import org.opensaml.xmlsec.impl.AbstractSecurityParametersResolver;
+import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.SignatureValidationConfiguration;
+import net.shibboleth.oidc.security.SignatureValidationParameters;
+import net.shibboleth.oidc.security.SignatureValidationParametersResolver;
+import net.shibboleth.oidc.security.criterion.JWTSignatureValidationConfigurationCriterion;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+public class OIDCProviderConfigurationSignatureValidationParametersResolver
+ extends AbstractSecurityParametersResolver<SignatureValidationParameters<SignedJWT>>
+ implements SignatureValidationParametersResolver<SignedJWT> {
+
+ /** Logger. */
+ @Nonnull
+ private final Logger log =
+ LoggerFactory.getLogger(OIDCProviderConfigurationSignatureValidationParametersResolver.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Iterable<SignatureValidationParameters<SignedJWT>> resolve(@Nonnull final CriteriaSet criteria)
+ throws ResolverException {
+
+ final SignatureValidationParameters<SignedJWT> params = resolveSingle(criteria);
+ if (params != null) {
+ return Collections.singletonList(params);
+ }
+ return Collections.emptyList();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public SignatureValidationParameters<SignedJWT> resolveSingle(@Nonnull final CriteriaSet criteria) throws ResolverException {
+ Constraint.isNotNull(criteria, "CriteriaSet was null");
+ Constraint.isNotNull(criteria.get(JWTSignatureValidationConfigurationCriterion.class),
+ "Resolver requires an instance of SignatureValidationConfigurationCriterion");
+
+ final SignatureValidationParameters<SignedJWT> params = new SignatureValidationParameters<>();
+
+ resolveAndPopulateIncludesExcludes(params, criteria,
+ criteria.get(JWTSignatureValidationConfigurationCriterion.class).getConfigurations());
+
+ params.setSignatureTrustEngine(resolveSignatureTrustEngine(criteria));
+
+ logResult(params);
+
+ return params;
+ }
+
+ /**
+ * Log the resolved parameters.
+ *
+ * @param params the resolved param
+ */
+ protected void logResult(@Nonnull final SignatureValidationParameters<SignedJWT> params) {
+ if (log.isDebugEnabled()) {
+ log.debug("Resolved JWT SignatureValidationParameters:");
+
+ log.debug("\tAlgorithm includes: {}", params.getIncludedAlgorithms());
+ log.debug("\tAlgorithm excludes: {}", params.getExcludedAlgorithms());
+
+ log.debug("\tSignatureTrustEngine: {}", params.getSignatureTrustEngine() != null ? "present" : "null");
+ }
+ }
+
+ /**
+ * Resolve and return the effective {@link SignatureTrustEngine}.
+ *
+ * @param criteria the input criteria being evaluated
+ *
+ * @return the effective resolver, or null
+ */
+ @Nullable
+ protected TrustEngine<SignedJWT> resolveSignatureTrustEngine(@Nonnull final CriteriaSet criteria) {
+
+ for (final SignatureValidationConfiguration<SignedJWT> config : criteria
+ .get(JWTSignatureValidationConfigurationCriterion.class).getConfigurations()) {
+ if (config.getSignatureTrustEngine() != null) {
+ return config.getSignatureTrustEngine();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParameters.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParameters.java
new file mode 100644
index 0000000..13759a0
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParameters.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.AbstractHandlerDelegatingProfileAction;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.SignatureValidationConfiguration;
+import net.shibboleth.oidc.security.SignatureValidationParameters;
+import net.shibboleth.oidc.security.SignatureValidationParametersResolver;
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that resolves and populates {@link SignatureValidationParameters} on a {@link JWTSecurityParametersContext}
+ * created/accessed via a lookup function, by default on the inbound message context.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#MESSAGE_PROC_ERROR}
+ */
+public class PopulateJWTSignatureValidationParameters
+ extends AbstractHandlerDelegatingProfileAction<PopulateJWTSignatureValidationParametersHandler> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PopulateJWTSignatureValidationParameters.class);
+
+ /** Strategy used to look up the {@link JWTSecurityParametersContext} to set the parameters for. */
+ @Nonnull private Function<ProfileRequestContext,JWTSecurityParametersContext> securityParametersContextLookupStrategy;
+
+ /** Strategy used to lookup a per-request {@link SignatureValidationConfiguration} list. */
+ @NonnullAfterInit
+ private Function<ProfileRequestContext,List<SignatureValidationConfiguration<SignedJWT>>> configurationLookupStrategy;
+
+ /** Resolver for parameters to store into context. */
+ @NonnullAfterInit private SignatureValidationParametersResolver<SignedJWT> resolver;
+
+ /**
+ * Constructor.
+ */
+ public PopulateJWTSignatureValidationParameters() {
+ super(PopulateJWTSignatureValidationParametersHandler.class, new InboundMessageContextLookup());
+
+ // Create context by default.
+ securityParametersContextLookupStrategy =
+ new ChildContextLookup<>(JWTSecurityParametersContext.class, true).compose(
+ new InboundMessageContextLookup());
+ }
+
+ /**
+ * Set the strategy used to look up the {@link JWTSecurityParametersContext} to set the parameters for.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityParametersContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,JWTSecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ securityParametersContextLookupStrategy = Constraint.isNotNull(strategy,
+ "SecurityParametersContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to look up a per-request {@link SignatureValidationConfiguration} list.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setConfigurationLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,List<SignatureValidationConfiguration<SignedJWT>>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ configurationLookupStrategy = Constraint.isNotNull(strategy,
+ "SignatureValidationConfiguration lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the resolver to use for the parameters to store into the context.
+ *
+ * @param newResolver resolver to use
+ */
+ public void setSignatureValidationParametersResolver(
+ @Nonnull final SignatureValidationParametersResolver<SignedJWT> newResolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ resolver = Constraint.isNotNull(newResolver, "SignatureValidationParametersResolver cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (resolver == null) {
+ throw new ComponentInitializationException("SignatureValidationParametersResolver cannot be null");
+ }
+// else if (configurationLookupStrategy == null) {
+// configurationLookupStrategy = new Function<>() {
+// public List<SignatureValidationConfiguration<?>> apply(final ProfileRequestContext input) {
+// return Collections.singletonList(
+// SecurityConfigurationSupport.getGlobalSignatureValidationConfiguration());
+// }
+// };
+// }
+
+ final PopulateJWTSignatureValidationParametersHandler delegate = getDelegate();
+ delegate.setSignatureValidationParametersResolver(resolver);
+ delegate.setConfigurationLookupStrategy(adapt(configurationLookupStrategy));
+ delegate.setSecurityParametersContextLookupStrategy(adapt(securityParametersContextLookupStrategy));
+ delegate.initialize();
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParametersHandler.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParametersHandler.java
new file mode 100644
index 0000000..7384ac9
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/PopulateJWTSignatureValidationParametersHandler.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.SignatureValidationConfiguration;
+import net.shibboleth.oidc.security.SignatureValidationParameters;
+import net.shibboleth.oidc.security.SignatureValidationParametersResolver;
+import net.shibboleth.oidc.security.context.JWTSecurityParametersContext;
+import net.shibboleth.oidc.security.criterion.JWTSignatureValidationConfigurationCriterion;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+
+/**
+ * Handler that resolves and populates {@link SignatureValidationParameters} on a {@link JWTSecurityParametersContext}
+ * created/accessed via a lookup function, by default as an immediate child context of the target
+ * {@link MessageContext}.
+ */
+public class PopulateJWTSignatureValidationParametersHandler extends AbstractMessageHandler {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PopulateJWTSignatureValidationParametersHandler.class);
+
+ /** Strategy used to look up the {@link JWTSecurityParametersContext} to set the parameters for. */
+ @Nonnull private Function<MessageContext,JWTSecurityParametersContext> securityParametersContextLookupStrategy;
+
+ /** Strategy used to lookup a per-request {@link SignatureValidationConfiguration} list. */
+ @NonnullAfterInit
+ private Function<MessageContext,List<SignatureValidationConfiguration<SignedJWT>>> configurationLookupStrategy;
+
+ /** Resolver for parameters to store into context. */
+ @NonnullAfterInit private SignatureValidationParametersResolver<SignedJWT> resolver;
+
+ /**
+ * Constructor.
+ */
+ public PopulateJWTSignatureValidationParametersHandler() {
+ // Create context by default.
+ securityParametersContextLookupStrategy = new ChildContextLookup<>(JWTSecurityParametersContext.class, true);
+ }
+
+ /**
+ * Set the strategy used to look up the {@link JWTSecurityParametersContext} to set the parameters for.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityParametersContextLookupStrategy(
+ @Nonnull final Function<MessageContext, JWTSecurityParametersContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ securityParametersContextLookupStrategy = Constraint.isNotNull(strategy,
+ "SecurityParametersContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to look up a per-request {@link SignatureValidationConfiguration} list.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setConfigurationLookupStrategy(
+ @Nonnull final Function<MessageContext,List<SignatureValidationConfiguration<SignedJWT>>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ configurationLookupStrategy = Constraint.isNotNull(strategy,
+ "SignatureValidationConfiguration lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the resolver to use for the parameters to store into the context.
+ *
+ * @param newResolver resolver to use
+ */
+ public void setSignatureValidationParametersResolver(
+ @Nonnull final SignatureValidationParametersResolver<SignedJWT> newResolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ resolver = Constraint.isNotNull(newResolver, "SignatureValidationParametersResolver cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (resolver == null) {
+ throw new ComponentInitializationException("SignatureValidationParametersResolver cannot be null");
+ }
+// else if (configurationLookupStrategy == null) {
+// configurationLookupStrategy = new Function<>() {
+// public List<SignatureValidationConfiguration> apply(final MessageContext input) {
+// return Collections.singletonList(
+// SecurityConfigurationSupport.getGlobalSignatureValidationConfiguration());
+// }
+// };
+// }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+ log.debug("{} Resolving SignatureValidationParameters for request", getLogPrefix());
+
+ final List<SignatureValidationConfiguration<SignedJWT>> configs =
+ configurationLookupStrategy.apply(messageContext);
+ if (configs == null || configs.isEmpty()) {
+ log.error("{} No SignatureValidationConfiguration returned by lookup strategy", getLogPrefix());
+ throw new MessageHandlerException("No SignatureValidationConfiguration returned by lookup strategy");
+ }
+
+ final JWTSecurityParametersContext paramsCtx =
+ securityParametersContextLookupStrategy.apply(messageContext);
+ if (paramsCtx == null) {
+ log.debug("{} No SecurityParametersContext returned by lookup strategy", getLogPrefix());
+ throw new MessageHandlerException("SecurityParametersContext returned by lookup strategy");
+ }
+
+ try {
+ final SignatureValidationParameters<SignedJWT> params = resolver.resolveSingle(
+ new CriteriaSet(new JWTSignatureValidationConfigurationCriterion(configs)));
+ paramsCtx.setSignatureValidationParameters(params);
+ log.debug("{} {} SignatureValidationParameters", getLogPrefix(),
+ params != null ? "Resolved" : "Failed to resolve");
+ } catch (final ResolverException e) {
+ log.error("{} Error resolving SignatureValidationParameters: {}", getLogPrefix(), e.getMessage());
+ throw new MessageHandlerException("Error resolving SignatureValidationParameters");
+ }
+ }
+
+}
+
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataCredentialResolver.java
new file mode 100644
index 0000000..b54d450
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataCredentialResolver.java
@@ -0,0 +1,180 @@
+package net.shibboleth.oidc.security.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.component.InitializableComponent;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+
+//TODO not a prototype? so ensure thread-safety
+//TODO Docs
+//TODO Algorithm filters?
+public class ProviderMetadataCredentialResolver extends AbstractCriteriaFilteringCredentialResolver
+ implements InitializableComponent {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ProviderMetadataCredentialResolver.class);
+
+ /** Initialization flag. */
+ private boolean isInitialized;
+
+ /** The cache for remote JWK key sets. */
+ @NonnullAfterInit private RemoteJwkSetCache remoteJwkSetCache;
+
+ /** The remote key refresh interval. Default value: 30 minutes. */
+ @Positive
+ private Duration keyFetchInterval = Duration.ofMinutes(30);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isInitialized() {
+ return isInitialized;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void initialize() throws ComponentInitializationException {
+
+ if (remoteJwkSetCache == null) {
+ throw new ComponentInitializationException("Remote JWK Set Cache can not be null");
+ }
+ isInitialized = true;
+ }
+
+ /**
+ * Set the remote key refresh interval.
+ *
+ * @param interval What to set.
+ */
+ public void setKeyFetchInterval(@Positive final Duration interval) {
+ Constraint.isFalse(interval == null || interval.isNegative(), "Remote key refresh must be greater than 0");
+ keyFetchInterval = interval;
+ }
+
+ /**
+ * Set the cache for remote JWK key sets.
+ *
+ * @param jwkSetCache What to set.
+ */
+ public void setRemoteJwkSetCache(final RemoteJwkSetCache jwkSetCache) {
+ remoteJwkSetCache = Constraint.isNotNull(jwkSetCache, "The remote JWK set cache cannot be null");
+ }
+
+ @Override
+ protected Iterable<Credential> resolveFromSource(@Nonnull final CriteriaSet criteriaSet) throws ResolverException {
+
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ Constraint.isNotNull(criteriaSet, "CriteriaSet was null");
+
+ final UsageType usage = getEffectiveUsageInput(criteriaSet);
+ if (criteriaSet.contains(ProviderMetadataCriterion.class)) {
+ final OIDCProviderMetadata metadata = criteriaSet.get(ProviderMetadataCriterion.class).getMetadata();
+ return resolveFromMetadata(criteriaSet, usage, metadata);
+
+ } else {
+ throw new ResolverException("Criteria did not contain a ProviderMetadataCriterion "
+ + "could not perform resolution");
+ }
+ }
+
+ @Nonnull protected Collection<Credential> resolveFromMetadata(@Nonnull final CriteriaSet criteriaSet,
+ @Nonnull final UsageType usage, @Nonnull final OIDCProviderMetadata metadata) {
+
+ final LinkedHashSet<Credential> credentials = new LinkedHashSet<>(1);
+
+ // TODO: Need something like this final List<String> algorithms =
+ // getEffectiveSignatureAlgorithms(criteria, whitelistBlacklistPredicate);
+ if (metadata.getJWKSetURI() != null) {
+ final JWKSet keySet = remoteJwkSetCache.fetch(metadata.getJWKSetURI(),
+ Instant.now().plus(keyFetchInterval));
+
+ for (final JWK key : keySet.getKeys()) {
+ if (!matchUsage(key.getKeyUse(), usage)) {
+ continue;
+ }
+ // Check against supported algo's here? although OP should only have keys for algo's it supports?
+ if (key instanceof RSAKey || key instanceof ECKey) {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(key.getAlgorithm());
+ jwkCredential.setKid(key.getKeyID());
+ jwkCredential.getKeyNames().add(key.getKeyID());
+ try {
+ jwkCredential.setPublicKey(((AsymmetricJWK) key).toPublicKey());
+ } catch (final JOSEException e) {
+ log.warn("Unable to parse key from keyset");
+ continue;
+ }
+ log.debug("Selected key '{}', of type '{}', for alg '{}'",
+ key.getKeyID(), key.getKeyType(), key.getAlgorithm());
+ credentials.add(jwkCredential);
+ }
+ }
+ } else {
+ log.debug("No JWK Set available, no credentials returned");
+ }
+ return credentials;
+
+ }
+
+ /**
+ * Match the JWK key usage matches that in the criteria set.
+ *
+ * @param metadataUsage the value from the 'use' parameter of the JWK.
+ * @param criteriaUsage the value from specified criteria
+ * @return true if the two usage specifiers match for purposes of resolving validation information, false otherwise
+ */
+ protected boolean matchUsage(@Nonnull final KeyUse metadataUsage, @Nonnull final UsageType criteriaUsage) {
+ if (KeyUse.SIGNATURE.equals(metadataUsage) && criteriaUsage == UsageType.SIGNING) {
+ return true;
+ } else if (KeyUse.ENCRYPTION.equals(metadataUsage) && criteriaUsage == UsageType.ENCRYPTION) {
+ return true;
+ }
+ //TODO support unspecified in some way?
+ return false;
+ }
+
+ /**
+ * Get the effective {@link UsageType} input to use.
+ *
+ * @param criteriaSet the criteria set being processed
+ * @return the effective usage value
+ */
+ @Nonnull protected UsageType getEffectiveUsageInput(@Nonnull final CriteriaSet criteriaSet) {
+ final UsageCriterion usageCriteria = criteriaSet.get(UsageCriterion.class);
+ if (usageCriteria != null) {
+ return usageCriteria.getUsage();
+ }
+ return UsageType.UNSPECIFIED;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java
new file mode 100644
index 0000000..18f6dd9
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java
@@ -0,0 +1,301 @@
+
+package net.shibboleth.oidc.security.impl;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+public class ExplicitKeySignedJWTTrustEngineTest {
+
+ private ExplicitKeySignedJWTTrustEngine engine;
+
+ private ECKey key;
+
+ private CredentialResolver credResolver;
+
+ @BeforeMethod
+ public void setup() throws JOSEException {
+ key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
+
+ credResolver = new CredentialResolver() {
+
+ @Override
+ public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(key.getAlgorithm());
+ jwkCredential.setKid(key.getKeyID());
+ try {
+ jwkCredential.setPublicKey(((AsymmetricJWK) key).toPublicKey());
+ } catch (final JOSEException e) {
+ fail();
+ }
+ return jwkCredential;
+ }
+
+ @Override
+ public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+ return List.of(resolveSingle(criteria));
+ }
+ };
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+ }
+
+ @Test
+ public void testSuccess_WithTrustedCredential() throws JOSEException, SecurityException {
+
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ final var valid = engine.validate(createECSignedJWT(key, key.getKeyID(), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertTrue(valid);
+ }
+
+ @Test
+ public void testSuccess_WithInlineJWK() throws JOSEException, SecurityException {
+
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ final var valid = engine.validate(createECSignedJWTWithInlineJWK(key, key.getKeyID(), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertTrue(valid);
+ }
+
+ @Test
+ public void testSuccess_WithInlineJKU() throws JOSEException, SecurityException, URISyntaxException {
+
+ // Create a new resolver which does not resolve any creds, the JKU ones need to be resolved.
+ credResolver = new CredentialResolver() {
+
+ @Override
+ public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+ return null;
+ }
+
+ @Override
+ public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+ return Collections.emptyList();
+ }
+ };
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+
+
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ final var valid = engine.validate(createECSignedJWTWithJKU(key, key.getKeyID(),
+ new URI("https://op.example.com/keys"), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertTrue(valid);
+ }
+
+ @Test
+ public void testFailure_InlineJWKWrongKid() throws JOSEException, SecurityException {
+
+ // Create a new resolver which does not resolve any creds, the kid JOSE headers are different
+ // than the one in the JWK, and should fail.
+ credResolver = new CredentialResolver() {
+
+ @Override
+ public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+ return null;
+ }
+
+ @Override
+ public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+ return Collections.emptyList();
+ }
+ };
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ final var valid = engine.validate(createECSignedJWTWithInlineJWK(key, "WRONG-KID", JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testFailure_InlineJWKInvalidSignature() throws JOSEException, SecurityException {
+
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ final var valid = engine.validate(createECSignedJWTWithDifferentInlineJWK(key, key.getKeyID(),JWSAlgorithm.ES256,
+ // A new signing key, different to the one described in the header
+ new ECKeyGenerator(Curve.P_256).keyID("new").generate(),
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+
+
+ /**
+ * Create a JWS without a JKU or inline JWK.
+ *
+ * @param key the key to sign the JWT.
+ * @param keyId the keyId to describe the key to use in the header.
+ * @param algo the key algorithm.
+ * @param issuer the issuer.
+ * @param audience the audience.
+ * @return the signed JWT
+ * @throws JOSEException on error.
+ */
+ private SignedJWT createECSignedJWT(final ECKey key, final String keyId,
+ final JWSAlgorithm algo, final String issuer,
+ final String audience) throws JOSEException {
+
+ final var header = new JWSHeader.Builder(algo)
+ .type(JOSEObjectType.JWT)
+ .keyID(keyId)
+ .build();
+ final var payload = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .audience(audience)
+ .subject("jdoe")
+ .claim("preferred_username", "jdoe")
+ .claim("name", "J Doe")
+ .build();
+
+ final var signedJWT = new SignedJWT(header, payload);
+ signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
+ return signedJWT;
+ }
+
+
+ /**
+ * Create a JWS with a JKU URL.
+ *
+ * @param key the key to sign the JWT.
+ * @param keyId the keyId to describe the key to use in the header.
+ * @param jku the JKU.
+ * @param algo the key algorithm.
+ * @param issuer the issuer.
+ * @param audience the audience.
+ * @return the signed JWT
+ * @throws JOSEException on error.
+ */
+ private SignedJWT createECSignedJWTWithJKU(final ECKey key, final String keyId,
+ final URI jku, final JWSAlgorithm algo, final String issuer,
+ final String audience) throws JOSEException, URISyntaxException {
+
+ final var header = new JWSHeader.Builder(algo)
+ .type(JOSEObjectType.JWT)
+ .keyID(keyId)
+ .jwkURL(jku)
+ .build();
+ final var payload = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .audience(audience)
+ .subject("jdoe")
+ .claim("preferred_username", "jdoe")
+ .claim("name", "J Doe")
+ .build();
+
+ final var signedJWT = new SignedJWT(header, payload);
+ signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
+ return signedJWT;
+ }
+
+ /**
+ * Create a JWS with an inline (in JOSE headers) JWK.
+ *
+ * @param key the key to sign the JWT.
+ * @param keyId the keyId to describe the key to use in the header.
+ * @param algo the key algorithm.
+ * @param issuer the issuer.
+ * @param audience the audience.
+ * @return the signed JWT
+ * @throws JOSEException on error.
+ */
+ private SignedJWT createECSignedJWTWithInlineJWK(final ECKey key, final String keyId,
+ final JWSAlgorithm algo, final String issuer,
+ final String audience) throws JOSEException {
+
+ final var header = new JWSHeader.Builder(algo)
+ .type(JOSEObjectType.JWT)
+ .keyID(keyId)
+ .jwk(key)
+ .build();
+ final var payload = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .audience(audience)
+ .subject("jdoe")
+ .claim("preferred_username", "jdoe")
+ .claim("name", "J Doe")
+ .build();
+
+ final var signedJWT = new SignedJWT(header, payload);
+ signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
+ return signedJWT;
+ }
+
+ /**
+ * Create a JWS using a a key that is different to that in the JOSE header.
+ *
+ * @param key the key to described in the JWK header.
+ * @param keyId the keyId to describe the key to use in the header.
+ * @param algo the key algorithm.
+ * @param signingKey the key used to sign the JWS, which could be different than the key
+ * described in the JWK.
+ * @param issuer the issuer.
+ * @param audience the audience.
+ * @return the signed JWT
+ * @throws JOSEException on error.
+ */
+ private SignedJWT createECSignedJWTWithDifferentInlineJWK(final ECKey key, final String keyId,
+ final JWSAlgorithm algo, final ECKey signingKey, final String issuer,
+ final String audience) throws JOSEException {
+
+ final var header = new JWSHeader.Builder(algo)
+ .type(JOSEObjectType.JWT)
+ .keyID(keyId)
+ .jwk(key)
+ .build();
+ final var payload = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .audience(audience)
+ .subject("jdoe")
+ .claim("preferred_username", "jdoe")
+ .claim("name", "J Doe")
+ .build();
+
+ final var signedJWT = new SignedJWT(header, payload);
+ signedJWT.sign(new ECDSASigner(signingKey.toECPrivateKey()));
+ return signedJWT;
+ }
+
+
+
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCAuthorizationConfiguration.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCAuthorizationConfiguration.java
index fb7ba9d..fc3ab2f 100644
--- a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCAuthorizationConfiguration.java
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCAuthorizationConfiguration.java
@@ -32,6 +32,7 @@ import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
import com.google.common.base.Predicates;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
import net.shibboleth.idp.authn.config.AuthenticationProfileConfiguration;
import net.shibboleth.idp.profile.config.OverriddenIssuerProfileConfiguration;
@@ -46,7 +47,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
* Profile configuration for the OpenID Connect authorization endpoint.
- *
+ *
* <p>It is also usable as a Token endpoint configuration if no non-OIDC use cases are needed.</p>
*/
public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
@@ -54,7 +55,7 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/** ID for this profile configuration. */
@Nonnull @NotEmpty public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidc/sso/browser";
-
+
/** Whether all acr claim requests should be treated as Essential. */
@Nonnull private Predicate<ProfileRequestContext> acrRequestAlwaysEssentialPredicate;
@@ -63,42 +64,59 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/** Lookup function to supply lifetime of authz code. */
@Nonnull private Function<ProfileRequestContext,Duration> authorizeCodeLifetimeLookupStrategy;
-
+
/** Lookup function to supply attribute IDs to embed in authorization code or access token. */
@Nonnull private Function<ProfileRequestContext,Set<String>> encodedAttributesLookupStrategy;
-
+
/** Whether to encode authentication request parameters inside a JWT request object .*/
@Nonnull private Predicate<ProfileRequestContext> useRequestObjectPredicate;
/** Lookup function to supply attribute IDs to omit from UserInfo token. */
@Nonnull private Function<ProfileRequestContext,Set<String>> deniedUserInfoAttributesLookupStrategy;
-
+
/** Whether to include iss parameter in the authentication response. */
@Nonnull private Predicate<ProfileRequestContext> includeIssuerInResponsePredicate;
/** Enumeration of the HTTP methods used in OIDC authentication requests.*/
- public enum OIDCHttpRequestMethod {
+ public enum OIDCHttpRequestMethod {
/**
* HTTP GET.
*/
- GET,
+ GET,
/**
* HTTP POST.
*/
POST
}
-
- /**
- * Which HTTP method should be used to issue OIDC authentication requests.
- * Supported values are POST and GET. The default is GET.
+
+ /**
+ * Which HTTP method should be used to issue OIDC authentication requests.
+ * Supported values are POST and GET. The default is GET.
*/
@Nonnull private Function<ProfileRequestContext,String> httpRequestMethodLookupStrategy;
- /** Lookup function to supply strategy bi-function for manipulating authorization code claims set. */
+ /** Lookup function to supply strategy bi-function for manipulating authorization code claims set. */
@Nonnull
private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
authorizationCodeClaimsSetManipulationStrategyLookupStrategy;
+ /** Whether to make a UserInfo Endpoint request for End-User claims.*/
+ @Nonnull private Predicate<ProfileRequestContext> retrieveUserInfoEndpointClaims;
+
+ /** An override to specify a specific redirect_uri to use over the normally computed one.*/
+ @Nonnull private Function<ProfileRequestContext, String> redirectUriOverrideLookupStrategy;
+
+ //TODO I think we need a new config class for these 'proxy' attributes
+ // because the switch of RP to OP is confusing otherwise
+ /**
+ * Lookup function to retrieve the client_id from the given profile request context.
+ * Typically obtained from the issuer_id of the downstream relying-party (OP).
+ */
+ @Nonnull private Function<ProfileRequestContext, String> clientIdLookupStrategy;
+
+ /** Lookup function to determine the client_authentication method. */
+ @Nonnull private Function<ProfileRequestContext, ClientAuthentication> clientAuthenticationLookupStrategy;
+
/**
* Constructor.
*/
@@ -117,23 +135,113 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
acrRequestAlwaysEssentialPredicate = Predicates.alwaysFalse();
encodeConsentInTokensPredicate = Predicates.alwaysFalse();
useRequestObjectPredicate = Predicates.alwaysFalse();
+ retrieveUserInfoEndpointClaims = Predicates.alwaysTrue();
authorizeCodeLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
-
+
encodedAttributesLookupStrategy = FunctionSupport.constant(null);
deniedUserInfoAttributesLookupStrategy = FunctionSupport.constant(null);
- httpRequestMethodLookupStrategy = FunctionSupport.constant(OIDCHttpRequestMethod.GET.toString());
+ redirectUriOverrideLookupStrategy = FunctionSupport.constant(null);
+ clientIdLookupStrategy = FunctionSupport.constant(null);
+ clientAuthenticationLookupStrategy = FunctionSupport.constant(null);
+ httpRequestMethodLookupStrategy = FunctionSupport.constant(OIDCHttpRequestMethod.GET);
includeIssuerInResponsePredicate = Predicates.alwaysFalse();
-
+
authorizationCodeClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
}
+ /**
+ * Set the strategy to locate a client_id.
+ *
+ * @param strategy the strategy to use
+ */
+ public void setClientIdLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ clientIdLookupStrategy = Constraint.isNotNull(strategy, "Client ID lookup strategy can not be null");
+ }
+
+ /**
+ * Set a fixed client_id to use no matter what the context/request.
+ *
+ * @param clientId the static clientId to use
+ */
+ public void setClientId(@Nullable final String clientId) {
+ clientIdLookupStrategy = FunctionSupport.constant(clientId);
+ }
+
+ /**
+ * Set the client authentication lookup strategy which will locate the client_authentication method
+ * for a given client.
+ *
+ * @param strategy the strategy to use
+ */
+ public void setClientAuthenticationLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, ClientAuthentication> strategy) {
+ clientAuthenticationLookupStrategy =
+ Constraint.isNotNull(strategy, "Client authentication strategy can not be null");
+ }
+
+ /**
+ * Get the client_authentication for the given context. Typically that associated with the
+ * current client_id.
+ *
+ * @param profileRequestContext the profile request context
+ *
+ * @return the client_authentication method
+ */
+ public ClientAuthentication getClientAuthentication(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return clientAuthenticationLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Get the client_id appropriate for the given context. Typically that associated
+ * with the chosen downstream OpenID Connect Provider.
+ *
+ * @param profileRequestContext the profile request context
+ *
+ * @return the client_id
+ */
+ public String getClientId(@Nullable final ProfileRequestContext profileRequestContext) {
+ return clientIdLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the redirect_uri lookup strategy to use to locate an override redirect_rui.
+ *
+ * @param strategy the strategy to use.
+ */
+ public void setRedirectUriOverrideLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext, String> strategy) {
+ redirectUriOverrideLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy can not be null");
+ }
+
+ /**
+ * Set the override redirect_uri.
+ *
+ * @param uri the redirect_uri
+ */
+ public void setRedirectUriOverride(@Nonnull @NotEmpty final String uri) {
+ redirectUriOverrideLookupStrategy = FunctionSupport.constant(
+ Constraint.isNotNull(uri, "Redirect_uri can not be null or empty"));
+ }
+
+ /**
+ * Get the redirect_uri override if set.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return the redirect_uri override
+ */
+ public String getRedirectUriOverride(@Nullable final ProfileRequestContext profileRequestContext) {
+ return redirectUriOverrideLookupStrategy.apply(profileRequestContext);
+ }
+
/**
* Get whether all acr claim requests should be treated as Essential.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return whether all acr claim requests should be treated as Essential
*/
public boolean isAcrRequestAlwaysEssential(@Nullable final ProfileRequestContext profileRequestContext) {
@@ -142,35 +250,35 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set whether all acr claim requests should be treated as Essential.
- *
+ *
* @param flag flag to set
*/
public void setAcrRequestAlwaysEssential(final boolean flag) {
acrRequestAlwaysEssentialPredicate = flag ? Predicates.alwaysTrue() : Predicates.alwaysFalse();
}
-
+
/**
* Set condition for whether all acr claim requests should be treated as Essential.
- *
+ *
* @param condition condition to set
*/
public void setAcrRequestAlwaysEssentialPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
acrRequestAlwaysEssentialPredicate = Constraint.isNotNull(condition, "Condition cannot be null");
}
-
+
/**
* Should authentication request parameters should be passed in a single, self contained, JWT?
- *
+ *
* @param profileRequestContext the profile request context
- *
+ *
* @return whether authentication request parameters should be passed in a single, self contained, JWT
*/
public boolean isUseRequestObject(@Nullable final ProfileRequestContext profileRequestContext) {
return useRequestObjectPredicate.test(profileRequestContext);
}
-
+
/**
- * Set whether the authentication request parameters should be passed in a single, self contained, JWT.
+ * Set whether the authentication request parameters should be passed in a single, self contained, JWT.
*
* @param flag flag to set
*/
@@ -179,9 +287,9 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
}
/**
- * Set condition for whether the authentication request parameters should be passed in a single,
- * self contained, JWT.
- *
+ * Set condition for whether the authentication request parameters should be passed in a single,
+ * self contained, JWT.
+ *
* @param condition condition to set
*/
public void setUseRequestObjectPredicate(
@@ -191,9 +299,9 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Get whether to encode consent in authorization code and access/refresh tokens.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return whether to encode consent in authorization code and access/refresh tokens
*/
public boolean isEncodeConsentInTokens(@Nullable final ProfileRequestContext profileRequestContext) {
@@ -202,7 +310,7 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set whether to encode consent in authorization code and access/refresh tokens.
- *
+ *
* @param flag flag to set
*/
public void setEncodeConsentInTokens(final boolean flag) {
@@ -211,26 +319,55 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set condition for whether to encode consent in authorization code and access/refresh tokens.
- *
+ *
* @param condition condition to set
*/
public void setEncodeConsentInTokensPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
encodeConsentInTokensPredicate = Constraint.isNotNull(condition, "Condition cannot be null");
}
-
+
+ /**
+ * Get whether to make a request to the UserInfo Endpoint to obtain authenticated End-User claims.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return whether to make a request to the UserInfo Endpoint
+ */
+ public boolean isRetrieveUserInfoEndpointClaims(@Nonnull final ProfileRequestContext profileRequestContext) {
+ return retrieveUserInfoEndpointClaims.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether to make a request to the UserInfo Endpoint to obtain authenticated End-User claims.
+ *
+ * @param flag flag to set
+ */
+ public void setRetrieveUserInfoEndpointClaims(final boolean flag) {
+ retrieveUserInfoEndpointClaims = flag ? Predicates.alwaysTrue() : Predicates.alwaysFalse();
+ }
+
+ /**
+ * Set condition for whether to make a request to the UserInfo Endpoint to obtain authenticated End-User claims.
+ *
+ * @param condition condition to set
+ */
+ public void setRetrieveUserInfoEndpointClaims(@Nonnull final Predicate<ProfileRequestContext> condition){
+ retrieveUserInfoEndpointClaims = Constraint.isNotNull(condition, "Condition cannot be null");
+ }
+
/**
* Get authz code lifetime.
- *
+ *
* <p>Defaults to 5 minutes.</p>
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return authz code lifetime
*/
@Positive @Nonnull
public Duration getAuthorizeCodeLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
final Duration lifetime = authorizeCodeLifetimeLookupStrategy.apply(profileRequestContext);
-
+
Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
"Authorization code lifetime must be greater than 0");
return lifetime;
@@ -238,16 +375,16 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set the lifetime of authz code.
- *
+ *
* @param lifetime lifetime of authz code
*/
public void setAuthorizeCodeLifetime(@Positive @Nonnull final Duration lifetime) {
Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
"Authorization code lifetime must be greater than 0");
-
+
authorizeCodeLifetimeLookupStrategy = FunctionSupport.constant(lifetime);
}
-
+
/**
* Set a lookup strategy for the authz code lifetime.
*
@@ -257,32 +394,32 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
@Nonnull final Function<ProfileRequestContext,Duration> strategy) {
authorizeCodeLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
-
+
/**
* Set a lookup strategy to determine the HTTP request method for an authentication request.
- *
+ *
* @param strategy the strategy to set.
*/
public void setHttpRequestMethodLookupStrategy(
@Nonnull final Function<ProfileRequestContext, String> strategy) {
- httpRequestMethodLookupStrategy =
+ httpRequestMethodLookupStrategy =
Constraint.isNotNull(strategy, "HTTP request method strategy can not be null");
}
-
+
/**
* Set the HTTP request method for an authentication request.
- *
+ *
* @param method the HTTP method to set, either POST or GET.
*/
public void setHttpRequestMethod(@Nonnull @NotEmpty final OIDCHttpRequestMethod method){
httpRequestMethodLookupStrategy = FunctionSupport.constant(method != null ? method.toString() : null);
}
-
+
/**
* Get the HTTP request method for an authentication request.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return the HTTP request method
*/
public OIDCHttpRequestMethod getHttpRequestMethod(@Nullable final ProfileRequestContext profileRequestContext) {
@@ -301,14 +438,14 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Get the set of attribute IDs which should be encoded in encrypted form into the authorization code
* and/or access/refresh tokens to enable recovery on the back-channel.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return the attribute IDs to encode
*/
@Nonnull @NonnullElements @NotLive public Set<String> getEncodedAttributes(
@Nullable final ProfileRequestContext profileRequestContext) {
-
+
final Set<String> attributes = encodedAttributesLookupStrategy.apply(profileRequestContext);
if (attributes != null) {
return Set.copyOf(attributes);
@@ -319,7 +456,7 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set the set of attribute IDs which should be encoded in encrypted form into the authorization code
* and/or access/refresh tokens to enable recovery on the back-channel.
- *
+ *
* @param attributes the attribute IDs to encode
*/
public void setEncodedAttributes(@Nullable @NonnullElements final Collection<String> attributes) {
@@ -345,17 +482,17 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Get the set of attribute IDs which should be omitted from the UserInfo token.
- *
+ *
* <p>Default behavior is to include all claims, but omiited claims also affect the set that
* may need to be embedded for recovery into the access/refresh tokens.</p>
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return the attribute IDs to omit from UserInfo token
*/
@Nonnull @NonnullElements @NotLive public Set<String> getDeniedUserInfoAttributes(
@Nullable final ProfileRequestContext profileRequestContext) {
-
+
final Set<String> attributes = deniedUserInfoAttributesLookupStrategy.apply(profileRequestContext);
if (attributes != null) {
return Set.copyOf(attributes);
@@ -365,10 +502,10 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set the set of attribute IDs which should be omitted from the UserInfo token.
- *
+ *
* <p>Default behavior is to include all claims, but omiited claims also affect the set that
* may need to be embedded for recovery into the access/refresh tokens.</p>
- *
+ *
* @param attributes the attribute IDs to omit from UserInfo token
*/
public void setDeniedUserInfoAttributes(@Nullable @NonnullElements final Collection<String> attributes) {
@@ -393,11 +530,11 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Get whether to include iss parameter in the authentication response.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return whether to include iss parameter in the authentication response
- *
+ *
* @since 2.1.0
*/
public boolean isIncludeIssuerInResponse(@Nullable final ProfileRequestContext profileRequestContext) {
@@ -406,9 +543,9 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set whether to include iss parameter in the authentication response.
- *
+ *
* @param flag flag to set
- *
+ *
* @since 2.1.0
*/
public void setIncludeIssuerInResponse(final boolean flag) {
@@ -417,9 +554,9 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set condition for whether to include iss parameter in the authentication response.
- *
+ *
* @param condition condition to set
- *
+ *
* @since 2.1.0
*/
public void setIncludeIssuerInResponsePredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
@@ -428,11 +565,11 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Get the bi-function for manipulating authorization code claims set.
- *
+ *
* @param profileRequestContext profile request context
- *
+ *
* @return the bi-function for manipulating authorization code claims set
- *
+ *
* @since 2.1.0
*/
@Nonnull
@@ -444,9 +581,9 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
/**
* Set the bi-function for manipulating authorization code claims set.
- *
+ *
* @param strategy bi-function for manipulating authorization code claims set
- *
+ *
* @since 2.1.0
*/
public void setAuthorizationCodeClaimsSetManipulationStrategy(
@@ -458,14 +595,14 @@ public class OIDCAuthorizationConfiguration extends AbstractOIDCSSOConfiguration
* Set a lookup strategy for the bi-function for manipulating authorization code claims set.
*
* @param strategy lookup strategy
- *
+ *
* @since 2.1.0
*/
- public void setAuthorizationCodeClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ public void setAuthorizationCodeClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
strategy) {
authorizationCodeClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
"Lookup strategy cannot be null");
}
-}
\ No newline at end of file
+}
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCSecurityConfiguration.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCSecurityConfiguration.java
index 950df20..f948cb4 100644
--- a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCSecurityConfiguration.java
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/config/OIDCSecurityConfiguration.java
@@ -18,6 +18,7 @@
package net.shibboleth.oidc.profile.config;
import javax.annotation.Nullable;
+
import org.opensaml.xmlsec.EncryptionConfiguration;
import org.opensaml.xmlsec.SignatureSigningConfiguration;
@@ -40,6 +41,10 @@ public class OIDCSecurityConfiguration extends SecurityConfiguration {
/** Configuration used when validating token endpoint authentication JWT signatures. */
@Nullable
private SignatureSigningConfiguration tokenEndpointJwtSignatureValidationConfig;
+
+ /** Configuration used when validating id_token JWT signatures. */
+ @Nullable
+ private net.shibboleth.oidc.security.SignatureValidationConfiguration<?> idTokenJwtSignatureValidationConfig;
/**
* Get the configuration used when decrypting request object information.
@@ -98,5 +103,24 @@ public class OIDCSecurityConfiguration extends SecurityConfiguration {
@Nullable final SignatureSigningConfiguration config) {
tokenEndpointJwtSignatureValidationConfig = config;
}
+
+ /**
+ * Get the configuration used when validating id_token JWT signatures.
+ *
+ * @return configuration used when validating id_token JWT signatures, or null
+ */
+ @Nullable
+ public net.shibboleth.oidc.security.SignatureValidationConfiguration<?> getIdTokenJwtSignatureValidationConfig() {
+ return idTokenJwtSignatureValidationConfig;
+ }
+
+ /**
+ * Set the configuration used when validating id_token JWT signatures.
+ *
+ * @param config configuration used when validating id_token JWT signatures, or null
+ */
+ public void setIdTokenJwtSignatureValidationConfig(@Nullable final net.shibboleth.oidc.security.SignatureValidationConfiguration<?> config) {
+ idTokenJwtSignatureValidationConfig = config;
+ }
}
diff --git a/pom.xml b/pom.xml
index af42654..337553a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
<idp.groupId>net.shibboleth.idp</idp.groupId>
<idp.version>4.2.0</idp.version>
<opensaml.groupId>org.opensaml</opensaml.groupId>
- <opensaml.version>4.2.0</opensaml.version>
+ <opensaml.version>4.2.1-SNAPSHOT</opensaml.version>
<java-support.version>8.3.1</java-support.version>
<nimbus-jose-jwt.version>9.14</nimbus-jose-jwt.version>
<oauth2-oidc-sdk.version>9.20</oauth2-oidc-sdk.version>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list