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 a65f782ee4 Tests for all supported signature algorithms new b8b130132d Merge branch 'main' of github.com:apache/tomee into main a65f782ee4 is described below commit a65f782ee417e8c6e37e38046f44d1c5a7a25b29 Author: David Blevins <dblev...@tomitribe.com> AuthorDate: Fri Sep 16 15:02:38 2022 -0700 Tests for all supported signature algorithms --- .../jwt/itest/SignatureAlgorithmsTest.java | 155 +++++++++++++++++++++ .../tomee/microprofile/jwt/itest/Tokens.java | 41 ++++-- 2 files changed, 186 insertions(+), 10 deletions(-) diff --git a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/SignatureAlgorithmsTest.java b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/SignatureAlgorithmsTest.java new file mode 100644 index 0000000000..7a1d4c87f8 --- /dev/null +++ b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/SignatureAlgorithmsTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 org.apache.tomee.microprofile.jwt.itest; + +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; +import jakarta.ws.rs.Path; +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; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class SignatureAlgorithmsTest { + + @Test + public void rs256() throws Exception { + assertAlgorithm("RS256", Tokens.rsa(2048, 256)); + } + + @Test + public void rs384() throws Exception { + assertAlgorithm("RS384", Tokens.rsa(2048, 384)); + } + + @Test + public void rs512() throws Exception { + assertAlgorithm("RS512", Tokens.rsa(2048, 512)); + } + + @Test + public void es256() throws Exception { + assertAlgorithm("ES256", Tokens.ec("secp256r1", 256)); + } + + public void assertAlgorithm(final String alg, final Tokens tokens) throws Exception { + final File appJar = Archive.archive() + .add(SignatureAlgorithmsTest.class) + .add(ColorService.class) + .add(Api.class) + .add("META-INF/microprofile-config.properties", "#\n" + + "mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())) + .asJar(); + + final TomEE tomee = TomEE.microprofile() + .add("webapps/test/WEB-INF/beans.xml", "") + .add("webapps/test/WEB-INF/lib/app.jar", appJar) +// .update() + .build(); + + final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL()); + + final String claims = "{" + + " \"sub\":\"Jane Awesome\"," + + " \"iss\":\"https://server.example.com\"," + + " \"groups\":[\"manager\",\"user\"]," + + " \"jti\":\"uB3r7zOr\"," + + " \"exp\":2552047942" + + "}"; + + {// valid token + final String token = tokens.asToken(claims); + + assertAlg(alg, token); + + final Response response = webClient.reset() + .path("/movies") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + token) + .get(); + assertEquals(200, response.getStatus()); + } + + {// invalid token + final String token = "a" + tokens.asToken(claims); + final Response response = webClient.reset() + .path("/movies") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + token) + .get(); + assertEquals(401, response.getStatus()); + } + } + + 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); + } + + @ApplicationPath("/api") + @LoginConfig(authMethod = "MP-JWT") + public class Api extends Application { + } + + @Path("/movies") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @RequestScoped + public static class ColorService { + + @GET + @RolesAllowed({"manager", "user"}) + public String getAllMovies() { + return "Green"; + } + } + +} \ No newline at end of file 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 8997764a2f..37c56635c4 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 @@ -20,16 +20,20 @@ import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.Requirement; +import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import io.churchkey.Keys; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECGenParameterSpec; import java.util.Base64; /** @@ -41,16 +45,27 @@ public class Tokens { private final PublicKey publicKey; private final int hashSize; private final String id; + private final String prefix; - public Tokens(final PrivateKey privateKey, final PublicKey publicKey, final int hashSize) { - this(privateKey, publicKey, hashSize, null); - } - - public Tokens(final PrivateKey privateKey, final PublicKey publicKey, final int hashSize, final String id) { + public Tokens(final PrivateKey privateKey, final PublicKey publicKey, final int hashSize, final String id, final String prefix) { this.privateKey = privateKey; this.publicKey = publicKey; this.hashSize = hashSize; this.id = id; + this.prefix = prefix; + + } + + public static Tokens ec(final String curveName, int hashSize) { + 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"); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + throw new IllegalStateException(e); + } } public int getHashSize() { @@ -70,7 +85,7 @@ public class Tokens { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(keyLength); final KeyPair pair = keyGen.generateKeyPair(); - return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, id); + return new Tokens(pair.getPrivate(), pair.getPublic(), hashSize, id, "RS"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } @@ -102,24 +117,30 @@ public class Tokens { public String asToken(final String claims) throws Exception { try { - final JWSHeader.Builder builder = new JWSHeader.Builder(new JWSAlgorithm("RS" + hashSize, Requirement.OPTIONAL)) + final JWSHeader.Builder builder = new JWSHeader.Builder(new JWSAlgorithm(prefix + hashSize, Requirement.OPTIONAL)) .type(JOSEObjectType.JWT); if (id != null) { builder.keyID(id); } - + final JWSHeader header = builder.build(); final JWTClaimsSet claimsSet = JWTClaimsSet.parse(claims); final SignedJWT jwt = new SignedJWT(header, claimsSet); - jwt.sign(new RSASSASigner(privateKey)); + if ("RS".equals(prefix)) { + jwt.sign(new RSASSASigner(privateKey)); + } else if ("ES".equals(prefix)) { + jwt.sign(new ECDSASigner((ECPrivateKey) privateKey)); + } else { + throw new IllegalStateException("Unsupported prefix: " + prefix); + } return jwt.serialize(); } catch (Exception e) { - throw new RuntimeException("Could not sign JWT"); + throw new RuntimeException("Could not sign JWT", e); } } }