[java-oidc-common] 07/35: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter ...

Phil Smart philip.smart at jisc.ac.uk
Tue Sep 20 14:19:07 UTC 2022


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

philsmart pushed a commit to branch dev/JCOMOIDC-41
in repository java-oidc-common.

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

commit 94dd3a154cc72d1454e1875e0aa6aca107f970c9
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