[java-oidc-common] 18/35: Add further criterion to JWT Decrypter
Phil Smart
philip.smart at jisc.ac.uk
Tue Sep 20 14:19:18 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=dace601102043f38b52ef470a6e685f5211649d3
commit dace601102043f38b52ef470a6e685f5211649d3
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jul 15 14:48:25 2022 +0100
Add further criterion to JWT Decrypter
With differences between which criterion are build between the 'alg'
header and the 'enc' header
---
.../impl/JWKEncryptionCredentialContext.java | 54 ----
.../impl/LocalJOSEObjectCredentialResolver.java | 48 ++++
.../impl/StaticJOSEObjectCredentialResolver.java | 50 ++++
...piringJWTSharedSecretCredentialFactoryBean.java | 35 +--
.../oidc/security/impl/JWTDecrypter.java | 277 ++++++++++++---------
.../oidc/security/impl/JWTDecrypterTest.java | 269 ++++++++++++++++++--
6 files changed, 521 insertions(+), 212 deletions(-)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/JWKEncryptionCredentialContext.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/JWKEncryptionCredentialContext.java
deleted file mode 100644
index 05a355d..0000000
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/JWKEncryptionCredentialContext.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.impl;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.security.credential.CredentialContext;
-
-import com.nimbusds.jose.EncryptionMethod;
-
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/**
- * A {@link CredentialContext} that holds additional JWE information.
- */
-public class JWKEncryptionCredentialContext implements CredentialContext {
-
- /** The encryption algorithm associated with this credential.*/
- @Nonnull private final EncryptionMethod encryptionAlgorithm;
-
- /**
- *
- * Constructor.
- *
- * @param enc the encryption (enc) methods associated with this credential.
- */
- public JWKEncryptionCredentialContext(@Nonnull final EncryptionMethod enc) {
- encryptionAlgorithm = Constraint.isNotNull(enc, "Encryption method can not be null");
- }
-
- /**
- * Get the encryption algorithm associated with this credential.
- *
- * @return the encryption algorithm
- */
- public EncryptionMethod getEncryptionAlgorithm() {
- return encryptionAlgorithm;
- }
-}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolver.java
new file mode 100644
index 0000000..a2cdd45
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolver.java
@@ -0,0 +1,48 @@
+package net.shibboleth.oidc.security.credential.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
+
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+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;
+
+/**
+ * A simple specialization of {@link BasicJOSEObjectCredentialResolver}
+ * which is capable of using information from a {@link org.opensaml.xmlsec.signature.KeyInfo} to resolve
+ * local credentials from a supplied {@link CredentialResolver} which manages local credentials.
+ *
+ * TODO: Finish
+ */
+public class LocalJOSEObjectCredentialResolver extends BasicJOSEObjectCredentialResolver {
+
+ /** The resolver which is used to resolve local credentials. */
+ private final JOSEObjectCredentialResolver localCredResolver;
+
+ /**
+ * Constructor.
+ *
+ * @param keyInfoProviders the list of {@link KeyInfoProvider}s to use in this resolver
+ * @param localCredentialResolver resolver of local credentials
+ */
+ public LocalJOSEObjectCredentialResolver(@Nonnull
+ @ParameterName(name="localCredentialResolver") final JOSEObjectCredentialResolver localCredentialResolver) {
+
+ localCredResolver = Constraint.isNotNull(localCredentialResolver, "Local credential resolver cannot be null");
+ }
+
+ @Override
+ @Nonnull protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet)
+ throws ResolverException {
+
+ return localCredResolver.resolve(criteriaSet);
+
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/StaticJOSEObjectCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/StaticJOSEObjectCredentialResolver.java
new file mode 100644
index 0000000..8e42170
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/StaticJOSEObjectCredentialResolver.java
@@ -0,0 +1,50 @@
+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 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;
+
+//TODO finish. This should be a local one? This is temporary until it is all figured out
+public class StaticJOSEObjectCredentialResolver extends BasicJOSEObjectCredentialResolver {
+
+ /** List of credentials held by this resolver. */
+ private final List<Credential> creds;
+
+ /**
+ * Constructor.
+ *
+ * @param credentials collection of credentials to be held by this resolver
+ */
+ public StaticJOSEObjectCredentialResolver(@Nonnull @ParameterName(name="credentials") final List<Credential> credentials) {
+ Constraint.isNotNull(credentials, "Input credentials list cannot be null");
+
+ creds = new ArrayList<>(credentials);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param credential a single credential to be held by this resolver
+ */
+ public StaticJOSEObjectCredentialResolver(@Nonnull @ParameterName(name="credential") final Credential credential) {
+ Constraint.isNotNull(credential, "Input credential cannot be null");
+
+ creds = new ArrayList<>();
+ creds.add(credential);
+ }
+
+ @Override
+ @Nonnull public Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteria) throws ResolverException {
+ return creds;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicExpiringJWTSharedSecretCredentialFactoryBean.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicExpiringJWTSharedSecretCredentialFactoryBean.java
index 13dcb11..5929dd2 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicExpiringJWTSharedSecretCredentialFactoryBean.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BasicExpiringJWTSharedSecretCredentialFactoryBean.java
@@ -22,22 +22,21 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.SecretKey;
import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.crypto.KeySupport;
import com.google.common.base.Enums;
import com.nimbusds.jose.Algorithm;
-import com.nimbusds.jose.EncryptionMethod;
import net.shibboleth.idp.profile.spring.factory.AbstractCredentialFactoryBean;
import net.shibboleth.oidc.security.credential.BasicExpiringJWKCredential;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
import net.shibboleth.oidc.security.credential.ExpiringJWKCredential;
-import net.shibboleth.oidc.security.credential.impl.JWKEncryptionCredentialContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
/** A factory bean for creating a {@link BasicExpiringJWKCredential} from the static secret injected.*/
public class BasicExpiringJWTSharedSecretCredentialFactoryBean extends AbstractCredentialFactoryBean<ExpiringJWKCredential> {
@@ -45,8 +44,8 @@ public class BasicExpiringJWTSharedSecretCredentialFactoryBean extends AbstractC
/** The secret to use when creating a BasicJWKCredential.*/
@Nullable private String secret;
- /** If the usage type is ENCRYPTION, this is the encryption method this credential supports.*/
- @Nullable private EncryptionMethod encMethod;
+ /** The JCA algorithm to set on the {@link SecretKey}. Defaults to AES.*/
+ @Nullable private String jcaAlg = "AES";
/** The algorithm ('alg') this credential supports.*/
@Nullable private Algorithm alg;
@@ -72,13 +71,14 @@ public class BasicExpiringJWTSharedSecretCredentialFactoryBean extends AbstractC
}
/**
- * Set the encryption method associated with this credential. Can be {@literal null} if this
- * credential does not support encryption, for example is only used for signing.
+ * Set the JCA Algorithm to associate with this credential.
*
- * @param method the encryption method. Can be {@literal null}.
+ * @param alg the jca algorithm.
*/
- public void setEncMethod(@Nullable @NonnullElements final String method) {
- encMethod = EncryptionMethod.parse(method);
+ public void setJcaAlg(@Nullable final String alg) {
+ if (StringSupport.trimOrNull(alg) != null) {
+ jcaAlg = alg;
+ }
}
/**
@@ -103,7 +103,7 @@ public class BasicExpiringJWTSharedSecretCredentialFactoryBean extends AbstractC
protected ExpiringJWKCredential doCreateInstance() throws Exception {
final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
- jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(JWSAssemblyUtils.getSecretBytes(secret), jcaAlg));
jwkCredential.setCredentialExpiresAt(credentialExpiresAt);
jwkCredential.setEntityId(getEntityID());
jwkCredential.setAlgorithm(alg);
@@ -116,16 +116,7 @@ public class BasicExpiringJWTSharedSecretCredentialFactoryBean extends AbstractC
if (keyNames != null) {
jwkCredential.getKeyNames().addAll(keyNames);
}
- // Check if usage is encryption at least one algorithm has been set
- if (UsageType.ENCRYPTION == jwkCredential.getUsageType() && encMethod == null){
- throw new Exception("Can not create JWK encryption credential without an encryption method specified");
- }
- // Set the enc methods
- if (encMethod != null) {
- final JWKEncryptionCredentialContext encContext = new JWKEncryptionCredentialContext(encMethod);
- jwkCredential.getCredentialContextSet().add(encContext);
- }
-
+
return jwkCredential;
}
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 ddee296..c340611 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
@@ -18,10 +18,9 @@
package net.shibboleth.oidc.security.impl;
import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
import java.text.ParseException;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -32,7 +31,6 @@ 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;
@@ -56,7 +54,6 @@ import org.slf4j.LoggerFactory;
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;
@@ -71,7 +68,6 @@ 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;
@@ -159,14 +155,14 @@ public class JWTDecrypter {
/**
* Build a criteria set using the additional criteria in the params, those supplied, and those
- * that can be extracted from the encrypted JWT headers.
+ * that relating to the encrypted JWT.
*
* @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(@Nonnull final EncryptedJWT encryptedObject,
+ @Nonnull private CriteriaSet buildCriteria(@Nonnull final EncryptedJWT encryptedObject,
@Nullable final List<Criterion> criteria) {
final CriteriaSet newCriteriaSet = new CriteriaSet();
@@ -180,77 +176,112 @@ public class JWTDecrypter {
// 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);
+
+ // 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
+ newCriteriaSet.add(new EvaluableKeyIDCredentialCriterion(encryptedObject.getHeader().getKeyID()));
}
return newCriteriaSet;
}
/**
- * Build decryption key credential criteria according to information in the encrypted object.
+ * Optionally build decryption key 'alg' (key management algorithm) credential criteria according to
+ * information in the encrypted object.
*
- * @param encryptedObject the encrypted JWT from which to deduce decryption key criteria
+ * @param criteriaSet the criteria set to add built criteria too
+ * @param encryptedObject the encrypted JWT from which to deduce decryption key 'alg' 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;
+ @Nullable private void buildKeyManagementAlgorithmCriteria(@Nonnull final CriteriaSet criteriaSet,
+ @Nonnull final EncryptedJWT encryptedObject) {
+
+ final JWEAlgorithm alg = encryptedObject.getHeader().getAlgorithm();
+ if (alg == null) {
+ // Is optionally built
+ return;
}
- final String encAlgorithmURI = StringSupport.trimOrNull(encMethod.getName());
- if (encAlgorithmURI == null) {
- return null;
+
+ final String algAlgorithmURI = StringSupport.trimOrNull(alg.getName());
+ if (algAlgorithmURI == null) {
+ // Is optionally built
+ return;
}
- final Set<Criterion> critSet = new HashSet<>(2);
-
- final KeyAlgorithmCriterion algoCrit = buildKeyAlgorithmCriteria(encAlgorithmURI);
+ final KeyAlgorithmCriterion algoCrit = buildKeyAlgorithmCriteria(algAlgorithmURI);
if (algoCrit != null) {
- critSet.add(algoCrit);
- log.debug("Added decryption key algorithm criteria: {}", algoCrit.getKeyAlgorithm());
+ criteriaSet.add(algoCrit);
+ log.debug("Added decryption key algorithm 'alg' criteria: {}", algoCrit.getKeyAlgorithm());
}
-
+
+ // Add key size criteria if possible
+ final KeyLengthCriterion lengthCrit = buildKeyLengthCriteria(algAlgorithmURI);
+ if (lengthCrit != null) {
+ criteriaSet.add(lengthCrit);
+ log.debug("Added decryption key length criteria from EncryptionMethod algorithm URI: {}", lengthCrit
+ .getKeyLength());
+ }
+ }
+
+ /**
+ * Optionally build decryption key 'enc' (encryption key algorithm) credential criteria according to
+ * information in the encrypted object.
+ *
+ * @param encryptedObject the encrypted JWT from which to deduce decryption key 'enc' criteria
+ * @return a set of credential criteria pertaining to the decryption key
+ */
+ @Nullable private void buildContentEncryptionKeyAlgorithmCriteria(
+ @Nonnull final CriteriaSet criteriaSet, @Nonnull final EncryptedJWT encryptedObject) {
+
+ final EncryptionMethod enc = encryptedObject.getHeader().getEncryptionMethod();
+ if (enc == null) {
+ // Is optionally built
+ return;
+ }
+
+ final String encAlgorithmURI = StringSupport.trimOrNull(enc.getName());
+ if (encAlgorithmURI == null) {
+ // Is optionally built
+ return;
+ }
+
+ final KeyAlgorithmCriterion algoCrit = buildKeyAlgorithmCriteria(encAlgorithmURI);
+ if (algoCrit != null) {
+ criteriaSet.add(algoCrit);
+ log.debug("Added decryption key algorithm 'enc' criteria: {}", algoCrit.getKeyAlgorithm());
+ }
+
+ // Add key size criteria if possible
KeyLengthCriterion lengthCrit = buildKeyLengthCriteria(encAlgorithmURI);
if (lengthCrit != null) {
- critSet.add(lengthCrit);
+ criteriaSet.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);
+ if (enc.cekBitLength() != 0) {
+ lengthCrit = new KeyLengthCriterion(enc.cekBitLength());
+ criteriaSet.add(lengthCrit);
log.debug("Added decryption key length criteria from EncryptionMethod/KeySize: {}", lengthCrit
.getKeyLength());
}
}
-
- // 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
+ * @param algURI 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)) {
+ @Nullable private KeyAlgorithmCriterion buildKeyAlgorithmCriteria(@Nullable final String algorithmURI) {
+ if (Strings.isNullOrEmpty(algorithmURI)) {
return null;
}
- final String jcaKeyAlgorithm = AlgorithmSupport.getKeyAlgorithm(encAlgorithmURI);
+ final String jcaKeyAlgorithm = AlgorithmSupport.getKeyAlgorithm(algorithmURI);
if (!Strings.isNullOrEmpty(jcaKeyAlgorithm)) {
return new KeyAlgorithmCriterion(jcaKeyAlgorithm);
}
@@ -299,20 +330,17 @@ public class JWTDecrypter {
final CriteriaSet criteria =
buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+ buildKeyManagementAlgorithmCriteria(criteria, encryptedObject);
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
- if (cred.getPrivateKey() instanceof ECPrivateKey) {
- try {
- validateKeyManagmentAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new ECDHDecrypter((ECPrivateKey)cred.getPrivateKey());
- encryptedObject.decrypt(decrypter);
- return;
- } catch (final JOSEException | DecryptionException e) {
- log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
- }
- } else {
- log.debug("Key encryption key '{}' does not contain a private key, skipping", cred.getKeyNames());
+ try {
+ validateKeyManagmentAlgorithm(encryptedObject, cred);
+ final JWEDecrypter decrypter = new ECDHDecrypter((ECPrivateKey)cred.getPrivateKey());
+ encryptedObject.decrypt(decrypter);
+ return;
+ } catch (final JOSEException | DecryptionException e) {
+ log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
}
}
} catch (final ResolverException e) {
@@ -345,20 +373,17 @@ public class JWTDecrypter {
final CriteriaSet criteria =
buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+ buildKeyManagementAlgorithmCriteria(criteria, encryptedObject);
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
- if (cred.getSecretKey() != null) {
- try {
- validateKeyManagmentAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new AESDecrypter(cred.getSecretKey());
- encryptedObject.decrypt(decrypter);
- return;
- } catch (final JOSEException | DecryptionException e) {
- log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
- }
- } else {
- log.debug("Key encryption key '{}' does not contain a private key, skipping", cred.getKeyNames());
+ try {
+ validateKeyManagmentAlgorithm(encryptedObject, cred);
+ final JWEDecrypter decrypter = new AESDecrypter(cred.getSecretKey());
+ encryptedObject.decrypt(decrypter);
+ return;
+ } catch (final JOSEException | DecryptionException e) {
+ log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
}
}
} catch (final ResolverException e) {
@@ -391,21 +416,18 @@ public class JWTDecrypter {
final CriteriaSet criteria =
buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+ buildKeyManagementAlgorithmCriteria(criteria, encryptedObject);
try {
- for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
- if (cred.getPrivateKey() != null) {
- try {
- validateKeyManagmentAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new RSADecrypter(cred.getPrivateKey());
- encryptedObject.decrypt(decrypter);
- return;
- } catch (final JOSEException | DecryptionException e) {
- log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
- }
- } else {
- log.debug("Key encryption key '{}' does not contain a private key, skipping", cred.getKeyNames());
- }
+ for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
+ try {
+ validateKeyManagmentAlgorithm(encryptedObject, cred);
+ final JWEDecrypter decrypter = new RSADecrypter(cred.getPrivateKey());
+ encryptedObject.decrypt(decrypter);
+ return;
+ } catch (final JOSEException | DecryptionException e) {
+ log.debug("Failed to decrypt JWE using key '{}', continuing: {}", cred.getKeyNames(), e.getMessage());
+ }
}
} catch (final ResolverException e) {
log.warn("Unable to decrypt JWE with Key Encryption", e);
@@ -439,21 +461,20 @@ public class JWTDecrypter {
final CriteriaSet criteria =
buildCriteria(encryptedObject, List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+ // As 'dir' type, we can add content encryption key criteria for the resolver to filter on
+ buildContentEncryptionKeyAlgorithmCriteria(criteria, encryptedObject);
+
try {
for (final Credential cred : params.getContentEncryptionKeyCredentialResolver().resolve(criteria)) {
- if (cred.getSecretKey() != null) {
- try {
- validateKeyManagmentAlgorithm(encryptedObject, cred);
- validateContentEncryptionAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new DirectDecrypter(cred.getSecretKey());
- encryptedObject.decrypt(decrypter);
- return;
- } catch (final JOSEException | DecryptionException e) {
- log.debug("Content encryption key '{}' failed to decrypt JWE, continuing: {}",
- cred.getKeyNames(), e.getMessage());
- }
- } else {
- log.debug("Content encryption key '{}' does not contain a secret key, skipping", cred.getKeyNames());
+ try {
+ validateKeyManagmentAlgorithm(encryptedObject, cred);
+ validateContentEncryptionAlgorithm(encryptedObject, cred);
+ final JWEDecrypter decrypter = new DirectDecrypter(cred.getSecretKey());
+ encryptedObject.decrypt(decrypter);
+ return;
+ } catch (final JOSEException | DecryptionException e) {
+ log.debug("Content encryption key '{}' failed to decrypt JWE, continuing: {}",
+ cred.getKeyNames(), e.getMessage());
}
}
} catch (final ResolverException e) {
@@ -463,10 +484,12 @@ public class JWTDecrypter {
}
/**
- * Validates the 'alg' algorithm in the header matches the algorithm specified for the credential.
+ * Validates the 'alg' algorithm in the header matches the algorithm specified for the credential,
+ * validates against the include and exclude algorithm URI lists, and the credential contains the
+ * correct key type.
*
* @param encryptedObject the JWE
- * @param cred the credential to check algorithm compatibility
+ * @param cred the credential to validate against the 'alg' header
*
* @throws DecryptionException if there is an algorithm mismatch.
*/
@@ -477,29 +500,43 @@ public class JWTDecrypter {
throw new DecryptionException("JWE did not contain a JOSE header, is in an illegal state");
}
- validateAlgorithmURI(encryptedObject.getHeader().getAlgorithm().getName());
-
+ final JWEAlgorithm headerAlg = encryptedObject.getHeader().getAlgorithm();
+ validateAlgorithmURI(headerAlg.getName());
+
if (cred instanceof JWKCredential) {
final JWKCredential jwkCred = (JWKCredential)cred;
- if (jwkCred.getAlgorithm() != null &&
- encryptedObject.getHeader().getAlgorithm().equals(jwkCred.getAlgorithm())) {
- // Matches
- return;
- } else {
+ if (jwkCred.getAlgorithm() == null || !headerAlg.equals(jwkCred.getAlgorithm())) {
throw new DecryptionException("Credential algorithm '"+jwkCred.getAlgorithm()+"' "
+ "was not a match for the "
+ "algorithm '"+encryptedObject.getHeader().getAlgorithm()+"'");
+
}
}
- throw new DecryptionException("Credential was not the correct type, expected a JWK Credential");
+ // Now check key is of the correct type
+ if (JWEAlgorithm.Family.RSA.contains(headerAlg) && !(cred.getPrivateKey() instanceof RSAPrivateKey)) {
+ throw new DecryptionException("Credential did not contain an RSA private key");
+ }
+ if (JWEAlgorithm.Family.ECDH_ES.contains(headerAlg) && !(cred.getPrivateKey() instanceof ECPrivateKey)) {
+ throw new DecryptionException("Credential did not contain an EC private key");
+ }
+ if ((JWEAlgorithm.Family.AES_GCM_KW.contains(headerAlg) || JWEAlgorithm.Family.AES_KW.contains(headerAlg))
+ && (cred.getSecretKey() == null || !"AES".equals(cred.getSecretKey().getAlgorithm()))) {
+ throw new DecryptionException("Credential did not contain an AES secret key");
+ }
+ if (JWEAlgorithm.DIR.equals(headerAlg) && cred.getSecretKey() == null) {
+ throw new DecryptionException("Credential did not contain an direct encryption secret key");
+ }
+
+ //All fine
}
/**
- * Validates the 'enc' algorithm in the header matches the encryption algorithm specified for the credential.
+ * Validates the 'enc' algorithm in the header matches the encryption algorithm specified for the credential,
+ * and validates against the include and exclude algorithm URI lists.
*
* @param encryptedObject the JWE
- * @param cred the credential to check algorithm compatibility
+ * @param cred the credential to validate the 'enc' header
*
* @throws DecryptionException if there is an algorithm mismatch.
*/
@@ -509,23 +546,27 @@ public class JWTDecrypter {
if (encryptedObject.getHeader() == null) {
throw new DecryptionException("JWE did not contain a JOSE header, is in an illegal state");
}
- validateAlgorithmURI(encryptedObject.getHeader().getEncryptionMethod().getName());
- if (cred.getCredentialContextSet().contains(
- JWKEncryptionCredentialContext.class)) {
- final EncryptionMethod encMethod =
- cred.getCredentialContextSet().get(
- JWKEncryptionCredentialContext.class).getEncryptionAlgorithm();
+ final EncryptionMethod enc = encryptedObject.getHeader().getEncryptionMethod();
+ if (enc == null) {
+ throw new DecryptionException("JWE did not contain an 'enc' JOSE header, is in an illegal state");
+ }
- if (!encryptedObject.getHeader().getEncryptionMethod().equals(encMethod)) {
- throw new DecryptionException("JOSE Header 'enc' algorithm "
- +encryptedObject.getHeader().getEncryptionMethod().getName()+
- " does not match credential algorithm "+encMethod);
- }
+ final String encAlgorithmURI = StringSupport.trimOrNull(enc.getName());
- } else {
- throw new DecryptionException("Credential did not specify which 'enc' algorithm it supports");
+ validateAlgorithmURI(encAlgorithmURI);
+
+ final String jcaKeyAlgorithm = AlgorithmSupport.getKeyAlgorithm(encAlgorithmURI);
+
+ if (cred.getSecretKey() == null) {
+ throw new DecryptionException("Credential does not contain a content encryption secret key");
+ }
+
+ if (!jcaKeyAlgorithm.equals(cred.getSecretKey().getAlgorithm())) {
+ throw new DecryptionException("JOSE Header 'enc' algorithm "
+ +jcaKeyAlgorithm+" does not match credential algorithm "+cred.getSecretKey().getAlgorithm());
}
+
// Otherwise, all fine.
}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTDecrypterTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTDecrypterTest.java
index 9a0240b..015b06f 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTDecrypterTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTDecrypterTest.java
@@ -5,17 +5,20 @@ import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.nio.charset.StandardCharsets;
+import java.security.KeyException;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.time.Instant;
import java.util.Date;
import java.util.List;
-import javax.crypto.spec.SecretKeySpec;
-
+import org.opensaml.core.config.InitializationException;
import org.opensaml.security.credential.Credential;
-import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.xmlsec.config.GlobalAlgorithmRegistryInitializer;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.nimbusds.jose.EncryptionMethod;
@@ -36,6 +39,7 @@ import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.PasswordBasedEncrypter;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
@@ -48,7 +52,6 @@ import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.oidc.security.JWTDecryptionParameters;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
-import net.shibboleth.oidc.security.credential.impl.JWKEncryptionCredentialContext;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
@@ -75,18 +78,31 @@ public class JWTDecrypterTest {
private SignedJWT createdSignedJWT() throws KeyLengthException, JOSEException {
final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT)
+ .keyID("mock-key")
.build();
final var signedJWT = new SignedJWT(header,createClaims());
signedJWT.sign(new MACSigner(CLIENT_SECRET));
return signedJWT;
}
+ @BeforeMethod
+ public void setup() {
+ //Create an algorithm registry here, as opensaml init will not take place for these tests
+ try {
+ final GlobalAlgorithmRegistryInitializer gar = new GlobalAlgorithmRegistryInitializer();
+ gar.init();
+ } catch (final InitializationException e) {
+ fail();
+ }
+ }
+
@Test(expectedExceptions = DecryptionException.class)
void testUnsupportedAlg() throws Exception {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.PBES2_HS256_A128KW, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new PasswordBasedEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8),8,1000));
@@ -102,6 +118,7 @@ public class JWTDecrypterTest {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new AESEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
@@ -115,8 +132,13 @@ public class JWTDecrypterTest {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.A256KW);
jwkCredential.getKeyNames().add("mock-key");
- jwkCredential.setSecretKey(new SecretKeySpec(
- JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
return jwkCredential;
}
@@ -132,12 +154,34 @@ public class JWTDecrypterTest {
assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
}
+ @Test
+ void testDecryptionByKeyWrapping_Using_EvaluableCriteriaFiltering() throws Exception {
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("mock-key")
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new AESEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+
+ final var params = new JWTDecryptionParameters();
+ params.setKEKCredentialResolver(new MockKeyWrapCriteriaFilteringCredentialResolver());
+ decrypter = new JWTDecrypter(params);
+ final JWT decryptedJWE = decrypter.decrypt(jwe);
+ assertTrue(jwe.getState() == State.DECRYPTED);
+ assertTrue(decryptedJWE instanceof SignedJWT);
+ assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
+ }
+
@Test
void testDecryptionByDirectEncryption() throws Exception {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new DirectEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
@@ -150,11 +194,14 @@ public class JWTDecrypterTest {
public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
- jwkCredential.getCredentialContextSet()
- .add(new JWKEncryptionCredentialContext(EncryptionMethod.A256GCM));
jwkCredential.getKeyNames().add("mock-key");
- jwkCredential.setSecretKey(new SecretKeySpec(
- JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
return jwkCredential;
}
@@ -170,12 +217,35 @@ public class JWTDecrypterTest {
assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
}
+
+ @Test
+ void testDecryptionByDirectEncryption_Using_EvaluableCriteriaFiltering() throws Exception {
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("mock-key")
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new DirectEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+
+ final var params = new JWTDecryptionParameters();
+ params.setContentEncryptionKeyCredentialResolver(new MockDirCriteriaFilteringCredentialResolver());
+ decrypter = new JWTDecrypter(params);
+ final JWT decryptedJWE = decrypter.decrypt(jwe);
+ assertTrue(jwe.getState() == State.DECRYPTED);
+ assertTrue(decryptedJWE instanceof SignedJWT);
+ assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
+ }
+
@Test
void testDecryptionByDirectEncryption_AlgorithmExcluded() throws Exception {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new DirectEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
@@ -188,11 +258,14 @@ public class JWTDecrypterTest {
public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
- jwkCredential.getCredentialContextSet()
- .add(new JWKEncryptionCredentialContext(EncryptionMethod.A256GCM));
jwkCredential.getKeyNames().add("mock-key");
- jwkCredential.setSecretKey(new SecretKeySpec(
- JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
return jwkCredential;
}
@@ -219,6 +292,7 @@ public class JWTDecrypterTest {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new DirectEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
@@ -233,8 +307,13 @@ public class JWTDecrypterTest {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.A128KW);
jwkCredential.getKeyNames().add("mock-key");
- jwkCredential.setSecretKey(new SecretKeySpec(
- JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
return jwkCredential;
}
@@ -258,6 +337,7 @@ public class JWTDecrypterTest {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new AESEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
@@ -271,8 +351,13 @@ public class JWTDecrypterTest {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.A128KW);
jwkCredential.getKeyNames().add("mock-key");
- jwkCredential.setSecretKey(new SecretKeySpec(
- JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
return jwkCredential;
}
@@ -301,6 +386,7 @@ public class JWTDecrypterTest {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new RSAEncrypter((RSAPublicKey) key.toPublicKey()));
@@ -313,6 +399,7 @@ public class JWTDecrypterTest {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
try {
jwkCredential.setPrivateKey(key.toPrivateKey());
jwkCredential.setPublicKey(key.toPublicKey());
@@ -335,6 +422,31 @@ public class JWTDecrypterTest {
assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
}
+ @Test
+ void testDecryptionByKeyEncryption_Using_EvaluableCriteriaFiltering() throws Exception {
+
+ final RSAKey key = new RSAKeyGenerator(2048)
+ .keyID("1")
+ .keyUse(KeyUse.ENCRYPTION)
+ .generate();
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("mock-key")
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter((RSAPublicKey) key.toPublicKey()));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ final var params = new JWTDecryptionParameters();
+ params.setKEKCredentialResolver(new MockRSACriteriaFilteringCredentialResolver(key));
+ decrypter = new JWTDecrypter(params);
+ final JWT decryptedJWE = decrypter.decrypt(jwe);
+ assertTrue(jwe.getState() == State.DECRYPTED);
+ assertTrue(decryptedJWE instanceof SignedJWT);
+ assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
+ }
+
@Test
void testDecryptionByKeyAgreement() throws Exception {
@@ -346,6 +458,7 @@ public class JWTDecrypterTest {
final JWEObject jweObject =
new JWEObject(new JWEHeader.Builder(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256GCM)
.contentType("JWT")
+ .keyID("mock-key")
.build(),
new Payload(createdSignedJWT()));
jweObject.encrypt(new ECDHEncrypter(key.toECPublicKey()));
@@ -358,6 +471,7 @@ public class JWTDecrypterTest {
final BasicJWKCredential jwkCredential = new BasicJWKCredential();
jwkCredential.setAlgorithm(JWEAlgorithm.ECDH_ES_A256KW);
jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
try {
jwkCredential.setPrivateKey(key.toPrivateKey());
jwkCredential.setPublicKey(key.toPublicKey());
@@ -379,4 +493,123 @@ public class JWTDecrypterTest {
assertTrue(decryptedJWE instanceof SignedJWT);
assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
}
+
+ @Test
+ void testDecryptionByKeyAgreement_Using_EvaluableCriteriaFiltering() throws Exception {
+
+ final var key = new ECKeyGenerator(Curve.P_256)
+ .keyUse(KeyUse.ENCRYPTION)
+ .keyID("1")
+ .generate();
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("mock-key")
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new ECDHEncrypter(key.toECPublicKey()));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ final var params = new JWTDecryptionParameters();
+ params.setKEKCredentialResolver(new MockKeyAgreementCriteriaFilteringCredentialResolver(key));
+ decrypter = new JWTDecrypter(params);
+ final JWT decryptedJWE = decrypter.decrypt(jwe);
+ assertTrue(jwe.getState() == State.DECRYPTED);
+ assertTrue(decryptedJWE instanceof SignedJWT);
+ assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");
+ }
+
+ private static class MockRSACriteriaFilteringCredentialResolver extends AbstractCriteriaFilteringCredentialResolver
+ implements JOSEObjectCredentialResolver {
+
+ private final RSAKey key;
+
+ public MockRSACriteriaFilteringCredentialResolver(final RSAKey theKey) {
+ key = theKey;
+ }
+
+ @Override
+ protected Iterable<Credential> resolveFromSource(final CriteriaSet criteriaSet) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
+ jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setPrivateKey(key.toPrivateKey());
+ jwkCredential.setPublicKey(key.toPublicKey());
+ } catch (final JOSEException e) {
+ fail();
+ }
+
+ return List.of(jwkCredential);
+ }
+
+ }
+
+ private static class MockDirCriteriaFilteringCredentialResolver extends AbstractCriteriaFilteringCredentialResolver
+ implements JOSEObjectCredentialResolver {
+
+ @Override
+ protected Iterable<Credential> resolveFromSource(final CriteriaSet criteriaSet) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
+ jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
+ return List.of(jwkCredential);
+ }
+
+ }
+
+ private static class MockKeyWrapCriteriaFilteringCredentialResolver extends AbstractCriteriaFilteringCredentialResolver
+ implements JOSEObjectCredentialResolver {
+
+ @Override
+ protected Iterable<Credential> resolveFromSource(final CriteriaSet criteriaSet) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(JWEAlgorithm.A256KW);
+ jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+ JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "AES"));
+ } catch (final KeyException e) {
+ throw new ResolverException(e);
+ }
+ return List.of(jwkCredential);
+ }
+
+ }
+
+ private static class MockKeyAgreementCriteriaFilteringCredentialResolver extends AbstractCriteriaFilteringCredentialResolver
+ implements JOSEObjectCredentialResolver {
+
+ private final ECKey key;
+
+ public MockKeyAgreementCriteriaFilteringCredentialResolver(final ECKey theKey) {
+ key = theKey;
+ }
+
+ @Override
+ protected Iterable<Credential> resolveFromSource(final CriteriaSet criteriaSet) throws ResolverException {
+ final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+ jwkCredential.setAlgorithm(JWEAlgorithm.ECDH_ES_A256KW);
+ jwkCredential.getKeyNames().add("mock-key");
+ jwkCredential.setKid("mock-key");
+ try {
+ jwkCredential.setPrivateKey(key.toPrivateKey());
+ jwkCredential.setPublicKey(key.toPublicKey());
+ } catch (final JOSEException e) {
+ fail();
+ }
+
+ return List.of(jwkCredential);
+ }
+
+ }
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list