[java-oidc-common] branch dev/JCOMOIDC-41 updated: JCOMOIDC-45 - Add a Decrypter for JWE tokens similar to the opensaml Decrypter
Phil Smart
philip.smart at jisc.ac.uk
Tue Aug 23 14:58:35 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=7ef6243f398c58f943b83069dd423d36961ba3d6
The following commit(s) were added to refs/heads/dev/JCOMOIDC-41 by this push:
new 7ef6243 JCOMOIDC-45 - Add a Decrypter for JWE tokens similar to the opensaml Decrypter
7ef6243 is described below
commit 7ef6243f398c58f943b83069dd423d36961ba3d6
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Aug 23 15:58:28 2022 +0100
JCOMOIDC-45 - Add a Decrypter for JWE tokens similar to the opensaml
Decrypter
- Updates to the basic JOSE Header credential resolver and the local
credential resolver. Includes local credential resolution based on a
public key match to that described in the 'jwk' in the JOSE header - if
there is one.
https://shibboleth.atlassian.net/browse/JCOMOIDC-45
---
.../impl/BasicJOSEObjectCredentialResolver.java | 42 +++-
.../impl/CriterionCredentialResolver.java | 4 +-
.../impl/LocalJOSEObjectCredentialResolver.java | 163 ++++++++++++-
.../oidc/security/impl/JWTDecrypter.java | 9 -
.../LocalJOSEObjectCredentialResolverTest.java | 254 +++++++++++++++++++++
5 files changed, 451 insertions(+), 21 deletions(-)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java
index b4d4d76..1dae249 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/BasicJOSEObjectCredentialResolver.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.opensaml.xmlsec.keyinfo.impl.KeyInfoResolutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,25 +74,53 @@ public class BasicJOSEObjectCredentialResolver extends AbstractCriteriaFiltering
throw new ResolverException("JOSEObjectCriterion did not contain an instance of JOSEObject");
}
+ // This will be the list of credentials to return.
+ List<Credential> credentials = null;
+
final Header header = joseObject.getHeader();
if (JWSHeader.class.isInstance(header)) {
- return processJWSHeader(JWSHeader.class.cast(header));
+ credentials = processJWSHeader(JWSHeader.class.cast(header));
} else if (JWEHeader.class.isInstance(header)) {
- return processJWEHeader(JWEHeader.class.cast(header));
+ credentials = processJWEHeader(JWEHeader.class.cast(header));
} else {
throw new ResolverException("Saw unknown JOSEObject header type: " +
header != null ? header.getClass().getName() : "null");
}
+ // Extention point for subclasses
+ postProcess(criteriaSet, joseObject, credentials);
+
+ log.debug("A total of {} credentials were resolved", credentials.size());
+
+ return credentials;
+
+ }
+
+ /**
+ * Hook for subclasses to do post-processing of the credential set after all JOSE header keys have been processed.
+ *
+ * For example, the previously resolved credentials might be used to index into a store of local credentials, where
+ * the index is a key name or the public half of a key pair extracted from the headers.
+ *
+ * @param criteriaSet the credential criteria used to resolve credentials
+ * @param joseObject the extracted JOSE object
+ * @param credentials the list which will store the resolved credentials
+ *
+ * @throws ResolverException thrown if there is an error during processing
+ */
+ protected void postProcess(@Nullable final CriteriaSet criteriaSet, @Nonnull final JOSEObject joseObject,
+ @Nonnull final List<Credential> credentials)
+ throws ResolverException {
+
}
/**
* Process credentials indicated by a JWS header.
*
* @param jwsHeader the JWS header to process
- * @return
+ * @return the list of credentials specified by the JWS header
*/
- @Nonnull @NonnullElements protected Iterable<Credential> processJWSHeader(@Nonnull final JWSHeader jwsHeader) {
+ @Nonnull @NonnullElements protected List<Credential> processJWSHeader(@Nonnull final JWSHeader jwsHeader) {
final List<Credential> credentials = new ArrayList<>();
// JWK
@@ -108,7 +137,7 @@ public class BasicJOSEObjectCredentialResolver extends AbstractCriteriaFiltering
return credentials;
}
- @Nonnull @NonnullElements protected Iterable<Credential> processJWEHeader(@Nonnull final JWEHeader jweHeader) {
+ @Nonnull @NonnullElements protected List<Credential> processJWEHeader(@Nonnull final JWEHeader jweHeader) {
final List<Credential> credentials = new ArrayList<>();
// JWK
@@ -144,6 +173,9 @@ public class BasicJOSEObjectCredentialResolver extends AbstractCriteriaFiltering
credential.getKeyNames().add(jwk.getKeyID());
credential.setKid(jwk.getKeyID());
}
+ if (jwk.getAlgorithm() != null) {
+ credential.setAlgorithm(jwk.getAlgorithm());
+ }
if (headerKid != null && !headerKid.equals(credential.getKid())) {
log.warn("Key ID in JOSE header does not match 'kid' in JWK");
return null;
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 35229ce..64b7db0 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
@@ -32,11 +32,9 @@ import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
/**
- * Extracts the credential from the {@link StaticCredentialCriterion} inside the given criteria set,
- * inspects its suitability and passes it back.
+ * Extracts the credential from the {@link StaticCredentialCriterion} inside the given criteria set.
*/
//TODO Docs
-//TODO Algorithm filters?
public class CriterionCredentialResolver extends BasicJOSEObjectCredentialResolver {
/** Class logger. */
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
index f24bc2f..7f11e47 100644
--- 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
@@ -17,16 +17,30 @@
package net.shibboleth.oidc.security.credential.impl;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.criteria.PublicKeyCriterion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Predicates;
+import com.nimbusds.jose.Header;
+import com.nimbusds.jose.JOSEObject;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWSHeader;
+
import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.impl.JWTDecrypter;
import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
@@ -35,9 +49,37 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
* A simple specialization of {@link BasicJOSEObjectCredentialResolver}
* which is capable of using resolving local credentials from a supplied {@link CredentialResolver}
* which manages local credentials.
+ *
+ * <p>
+ * The local credential resolver supplied should manage and return credentials
+ * which contain either a secret (symmetric) key or the private key half of a
+ * key pair.
+ * </p>
+ *
+ * <p>
+ * A typical use case for this class would be as a resolver of decryption keys,
+ * such as is needed by {@link JWTDecrypter}}.
+ * </p>
+ *
+ * <p>
+ * Resolution proceeds as follows:
+ * </p>
+ * <ol>
+ * <li>Any credential resolved via the standard {@link BasicJOSEObjectCredentialResolver}
+ * resolution process which is a local credential (contains private key) will be removed
+ * from the effective set of credentials to be returned. </li>
+ * <li>If a credential so removed contained a public key, that key will be used as a
+ * resolution criteria input to the local credential resolver (along with the keyID 'kid'
+ * if one exists in the JOSE headers). Any local credentials
+ * so resolved will be added to the set to be returned.</li>
+ * <li>Similarly, the keyID 'kid' from the JOSE headers will also
+ * be used as resolution criteria for local credentials and the resultant credentials
+ * added to the set to be returned providing they do not share the same private and public key
+ * pair as one already resolved - avoiding key duplication in the result.</li>
+ * </ol>
+ * Adds either 'kid' or 'public key' to the criteria set sent to the local credential resolver for resolution.
+ *
*/
-// TODO use the 'public' credentials on the JOSE Object headers to locate local private keys with in addition to
-// those located by 'kid'
public class LocalJOSEObjectCredentialResolver extends BasicJOSEObjectCredentialResolver {
/** Class logger. */
@@ -69,9 +111,122 @@ public class LocalJOSEObjectCredentialResolver extends BasicJOSEObjectCredential
}
@Override
- @Nonnull protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet)
+ @Nonnull protected void postProcess(@Nullable final CriteriaSet criteriaSet,
+ @Nonnull final JOSEObject joseObject, @Nonnull final List<Credential> credentials)
throws ResolverException {
- return getLocalCredentialResolver().resolve(criteriaSet);
+
+ final List<Credential> results = new ArrayList<>();
+
+ final String kid = resolveKeyIdFromJoseHeader(joseObject.getHeader());
+
+ for (final Credential inputCred : credentials) {
+ if (isLocalCredential(inputCred)) {
+ log.debug("Input credential was local, including in results");
+ results.add(inputCred);
+ } else if (inputCred.getPublicKey() != null) {
+
+ final CriteriaSet criteria = new CriteriaSet();
+ // Add public key criterion
+ criteria.add(new PublicKeyCriterion(inputCred.getPublicKey()));
+
+ if (kid != null) {
+ // Also filter public key by keyID if one exists in the 'kid' parameter.
+ // This is also caught upstream, so add in-case resolver needs it and filter
+ // anyway.
+ criteria.add(new EvaluableKeyIDCredentialCriterion(kid));
+ }
+ final List<Credential> localCredentials = resolveLocalCredentialsByCriteria(criteria);
+ log.trace("Matched {} local credential(s) from {} input credential(s) on 'public key' and optionally "
+ + "'kid' criterion", localCredentials.size(), credentials.size());
+ if (!localCredentials.isEmpty()) {
+ results.addAll(localCredentials);
+ }
+ }
+ }
+
+ // If 'kid' exists in the header, also resolve by kid (in-case there were no input credentials)
+ if (kid != null) {
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new EvaluableKeyIDCredentialCriterion(kid));
+ final List<Credential> localCredentials = resolveLocalCredentialsByCriteria(criteria);
+ log.trace("Found {} credential(s) from 'kid' criterion alone", localCredentials.size());
+ // There is no point in adding a duplicate credential, so filter out those already resolved
+ results.addAll(filterAlreadyContained(results, localCredentials));
+ }
+
+ credentials.clear();
+ credentials.addAll(results);
+ }
+
+ /**
+ * Return a new list of credentials based on the {@code credentialsToFilter} that are not contained in
+ * {@code credentialsToFilterOn}. Containment is determined by equality of both private and public keys.
+ *
+ * @param credentialsToFilterOn the credentials used to filter the {@code credentialsToFilter}
+ * @param credentialsToFilter the credentials which will be filtered
+ *
+ * @return credentials contained in {@code credentialsToFilter} not in {@code credentialsToFilterOn}.
+ */
+ @Nonnull @NonnullElements @Live private List<Credential> filterAlreadyContained(
+ @Nonnull final List<Credential> credentialsToFilterOn, @Nonnull final List<Credential> credentialsToFilter){
+
+ return credentialsToFilter.stream().filter(Predicates.not(lc -> credentialsToFilterOn.stream()
+ .anyMatch(r -> lc.getPrivateKey().equals(r.getPrivateKey())
+ && lc.getPublicKey().equals(r.getPublicKey()))))
+ .collect(Collectors.toList());
+
+ }
+
+ /**
+ * Resolve credentials using the {@link #localCredResolver} and the supplied criteria.
+ *
+ * @param criteriaSet the criterion to pass to the credential resolver
+ *
+ * @return collection of local credentials identified by the criteria
+ *
+ * @throws ResolverException thrown if there is a problem resolving credentials from the
+ * local credential resolver
+ */
+ @Nonnull @NonnullElements @Live private List<Credential> resolveLocalCredentialsByCriteria(
+ final CriteriaSet criteriaSet) throws ResolverException{
+
+ final ArrayList<Credential> localCreds = new ArrayList<>();
+
+ for (final Credential cred : getLocalCredentialResolver().resolve(criteriaSet)) {
+ if (isLocalCredential(cred)) {
+ localCreds.add(cred);
+ }
+ }
+ return localCreds;
+ }
+
+ /**
+ * Return the KeyId from either a {@link JWSHeader} or a {@link JWEHeader}. Returns
+ * {@literal null} if not found.
+ *
+ * @param header the JOSE header to find a kid from
+ *
+ * @return the keyId or null
+ */
+ @Nullable private String resolveKeyIdFromJoseHeader(@Nonnull final Header header) {
+ if (header instanceof JWEHeader) {
+ return ((JWEHeader)header).getKeyID();
+ } else if (header instanceof JWSHeader) {
+ return ((JWSHeader)header).getKeyID();
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the credential is a local credential.
+ *
+ * A local credential will have either a private key or a secret (symmetric) key.
+ *
+ * @param credential the credential to evaluate
+ * @return true if the credential has either a private or secret key, false otherwise
+ */
+ protected boolean isLocalCredential(@Nonnull final Credential credential) {
+ return credential.getPrivateKey() != null || credential.getSecretKey() != null;
}
}
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 2fb158a..a199551 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
@@ -67,7 +67,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.criterion.JOSEObjectCriterion;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -177,14 +176,6 @@ public class JWTDecrypter {
// Add the entire object so the resolver can access it
newCriteriaSet.add(new JOSEObjectCriterion(encryptedObject));
- // 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. There is a hook for this, to add your own
- newCriteriaSet.add(new EvaluableKeyIDCredentialCriterion(encryptedObject.getHeader().getKeyID()));
- }
-
return newCriteriaSet;
}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolverTest.java
new file mode 100644
index 0000000..39fd646
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/LocalJOSEObjectCredentialResolverTest.java
@@ -0,0 +1,254 @@
+package net.shibboleth.oidc.security.credential.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.KeyLengthException;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSAEncrypter;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.criterion.JOSEObjectCriterion;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/** Tests for {@link LocalJOSEObjectCredentialResolver}.*/
+public class LocalJOSEObjectCredentialResolverTest {
+
+ private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+
+ private LocalJOSEObjectCredentialResolver resolver;
+
+ /** The key resolved from the local credentials source.*/
+ private RSAKey localRSAKey;
+
+
+ @BeforeMethod
+ public void setup() throws Exception {
+
+ localRSAKey = new RSAKeyGenerator(2048)
+ .algorithm(JWEAlgorithm.RSA_OAEP_256)
+ .keyUse(KeyUse.ENCRYPTION)
+ .keyID("mock-key")
+ .generate();
+
+ resolver = new LocalJOSEObjectCredentialResolver(
+ new MockRSACriteriaFilteringCredentialResolver(localRSAKey));
+ }
+
+ 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;
+ }
+
+ private JWTClaimsSet createClaims() {
+ return new JWTClaimsSet.Builder()
+ .issuer("https://localhost:9918")
+ .audience(List.of("test-client"))
+ .subject("jdoe")
+ .claim("nonce", "abadnonce")
+ .claim("azp", "test-client")
+ .claim("name","jdoe")
+ .expirationTime(Date.from(Instant.now().plusSeconds(120)))
+ .build();
+ }
+
+ /* Resolve a credential using the public key found in the JOSEHeaders. The public component of
+ * the JWK in the header is different than the one resolved locally.*/
+ @Test
+ public void testSuccessful_PublicKeyInJOSEHeaderMatchesLocal() throws Exception {
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ // No KeyID, so only has public key to resolve on
+ .jwk(localRSAKey.toPublicJWK())
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter(localRSAKey));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final Credential resolvedCredential = resolver.resolveSingle(criteria);
+ assertNotNull(resolvedCredential);
+ assertNotNull(resolvedCredential.getPrivateKey());
+ assertTrue(resolvedCredential.getKeyNames().contains("mock-key"));
+ }
+
+ /* Resolve a credential using the public key found in the JOSEHeaders. The public component of
+ * the JWK in the header is different than the one resolved locally.*/
+ @Test
+ public void testUnsuccessful_PublicKeyInJOSEHeaderDoesNotMatchLocal() throws Exception {
+ final RSAKey keyInJoseHeader = new RSAKeyGenerator(2048)
+ .algorithm(JWEAlgorithm.RSA_OAEP_256)
+ .keyUse(KeyUse.ENCRYPTION)
+ .keyID("enc-key")
+ .generate();
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("enc-key")
+ .jwk(keyInJoseHeader.toPublicJWK())
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter(keyInJoseHeader));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final Credential resolvedCredential = resolver.resolveSingle(criteria);
+ assertNull(resolvedCredential);
+ }
+
+ /* Just 'kid' no 'jwk'.*/
+ @Test
+ public void testSuccessful_KeyIDInJOSEHeader() throws Exception {
+ 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(localRSAKey));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final List<Credential> resolvedCredential = StreamSupport.stream(
+ resolver.resolve(criteria).spliterator(), false).collect(Collectors.toList());
+ assertNotNull(resolvedCredential);
+ assertEquals(resolvedCredential.size(),1);
+ assertNotNull(resolvedCredential.get(0).getPrivateKey());
+ assertTrue(resolvedCredential.get(0).getKeyNames().contains("mock-key"));
+ }
+
+ /* Just 'kid' no 'jwk'.*/
+ @Test
+ public void testUnSuccessful_KeyIDInJOSEHeaderDifferentThanLocalCred() throws Exception {
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("different-than-local-cred")
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter(localRSAKey));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final Credential resolvedCredential = resolver.resolveSingle(criteria);
+ assertNull(resolvedCredential);
+ }
+
+ //TODO test kid in header different than JWK should match? (https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4)
+
+ /* There is a 'kid' in the header and a 'jwk', the 'kid' matches the key. Resolve the key once.*/
+ @Test
+ public void testSuccessful_KeyIDInJOSEHeader_And_JWK() throws Exception {
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("mock-key")
+ .jwk(localRSAKey.toPublicJWK())
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter(localRSAKey));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final List<Credential> resolvedCredential = StreamSupport.stream(
+ resolver.resolve(criteria).spliterator(), false).collect(Collectors.toList());
+ assertNotNull(resolvedCredential);
+ assertEquals(resolvedCredential.size(),1);
+ assertNotNull(resolvedCredential.get(0).getPrivateKey());
+ assertTrue(resolvedCredential.get(0).getKeyNames().contains("mock-key"));
+ }
+
+ /* There is a 'kid' in the header and 'jwk', and the 'kid' is different than the kid of the 'jwk'.*/
+ @Test
+ public void testUnsuccessful_KeyIDInJOSEHeader_And_JWK_KidDoesNotMatch() throws Exception {
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .keyID("different-than-jwk")
+ .jwk(localRSAKey.toPublicJWK())
+ .build(),
+ new Payload(createdSignedJWT()));
+ jweObject.encrypt(new RSAEncrypter(localRSAKey));
+ final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+ System.out.println(jwe.serialize());
+
+ final CriteriaSet criteria = new CriteriaSet(new JOSEObjectCriterion(jwe));
+ final List<Credential> resolvedCredential = StreamSupport.stream(
+ resolver.resolve(criteria).spliterator(), false).collect(Collectors.toList());
+ assertNotNull(resolvedCredential);
+ assertEquals(resolvedCredential.size(),0);
+ }
+
+ /** Mock RSA credential resolver that is filterable.*/
+ 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) {
+ Assert.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