You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2022/09/16 22:03:27 UTC

[tomee] branch main updated: Tests for all supported signature algorithms

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 <db...@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);
         }
     }
 }