This is an automated email from the ASF dual-hosted git repository. dblevins pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomee.git
The following commit(s) were added to refs/heads/main by this push: new 35d43a6916 iTests for supported JWE algorithms 35d43a6916 is described below commit 35d43a6916c5318a68f3401c31bf00db38a32c15 Author: David Blevins <dblev...@tomitribe.com> AuthorDate: Thu Oct 6 17:04:27 2022 -0500 iTests for supported JWE algorithms --- itests/microprofile-jwt-itests/pom.xml | 4 +- ...izesTest.java => EncryptionAlgorithmsTest.java} | 77 ++++++++++++++------ .../microprofile/jwt/itest/RsaKeySizesTest.java | 5 -- .../tomee/microprofile/jwt/itest/Tokens.java | 81 +++++++++++++++++++--- .../apache/tomee/microprofile/jwt/MPJWTFilter.java | 1 + 5 files changed, 131 insertions(+), 37 deletions(-) diff --git a/itests/microprofile-jwt-itests/pom.xml b/itests/microprofile-jwt-itests/pom.xml index 080e4e8acb..2a20483ad5 100644 --- a/itests/microprofile-jwt-itests/pom.xml +++ b/itests/microprofile-jwt-itests/pom.xml @@ -89,7 +89,7 @@ <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> - <version>4.23</version> + <version>9.25.4</version> <scope>test</scope> </dependency> <dependency> @@ -101,7 +101,7 @@ <dependency> <groupId>io.churchkey</groupId> <artifactId>churchkey</artifactId> - <version>1.21</version> + <version>1.22</version> <scope>test</scope> </dependency> <dependency> diff --git a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/EncryptionAlgorithmsTest.java similarity index 64% copy from itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java copy to itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/EncryptionAlgorithmsTest.java index de7a87aafa..a8ed538a2e 100644 --- a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java +++ b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/EncryptionAlgorithmsTest.java @@ -18,16 +18,11 @@ package org.apache.tomee.microprofile.jwt.itest; -import org.apache.cxf.feature.LoggingFeature; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.johnzon.jaxrs.JohnzonProvider; -import org.apache.tomee.server.composer.Archive; -import org.apache.tomee.server.composer.TomEE; -import org.eclipse.microprofile.auth.LoginConfig; -import org.junit.Test; - import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -36,6 +31,15 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.apache.cxf.feature.LoggingFeature; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.johnzon.jaxrs.JohnzonProvider; +import org.apache.tomee.server.composer.Archive; +import org.apache.tomee.server.composer.TomEE; +import org.eclipse.microprofile.auth.LoginConfig; +import org.junit.Test; + +import java.io.ByteArrayInputStream; import java.io.File; import java.net.URL; import java.util.Base64; @@ -43,36 +47,55 @@ import java.util.Base64; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -public class RsaKeySizesTest { +public class EncryptionAlgorithmsTest { @Test - public void test1024() throws Exception { - assertKey(Tokens.rsa(1024, 256)); + public void rsaOaep() throws Exception { + assertAlgorithm(Tokens.rsa(2048, "RSA-OAEP"), Tokens.rsa(2048, 256)); } @Test - public void test2048() throws Exception { - assertKey(Tokens.rsa(2048, 256)); + public void rsaOaep256() throws Exception { + assertAlgorithm(Tokens.rsa(2048, "RSA-OAEP-256"), Tokens.rsa(2048, 256)); } @Test - public void test4096() throws Exception { - assertKey(Tokens.rsa(4096, 256)); + public void ecdhEs() throws Exception { + assertAlgorithm(Tokens.ec("secp256r1", "ECDH-ES"), Tokens.ec("secp256r1", 256)); } - public void assertKey(final Tokens tokens) throws Exception { + @Test + public void ecdhEsA128KW() throws Exception { + assertAlgorithm(Tokens.ec("secp256r1", "ECDH-ES+A128KW"), Tokens.ec("secp256r1", 256)); + } + + @Test + public void ecdhEsA192KW() throws Exception { + assertAlgorithm(Tokens.ec("secp256r1", "ECDH-ES+A192KW"), Tokens.ec("secp256r1", 256)); + } + + @Test + public void ecdhEsA256KW() throws Exception { + assertAlgorithm(Tokens.ec("secp256r1", "ECDH-ES+A256KW"), Tokens.ec("secp256r1", 256)); + } + + public void assertAlgorithm(final Tokens encryptor, final Tokens singer) throws Exception { final File appJar = Archive.archive() - .add(RsaKeySizesTest.class) + .add(EncryptionAlgorithmsTest.class) .add(ColorService.class) .add(Api.class) + .add("signer.pem", singer.getPemPublicKey()) + .add("encryptor.pem", encryptor.getPemPrivateKey()) .add("META-INF/microprofile-config.properties", "#\n" + - "mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())) + "mp.jwt.decrypt.key.location=encryptor.pem\n" + + "mp.jwt.verify.publickey.location=signer.pem\n") .asJar(); final TomEE tomee = TomEE.microprofile() .add("webapps/test/WEB-INF/beans.xml", "") .add("webapps/test/WEB-INF/lib/app.jar", appJar) // .update() +// .debug(5005) .build(); final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL()); @@ -86,7 +109,10 @@ public class RsaKeySizesTest { "}"; {// valid token - final String token = tokens.asToken(claims); + final String token = encryptor.asEncryptedToken(claims, singer); + + assertAlg(encryptor.getAlg(), token); + final Response response = webClient.reset() .path("/movies") .header("Content-Type", "application/json") @@ -96,7 +122,7 @@ public class RsaKeySizesTest { } {// invalid token - final String token = tokens.asToken(claims) + "a"; + final String token = "a" + encryptor.asEncryptedToken(claims, singer); final Response response = webClient.reset() .path("/movies") .header("Content-Type", "application/json") @@ -106,6 +132,17 @@ public class RsaKeySizesTest { } } + private void assertAlg(final String expected, final String token) { + final String encodedHeader = token.split("\\.")[0]; + + final byte[] decoded = Base64.getDecoder().decode(encodedHeader); + final JsonReader reader = Json.createReader(new ByteArrayInputStream(decoded)); + final JsonObject jsonObject = reader.readObject(); + final String actual = jsonObject.getString("alg"); + + assertEquals(expected, actual); + } + private static WebClient createWebClient(final URL base) { return WebClient.create(base.toExternalForm(), singletonList(new JohnzonProvider<>()), singletonList(new LoggingFeature()), null); diff --git a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java index de7a87aafa..737cc46118 100644 --- a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java +++ b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/RsaKeySizesTest.java @@ -45,11 +45,6 @@ import static org.junit.Assert.assertEquals; public class RsaKeySizesTest { - @Test - public void test1024() throws Exception { - assertKey(Tokens.rsa(1024, 256)); - } - @Test public void test2048() throws Exception { assertKey(Tokens.rsa(2048, 256)); diff --git a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/Tokens.java b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/Tokens.java index a9754a709a..5631f0b330 100644 --- a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/Tokens.java +++ b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/Tokens.java @@ -16,11 +16,18 @@ */ package org.apache.tomee.microprofile.jwt.itest; +import com.nimbusds.jose.EncryptionMethod; 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.Payload; import com.nimbusds.jose.Requirement; +import com.nimbusds.jose.crypto.ECDHEncrypter; import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.RSAEncrypter; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -36,6 +43,8 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; import java.security.spec.ECGenParameterSpec; import java.util.Base64; @@ -48,24 +57,36 @@ public class Tokens { private final PublicKey publicKey; private final int hashSize; private final String id; - private final String prefix; + private final String alg; - public Tokens(final PrivateKey privateKey, final PublicKey publicKey, final int hashSize, final String id, final String prefix) { + public Tokens(final PrivateKey privateKey, final PublicKey publicKey, final int hashSize, final String id, final String alg) { this.privateKey = privateKey; this.publicKey = publicKey; this.hashSize = hashSize; this.id = id; - this.prefix = prefix; + this.alg = alg; } + public String getAlg() { + return alg; + } + public static Tokens ec(final String curveName, int hashSize) { + return ec(curveName, hashSize, "ES"); + } + + public static Tokens ec(final String curveName, final String alg) { + return ec(curveName, -1, alg); + } + + public static Tokens ec(final String curveName, int hashSize, final String alg) { try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); ECGenParameterSpec spec = new ECGenParameterSpec(curveName); keyGen.initialize(spec); final KeyPair pair = keyGen.generateKeyPair(); - return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, null, "ES"); + return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, null, alg); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new IllegalStateException(e); } @@ -84,11 +105,19 @@ public class Tokens { } public static Tokens rsa(int keyLength, int hashSize, final String id) { + return rsa(keyLength, hashSize, id, "RS"); + } + + public static Tokens rsa(int keyLength, final String alg) { + return rsa(keyLength, -1, null, alg); + } + + public static Tokens rsa(int keyLength, int hashSize, final String id, final String rs) { try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(keyLength); final KeyPair pair = keyGen.generateKeyPair(); - return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, id, "RS"); + return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, id, rs); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } @@ -127,8 +156,12 @@ public class Tokens { } public String asToken(final String claims) { + return sign(claims).serialize(); + } + + public SignedJWT sign(final String claims) { try { - final JWSHeader.Builder builder = new JWSHeader.Builder(new JWSAlgorithm(prefix + hashSize, Requirement.OPTIONAL)) + final JWSHeader.Builder builder = new JWSHeader.Builder(new JWSAlgorithm(alg + hashSize, Requirement.OPTIONAL)) .type(JOSEObjectType.JWT); if (id != null) { @@ -141,20 +174,48 @@ public class Tokens { final SignedJWT jwt = new SignedJWT(header, claimsSet); - if ("RS".equals(prefix)) { + if (alg.startsWith("RS")) { jwt.sign(new RSASSASigner(privateKey)); - } else if ("ES".equals(prefix)) { + } else if (alg.startsWith("ES")) { jwt.sign(new ECDSASigner((ECPrivateKey) privateKey)); } else { - throw new IllegalStateException("Unsupported prefix: " + prefix); + throw new IllegalStateException("Unsupported prefix: " + alg); } - return jwt.serialize(); + return jwt; } catch (Exception e) { throw new RuntimeException("Could not sign JWT", e); } } + public String asEncryptedToken(final String claims, final Tokens signer) { + try { + + final SignedJWT signedJWT = signer.sign(claims); + + // Create JWE object with signed JWT as payload + final JWEObject jweObject = new JWEObject( + new JWEHeader.Builder(new JWEAlgorithm(alg), EncryptionMethod.A256GCM) + .contentType("JWT") // required to indicate nested JWT + .build(), + new Payload(signedJWT)); + + // Encrypt with the recipient's public key + if (alg.startsWith("RS")) { + jweObject.encrypt(new RSAEncrypter((RSAPublicKey) this.getPublicKey())); + } else if (alg.startsWith("EC")) { + jweObject.encrypt(new ECDHEncrypter((ECPublicKey) this.getPublicKey())); + } else { + throw new IllegalStateException("Unsupported prefix: " + alg); + } + + // Serialise to JWE compact form + return jweObject.serialize(); + } catch (Exception e) { + throw new RuntimeException("Could not encrypt JWT", e); + } + } + public static Tokens fromPrivateKey(final String contents) { final Key key; try { diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java index 948649718c..3bf36224d6 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java @@ -378,6 +378,7 @@ public class MPJWTFilter implements Filter { try { final JwtConsumerBuilder builder = new JwtConsumerBuilder() .setRelaxVerificationKeyValidation() + .setRelaxDecryptionKeyValidation() .setRequireSubject(); if (authContextInfo.getSignatureAlgorithm() != null) {