[java-oidc-common] branch dev/JCOMOIDC-41 updated: Add additional key criteria to the criteria set for credential resolvers
Phil Smart
philip.smart at jisc.ac.uk
Thu Jul 14 15:18:33 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=4621c611398ace153a3344deb220c2e31662fb21
The following commit(s) were added to refs/heads/dev/JCOMOIDC-41 by this push:
new 4621c61 Add additional key criteria to the criteria set for credential resolvers
4621c61 is described below
commit 4621c611398ace153a3344deb220c2e31662fb21
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Jul 14 16:18:27 2022 +0100
Add additional key criteria to the criteria set for credential resolvers
- Added KeyAlgorithmCriterion, KeyLengthCriterion, JOSEObjectCriterion,
and EvaluableKeyIDCredentialCriterion to the resolver criteria set.
---
.../impl/CriterionCredentialResolver.java | 7 +-
.../oidc/security/impl/JWTDecrypter.java | 146 ++++++++++++++++++---
2 files changed, 133 insertions(+), 20 deletions(-)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/CriterionCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/CriterionCredentialResolver.java
index ca790a6..11e8b38 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/CriterionCredentialResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/CriterionCredentialResolver.java
@@ -58,10 +58,7 @@ public class CriterionCredentialResolver extends BasicJOSEObjectCredentialResolv
final Credential credential = credentialCriterion.getCredential();
if (matchUsage(credential.getUsageType(), usage)) {
- final KeyAlgorithmCriterion keyAlg = criteriaSet.get(KeyAlgorithmCriterion.class);
-
- log.debug("Selected key '{}', for usage '{}', matched to alg '{}'",
- credential.getKeyNames(),usage, keyAlg);
+ log.debug("Selected key '{}', for usage '{}'", credential.getKeyNames(),usage);
return List.of(credential);
} else {
log.warn("Usage type of credential from criteria did not match the effective usage input");
@@ -74,7 +71,7 @@ public class CriterionCredentialResolver extends BasicJOSEObjectCredentialResolv
}
}
-
+ //FIXME are these needed if we have an evaluable usage type?
/**
* Match usage enum type values from credential in the criteria set to the usage in the criteria.
*
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
index d2fe1dd..ddee296 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
@@ -19,15 +19,20 @@ package net.shibboleth.oidc.security.impl;
import java.security.interfaces.ECPrivateKey;
import java.text.ParseException;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.KeyAlgorithmCriterion;
+import org.opensaml.security.criteria.KeyLengthCriterion;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.opensaml.xmlsec.encryption.EncryptedType;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,8 +53,10 @@ import org.slf4j.LoggerFactory;
* limitations under the License.
*/
+import com.google.common.base.Strings;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObject;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEDecrypter;
import com.nimbusds.jose.JWEObject.State;
@@ -63,9 +70,11 @@ import com.nimbusds.jwt.JWTParser;
import net.shibboleth.oidc.security.JWTDecryptionParameters;
import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.credential.impl.EvaluableKeyIDCredentialCriterion;
import net.shibboleth.oidc.security.credential.impl.JWKEncryptionCredentialContext;
import net.shibboleth.oidc.security.criterion.JOSEObjectCriterion;
import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.Criterion;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
@@ -107,6 +116,11 @@ public class JWTDecrypter {
*/
@Nonnull public JWT decrypt(@Nonnull final EncryptedJWT encryptedObject) throws DecryptionException {
+ if (encryptedObject.getHeader() == null) {
+ // Not sure it should ever get here, but just in-case
+ throw new DecryptionException("JWT headers are not available, decryption failed");
+ }
+
final JWEAlgorithm jwtAlg = encryptedObject.getHeader().getAlgorithm();
// Direct Encryption
@@ -144,21 +158,123 @@ public class JWTDecrypter {
}
/**
- * Build a criteria set using the additional criteria in the params and those supplied.
+ * Build a criteria set using the additional criteria in the params, those supplied, and those
+ * that can be extracted from the encrypted JWT headers.
*
+ * @param encryptedObject the encrypted JWT to build criterion from
* @param criteria criteria supplied, can be {@literal null}.
*
* @return the build criteria set.
*/
- private CriteriaSet buildCriteria(@Nullable final List<Criterion> criteria) {
- final CriteriaSet crit = new CriteriaSet();
+ private CriteriaSet buildCriteria(@Nonnull final EncryptedJWT encryptedObject,
+ @Nullable final List<Criterion> criteria) {
+ final CriteriaSet newCriteriaSet = new CriteriaSet();
+
+ // Add any additional criteria that have been passed through from the params.
if (params.getAdditionalCriteria() != null) {
- params.getAdditionalCriteria().forEach(c -> crit.add(c));
+ params.getAdditionalCriteria().forEach(newCriteriaSet::add);
}
if (criteria != null) {
- criteria.forEach(c -> crit.add(c));
+ criteria.forEach(newCriteriaSet::add);
+ }
+
+ // Add the entire object so the resolver can access it
+ newCriteriaSet.add(new JOSEObjectCriterion(encryptedObject));
+
+ final Set<Criterion> keyCriteria = buildKeyCriteria(encryptedObject);
+ if (keyCriteria != null && !keyCriteria.isEmpty()) {
+ newCriteriaSet.addAll(keyCriteria);
+ }
+
+ return newCriteriaSet;
+ }
+
+ /**
+ * Build decryption key credential criteria according to information in the encrypted object.
+ *
+ * @param encryptedObject the encrypted JWT from which to deduce decryption key criteria
+ * @return a set of credential criteria pertaining to the decryption key
+ */
+ @Nullable private Set<Criterion> buildKeyCriteria(@Nonnull final EncryptedJWT encryptedObject) {
+ final EncryptionMethod encMethod = encryptedObject.getHeader().getEncryptionMethod();
+ if (encMethod == null) {
+ // This element is optional
+ return null;
+ }
+ final String encAlgorithmURI = StringSupport.trimOrNull(encMethod.getName());
+ if (encAlgorithmURI == null) {
+ return null;
+ }
+
+ final Set<Criterion> critSet = new HashSet<>(2);
+
+ final KeyAlgorithmCriterion algoCrit = buildKeyAlgorithmCriteria(encAlgorithmURI);
+ if (algoCrit != null) {
+ critSet.add(algoCrit);
+ log.debug("Added decryption key algorithm criteria: {}", algoCrit.getKeyAlgorithm());
+ }
+
+ KeyLengthCriterion lengthCrit = buildKeyLengthCriteria(encAlgorithmURI);
+ if (lengthCrit != null) {
+ critSet.add(lengthCrit);
+ log.debug("Added decryption key length criteria from EncryptionMethod algorithm URI: {}", lengthCrit
+ .getKeyLength());
+ } else {
+ if (encMethod.cekBitLength() != 0) {
+ lengthCrit = new KeyLengthCriterion(encMethod.cekBitLength());
+ critSet.add(lengthCrit);
+ log.debug("Added decryption key length criteria from EncryptionMethod/KeySize: {}", lengthCrit
+ .getKeyLength());
+ }
}
- return crit;
+
+ // If 'kid' exists in the header, create an EvaluableKeyID criterion
+ if (encryptedObject.getHeader().getKeyID() != null) {
+ // FIXME: This is created directly as an EvaluableCriterion as I can not see a way to
+ // add to the default mappings in EvaluableCredentialCriteriaRegistry without overriding the opensaml
+ // version
+ critSet.add(new EvaluableKeyIDCredentialCriterion(encryptedObject.getHeader().getKeyID()));
+ }
+
+ return critSet;
+ }
+
+ /**
+ * Dynamically construct key algorithm credential criteria based on the specified algorithm URI.
+ *
+ * @param encAlgorithmURI the algorithm URI
+ * @return a new key algorithm credential criteria instance, or null if criteria could not be determined
+ */
+ @Nullable private KeyAlgorithmCriterion buildKeyAlgorithmCriteria(@Nullable final String encAlgorithmURI) {
+ if (Strings.isNullOrEmpty(encAlgorithmURI)) {
+ return null;
+ }
+
+ final String jcaKeyAlgorithm = AlgorithmSupport.getKeyAlgorithm(encAlgorithmURI);
+ if (!Strings.isNullOrEmpty(jcaKeyAlgorithm)) {
+ return new KeyAlgorithmCriterion(jcaKeyAlgorithm);
+ }
+
+ return null;
+ }
+
+ /**
+ * Dynamically construct key length credential criteria based on the specified algorithm URI.
+ *
+ * @param encAlgorithmURI the algorithm URI
+ * @return a new key length credential criteria instance, or null if the value could not be determined
+ */
+ @Nullable private KeyLengthCriterion buildKeyLengthCriteria(@Nullable final String encAlgorithmURI) {
+ if (Strings.isNullOrEmpty(encAlgorithmURI)) {
+ return null;
+ }
+
+ final Integer keyLength = AlgorithmSupport.getKeyLength(encAlgorithmURI);
+ if (keyLength != null) {
+ return new KeyLengthCriterion(keyLength);
+ }
+
+ return null;
}
/**
@@ -181,8 +297,8 @@ public class JWTDecrypter {
throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
}
- final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION),
- new JOSEObjectCriterion(encryptedObject)));
+ final CriteriaSet criteria =
+ buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
@@ -227,8 +343,8 @@ public class JWTDecrypter {
throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
}
- final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION),
- new JOSEObjectCriterion(encryptedObject)));
+ final CriteriaSet criteria =
+ buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
@@ -273,8 +389,8 @@ public class JWTDecrypter {
throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
}
- final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION),
- new JOSEObjectCriterion(encryptedObject)));
+ final CriteriaSet criteria =
+ buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
@@ -320,8 +436,8 @@ public class JWTDecrypter {
throw new DecryptionException("Decryption can not be attempted, CEK resolver is not available");
}
- final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION),
- new JOSEObjectCriterion(encryptedObject)));
+ final CriteriaSet criteria =
+ buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
try {
for (final Credential cred : params.getContentEncryptionKeyCredentialResolver().resolve(criteria)) {
@@ -343,7 +459,7 @@ public class JWTDecrypter {
} catch (final ResolverException e) {
log.warn("Unable to decrypt JWE using Direct Encryption", e);
}
- throw new DecryptionException("All attempts to decrypt the JWE using Direct Encryption have failed");
+ throw new DecryptionException("Failed to decrypt JWE, no suitable credential found");
}
/**
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list