[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