[java-oidc-common] 01/01: JCOMOIDC-41 WIP.
Brent Putman
putmanb at georgetown.edu
Fri Feb 18 15:41:38 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=621b922d90007d26344d10caeffa3b10cbe27f4b
commit 621b922d90007d26344d10caeffa3b10cbe27f4b
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Feb 17 19:15:42 2022 -0500
JCOMOIDC-41 WIP.
---
.../security/impl/BaseSignedJWTTrustEngine.java | 257 +++++++++++++++++++++
.../impl/ExplicitKeySignedJWTTrustEngine.java | 101 ++++++++
.../security/impl/ExplicitKeyTrustEvaluator.java | 119 ++++++++++
3 files changed, 477 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..79d5366
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ExplicitKeySignedJWTTrustEngine.java
@@ -0,0 +1,101 @@
+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.KeyAlgorithmCriterion;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.trust.TrustedCredentialTrustEngine;
+import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+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));
+ }
+
+ final String jcaAlgorithm = AlgorithmSupport.getKeyAlgorithm(signedJWT.getHeader().getAlgorithm().getName());
+ 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