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);
}
}
}