[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