[java-oidc-common] 01/01: WIP.

Brent Putman putmanb at georgetown.edu
Fri Feb 18 01:25:21 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=7a9a361a2157f8e144f888c270f876209133bddc

commit 7a9a361a2157f8e144f888c270f876209133bddc
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Feb 17 19:15:42 2022 -0500

    WIP.
---
 .../security/impl/BaseSignedJWTTrustEngine.java    | 257 +++++++++++++++++++++
 .../impl/ExplicitKeySignedJWTTrustEngine.java      | 100 ++++++++
 .../security/impl/ExplicitKeyTrustEvaluator.java   | 119 ++++++++++
 3 files changed, 476 insertions(+)

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
new file mode 100644
index 0000000..2e426ab
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseSignedJWTTrustEngine.java
@@ -0,0 +1,257 @@
+package net.shibboleth.oidc.security.impl;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+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.JWKCredential;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public abstract class BaseSignedJWTTrustEngine<TrustBasisType> implements TrustEngine<SignedJWT> {
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(BaseSignedJWTTrustEngine.class);
+    
+    @Override
+    public boolean validate(@Nonnull final SignedJWT signedJWT,
+            @Nonnull final CriteriaSet trustBasisCriteria) throws SecurityException {
+        
+        checkParams(signedJWT, trustBasisCriteria);
+        
+        /* TODO - need correct components, and/or to account for diffs between XML and JOSE algorithm identifiers
+         * 
+        final SignatureValidationParametersCriterion validationCriterion = 
+                trustBasisCriteria.get(SignatureValidationParametersCriterion.class);
+        if (validationCriterion != null) {
+            log.debug("Performing signature algorithm include/exclude validation using params from CriteriaSet");
+            final SignatureAlgorithmValidator algorithmValidator = 
+                    new SignatureAlgorithmValidator(validationCriterion.getSignatureValidationParameters());
+            try {
+                algorithmValidator.validate(signature);
+            } catch (final SignatureException e) {
+                log.warn("XML signature failed algorithm include/exclude validation");
+                return false;
+            }
+        }
+        */
+        
+        return doValidate(signedJWT, trustBasisCriteria);
+    }
+    
+    /**
+     * Validate the signed JWT using the supplied trust criteria.
+     * 
+     * @param signedJWT the signed JWT to validate
+     * @param trustBasisCriteria criteria used to describe and/or resolve the information
+     *          which serves as the basis for trust evaluation
+     * @return true if signature is valid and trusted, false otherwise
+     * @throws SecurityException if there is a fatal error evaluating the signature
+     */
+    protected abstract boolean doValidate(@Nonnull final SignedJWT signedJWT, @Nonnull final CriteriaSet trustBasisCriteria)
+            throws  SecurityException ;
+    
+    /**
+     * Attempt to establish trust by resolving token verification credentials from the token itself. If any
+     * credentials so resolved correctly verify the signature, attempt to establish trust using subclass-specific trust
+     * logic against trusted information as implemented in {@link #evaluateTrust(Credential, Object)}.
+     * 
+     * @param signedJWT the signed JWT to evaluate
+     * @param trustBasis the information which serves as the basis for trust evaluation
+     * @return true if the signature is verified by any token-derived credential which can be established as trusted,
+     *         otherwise false
+     * @throws SecurityException if an error occurs during signature verification or trust processing
+     */
+    protected boolean validate(@Nonnull final SignedJWT signedJWT, @Nullable final TrustBasisType trustBasis)
+            throws SecurityException {
+
+        log.debug("Attempting to verify signature and establish trust using token-derived credentials");
+        
+        final Collection<Credential> tokenCredentials = resolveTokenCredentials(signedJWT);
+        
+        if (!tokenCredentials.isEmpty()) {
+            log.debug("Resolved {} token-derived credentials", tokenCredentials.size());
+            for (final Credential tokenCred : tokenCredentials) {
+                if (verifySignature(signedJWT, tokenCred)) {
+                    log.debug("Successfully verified signature using token-derived credential");
+                    log.debug("Attempting to establish trust of token-derived credential");
+                    if (evaluateTrust(tokenCred, trustBasis)) {
+                        log.debug("Successfully established trust of token-derived credential");
+                        return true;
+                    }
+                    log.debug("Failed to establish trust of token-derived credential");
+                }
+            }
+        } else {
+            log.debug("SignedJWT contained no resolveable credentials, nothing to evaluate");
+        }
+
+        log.debug("Failed to verify signature and/or establish trust using any token-derived credentials");
+        return false;
+    }
+    
+    /**
+     * Attempt to verify a signature using the key from the supplied credential.
+     * 
+     * @param signature the signature on which to attempt verification
+     * @param credential the credential containing the candidate validation key
+     * @return true if the signature can be verified using the key from the credential, otherwise false
+     */
+    protected boolean verifySignature(@Nonnull final SignedJWT signedJWT, @Nonnull final Credential credential)
+            throws SecurityException {
+        
+        try {
+            final Algorithm algorithm = signedJWT.getHeader().getAlgorithm();
+            final JWSVerifier verifier = initializeVerifier(algorithm, credential);
+            if (verifier == null) {
+                log.debug("No verifier for given JWT and Credential pair for alg {}", algorithm.getName());
+                return false;
+            }
+            if (signedJWT.verify(verifier)) {
+                if (log.isDebugEnabled()) {
+                    final String kid = credential instanceof JWKCredential ?
+                            ((JWKCredential) credential).getKid() : null;
+                    log.debug("JWT {} verified using algorithm {}{}", signedJWT.serialize(),
+                            algorithm.getName(), 
+                            kid != null ? " and key " + kid : "");
+                }
+                log.debug("Signature validation using candidate credential was successful");
+                return true;
+            }
+            log.debug("Unable to validate given JWT with credential");
+            return false;
+        } catch (final JOSEException | IllegalStateException e) {
+            final String kid = credential instanceof JWKCredential ?
+                    ((JWKCredential) credential).getKid() : null;
+            log.warn("Exception caught when validating given JWT{}",
+                    kid != null ? " with credential " + kid : "", e);
+            return false;
+        }
+    }
+    
+    /**
+     * Initializes a {@link JWSVerifier} for the given algorithm, using the provided {@link Credential}.
+     * @param algorithm The algorithm used for deciding the verifier.
+     * @param credential The credential to be used for the verifier.
+     * @return A corresponding verifier, or null if no supported found.
+     * @throws JOSEException If the credential doesn't meet the verifier requirements.
+     */
+    private static JWSVerifier initializeVerifier(final Algorithm algorithm, final Credential credential)
+            throws JOSEException {
+        if (JWSAlgorithm.Family.HMAC_SHA.contains(algorithm) && credential.getSecretKey() != null) {
+            return new MACVerifier(credential.getSecretKey());
+        }
+        if (JWSAlgorithm.Family.RSA.contains(algorithm) && credential.getPublicKey() instanceof RSAPublicKey) {
+            return new RSASSAVerifier((RSAPublicKey) credential.getPublicKey());
+        }
+        if (JWSAlgorithm.Family.EC.contains(algorithm) && credential.getPublicKey() instanceof ECPublicKey) {
+            return new ECDSAVerifier((ECPublicKey) credential.getPublicKey());
+        }
+        return null;
+    }
+
+    /**
+     * Evaluate the untrusted KeyInfo-derived credential with respect to the specified trusted information.
+     * 
+     * @param untrustedCredential the untrusted credential being evaluated
+     * @param trustBasis the information which serves as the basis for trust evaluation
+     * 
+     * @return true if the trust can be established for the untrusted credential, otherwise false
+     * 
+     * @throws SecurityException if an error occurs during trust processing
+     */
+    protected abstract boolean evaluateTrust(@Nonnull final Credential untrustedCredential,
+            @Nullable final TrustBasisType trustBasis) throws SecurityException;
+
+    /**
+     * Check the signed JWT and supplied criteria for required values.
+     * 
+     * @param signedJWT the signed JWT to be evaluated
+     * @param trustBasisCriteria the set of trusted credential criteria
+     * @throws SecurityException thrown if required values are absent or otherwise invalid
+     */
+    protected void checkParams(@Nonnull final SignedJWT signedJWT, @Nonnull final CriteriaSet trustBasisCriteria)
+            throws SecurityException {
+
+        if (signedJWT == null) {
+            throw new SecurityException("SignedJWT cannot be null");
+        } else if (trustBasisCriteria == null) {
+            throw new SecurityException("Trust basis criteria set cannot be null");
+        } else if (trustBasisCriteria.isEmpty()) {
+            throw new SecurityException("Trust basis criteria set cannot be empty");
+        }
+    }
+    
+    /**
+     * Resolve any credentials indicated in the token, such as by inline JWK.
+     * 
+     * @param signedJWT the signed JWT to be evaluated
+     * @return a collection of credentials derived from the token, may be empty
+     * @throws SecurityException thrown if here is a fatal exception resolving credentials
+     */
+    @Nonnull protected Collection<Credential> resolveTokenCredentials(@Nonnull final SignedJWT signedJWT)
+            throws SecurityException {
+        
+        //TODO handle JWK Key Set (jku)?
+        
+        List<Credential> credentials = new ArrayList<>();
+        
+        if (signedJWT.getHeader().getJWK() != null) {
+            final Credential cred = buildCredential(signedJWT.getHeader().getJWK());
+            if (cred != null) {
+                credentials.add(cred);
+            }
+        }
+        
+        return credentials;
+    }
+    
+    //TODO maybe just temporary in favor of separate components?
+    @Nullable protected BasicJWKCredential buildCredential(@Nonnull final JWK jwk) {
+        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 (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
new file mode 100644
index 0000000..7816c14
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
@@ -0,0 +1,100 @@
+package net.shibboleth.oidc.security.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.trust.TrustedCredentialTrustEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+public class ExplicitKeySignedJWTTrustEngine extends BaseSignedJWTTrustEngine<Iterable<Credential>>
+    implements TrustedCredentialTrustEngine<SignedJWT> {
+    
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(ExplicitKeySignedJWTTrustEngine.class);
+
+    /** 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;
+
+    /**
+     * Constructor.
+     * 
+     * @param resolver credential resolver used to resolve trusted credentials.
+     * @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) {
+        credentialResolver = Constraint.isNotNull(resolver, "Credential resolver cannot be null");
+        keyTrust = new ExplicitKeyTrustEvaluator();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public CredentialResolver getCredentialResolver() {
+        return credentialResolver;
+    }
+
+    @Override
+    protected boolean doValidate(@Nonnull final SignedJWT signedJWT, @Nonnull final CriteriaSet trustBasisCriteria)
+            throws SecurityException {
+        
+        final CriteriaSet criteriaSet = new CriteriaSet();
+        criteriaSet.addAll(trustBasisCriteria);
+        if (!criteriaSet.contains(UsageCriterion.class)) {
+            criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
+        }
+        
+        /* TODO reconcile how algo IDs are to be handled
+        final String jcaAlgorithm = AlgorithmSupport.getKeyAlgorithm(signature.getSignatureAlgorithm());
+        if (!Strings.isNullOrEmpty(jcaAlgorithm)) {
+            criteriaSet.add(new KeyAlgorithmCriterion(jcaAlgorithm), true);
+        }
+        */
+
+        final Iterable<Credential> trustedCredentials;
+        try {
+            trustedCredentials = getCredentialResolver().resolve(criteriaSet);
+        } catch (final ResolverException e) {
+            throw new SecurityException("Error resolving trusted credentials", e);
+        }
+
+        if (validate(signedJWT, trustedCredentials)) {
+            return true;
+        }
+
+        // If the credentials extracted from token (if any) did not verify the
+        // signature and/or establish trust, as a fall back attempt verify the signature with
+        // the trusted credentials directly.
+        log.debug("Attempting to verify signature using trusted credentials");
+
+        for (final Credential trustedCredential : trustedCredentials) {
+            if (verifySignature(signedJWT, trustedCredential)) {
+                log.debug("Successfully verified signature using resolved trusted credential");
+                return true;
+            }
+        }
+        log.debug("Failed to verify signature using either token-derived or directly trusted credentials");
+        return false;
+    }
+
+    @Override
+    protected boolean evaluateTrust(@Nonnull final Credential untrustedCredential,
+            @Nonnull final Iterable<Credential> trustedCredentials) throws SecurityException {
+        
+        return keyTrust.validate(untrustedCredential, trustedCredentials);
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeyTrustEvaluator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeyTrustEvaluator.java
new file mode 100644
index 0000000..b3074b6
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeyTrustEvaluator.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.security.Key;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Auxillary trust evaluator for evaluating an untrusted key or credential against a trusted key or credential. Trust is
+ * established if the untrusted key (a public key or symmetric key from the untrusted credential) matches one of the
+ * trusted keys supplied.
+ */
+public class ExplicitKeyTrustEvaluator {
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(ExplicitKeyTrustEvaluator.class);
+
+    /**
+     * Evaluate trust.
+     * 
+     * @param untrustedKey the untrusted key to evaluate
+     * @param trustedKey basis for trust
+     * @return true if trust can be established, false otherwise
+     */
+    public boolean validate(@Nonnull final Key untrustedKey, @Nonnull final Key trustedKey) {
+        return untrustedKey.equals(trustedKey);
+    }
+
+    /**
+     * Evaluate trust.
+     * 
+     * @param untrustedKey the untrusted key to evaluate
+     * @param trustedKeys basis for trust
+     * @return true if trust can be established, false otherwise
+     */
+    public boolean validate(@Nonnull final Key untrustedKey, @Nonnull final Iterable<Key> trustedKeys) {
+        for (final Key trustedKey : trustedKeys) {
+            if (untrustedKey.equals(trustedKey)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Evaluate trust.
+     * 
+     * @param untrustedCredential the untrusted credential to evaluate
+     * @param trustedCredential basis for trust
+     * @return true if trust can be established, false otherwise
+     */
+    public boolean validate(@Nonnull final Credential untrustedCredential,
+            @Nonnull final Credential trustedCredential) {
+
+        Key untrustedKey = null;
+        Key trustedKey = null;
+        if (untrustedCredential.getPublicKey() != null) {
+            untrustedKey = untrustedCredential.getPublicKey();
+            trustedKey = trustedCredential.getPublicKey();
+        } else {
+            untrustedKey = untrustedCredential.getSecretKey();
+            trustedKey = trustedCredential.getSecretKey();
+        }
+        if (untrustedKey == null) {
+            log.debug("Untrusted credential contained no key, unable to evaluate");
+            return false;
+        } else if (trustedKey == null) {
+            log.debug("Trusted credential contained no key of the appropriate type, unable to evaluate");
+            return false;
+        }
+
+        if (validate(untrustedKey, trustedKey)) {
+            log.debug("Successfully validated untrusted credential against trusted key");
+            return true;
+        }
+
+        log.debug("Failed to validate untrusted credential against trusted key");
+        return false;
+    }
+
+    /**
+     * Evaluate trust.
+     * 
+     * @param untrustedCredential the untrusted credential to evaluate
+     * @param trustedCredentials basis for trust
+     * @return true if trust can be established, false otherwise
+     */
+    public boolean validate(@Nonnull final Credential untrustedCredential,
+            @Nonnull final Iterable<Credential> trustedCredentials) {
+
+        for (final Credential trustedCredential : trustedCredentials) {
+            if (validate(untrustedCredential, trustedCredential)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list