[java-oidc-common] branch main updated: JCOMOIDC-1 - JWT Support
Phil Smart
philip.smart at jisc.ac.uk
Fri Jan 29 10:58:00 UTC 2021
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-oidc-common.
View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=f32cb8016d6bd7065e31ddb10854a0d3ec97b371
The following commit(s) were added to refs/heads/main by this push:
new f32cb80 JCOMOIDC-1 - JWT Support
f32cb80 is described below
commit f32cb8016d6bd7065e31ddb10854a0d3ec97b371
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 29 10:54:40 2021 +0000
JCOMOIDC-1 - JWT Support
Add JWT claims validation framework and validators to support OIDC
claims validation - OpenID Connect core 1.0 section 3.1.3.7.
Employs strategies for exploiting runtime information in the context
or claims set.
https://issues.shibboleth.net/jira/browse/JCOMOIDC-1
---
.../oidc/jwt/claims/AbstractClaimsValidator.java | 82 +++++++
.../oidc/jwt/claims/ClaimsValidator.java | 55 +++++
.../oidc/jwt/claims/JWTClaimsValidation.java | 34 +++
.../oidc/jwt/claims/JWTValidationException.java | 43 ++++
oidc-common-crypto-impl/pom.xml | 25 +-
.../jwt/claims/impl/AudienceClaimsValidator.java | 97 ++++++++
.../impl/AuthenticationTimeClaimsValidator.java | 139 +++++++++++
.../claims/impl/ChainingJWTClaimsValidation.java | 100 ++++++++
.../jwt/claims/impl/ExactMatchClaimsValidator.java | 111 +++++++++
.../jwt/claims/impl/ExpiryClaimsValidator.java | 87 +++++++
.../ForcedAuthenticationActivationCondition.java | 49 ++++
.../security/jwt/claims/impl/IDTokenClaims.java | 66 +++++
.../jwt/claims/impl/IssuedAtClaimsValidator.java | 89 +++++++
.../oidc/security/jwt/claims/impl/JWTClaims.java | 74 ++++++
.../impl/NonceValidationActiviationCondition.java | 43 ++++
.../jwt/claims/impl/NotBeforeClaimsValidator.java | 82 +++++++
.../jwt/claims/impl/ProhibitedClaimsValidator.java | 84 +++++++
.../jwt/claims/impl/RequiredClaimsValidator.java | 81 +++++++
.../security/jwt/claims/impl/package-info.java | 19 ++
.../claims/impl/AbstractClaimsValidatorTest.java | 62 +++++
.../claims/impl/AudienceClaimsValidatorTest.java | 115 +++++++++
.../AuthenticationTimeClaimsValidatorTest.java | 136 +++++++++++
.../impl/ChainingJWTClaimsValidationTest.java | 267 +++++++++++++++++++++
.../claims/impl/ExactMatchClaimsValidatorTest.java | 153 ++++++++++++
.../jwt/claims/impl/ExpiryClaimsValidatorTest.java | 92 +++++++
.../claims/impl/IssuedAtClaimsValidatorTest.java | 99 ++++++++
.../claims/impl/NotBeforeClaimsValidatorTest.java | 99 ++++++++
.../claims/impl/ProhibitedClaimsValidatorTest.java | 84 +++++++
.../claims/impl/RequiredClaimsValidatorTest.java | 83 +++++++
.../security/jwt/claims/impl/package-info.java | 19 ++
...seStorageServiceClientInformationComponent.java | 2 +
31 files changed, 2569 insertions(+), 2 deletions(-)
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java
new file mode 100644
index 0000000..336f52a
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.jwt.claims;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Base class for {@link ClaimsValidator claims validators}.
+ */
+public abstract class AbstractClaimsValidator
+ extends AbstractIdentifiableInitializableComponent implements ClaimsValidator{
+
+ /** Does this validator apply to this request? Default is true. */
+ @Nonnull private BiPredicate<ProfileRequestContext, JWTClaimsSet> activationCondition;
+
+ /** Constructor. */
+ public AbstractClaimsValidator() {
+ //default is always true
+ activationCondition = (prc,claims) -> true;
+ }
+
+ /**
+ * Set an activation condition for this validator.
+ *
+ * @param condition condition to set
+ */
+ public void setActivationCondition(@Nonnull final BiPredicate<ProfileRequestContext, JWTClaimsSet> condition) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void validate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ if (!activationCondition.test(context,claims)) {
+ //not active for this request
+ return;
+ }
+ doValidate(claims,context);
+ }
+
+ /**
+ * Perform validation of the given claims supported by the supplied context.
+ *
+ * @param claims the claims to validate.
+ * @param context the profile request context.
+ *
+ * @throws JWTValidationException when validation is unsuccessful due to a failed attempt
+ */
+ protected abstract void doValidate(@Nonnull final JWTClaimsSet claims,
+ @Nonnull final ProfileRequestContext context) throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java
new file mode 100644
index 0000000..edf079c
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jwt.claims;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.utilities.java.support.component.IdentifiableComponent;
+
+/**
+ * High-level API for validating a JWT's claims set.
+ *
+ * <p>Instances of this interface must be stateless.</p>
+ *
+ */
+ at ThreadSafe
+ at Immutable
+public interface ClaimsValidator extends IdentifiableComponent{
+
+ /**
+ * Validate all, or part of, the given JWT claims set. Can take supporting information
+ * from the given context tree.
+ *
+ * <p>Throws an exception if validation fails.</p>
+ *
+ * @param claims the claims to validate.
+ * @param context the profile request context.
+ *
+ * @throws JWTValidationException when validation is unsuccessful due to a failed attempt
+ */
+ void validate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java
new file mode 100644
index 0000000..eed0540
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java
@@ -0,0 +1,34 @@
+/*
+ * 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.jwt.claims;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+public interface JWTClaimsValidation {
+
+
+ void validate(@Nullable final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java
new file mode 100644
index 0000000..d3a0b5c
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java
@@ -0,0 +1,43 @@
+package net.shibboleth.oidc.jwt.claims;
+
+import javax.annotation.Nullable;
+
+public class JWTValidationException extends Exception{
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -7779377025440142747L;
+
+ /** Constructor. */
+ public JWTValidationException() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message
+ */
+ public JWTValidationException(@Nullable final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param wrappedException exception to be wrapped by this one
+ */
+ public JWTValidationException(@Nullable final Exception wrappedException) {
+ super(wrappedException);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message
+ * @param wrappedException exception to be wrapped by this one
+ */
+ public JWTValidationException(@Nullable final String message, @Nullable final Exception wrappedException) {
+ super(message, wrappedException);
+ }
+
+}
diff --git a/oidc-common-crypto-impl/pom.xml b/oidc-common-crypto-impl/pom.xml
index ee66b8a..ab977ec 100644
--- a/oidc-common-crypto-impl/pom.xml
+++ b/oidc-common-crypto-impl/pom.xml
@@ -20,20 +20,35 @@
<dependencies>
<dependency>
- <groupId>net.shibboleth.oidc</groupId>
+ <groupId>${project.groupId}</groupId>
<artifactId>oidc-common-crypto-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.opensaml</groupId>
+ <groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-saml-api</artifactId>
<scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-api</artifactId>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-core</artifactId>
@@ -41,6 +56,12 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-profile-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
</dependencies>
<build>
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
new file mode 100644
index 0000000..230059b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Verifies the Audience (aud) claim contains the client_id of this client (as registered at the issuer). See
+ * section 3.1.3.7 of OpenID Connect core 1.0. The audience is determined at runtime using an appropriate strategy.
+ */
+ at ThreadSafeAfterInit
+public class AudienceClaimsValidator extends AbstractClaimsValidator{
+
+ /** Strategy to find the audience value from the context.*/
+ @NonnullAfterInit private Function<ProfileRequestContext,String> audienceLookupStrategy;
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (audienceLookupStrategy == null) {
+ throw new ComponentInitializationException("Audience lookup strategy can not be null");
+ }
+ }
+
+ /**
+ * Set the audience lookup strategy.
+ *
+ * @param strategy the strategy.
+ */
+ public void setAudienceLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ audienceLookupStrategy = Constraint.isNotNull(strategy, "Audience lookup strategy can not be null");
+ }
+
+ @Override
+ protected void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ final String acceptedAudience = audienceLookupStrategy.apply(context);
+ if (acceptedAudience == null) {
+ throw new JWTValidationException("Audience value not present in the context");
+ }
+
+ final List<String> audList = claims.getAudience();
+ if (audList != null && !audList.isEmpty()) {
+ boolean audMatch = false;
+ for (final String aud : audList) {
+ if (acceptedAudience.equals(aud)) {
+ audMatch = true;
+ break;
+ }
+ }
+ if (!audMatch) {
+ throw new JWTValidationException("JWT audience rejected: " + audList);
+ }
+ } else {
+ throw new JWTValidationException("JWT missing required audience");
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java
new file mode 100644
index 0000000..4e540c5
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Verifies the auth_time (when the End-User authentication took place) is within a valid expiration window.
+ * <p>Uses a predicate to determine if the auth_time was request e.g. was explicitly requested, or the max_age
+ * claim was requested. Defaults to true.</p>
+ */
+ at ThreadSafeAfterInit
+public class AuthenticationTimeClaimsValidator extends AbstractClaimsValidator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AuthenticationTimeClaimsValidator.class);
+
+ /**
+ * If request, the amount of time for which a token is valid
+ * after if it was issued. (Default value: 60 seconds)
+ */
+ @Nonnull private Duration authnLifetime;
+
+ /**
+ * Has the auth_time been requested, either explicitly or from the max_age parameter?
+ * Defaults to true.
+ */
+ @Nonnull private Predicate<ProfileRequestContext> requested;
+
+ /** Constructor.*/
+ public AuthenticationTimeClaimsValidator() {
+ authnLifetime = Duration.ofSeconds(60);
+ requested = Predicates.alwaysTrue();
+ }
+
+
+ /**
+ * Has the auth_time been request e.g. explicitly, or by using the max_age parameter.
+ *
+ * @param isRequested has auth_time been requested.
+ */
+ public void setRequested(final Predicate<ProfileRequestContext> isRequested) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ requested = isRequested;
+ }
+
+ /**
+ * Sets the amount of time for which a token is valid from when the original authentication took place.
+ *
+ * @param lifetime amount of time for which a token is valid
+ */
+ public void setAuthnLifetime(@Nonnull final Duration lifetime) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ Constraint.isNotNull(lifetime, "Token authentication lifetime cannot be null");
+ Constraint.isFalse(lifetime.isNegative(), "Token authentication lifetime cannot be negative");
+
+ authnLifetime = lifetime;
+ }
+
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claimsSet, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ if (!requested.test(context)) {
+ //not requested, so nothing to check.
+ return;
+ } else {
+
+ try {
+ final Date authTimeDate = claimsSet.getDateClaim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName());
+ if (authTimeDate == null) {
+ throw new JWTValidationException("No authentication time found in token");
+ }
+ final Instant authTime = authTimeDate.toInstant();
+ final Instant now = Instant.now();
+ final Instant expiration = authTime.plus(authnLifetime);
+
+ // Check time of authentication wasn't in the future
+ if (authTime.isAfter(now)) {
+ log.warn("Authentication is not yet valid: auth_time was {}, latest valid is: {}",
+ authTime, now);
+ throw new JWTValidationException("JWT token authentication time is not yet valid");
+ }
+
+ // Check time of authentication has not expired
+ if (expiration.isBefore(now)) {
+ log.warn(
+ "Authentication has expired: auth_time was '{}', "
+ + "expired at: '{}', current time: '{}'",
+ authTime, expiration, now);
+ throw new JWTValidationException("JWT token authentication time has expired");
+ }
+ //is OK.
+
+ } catch (final ParseException e) {
+ throw new JWTValidationException("Autentication forced, but no authentication time found in token",e);
+ }
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java
new file mode 100644
index 0000000..47588e8
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java
@@ -0,0 +1,100 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+/**
+ * A {@link JWTClaimsValidation JWT claims validation} implementation that validates a JWT claims set from a chain
+ * of configured validators. Validation terminates when one of the validators throws a {@link JWTValidationException}.
+ * If no {@link JWTValidationException} is thrown, the claims set is 'valid'.
+ *
+ * <p>Note, does not represent a chain of responsibility pattern, despite the name.</p>
+ */
+public class ChainingJWTClaimsValidation extends AbstractInitializableComponent implements JWTClaimsValidation {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ChainingJWTClaimsValidation.class);
+
+ /** List of claim validators. Ordering is not important.*/
+ @Nonnull @NonnullElements private List<ClaimsValidator> claimValidators;
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (claimValidators == null) {
+ throw new ComponentInitializationException("List of claims validators can not be null");
+ }
+ }
+
+ /**
+ * Set the list of validators to use.
+ *
+ * @param validators validators to use
+ */
+ public void setClaimValidators(@Nullable @NonnullElements final List<ClaimsValidator> validators) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (validators != null) {
+ claimValidators = List.copyOf(validators);
+ } else {
+ claimValidators = Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void validate(@Nullable final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ if (claims == null) {
+ log.trace("No claims to verify, nothing todo");
+ return;
+ }
+ log.trace("Attempting JWT claims verification for subject '{}'",claims.getSubject());
+
+ for (final ClaimsValidator validator : claimValidators) {
+ log.trace("Attempting JWT claims validator '{}'", validator.getId());
+ validator.validate(claims, context);
+ }
+ log.debug("JWT claims verification for subject '{}' succeeded",claims.getSubject());
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java
new file mode 100644
index 0000000..606e1de
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Validator that checks a JWT claim exactly matches (by Object equality) a value returned by a lookup strategy.
+ */
+ at ThreadSafeAfterInit
+public class ExactMatchClaimsValidator extends AbstractClaimsValidator{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExactMatchClaimsValidator.class);
+
+ /** Strategy to retrieve the value to match to an ID Token claim.*/
+ @NonnullAfterInit private Function<ProfileRequestContext,String> valueToMatchLookupStrategy;
+
+ /** The name of the claim to match from the ID token.*/
+ @NonnullAfterInit @NotEmpty private String claimName;
+
+ /**
+ * Set the name of the claim to match.
+ *
+ * @param name the claim name.
+ */
+ public void setClaimName(@Nonnull @NotEmpty final String name) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ claimName = Constraint.isNotEmpty(name, "Claim name can not be null or empty");
+ }
+
+ /**
+ * Set the value to match lookup strategy.
+ *
+ * @param strategy the strategy to use.
+ */
+ public void setValueToMatchLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ valueToMatchLookupStrategy = Constraint.isNotNull(strategy,
+ "Claim value to match lookup strategy can not be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (claimName == null) {
+ throw new ComponentInitializationException("Claim name can not be null");
+ }
+ if (valueToMatchLookupStrategy == null) {
+ throw new ComponentInitializationException("Matching value lookup strategy can not be null");
+ }
+ }
+
+ @Override
+ protected void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ final Object actualClaim = claims.getClaim(claimName);
+ if (actualClaim == null) {
+ throw new JWTValidationException("Claim '"+claimName+"' does not exist");
+ }
+ final String expectedClaim = valueToMatchLookupStrategy.apply(context);
+ log.trace("{}: Checking actual claim '{}' matches expected claim '{}'", getId(), actualClaim, expectedClaim);
+ //handle null in the yoda condition.
+ if (! actualClaim.equals(expectedClaim)) {
+ throw new JWTValidationException("JWT \"" + claimName + "\" claim has value "
+ + actualClaim + " but should be " + expectedClaim);
+ }
+ //all fine
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java
new file mode 100644
index 0000000..0315893
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff an expiration time (exp) claim is present, verifies that it is ahead of the current time, else the JWT claims set
+ * is rejected. A few minutes of {@code clockSkew} is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.
+ */
+ at ThreadSafeAfterInit
+public class ExpiryClaimsValidator extends AbstractClaimsValidator {
+
+ /**
+ * Positive clock skew adjustment to consider when checking JWT not before and expiration
+ * (Default value: 60 seconds).
+ */
+ @Nonnull private Duration clockSkew;
+
+ /** Constructor.*/
+ public ExpiryClaimsValidator() {
+ clockSkew = Duration.ofSeconds(60);
+ }
+
+ /**
+ * Set the clock skew.
+ *
+ * @param skew clock skew to set
+ */
+ public void setClockSkew(@Nonnull final Duration skew) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ final Instant now = Instant.now();
+
+ final Date exp = claims.getExpirationTime();
+ if (exp != null) {
+ final Instant expInstant = exp.toInstant();
+ final Instant expirationPlusSkew = expInstant.plus(clockSkew);
+
+ if (now.isAfter(expirationPlusSkew)) {
+ throw new JWTValidationException("Expired JWT");
+ }
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java
new file mode 100644
index 0000000..91033d2
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+
+/**
+ * Has forced authentication been requested?
+ *
+ * <p>Note, returns <b>true</b> if an authentication context is not contained in the profile request
+ * context - enabling the validator seems a better security posture than disabling it.</p>
+ */
+public final class ForcedAuthenticationActivationCondition implements BiPredicate<ProfileRequestContext, JWTClaimsSet> {
+
+ @Override
+ public boolean test(@Nonnull final ProfileRequestContext prc, @Nonnull final JWTClaimsSet claims) {
+
+ final AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class);
+
+ if (authnContext == null) {
+ return true;
+ }
+ return authnContext.isForceAuthn();
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java
new file mode 100644
index 0000000..5a85159
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/** Enum of those OIDC ID token claims that are not part of the standard {@link JWTClaims JWT claims set}.*/
+public enum IDTokenClaims {
+
+ /** Time when the End-User authentication occurred.*/
+ AUTHENTICATION_TIME("auth_time"),
+
+ /** String value used to associate a Client session with an ID Token, and to mitigate replay attacks.*/
+ NONCE("nonce"),
+
+ /** Authentication Context Class Reference.*/
+ AUTHENTICATION_CONTEXT_CLASS_REFERENCE("acr"),
+
+ /** Authentication Methods References.*/
+ AUTHENTICATION_METHODS_REFERENCES("amr"),
+
+ /** Authorized party - the party to which the ID Token was issued.*/
+ AUTHORIZED_PARTY("azp");
+
+ /** The registered claim name.*/
+ @Nonnull @NotEmpty private final String claimName;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param name the registered claim name.
+ */
+ private IDTokenClaims(@Nonnull @NotEmpty final String name) {
+ claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
+ }
+
+ /**
+ * Get the registered claim name.
+ *
+ * @return the registered claim name.
+ */
+ @Nonnull public String getClaimName() {
+ return claimName;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java
new file mode 100644
index 0000000..540db8f
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java
@@ -0,0 +1,89 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff the 'iat' claim is present in the ID Token, verifies it is not to far away from
+ * the current time. A configured window/deviation is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.
+ */
+ at ThreadSafeAfterInit
+public class IssuedAtClaimsValidator extends AbstractClaimsValidator {
+
+ /**
+ * Maximum amount (in either direction from now) of duration for which a token is valid after
+ * it is issued (Default value: 60 seconds).
+ */
+ @Nonnull private Duration iatWindow;
+
+ /** Constructor.*/
+ public IssuedAtClaimsValidator() {
+ iatWindow = Duration.ofSeconds(60);
+ }
+
+ /**
+ * Sets the amount of time for which a token is valid from when it was issued.
+ *
+ * @param window amount of time for which a token is valid
+ */
+ public void setIatWindow(@Nonnull final Duration window) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ Constraint.isNotNull(window, "Token issued at window cannot be null");
+ Constraint.isFalse(window.isNegative(), "Token issued at window cannot be negative");
+
+ iatWindow = window;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claims,
+ @Nonnull final ProfileRequestContext context) throws JWTValidationException {
+
+ final Date iatDate = claims.getIssueTime();
+ if (iatDate != null) {
+ final Instant iat = iatDate.toInstant();
+ final Instant now = Instant.now();
+ final Duration iatDifference = Duration.between(now, iat).abs();
+
+ if (iatWindow.compareTo(iatDifference) < 0) {
+ throw new JWTValidationException("JWT issued-at time is too far away from the current time. "
+ + "Token issued at '"+iat+"' was too far away from the current time '"+now+"' "
+ + "with acceptable deviation of "
+ + "'"+iatWindow+"', difference is '"+iatDifference+"'");
+ }
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java
new file mode 100644
index 0000000..e82c626
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java
@@ -0,0 +1,74 @@
+/*
+ * 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.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/** Enum that represents the standard set of registered JWT claims. */
+public enum JWTClaims {
+
+ /** The "jti" (JWT ID) claim provides a unique identifier for the JWT.*/
+ JWT_ID_CLAIM("jti"),
+
+ /**Issuer Identifier for the Issuer of the response.*/
+ ISSUER_CLAIM("iss"),
+
+ /** Subject Identifier. A locally unique and never reassigned identifier within the
+ * Issuer for the End-User.*/
+ SUBJECT_CLAIM("sub"),
+
+ /** Audience(s) that this ID Token is intended for.*/
+ AUDIENCE_CLAIM("aud"),
+
+ /** Expiration time on or after which the ID Token MUST NOT be accepted for processing.*/
+ EXPIRATION_TIME_CLAIM("exp"),
+
+ /** Time at which the JWT was issued.*/
+ ISSUED_AT_CLAIM("iat"),
+
+ /** The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. */
+ NOT_BEFORE_CLAIM("nbf");
+
+ /** The IANA registered claim name.*/
+ @Nonnull @NotEmpty private final String claimName;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param name the IANA registered claim name.
+ */
+ private JWTClaims(@Nonnull @NotEmpty final String name) {
+ claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
+ }
+
+ /**
+ * Get the IANA registered claim name.
+ *
+ * @return the IANA registered claim name.
+ */
+ @Nonnull
+ public String getClaimName() {
+ return claimName;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java
new file mode 100644
index 0000000..8cc3e7a
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java
@@ -0,0 +1,43 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+/**
+ * Determines if nonce validation should occur based on if it exists in the JWT claims set. see section 3.1.3.7
+ * of the OpenID Connect core 1.0 specification.
+ */
+public class NonceValidationActiviationCondition implements BiPredicate<ProfileRequestContext, JWTClaimsSet>{
+
+ @Override
+ public boolean test(@Nonnull final ProfileRequestContext context, @Nonnull final JWTClaimsSet claims) {
+
+ if (claims.getClaim(IDTokenClaims.NONCE.getClaimName()) != null) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java
new file mode 100644
index 0000000..45d8051
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff a not-before-time (nbf) claim is present, makes sure it is before the current time. RFC7519 section 4.1.5.
+ */
+ at ThreadSafeAfterInit
+public class NotBeforeClaimsValidator extends AbstractClaimsValidator {
+
+ /**
+ * Positive clock skew adjustment to consider when checking JWT not before and expiration
+ * (Default value: 60 seconds).
+ */
+ @Nonnull private Duration clockSkew;
+
+ /** Constructor. */
+ public NotBeforeClaimsValidator() {
+ clockSkew = Duration.ofSeconds(60);
+ }
+
+ /**
+ * Set the clock skew.
+ *
+ * @param skew clock skew to set
+ */
+ public void setClockSkew(@Nonnull final Duration skew) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ final Instant now = Instant.now();
+ final Date nbf = claims.getNotBeforeTime();
+ if (nbf != null) {
+ final Instant nbfInstant = nbf.toInstant();
+ final Instant nbfInstantMinusSkew = nbfInstant.minus(clockSkew);
+ if (!nbfInstantMinusSkew.isBefore(now)) {
+ throw new JWTValidationException("JWT before use time");
+ }
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java
new file mode 100644
index 0000000..b20c47e
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Verify the claims set does not contain any of the described set of claims.
+ */
+ at ThreadSafeAfterInit
+public class ProhibitedClaimsValidator extends AbstractClaimsValidator {
+
+ /** The names of the JWT claims that must not be present, empty set if none. */
+ @Nonnull @NonnullElements private Set<String> prohibitedClaims;
+
+ /** Constructor.*/
+ public ProhibitedClaimsValidator() {
+ prohibitedClaims = Collections.emptySet();
+ }
+
+ /**
+ * Set the prohibited claims.
+ *
+ * @param claims the prohibited claims.
+ */
+ public void setProhibitedClaims(@Nullable final Set<String> claims) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (claims !=null) {
+ prohibitedClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
+ } else {
+ prohibitedClaims = Collections.emptySet();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ final Set<String> presentProhibitedClaims = new HashSet<>();
+ for (final String prohibited : prohibitedClaims) {
+ if (claims.getClaims().containsKey(prohibited)) {
+ presentProhibitedClaims.add(prohibited);
+ }
+ if (!presentProhibitedClaims.isEmpty()) {
+ throw new JWTValidationException("JWT has prohibited claims: " + presentProhibitedClaims);
+ }
+ }
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java
new file mode 100644
index 0000000..2ded0ef
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Verify the claims set contains the given set of required claims.
+ */
+ at ThreadSafeAfterInit
+public class RequiredClaimsValidator extends AbstractClaimsValidator{
+
+ /** The names of the JWT claims that must be present, if empty no claims are required.*/
+ @Nonnull @NonnullElements private Set<String> requiredClaims;
+
+ /** Constructor.*/
+ public RequiredClaimsValidator() {
+ requiredClaims = Collections.emptySet();
+ }
+
+ /**
+ * Set the required claims.
+ *
+ * @param claims the required claims.
+ */
+ public void setRequiredClaims(@Nullable final Set<String> claims) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (claims !=null) {
+ requiredClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
+ } else {
+ requiredClaims = Collections.emptySet();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+ throws JWTValidationException {
+
+ if (!claims.getClaims().keySet().containsAll(requiredClaims)) {
+ final Set<String> missingClaims = new HashSet<>(requiredClaims);
+ missingClaims.removeAll(claims.getClaims().keySet());
+ throw new JWTValidationException("JWT missing required claims: " + missingClaims);
+ }
+
+ }
+
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
new file mode 100644
index 0000000..618b2e3
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Validation functions for JWT claims. */
+package net.shibboleth.oidc.security.jwt.claims.impl;
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java
new file mode 100644
index 0000000..a4c708b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.jwt.claims.impl;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Abstract class for claims validator tests.
+ */
+public class AbstractClaimsValidatorTest {
+
+ /** The request context.*/
+ protected RequestContext src;
+
+ /** The profile request context.*/
+ protected ProfileRequestContext prc;
+
+ /** The authentication context to add to the profile request context.*/
+ protected AuthenticationContext ac;
+
+ /**
+ * <p>Setup the relevant contexts per method execution.</p>
+ *
+ * <p>Is not inherited, so must be enabled in concrete test classes, e.g.
+ * add a setup method and call super.</p>
+ *
+ * @throws ComponentInitializationException on error.
+ */
+ public void setup() throws ComponentInitializationException {
+
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+ ac = new AuthenticationContext();
+
+ prc.addSubcontext(ac);
+
+ }
+
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
new file mode 100644
index 0000000..1246f3b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link AudienceClaimsValidator}. */
+public class AudienceClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private AudienceClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new AudienceClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestMultipleAudiences() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().
+ audience(List.of("client-id","another-audience")).build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestMultipleAudiences() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().
+ audience(List.of("wrong-client","another-audience")).build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestEmptyAudiences() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience(Collections.emptyList()).build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id-different");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestNullAudienceInContext()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> null);
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestNullAudienceInJWT() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setAudienceLookupStrategy(prc -> "client-id");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java
new file mode 100644
index 0000000..036f3fc
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link AuthenticationTimeClaimsValidator}. */
+public class AuthenticationTimeClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private AuthenticationTimeClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new AuthenticationTimeClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc); }
+
+
+
+ @Test
+ public void doValidateTestWithinLifetime() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateNoClaimButNotActive() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setActivationCondition((prc,claims) -> false);
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestCanNotParseDate() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),"not-a-date").build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofMinutes(10)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateInTheFuture() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().plus(Duration.ofSeconds(30)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateNoClaim() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Would fail, but the claim was not requested, so it is not checked.
+ *
+ * @throws JWTValidationException on error.
+ * @throws ComponentInitializationException on error.
+ */
+ @Test
+ public void doValidateAuthTimeNotRequested() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setRequested(prc -> false);
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java
new file mode 100644
index 0000000..31f0761
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ChainingJWTClaimsValidation} class.*/
+public class ChainingJWTClaimsValidationTest extends AbstractClaimsValidatorTest{
+
+ /** Validation to check.*/
+ @Nonnull private ChainingJWTClaimsValidation validation;
+
+ /** Default set of claims.*/
+ @Nonnull private List<ClaimsValidator> validators;
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validation = new ChainingJWTClaimsValidation();
+
+ validators = new ArrayList<>(3);
+
+ //aud validator
+ final AudienceClaimsValidator audV = new AudienceClaimsValidator();
+ audV.setAudienceLookupStrategy(prc -> "audience");
+ audV.setId("audience-check");
+ audV.initialize();
+ validators.add(audV);
+
+ //nbf validator
+ final NotBeforeClaimsValidator nbfV = new NotBeforeClaimsValidator();
+ nbfV.setId("nbf-validator");
+ nbfV.setClockSkew(Duration.ofMinutes(1));
+ nbfV.initialize();
+ validators.add(nbfV);
+
+ //exp validator
+ final ExpiryClaimsValidator expV = new ExpiryClaimsValidator();
+ expV.setId("exp-validator");
+ expV.setClockSkew(Duration.ofMinutes(1));
+ expV.initialize();
+ validators.add(expV);
+
+ //iat validator
+ final IssuedAtClaimsValidator iatV = new IssuedAtClaimsValidator();
+ iatV.setId("iat-validator");
+ iatV.initialize();
+ validators.add(iatV);
+
+ //username exact validator
+ final ExactMatchClaimsValidator usernameExactV = new ExactMatchClaimsValidator();
+ usernameExactV.setId("username-validation");
+ usernameExactV.setClaimName("username");
+ usernameExactV.setValueToMatchLookupStrategy(prc -> "jdoe");
+ usernameExactV.initialize();
+ validators.add(usernameExactV);
+
+ //auth time
+ final AuthenticationTimeClaimsValidator authTimeV = new AuthenticationTimeClaimsValidator();
+ authTimeV.setId("auth-time-validator");
+ authTimeV.setAuthnLifetime(Duration.ofMinutes(1));
+ authTimeV.setRequested(prc -> true);
+ authTimeV.initialize();
+ validators.add(authTimeV);
+
+ //nonce
+ final ExactMatchClaimsValidator nonceV = new ExactMatchClaimsValidator();
+ nonceV.setId("nonce-validator");
+ nonceV.setActivationCondition((prc,claims) -> true);
+ nonceV.setClaimName(IDTokenClaims.NONCE.getClaimName());
+ nonceV.setValueToMatchLookupStrategy(prc -> "nonce");
+ validators.add(nonceV);
+
+ //required claims
+ final RequiredClaimsValidator reqV = new RequiredClaimsValidator();
+ reqV.setId("required-claims-validator");
+ reqV.setRequiredClaims(Set.of("iss","sub","aud","exp","iat"));
+ reqV.initialize();
+ validators.add(reqV);
+
+ validation.setClaimValidators(validators);
+ validation.initialize();
+ }
+
+ @Test(expectedExceptions = ComponentInitializationException.class)
+ public void testNoValidators() throws ComponentInitializationException {
+ final ChainingJWTClaimsValidation validationNew = new ChainingJWTClaimsValidation();
+ validationNew.initialize();
+ }
+
+ @Test
+ public void testNullValidators() throws ComponentInitializationException {
+ final ChainingJWTClaimsValidation validationNew = new ChainingJWTClaimsValidation();
+ validationNew.setClaimValidators(null);
+ validationNew.initialize();
+ }
+
+ @Test
+ public void testNullClaims() throws ComponentInitializationException, JWTValidationException {
+
+ validation.validate(null, prc);
+ }
+
+ @Test
+ public void validationSuccess() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedNoSubject() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedExpired() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().minus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedNotBefore() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedWrongNonce() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "wrong-nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedWrongUsername() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+ .claim("username", "wrong")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void validationFailedAuthTimeTooFarInPast() throws ComponentInitializationException, JWTValidationException {
+
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("issuer")
+ .subject("jdoe")
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+ .audience("audience")
+ .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+ .issueTime(Date.from(Instant.now()))
+ .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(300)).getEpochSecond())
+ .claim("username", "jdoe")
+ .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+ .build();
+
+ validation.validate(claimsSet, prc);
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java
new file mode 100644
index 0000000..3013972
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+
+/** Test for the {@link ExactMatchClaimsValidator}. */
+public class ExactMatchClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private ExactMatchClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new ExactMatchClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTestUsername() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("username", "jdoe").build();
+ validator.setId("test-validator");
+ validator.setClaimName("username");
+ validator.setValueToMatchLookupStrategy(prc -> "jdoe");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestNonce() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+ validator.setId("test-validator");
+ validator.setClaimName("nonce");
+ validator.setValueToMatchLookupStrategy(prc -> "a-nonce");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Test when trying a nonce that does not exist in the claimsset.
+ *
+ * @throws JWTValidationException on error.
+ * @throws ComponentInitializationException on error.
+ */
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestNonce() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setClaimName("nonce");
+ validator.setValueToMatchLookupStrategy(prc -> "a-nonce");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Test when trying a nonce that does not exist in the context.
+ *
+ * @throws JWTValidationException on error.
+ * @throws ComponentInitializationException on error.
+ */
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestNullContextNonce() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+ validator.setId("test-validator");
+ validator.setClaimName("nonce");
+ validator.setValueToMatchLookupStrategy(prc -> null);
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Test when trying a nonce but the validator is not active (no nonce in request).
+ *
+ * @throws JWTValidationException on error.
+ * @throws ComponentInitializationException on error.
+ */
+ @Test
+ public void doValidateTestFalseActivationCondition()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+ //mimic no nonce in the context, so do not activate validator - nothing should happen.
+ validator.setActivationCondition((prc,claims) -> false);
+ validator.setId("test-validator");
+ validator.setClaimName("nonce");
+ validator.setValueToMatchLookupStrategy(prc -> "wrong-nonce-should-not-matter");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Test when trying an invalid nonce and the activator is enabled.
+ *
+ * @throws JWTValidationException on error.
+ * @throws ComponentInitializationException on error.
+ */
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestTrueActivationCondition()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+
+ validator.setActivationCondition((prc,claims) -> true);
+ validator.setId("test-validator");
+ validator.setClaimName("nonce");
+ validator.setValueToMatchLookupStrategy(prc -> "wrong-nonce");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = ComponentInitializationException.class)
+ public void setRequiredFieldsNotSet() throws ComponentInitializationException {
+ validator.setId("test-validator");
+ validator.initialize();
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void setClaimNameTest() {
+ validator.setClaimName(null);
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void setValueToMatchLookupStrategyTest() {
+ validator.setActivationCondition(null);
+ }
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java
new file mode 100644
index 0000000..d66e2bd
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ExpiryClaimsValidatorTest}. */
+public class ExpiryClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private ExpiryClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new ExpiryClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now())).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now()
+ .minus(Duration.ofMinutes(5))))
+ .build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doInValidateTestButNotActive() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now()
+ .minus(Duration.ofMinutes(5))))
+ .build();
+ validator.setActivationCondition((prc,claims) -> false);
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doInValidateTestNoExp() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java
new file mode 100644
index 0000000..a450b82
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link IssuedAtClaimsValidator}. */
+public class IssuedAtClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private IssuedAtClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new IssuedAtClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(Date.from(Instant.now())).build();
+ validator.setId("test-validator");
+ validator.setIatWindow(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestIssuedInPastButInWindow()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+ Date.from(Instant.now().minus(Duration.ofMinutes(10)))).build();
+ validator.setId("test-validator");
+ validator.setIatWindow(Duration.ofMinutes(20));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestNoIssuedAtClaim() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setIatWindow(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestIssuedTooFarInPast() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+ Date.from(Instant.now().minus(Duration.ofMinutes(10)))).build();
+ validator.setId("test-validator");
+ validator.setIatWindow(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestIssuedTooFarInFuture() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+ Date.from(Instant.now().plus(Duration.ofMinutes(10)))).build();
+ validator.setId("test-validator");
+ validator.setIatWindow(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java
new file mode 100644
index 0000000..70f1245
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link NotBeforeClaimsValidator}. */
+public class NotBeforeClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private NotBeforeClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new NotBeforeClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now())).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestInFutureInsideWindow() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+ .plus(Duration.ofMinutes(1)))).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(2));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestInPast() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+ .minus(Duration.ofMinutes(10)))).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestNoNBFTime() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTestInFuture() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+ .plus(Duration.ofMinutes(10)))).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java
new file mode 100644
index 0000000..a028a9d
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ProhibitedClaimsValidator}. */
+public class ProhibitedClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private ProhibitedClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new ProhibitedClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+ validator.setId("test-validator");
+ validator.setProhibitedClaims(Set.of("prohibited"));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("prohibited", "whatever").build();
+ validator.setId("test-validator");
+ validator.setProhibitedClaims(Set.of("prohibited"));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+ @Test
+ public void doValidateTestNoProhibitedClaims() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+ validator.setId("test-validator");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestSetNullProhibitedClaims()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+ validator.setId("test-validator");
+ validator.setProhibitedClaims(null);
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java
new file mode 100644
index 0000000..8fa627c
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.jwt.claims.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link RequiredClaimsValidator}. */
+public class RequiredClaimsValidatorTest extends AbstractClaimsValidatorTest {
+
+ /** The validator to test.*/
+ @Nonnull private RequiredClaimsValidator validator;
+
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.setup();
+ validator = new RequiredClaimsValidator();
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("required", "whatever").build();
+ validator.setId("test-validator");
+ validator.setRequiredClaims(Set.of("required"));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("no-required", "whatever").build();
+ validator.setId("test-validator");
+ validator.setRequiredClaims(Set.of("required"));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+ @Test
+ public void doValidateTestNoRquiredClaims() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-required", "whatever").build();
+ validator.setId("test-validator");
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestSetNullRequiredClaims() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-required", "whatever").build();
+ validator.setId("test-validator");
+ validator.setRequiredClaims(null);
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
new file mode 100644
index 0000000..0752fb5
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Validation function tests for JWT claims. */
+package net.shibboleth.oidc.security.jwt.claims.impl;
\ No newline at end of file
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
index 9bac215..f5c8402 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
@@ -21,6 +21,8 @@ import javax.annotation.Nonnull;
import org.opensaml.storage.StorageService;
+import net.shibboleth.oidc.metadata.ClientInformationManager;
+import net.shibboleth.oidc.metadata.ClientInformationResolver;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list