[java-oidc-common] 06/35: JCOMOIDC-45 - Add a Decrypter for JWE tokens similar to the opensaml Decrypter

Phil Smart philip.smart at jisc.ac.uk
Tue Sep 20 14:19:06 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=04b1c01071f316716e49ce54ca1c3c45a89ce50b

commit 04b1c01071f316716e49ce54ca1c3c45a89ce50b
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jun 1 17:14:34 2022 +0100

    JCOMOIDC-45 - Add a Decrypter for JWE tokens similar to the opensaml
    Decrypter
    
     - Add a JWT Decrypter. Taking inspiration from the existing OP request
    object decrypter and the existing XML decrypter.
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-45
---
 .../oidc/security/impl/JWTDecrypter.java           | 429 +++++++++++++++++++++
 .../oidc/security/impl/JWTDecrypterTest.java       | 381 ++++++++++++++++++
 2 files changed, 810 insertions(+)

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
new file mode 100644
index 0000000..853a016
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
@@ -0,0 +1,429 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.security.impl;
+
+import java.security.interfaces.ECPrivateKey;
+import java.text.ParseException;
+import java.util.List;
+
+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.UsageCriterion;
+import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.opensaml.xmlsec.encryption.support.DecryptionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/*
+ * 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.
+ */
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEDecrypter;
+import com.nimbusds.jose.JWEObject.State;
+import com.nimbusds.jose.crypto.AESDecrypter;
+import com.nimbusds.jose.crypto.DirectDecrypter;
+import com.nimbusds.jose.crypto.ECDHDecrypter;
+import com.nimbusds.jose.crypto.RSADecrypter;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+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.JWKEncryptionCredentialContext;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.Criterion;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * Supports decryption of encrypted JSON Web Tokens using the JSON Web Encryption standard. 
+ * The {@link EncryptedJWT} will be decrypted in-place, with its {@link State} changing to {@link State#DECRYPTED}
+ * on successful decryption. Any error will throw a {@link DecryptionException}.
+ * 
+ * <p>A decrypter should be created for each new decryption operation.</p>
+ */
+public class JWTDecrypter {
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(JWTDecrypter.class);
+
+    /** The OIDC decryption parameters. */
+    private final JWTDecryptionParameters params;
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param decryptionParams the parameters to use during decryption
+     */
+    public JWTDecrypter(@Nonnull final JWTDecryptionParameters decryptionParams) {
+        params = Constraint.isNotNull(decryptionParams, "Decryption params can not be null");
+    }
+
+    /**
+     * Decrypt a JWE object using credentials resolved from the CEK and KEK resolvers 
+     * inside the decryption parameters.
+     * 
+     * @param encryptedObject JWE object to decrypt.
+     * 
+     * @return Decrypted request object.
+     * 
+     * @throws DecryptionException on failure to decrypt the JWT.
+     */
+    @Nonnull public JWT decrypt(@Nonnull final EncryptedJWT encryptedObject) throws DecryptionException {
+        
+        final JWEAlgorithm jwtAlg = encryptedObject.getHeader().getAlgorithm();
+
+        // Direct Encryption
+        if (JWEAlgorithm.DIR.equals(jwtAlg)) {
+            decryptUsingDirectEncryption(encryptedObject);
+        }
+        // Key encryption
+        else if (JWEAlgorithm.Family.RSA.contains(jwtAlg)) {
+            decryptUsingKeyEncryption(encryptedObject);         
+        }
+        // Key wrapped
+        else if (JWEAlgorithm.Family.AES_GCM_KW.contains(jwtAlg) || JWEAlgorithm.Family.AES_KW.contains(jwtAlg)) {
+            decryptUsingKeyWrapping(encryptedObject);            
+        } 
+        // Key agreement
+        else if (JWEAlgorithm.Family.ECDH_ES.contains(jwtAlg)) {
+            decryptUsingKeyAgreement(encryptedObject);          
+        } else {
+            throw new DecryptionException("JWE algorithm '"+jwtAlg.getName()+"' not supported");
+        }
+               
+        // Check decrypted
+        if (encryptedObject.getState() == State.DECRYPTED) {
+            try {
+                return JWTParser.parse(encryptedObject.getPayload().toString());
+            } catch (final ParseException e) {
+                throw new DecryptionException("Error decrypting JWT", e);
+            }
+        } else {
+            // Should not happen if all the above key managment modes are correctly captured
+            throw new DecryptionException("JWE failed to decrypt without error");
+        }  
+
+
+    }
+    
+    /**
+     * Build a criteria set using the additional criteria in the params and those supplied.
+     * 
+     * @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();
+        if (params.getAdditionalCriteria() != null) {
+            params.getAdditionalCriteria().forEach(c -> crit.add(c));
+        }
+        if (criteria != null) {
+            criteria.forEach(c -> crit.add(c));          
+        }
+        return crit;
+    }
+    
+    /**
+     * Decrypt the encrypted JWT by computing the content encryption key using Elliptic Curve Diffie-Hellman key
+     * agreement. The private EC key is resolved from the {@link JWTDecryptionParameters#getKEKCredentialResolver()}. 
+     * 
+     * <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
+     * includes and excludes lists configured.</p>
+     * 
+     * <p>The process terminates when a resolved credential decrypts the JWT.</p>
+     * 
+     * @param encryptedObject the encrypted JWT to decrypt - in place.
+     * 
+     * @throws DecryptionException if the resolved credentials could not be used to decrypt the JWT.
+     */
+    private void decryptUsingKeyAgreement(final EncryptedJWT encryptedObject) throws DecryptionException {
+  
+        log.debug("Attempting decryption of JWE using Key Agreement managment mode");
+        if (params.getKEKCredentialResolver()== null) {
+            throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
+        }
+        
+        final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+        
+        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());
+                }
+            }
+        } catch (final ResolverException e) {
+            log.warn("Unable to decrypt JWE with Key Encryption", e);
+        }
+        throw new DecryptionException("All attempts to decrypt the JWE using a Key Wrapped CEK have failed");
+        
+    }
+
+    /**
+     * Decrypt the encrypted JWT by first decrypting the wrapped content encryption key using on of the shared 
+     * (symmetric) key encryption keys resolved by the {@link JWTDecryptionParameters#getKEKCredentialResolver()} resolver. 
+     * 
+     * 
+     * <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
+     * includes and excludes lists configured.</p>
+     * 
+     * <p>The process terminates when the first resolved credential (that decrypts the CEK) decrypts the JWT.</p>
+     * 
+     * @param encryptedObject the encrypted JWT to decrypt - in place.
+     * 
+     * @throws DecryptionException if the resolved credentials could not be used to decrypt the JWT.
+     */
+    private void decryptUsingKeyWrapping(@Nonnull final EncryptedJWT encryptedObject) throws DecryptionException {
+        
+        log.debug("Attempting decryption of JWE using Key Wrapping managment mode");
+        if (params.getKEKCredentialResolver()== null) {
+            throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
+        }
+        
+        final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+        
+        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());
+                }
+            }
+        } catch (final ResolverException e) {
+            log.warn("Unable to decrypt JWE with Key Encryption", e);
+        }
+        throw new DecryptionException("All attempts to decrypt the JWE using a Key Wrapped CEK have failed");
+        
+    }
+
+    /**
+     * Decrypt the encrypted JWT by first decrypting the content encryption key using one of the (asymmetric) 
+     * key encryption keys resolved by the {@link JWTDecryptionParameters#getKEKCredentialResolver()} resolver. 
+     * 
+     * 
+     * <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
+     * includes and excludes lists configured.</p>
+     * 
+     * <p>The process terminates when the first resolved credential (that decrypts the CEK) decrypts the JWT.</p>
+     * 
+     * @param encryptedObject the encrypted JWT to decrypt - in place.
+     * 
+     * @throws DecryptionException if the resolved credentials could not be used to decrypt the JWT.
+     */
+    private void decryptUsingKeyEncryption(final EncryptedJWT encryptedObject) throws DecryptionException {
+        
+        log.debug("Attempting decryption of JWE using Key Encryption managment mode");
+        if (params.getKEKCredentialResolver()== null) {
+            throw new DecryptionException("Decryption can not be attempted, KEK resolver is not available");
+        }
+        
+        final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+        
+        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());
+                }
+            }
+        } catch (final ResolverException e) {
+            log.warn("Unable to decrypt JWE with Key Encryption", e);
+        }
+        throw new DecryptionException("All attempts to decrypt the JWE using a Key Encrypted CEK have failed");
+
+    }
+
+    /**
+     * Decrypt the encrypted JWT using direct encryption. A shared symmetric key resolved by the
+     * {@link JWTDecryptionParameters#getContentEncryptionKeyCredentialResolver()} is used directly
+     * as the content encryption key.
+     * 
+     * <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
+     * includes and excludes lists configured.</p>
+     * 
+     * <p>The first resolved credential that decrypts the JWT produces a result, and the process terminates.</p>
+     * 
+     * @param encryptedObject the encrypted JWT to decrypt - in place.
+     * 
+     * @throws DecryptionException if the resolved credentials could not be used to decrypt the JWT.
+     */
+    private void decryptUsingDirectEncryption(@Nonnull final EncryptedJWT encryptedObject) 
+                throws DecryptionException {
+        
+        log.debug("Attempting decryption of JWE using Direct Encryption managment mode");
+        if (params.getContentEncryptionKeyCredentialResolver() == null) {
+            throw new DecryptionException("Decryption can not be attempted, CEK resolver is not available");
+        }
+        
+        final CriteriaSet criteria = buildCriteria(List.of(new UsageCriterion(UsageType.ENCRYPTION)));
+        
+        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());
+                }
+            }
+        } 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");
+    }
+    
+    /**
+     * Validates the 'alg' algorithm in the header matches the algorithm specified for the credential.
+     * 
+     * @param encryptedObject the JWE
+     * @param cred the credential to check algorithm compatibility
+     * 
+     * @throws DecryptionException if there is an algorithm mismatch.
+     */
+    private void validateKeyManagmentAlgorithm(
+            @Nonnull final EncryptedJWT encryptedObject, @Nonnull final Credential cred) throws DecryptionException {
+        
+        if (encryptedObject.getHeader() == null) {
+            throw new DecryptionException("JWE did not contain a JOSE header, is in an illegal state");
+        }
+        
+        validateAlgorithmURI(encryptedObject.getHeader().getAlgorithm().getName());
+        
+        if (cred instanceof JWKCredential) {
+            final JWKCredential jwkCred = (JWKCredential)cred;
+            if (jwkCred.getAlgorithm() != null && 
+                    encryptedObject.getHeader().getAlgorithm().equals(jwkCred.getAlgorithm())) {
+                // Matches
+                return;                
+            } else {
+                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");      
+    }
+    
+    
+    /**
+     * Validates the 'enc' algorithm in the header matches the encryption algorithm specified for the credential.
+     * 
+     * @param encryptedObject the JWE
+     * @param cred the credential to check algorithm compatibility
+     * 
+     * @throws DecryptionException if there is an algorithm mismatch.
+     */
+    private void validateContentEncryptionAlgorithm(
+            @Nonnull final EncryptedJWT encryptedObject, @Nonnull final Credential cred) throws DecryptionException {
+        
+        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();
+            
+            if (!encryptedObject.getHeader().getEncryptionMethod().equals(encMethod)) {
+                throw new DecryptionException("JOSE Header 'enc' algorithm "
+                        +encryptedObject.getHeader().getEncryptionMethod().getName()+
+                        " does not match credential algorithm "+encMethod);
+            }
+        
+        } else {
+            throw new DecryptionException("Credential did not specify which 'enc' algorithm it supports");
+        }
+        // Otherwise, all fine.
+    }
+    
+    /**
+     * Validate the supplied algorithm URI against the configured include and exclude lists.
+     * 
+     * @param algorithmURI the algorithm URI to evaluate
+     * @throws DecryptionException if the algorithm URI does not satisfy the include/exclude policy
+     */
+    private void validateAlgorithmURI(@Nonnull final String algorithmURI) throws DecryptionException {
+        log.debug("Validating algorithm URI against include and exclude lists: "
+                + "algorithm: {}, included: {}, excluded: {}",
+                algorithmURI, params.getIncludedAlgorithms(), params.getExcludedAlgorithms());
+        
+        if (!AlgorithmSupport.validateAlgorithmURI(algorithmURI,  
+                params.getIncludedAlgorithms(), params.getExcludedAlgorithms())) {
+            throw new DecryptionException("Algorithm failed include/exclude validation: " + algorithmURI);
+        }
+        
+    }
+
+}
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
new file mode 100644
index 0000000..5c2159e
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTDecrypterTest.java
@@ -0,0 +1,381 @@
+package net.shibboleth.oidc.security.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.nio.charset.StandardCharsets;
+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.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.xmlsec.encryption.support.DecryptionException;
+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.JWEObject.State;
+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.AESEncrypter;
+import com.nimbusds.jose.crypto.DirectEncrypter;
+import com.nimbusds.jose.crypto.ECDHEncrypter;
+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.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.JWTDecryptionParameters;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.impl.JWKEncryptionCredentialContext;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/** Tests for the JWTDecrypter.*/
+public class JWTDecrypterTest {
+    
+    private JWTDecrypter decrypter;
+    
+    private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+    
+    
+    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();
+    }
+    
+    private SignedJWT createdSignedJWT() throws KeyLengthException, JOSEException {        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
+                .type(JOSEObjectType.JWT)
+                .build();       
+        final var signedJWT = new SignedJWT(header,createClaims());
+        signedJWT.sign(new MACSigner(CLIENT_SECRET));
+        return signedJWT;
+    }
+    
+    @Test(expectedExceptions = DecryptionException.class)
+    void testUnsupportedAlg() throws Exception {       
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.PBES2_HS256_A128KW, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .build(),
+                new Payload(createdSignedJWT()));
+        jweObject.encrypt(new PasswordBasedEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8),8,1000));
+        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());        
+       
+        decrypter = new JWTDecrypter(new JWTDecryptionParameters());
+        decrypter.decrypt(jwe);
+    }
+    
+    @Test
+    void testDecryptionByKeyWrapping() throws Exception {       
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .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 CredentialResolver() {
+            
+            @Override
+            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+                jwkCredential.setAlgorithm(JWEAlgorithm.A256KW);
+                jwkCredential.getKeyNames().add("mock-key");
+                jwkCredential.setSecretKey(new SecretKeySpec(
+                        JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));          
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        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")
+                .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 CredentialResolver() {
+            
+            @Override
+            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"));          
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        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")
+                .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 CredentialResolver() {
+            
+            @Override
+            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"));          
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        
+        // Include the 'enc' algorithm but not the 'alg' algorithm. So decrypt should fail.
+        params.setIncludedAlgorithms(List.of("A256GCM"));
+        try {
+            decrypter = new JWTDecrypter(params);
+            decrypter.decrypt(jwe);
+        } catch (final DecryptionException e) {
+            // Do nothing, JWE should not be decrypted.
+        }
+        assertTrue(jwe.getState() == State.ENCRYPTED);
+    }
+    
+    @Test
+    void testDecryptionByDirectEncryption_NoSuitableKey() throws Exception {       
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .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();
+        // Wrong credential type used here
+        params.setContentEncryptionKeyCredentialResolver(new CredentialResolver() {
+            
+            @Override
+            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+                jwkCredential.setAlgorithm(JWEAlgorithm.A128KW);
+                jwkCredential.getKeyNames().add("mock-key");
+                jwkCredential.setSecretKey(new SecretKeySpec(
+                        JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));          
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        try {
+            decrypter = new JWTDecrypter(params);
+            decrypter.decrypt(jwe);
+        } catch (final DecryptionException e) {
+            // Do nothing, JWE should not be decrypted.
+        }
+        assertTrue(jwe.getState() == State.ENCRYPTED);
+    }
+    
+    @Test
+    void testDecryptionByKeyWrapping_WrongAlgorithm() throws KeyLengthException, JOSEException, ParseException  {       
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .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 CredentialResolver() {
+            
+            @Override
+            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+                jwkCredential.setAlgorithm(JWEAlgorithm.A128KW);
+                jwkCredential.getKeyNames().add("mock-key");
+                jwkCredential.setSecretKey(new SecretKeySpec(
+                        JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));          
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        decrypter = new JWTDecrypter(params);       
+        try {
+            decrypter.decrypt(jwe);
+        } catch (final DecryptionException e) {
+            // Do nothing, JWE should not be decrypted.
+        }
+        assertTrue(jwe.getState() == State.ENCRYPTED);    
+    }
+    
+    @Test
+    void testDecryptionByKeyEncryption() 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")
+                .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 CredentialResolver() {
+            
+            @Override
+            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+                jwkCredential.setAlgorithm(JWEAlgorithm.RSA_OAEP_256);
+                jwkCredential.getKeyNames().add("mock-key");
+                try {
+                    jwkCredential.setPrivateKey(key.toPrivateKey());
+                    jwkCredential.setPublicKey(key.toPublicKey());
+                } catch (final JOSEException e) {
+                    fail();
+                }
+                
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        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  {       
+        
+        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")
+                .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 CredentialResolver() {
+            
+            @Override
+            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
+                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+                jwkCredential.setAlgorithm(JWEAlgorithm.ECDH_ES_A256KW);
+                jwkCredential.getKeyNames().add("mock-key");
+                try {
+                    jwkCredential.setPrivateKey(key.toPrivateKey());
+                    jwkCredential.setPublicKey(key.toPublicKey());
+                } catch (final JOSEException e) {
+                    fail();
+                }
+                
+                return jwkCredential;
+            }
+            
+            @Override
+            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
+                return List.of(resolveSingle(criteria));
+            }
+        });
+        decrypter = new JWTDecrypter(params);
+        final JWT decryptedJWE = decrypter.decrypt(jwe);
+        assertTrue(jwe.getState() == State.DECRYPTED);
+        assertTrue(decryptedJWE instanceof SignedJWT);
+        assertEquals(decryptedJWE.getJWTClaimsSet().getSubject(), "jdoe");    
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list