[java-oidc-common] branch dev/JCOMOIDC-41 updated: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter ...
Brent Putman
putmanb at georgetown.edu
Thu Jun 2 22:24:53 UTC 2022
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch dev/JCOMOIDC-41
in repository java-oidc-common.
View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=657d1082d3deb285d6a2246595b5dc3177c8b950
The following commit(s) were added to refs/heads/dev/JCOMOIDC-41 by this push:
new 657d108 JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter ...
657d108 is described below
commit 657d1082d3deb285d6a2246595b5dc3177c8b950
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Jun 2 18:10:07 2022 -0400
JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter ...
Refactor credential extraction from JOSE headers to separate resolver
component with interface and impl.
---
.../credential/JOSEObjectCredentialResolver.java | 35 +++++
.../security/criterion/JOSEObjectCriterion.java | 99 ++++++++++++++
.../impl/BasicJOSEObjectCredentialResolver.java | 150 +++++++++++++++++++++
.../security/impl/BaseSignedJWTTrustEngine.java | 71 ++++------
.../impl/ExplicitKeySignedJWTTrustEngine.java | 7 +-
.../impl/ExplicitKeySignedJWTTrustEngineTest.java | 13 +-
6 files changed, 323 insertions(+), 52 deletions(-)
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JOSEObjectCredentialResolver.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JOSEObjectCredentialResolver.java
new file mode 100644
index 0000000..2147d15
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JOSEObjectCredentialResolver.java
@@ -0,0 +1,35 @@
+/*
+ * 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.credential;
+
+import org.opensaml.security.credential.CredentialResolver;
+
+import com.nimbusds.jose.JOSEObject;
+
+import net.shibboleth.oidc.security.criterion.JOSEObjectCriterion;
+
+/**
+ * Specialized {@link CredentialResolver} marker interface for resolvers which resolve
+ * credentials from the headers of a {@link JOSEObject} instance.
+ *
+ * Implementations will typically require an instance of {@link JOSEObjectCriterion} within the
+ * criteria set which is supplied as input to the resolve methods.
+ */
+public interface JOSEObjectCredentialResolver extends CredentialResolver {
+
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JOSEObjectCriterion.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JOSEObjectCriterion.java
new file mode 100644
index 0000000..75eb799
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/criterion/JOSEObjectCriterion.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.criterion;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+
+import com.nimbusds.jose.JOSEObject;
+
+/**
+ * An implementation of {@link Criterion} which specifies criteria based
+ * on the contents of a {@link JOSEObject} element.
+ */
+public final class JOSEObjectCriterion implements Criterion {
+
+ /** The JOSEObject which serves as the source for credential criteria. */
+ private JOSEObject joseObject;
+
+ /**
+ * Constructor.
+ *
+ * @param newJOSEObject the JOSEObject credential criteria to use
+ */
+ public JOSEObjectCriterion(@Nullable final JOSEObject newJOSEObject) {
+ setJOSEObject(newJOSEObject);
+ }
+
+ /**
+ * Gets the JOSEObject which is the source of credential criteria.
+ *
+ * @return the JOSEObject credential criteria
+ */
+ @Nullable public JOSEObject getJOSEObject() {
+ return joseObject;
+ }
+
+ /**
+ * Sets the JOSEObject which is the source of credential criteria.
+ *
+ * @param newJOSEObject the JOSEObject to use as credential criteria
+ *
+ */
+ public void setJOSEObject(@Nullable final JOSEObject newJOSEObject) {
+ // Note: we allow JOSEObject to be null to handle case where application context,
+ // other accompanying criteria, etc should be used to resolve credentials.
+ joseObject = newJOSEObject;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("JOSEObjectCriterion [JOSEObject=");
+ builder.append("<contents not displayable>");
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ if (joseObject != null) {
+ return joseObject.hashCode();
+ }
+ return super.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof JOSEObjectCriterion) {
+ return joseObject.equals(((JOSEObjectCriterion) obj).joseObject);
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java
new file mode 100644
index 0000000..c3690c4
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java
@@ -0,0 +1,150 @@
+package net.shibboleth.oidc.security.credential.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Header;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObject;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.KeyType;
+
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.criterion.JOSEObjectCriterion;
+import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * A basic implementation of {@link JOSEObjectCredentialResolver}.
+ *
+ */
+public class BasicJOSEObjectCredentialResolver implements JOSEObjectCredentialResolver {
+ // TODO support auto-magical filtering ala OpenSAML AbstractCriteriaFilteringCredentialResolver?
+ // It's impl, so would have to copy or refactor to common library or promote to API.
+
+ /** Logger. */
+ private final Logger log = LoggerFactory.getLogger(BasicJOSEObjectCredentialResolver.class);
+
+ /** {@inheritDoc} */
+ @Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+ final Iterable<Credential> creds = resolve(criteriaSet);
+ if (creds.iterator().hasNext()) {
+ return creds.iterator().next();
+ }
+ return null;
+ }
+
+ @Override
+ @Nonnull @NonnullElements public Iterable<Credential> resolve(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+ JOSEObjectCriterion joseObjectCriteria = null;
+ if (criteriaSet != null) {
+ joseObjectCriteria = criteriaSet.get(JOSEObjectCriterion.class);
+ }
+
+ if (joseObjectCriteria == null) {
+ log.error("No JOSEObject criteria supplied, resolver could not process");
+ throw new ResolverException(
+ "Credential criteria set did not contain an instance of JOSEObjectCriterion");
+ }
+ final JOSEObject joseObject = joseObjectCriteria.getJOSEObject();
+ if (joseObject == null) {
+ throw new ResolverException("JOSEObjectCriterion did not contain an instance of JOSEObject");
+ }
+
+ final Header header = joseObject.getHeader();
+ if (JWSHeader.class.isInstance(header)) {
+ return processJWSHeader(JWSHeader.class.cast(header));
+ } else if (JWEHeader.class.isInstance(header)) {
+ return processJWEHeader(JWEHeader.class.cast(header));
+ } else {
+ throw new ResolverException("Saw unknown JOSEObject header type: " +
+ header != null ? header.getClass().getName() : "null");
+ }
+
+ }
+
+ /**
+ * Process credentials indicated by a JWS header.
+ *
+ * @param jwsHeader the JWS header to process
+ * @return
+ */
+ @Nonnull @NonnullElements protected Iterable<Credential> processJWSHeader(@Nonnull final JWSHeader jwsHeader) {
+ final List<Credential> credentials = new ArrayList<>();
+
+ // JWK
+ if (jwsHeader.getJWK() != null) {
+ final Credential cred = buildJWKCredential(jwsHeader.getJWK(), jwsHeader.getKeyID());
+ if (cred != null) {
+ credentials.add(cred);
+ }
+ }
+
+ // TODO JWK URL
+ // TODO X509 cert and chain types
+
+ return credentials;
+ }
+
+ @Nonnull @NonnullElements protected Iterable<Credential> processJWEHeader(@Nonnull final JWEHeader jweHeader) {
+ final List<Credential> credentials = new ArrayList<>();
+
+ // JWK
+ if (jweHeader.getJWK() != null) {
+ final Credential cred = buildJWKCredential(jweHeader.getJWK(), jweHeader.getKeyID());
+ if (cred != null) {
+ credentials.add(cred);
+ }
+ }
+
+ // TODO JWK URL
+ // TODO X509 cert and chain types
+ // TODO ECDH key agreement
+
+ return credentials;
+ }
+
+ @Nullable protected BasicJWKCredential buildJWKCredential(@Nonnull final JWK jwk, @Nullable final String headerKid) {
+
+ final BasicJWKCredential credential = new BasicJWKCredential();
+ if (jwk.getKeyType() == KeyType.EC || jwk.getKeyType() == KeyType.RSA) {
+ try {
+ credential.setPublicKey(((AsymmetricJWK) jwk).toPublicKey());
+ } catch (final JOSEException e) {
+ log.warn("Could not parse public key from JWK", e);
+ return null;
+ }
+ } else {
+ log.warn("Unsupported key type {} found from JWK", jwk.getKeyType());
+ return null;
+ }
+ if (jwk.getKeyID() != null) {
+ credential.getKeyNames().add(jwk.getKeyID());
+ credential.setKid(jwk.getKeyID());
+ }
+ if (headerKid != null && !headerKid.equals(credential.getKid())) {
+ log.warn("Key ID in JOSE header does not match 'kid' in JWK");
+ return null;
+
+ }
+
+ if (jwk.getKeyUse() != null) {
+ credential.setUsageType(CredentialConversionUtil.getUsageType(jwk));
+ }
+ return credential;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
index e372896..7fd80b6 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
@@ -22,20 +22,33 @@ import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
-import com.nimbusds.jose.jwk.AsymmetricJWK;
-import com.nimbusds.jose.jwk.JWK;
-import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jwt.SignedJWT;
-import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.criterion.JOSEObjectCriterion;
+import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustEngine<SignedJWT> {
/** Logger. */
private final Logger log = LoggerFactory.getLogger(BaseSignedJWTTrustEngine.class);
+ /** Resolver of credentials from JOSEObject headers. */
+ private JOSEObjectCredentialResolver joseObjectCredentialResolver;
+
+ /**
+ * Constructor.
+ *
+ * @param joseObjectResolver resolver of credentials from JOSEObject headers.
+ */
+ public BaseSignedJWTTrustEngine(@Nonnull final JOSEObjectCredentialResolver joseObjectResolver) {
+ joseObjectCredentialResolver = Constraint.isNotNull(joseObjectResolver,
+ "JOSEObject credential resolver cannot be null");
+ }
+
@Override
public boolean validate(@Nonnull final SignedJWT signedJWT,
@Nonnull final CriteriaSet trustBasisCriteria) throws SecurityException {
@@ -215,51 +228,15 @@ public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustE
@Nonnull protected Collection<Credential> resolveTokenCredentials(@Nonnull final SignedJWT signedJWT)
throws SecurityException {
- //TODO handle JWK Key Set (jku)?
-
- final List<Credential> credentials = new ArrayList<>();
-
- if (signedJWT.getHeader().getJWK() != null) {
- final Credential cred = buildCredential(signedJWT.getHeader().getJWK(),
- signedJWT.getHeader().getKeyID());
- if (cred != null) {
- credentials.add(cred);
- }
+ try {
+ final List<Credential> creds = new ArrayList<>();
+ joseObjectCredentialResolver.resolve(new CriteriaSet(new JOSEObjectCriterion(signedJWT)))
+ .forEach(creds::add);
+ return creds;
+ } catch (final ResolverException e) {
+ throw new SecurityException("Error resolving credentials from JOSEObject", e);
}
- return credentials;
- }
-
- //TODO maybe just temporary in favor of separate components?
- @Nullable protected BasicJWKCredential buildCredential(@Nonnull final JWK jwk,
- @Nullable final String headerKid) {
-
- final BasicJWKCredential credential = new BasicJWKCredential();
- if (jwk.getKeyType() == KeyType.EC || jwk.getKeyType() == KeyType.RSA) {
- try {
- credential.setPublicKey(((AsymmetricJWK) jwk).toPublicKey());
- } catch (final JOSEException e) {
- log.warn("Could not parse public key from JWK", e);
- return null;
- }
- } else {
- log.warn("Unsupported key type {} found from JWK", jwk.getKeyType());
- return null;
- }
- if (jwk.getKeyID() != null) {
- credential.getKeyNames().add(jwk.getKeyID());
- credential.setKid(jwk.getKeyID());
- }
- if (headerKid != null && !headerKid.equals(credential.getKid())) {
- log.warn("Key ID in JOSE header does not match 'kid' in JWK");
- return null;
-
- }
-
- if (jwk.getKeyUse() != null) {
- credential.setUsageType(CredentialConversionUtil.getUsageType(jwk));
- }
- return credential;
}
}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
index 3ecdf82..367e63a 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
import com.nimbusds.jwt.SignedJWT;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
import net.shibboleth.oidc.security.credential.impl.EvaluableKeyIDCredentialCriterion;
import net.shibboleth.oidc.security.criterion.KeyIdCriterion;
import net.shibboleth.utilities.java.support.annotation.ParameterName;
@@ -48,7 +49,7 @@ public class ExplicitKeySignedJWTTrustEngine extends BaseSignedJWTTrustEngine<It
/** Resolver used for resolving trusted credentials. */
private final CredentialResolver credentialResolver;
-
+
/** The external explicit key trust engine to use as a basis for trust in this implementation. */
private final ExplicitKeyTrustEvaluator keyTrust;
@@ -59,7 +60,9 @@ public class ExplicitKeySignedJWTTrustEngine extends BaseSignedJWTTrustEngine<It
* @param keyInfoResolver KeyInfo credential resolver used to obtain the (advisory) signing credential from a
* Signature's KeyInfo element.
*/
- public ExplicitKeySignedJWTTrustEngine(@Nonnull final @ParameterName(name="resolver") CredentialResolver resolver) {
+ public ExplicitKeySignedJWTTrustEngine(@Nonnull final @ParameterName(name="resolver") CredentialResolver resolver,
+ @Nonnull final @ParameterName(name="JOSEObjectResolver") JOSEObjectCredentialResolver joseObjectResolver) {
+ super(joseObjectResolver);
credentialResolver = Constraint.isNotNull(resolver, "Credential resolver cannot be null");
keyTrust = new ExplicitKeyTrustEvaluator();
}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java
index 18f6dd9..1bd1791 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngineTest.java
@@ -31,6 +31,8 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
@@ -42,6 +44,8 @@ public class ExplicitKeySignedJWTTrustEngineTest {
private CredentialResolver credResolver;
+ private JOSEObjectCredentialResolver joseObjectCredResolver;
+
@BeforeMethod
public void setup() throws JOSEException {
key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
@@ -66,7 +70,10 @@ public class ExplicitKeySignedJWTTrustEngineTest {
return List.of(resolveSingle(criteria));
}
};
- engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+
+ joseObjectCredResolver = new BasicJOSEObjectCredentialResolver();
+
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver, joseObjectCredResolver);
}
@Test
@@ -107,7 +114,7 @@ public class ExplicitKeySignedJWTTrustEngineTest {
return Collections.emptyList();
}
};
- engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver, joseObjectCredResolver);
final CriteriaSet criteria = new CriteriaSet();
@@ -136,7 +143,7 @@ public class ExplicitKeySignedJWTTrustEngineTest {
return Collections.emptyList();
}
};
- engine = new ExplicitKeySignedJWTTrustEngine(credResolver);
+ engine = new ExplicitKeySignedJWTTrustEngine(credResolver, joseObjectCredResolver);
final CriteriaSet criteria = new CriteriaSet();
criteria.add(new UsageCriterion(UsageType.SIGNING));
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list