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

commit 27ba03dc9a46d4775eb160add4cb77902db6898c
Author: David Blevins <dblev...@tomitribe.com>
AuthorDate: Tue Aug 30 13:43:28 2022 -0700

    TOMEE-3947 Elliptic Curve ES256 signature algorithm
---
 boms/tomee-microprofile/pom.xml                    |  11 +
 boms/tomee-plume/pom.xml                           |  11 +
 boms/tomee-plus/pom.xml                            |  11 +
 .../jwt/itest/PublicKeyLocationTest.java           |   1 +
 mp-jwt/pom.xml                                     |   5 +
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java |   5 +-
 .../jwt/config/JWTAuthConfigurationProperties.java |  17 +-
 .../microprofile/jwt/config/PublicKeyResolver.java | 228 ++++++---------------
 pom.xml                                            |   2 +-
 9 files changed, 109 insertions(+), 182 deletions(-)

diff --git a/boms/tomee-microprofile/pom.xml b/boms/tomee-microprofile/pom.xml
index 4af267bce1..996d744a03 100644
--- a/boms/tomee-microprofile/pom.xml
+++ b/boms/tomee-microprofile/pom.xml
@@ -210,6 +210,17 @@
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>io.churchkey</groupId>
+      <artifactId>churchkey</artifactId>
+      <version>1.22</version>
+      <exclusions>
+        <exclusion>
+          <artifactId>*</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>io.opentracing.contrib</groupId>
       <artifactId>opentracing-concurrent</artifactId>
diff --git a/boms/tomee-plume/pom.xml b/boms/tomee-plume/pom.xml
index f4a48441b8..247953c7e2 100644
--- a/boms/tomee-plume/pom.xml
+++ b/boms/tomee-plume/pom.xml
@@ -221,6 +221,17 @@
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>io.churchkey</groupId>
+      <artifactId>churchkey</artifactId>
+      <version>1.22</version>
+      <exclusions>
+        <exclusion>
+          <artifactId>*</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>io.opentracing.contrib</groupId>
       <artifactId>opentracing-concurrent</artifactId>
diff --git a/boms/tomee-plus/pom.xml b/boms/tomee-plus/pom.xml
index 51e84ce84b..91e16806b6 100644
--- a/boms/tomee-plus/pom.xml
+++ b/boms/tomee-plus/pom.xml
@@ -232,6 +232,17 @@
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>io.churchkey</groupId>
+      <artifactId>churchkey</artifactId>
+      <version>1.22</version>
+      <exclusions>
+        <exclusion>
+          <artifactId>*</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>io.opentracing.contrib</groupId>
       <artifactId>opentracing-concurrent</artifactId>
diff --git 
a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/PublicKeyLocationTest.java
 
b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/PublicKeyLocationTest.java
index 4cd49c1e87..453bf1686f 100644
--- 
a/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/PublicKeyLocationTest.java
+++ 
b/itests/microprofile-jwt-itests/src/test/java/org/apache/tomee/microprofile/jwt/itest/PublicKeyLocationTest.java
@@ -82,6 +82,7 @@ public class PublicKeyLocationTest {
                 .asJar();
 
         final TomEE tomee = TomEE.microprofile()
+//                .update()
                 .add("webapps/test/WEB-INF/beans.xml", "")
                 .add("webapps/test/WEB-INF/lib/app.jar", appJar)
                 .build();
diff --git a/mp-jwt/pom.xml b/mp-jwt/pom.xml
index cad40a98d7..183868b28d 100644
--- a/mp-jwt/pom.xml
+++ b/mp-jwt/pom.xml
@@ -84,6 +84,11 @@
       <artifactId>jose4j</artifactId>
       <version>0.7.9</version>
     </dependency>
+    <dependency>
+      <groupId>io.churchkey</groupId>
+      <artifactId>churchkey</artifactId>
+      <version>1.22</version>
+    </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
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 ea6a1de6eb..3ded1ff31b 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
@@ -335,7 +335,10 @@ public class MPJWTFilter implements Filter {
                                 new 
AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
                                         AlgorithmIdentifiers.RSA_USING_SHA256,
                                         AlgorithmIdentifiers.RSA_USING_SHA384,
-                                        AlgorithmIdentifiers.RSA_USING_SHA512
+                                        AlgorithmIdentifiers.RSA_USING_SHA512,
+                                        
AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256,
+                                        
AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384,
+                                        
AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512
                                 ));
 
                 if (!authContextInfo.isAllowNoExpiryClaim()) {
diff --git 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
index 47050a6885..87a8350d6b 100644
--- 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
+++ 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
@@ -16,17 +16,15 @@
  */
 package org.apache.tomee.microprofile.jwt.config;
 
-import org.eclipse.microprofile.config.Config;
-import org.eclipse.microprofile.config.ConfigProvider;
-
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.enterprise.context.Initialized;
 import jakarta.enterprise.event.Observes;
 import jakarta.enterprise.inject.spi.DeploymentException;
 import jakarta.servlet.ServletContext;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+
 import java.security.Key;
-import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
@@ -44,7 +42,6 @@ import static 
org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY_LOCA
  */
 @ApplicationScoped
 public class JWTAuthConfigurationProperties {
-    public static final List<String> JWK_SUPPORTED_KEY_TYPES = 
Collections.singletonList("RSA");
     public static final String PUBLIC_KEY_ERROR = "Could not read MicroProfile 
Public Key";
     public static final String PUBLIC_KEY_ERROR_LOCATION = PUBLIC_KEY_ERROR + 
" from Location: ";
 
@@ -75,10 +72,10 @@ public class JWTAuthConfigurationProperties {
     private JWTAuthConfiguration createJWTAuthConfiguration() {
         if (getVerifierPublicKey().isPresent() && 
getPublicKeyLocation().isPresent()) {
             throw new DeploymentException("Both " +
-                                          VERIFIER_PUBLIC_KEY +
-                                          " and " +
-                                          VERIFIER_PUBLIC_KEY_LOCATION +
-                                          " are being supplied. You must use 
only one.");
+                    VERIFIER_PUBLIC_KEY +
+                    " and " +
+                    VERIFIER_PUBLIC_KEY_LOCATION +
+                    " are being supplied. You must use only one.");
         }
 
         final Optional<String> publicKeyContents = getVerifierPublicKey();
diff --git 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolver.java
 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolver.java
index 89b928950a..a32e669473 100644
--- 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolver.java
+++ 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolver.java
@@ -16,39 +16,26 @@
  */
 package org.apache.tomee.microprofile.jwt.config;
 
-import org.jose4j.jwk.JsonWebKey;
-import org.jose4j.jwk.JsonWebKeySet;
-import org.jose4j.lang.JoseException;
-
+import io.churchkey.Keys;
+import io.churchkey.util.Pem;
 import jakarta.enterprise.inject.spi.DeploymentException;
-import jakarta.json.Json;
-import jakarta.json.JsonArray;
-import jakarta.json.JsonObject;
-import jakarta.json.JsonValue;
-import jakarta.json.stream.JsonParsingException;
-import java.io.BufferedReader;
+import org.apache.openejb.loader.IO;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.io.StringWriter;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.security.Key;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static org.jose4j.jwk.JsonWebKeySet.JWK_SET_MEMBER_NAME;
+import static io.churchkey.Key.Type.PRIVATE;
+import static io.churchkey.Key.Type.SECRET;
 
 public class PublicKeyResolver {
 
@@ -65,25 +52,60 @@ public class PublicKeyResolver {
     }
 
     public Map<String, Key> readPublicKeys(final String publicKey) {
-        final Stream<Supplier<Map<String, Key>>> possiblePublicKeysParses =
-                Stream.of(() -> parsePCKS8(publicKey),
-                        () -> parseJwk(publicKey),
-                        () -> parseJwkDecoded(publicKey),
-                        () -> parseJwks(publicKey),
-                        () -> parseJwksDecoded(publicKey));
+        final List<io.churchkey.Key> keys;
+        try {
+            keys = Keys.decodeSet(publicKey);
+        } catch (Exception e) {
+            throw new DeploymentException("Unable to decode key contents: " + 
publicKey);
+        }
 
-        return possiblePublicKeysParses
-                .map(Supplier::get)
-                .filter(keys -> !keys.isEmpty())
-                .findFirst()
-                .orElseThrow(() -> new DeploymentException(": " + publicKey));
+        if (keys.size() == 0) {
+            throw new DeploymentException("No keys found in key contents: " + 
publicKey);
+        }
+
+        checkInvalidTypes(keys, PRIVATE);
+        checkInvalidTypes(keys, SECRET);
+
+        int unknown = 0;
+
+        final Map<String, Key> map = new HashMap<>();
+        for (final io.churchkey.Key key : keys) {
+            final String id;
+            if (defined(key, "kid")) {
+                id = key.getAttribute("kid");
+            } else if (defined(key, "Comment")) {
+                id = key.getAttribute("Comment");
+            } else {
+                id = "Unknown " + (unknown++);
+            }
+
+            map.put(id, key.getKey());
+        }
+
+        return map;
+    }
+
+    private boolean defined(final io.churchkey.Key key, final String kid) {
+        final String attribute = key.getAttribute(kid);
+        return attribute != null && attribute.length() > 0;
+    }
+
+    private void checkInvalidTypes(final List<io.churchkey.Key> keys, final 
io.churchkey.Key.Type type) {
+        final long invalid = keys.stream()
+                .map(io.churchkey.Key::getType)
+                .filter(type::equals)
+                .count();
+
+        if (invalid > 0) {
+            throw new DeploymentException("Found " + invalid + " " + 
type.name().toLowerCase() +
+                    " keys in MP JWT key configuration.  Only Public Keys must 
be configured for JWT validation");
+        }
     }
 
     private Map<String, Key> readPublicKeysFromLocation(final String 
publicKeyLocation) {
         final Stream<Supplier<Optional<String>>> possiblePublicKeysLocations =
                 Stream.of(() -> readPublicKeysFromClasspath(publicKeyLocation),
                         () -> readPublicKeysFromFile(publicKeyLocation),
-                        () -> readPublicKeysFromHttp(publicKeyLocation),
                         () -> readPublicKeysFromUrl(publicKeyLocation));
 
         return possiblePublicKeysLocations
@@ -103,7 +125,7 @@ public class PublicKeyResolver {
             if (is == null) {
                 return Optional.empty();
             }
-            return Optional.of(readPublicKeyFromInputStream(is));
+            return Optional.of(IO.slurp(is));
         } catch (final IOException e) {
             throw new DeploymentException(
                     JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR_LOCATION + 
publicKeyLocation, e);
@@ -125,155 +147,21 @@ public class PublicKeyResolver {
                                 publicKeyLocation +
                                 ". File does not exist or it is a directory.");
             }
-            return 
Optional.of(readPublicKeyFromInputStream(locationURL.openStream()));
+            return Optional.of(IO.slurp(locationURL));
         } catch (final IOException | URISyntaxException e) {
             throw new DeploymentException(
                     JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR_LOCATION + 
publicKeyLocation, e);
         }
     }
 
-    private Optional<String> readPublicKeysFromHttp(final String 
publicKeyLocation) {
-        if (!publicKeyLocation.startsWith("http")) {
-            return Optional.empty();
-        }
-
-        try {
-            final URL locationURL = new URL(publicKeyLocation);
-            return 
Optional.of(readPublicKeyFromInputStream(locationURL.openStream()));
-        } catch (final IOException e) {
-            throw new DeploymentException(
-                    JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR_LOCATION + 
publicKeyLocation, e);
-        }
-    }
-
     private Optional<String> readPublicKeysFromUrl(final String 
publicKeyLocation) {
         try {
             final URL locationURL = new URL(publicKeyLocation);
-            return 
Optional.of(readPublicKeyFromInputStream(locationURL.openStream()));
+            return Optional.of(IO.slurp(locationURL));
         } catch (final IOException e) {
             throw new DeploymentException(
                     JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR_LOCATION + 
publicKeyLocation, e);
         }
     }
 
-    private String readPublicKeyFromInputStream(final InputStream publicKey) 
throws IOException {
-        final StringWriter content = new StringWriter();
-        try (final BufferedReader reader = new BufferedReader(new 
InputStreamReader(publicKey))) {
-            String line = reader.readLine();
-            while (line != null) {
-                content.write(line);
-                content.write('\n');
-                line = reader.readLine();
-            }
-        }
-        return content.toString();
-    }
-
-    private Map<String, Key> parsePCKS8(final String publicKey) {
-        try {
-            final X509EncodedKeySpec spec = new 
X509EncodedKeySpec(normalizeAndDecodePCKS8(publicKey));
-            final KeyFactory kf = KeyFactory.getInstance("RSA");
-            return Collections.singletonMap(null, kf.generatePublic(spec));
-        } catch (final NoSuchAlgorithmException | InvalidKeySpecException | 
IllegalArgumentException e) {
-            return Collections.emptyMap();
-        }
-    }
-
-    private Map<String, Key> parseJwk(final String publicKey) {
-        final JsonObject jwk;
-        try {
-            jwk = Json.createReader(new StringReader(publicKey)).readObject();
-        } catch (final JsonParsingException e) {
-            return Collections.emptyMap();
-        }
-
-        if (jwk.containsKey(JWK_SET_MEMBER_NAME)) {
-            return Collections.emptyMap();
-        }
-
-        validateJwk(jwk);
-
-        try {
-            final JsonWebKey key = JsonWebKey.Factory.newJwk(publicKey);
-            return Collections.singletonMap(key.getKeyId(), key.getKey());
-        } catch (final JoseException e) {
-            throw new 
DeploymentException(JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR + " JWK.", 
e);
-        }
-    }
-
-    private Map<String, Key> parseJwkDecoded(final String publicKey) {
-        final String publicKeyDecoded;
-        try {
-            publicKeyDecoded = new 
String(Base64.getDecoder().decode(publicKey));
-        } catch (final Exception e) {
-            return Collections.emptyMap();
-        }
-
-        return parseJwk(publicKeyDecoded);
-    }
-
-    private Map<String, Key> parseJwks(final String publicKey) {
-        final JsonObject jwks;
-        try {
-            jwks = Json.createReader(new StringReader(publicKey)).readObject();
-        } catch (final JsonParsingException e) {
-            return Collections.emptyMap();
-        }
-
-        try {
-            final JsonArray keys = jwks.getJsonArray(JWK_SET_MEMBER_NAME);
-            for (final JsonValue key : keys) {
-                validateJwk(key.asJsonObject());
-            }
-        } catch (final Exception e) {
-            throw new DeploymentException("MicroProfile Public Key JWKS 
invalid format.");
-        }
-
-        try {
-            final JsonWebKeySet keySet = new JsonWebKeySet(publicKey);
-            final Map<String, Key> keys =
-                    keySet.getJsonWebKeys()
-                            .stream()
-                            .collect(Collectors.toMap(JsonWebKey::getKeyId, 
JsonWebKey::getKey));
-            return Collections.unmodifiableMap(keys);
-        } catch (final JoseException e) {
-            throw new 
DeploymentException(JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR + " JWK.", 
e);
-        }
-    }
-
-    private Map<String, Key> parseJwksDecoded(final String publicKey) {
-        final String publicKeyDecoded;
-        try {
-            publicKeyDecoded = new 
String(Base64.getDecoder().decode(publicKey));
-        } catch (final Exception e) {
-            return Collections.emptyMap();
-        }
-
-        return parseJwks(publicKeyDecoded);
-    }
-
-    private void validateJwk(final JsonObject jwk) {
-        final String keyType = jwk.getString("kty", null);
-        if (keyType == null) {
-            throw new DeploymentException("MicroProfile Public Key JWK kty 
field is missing.");
-        }
-
-        if 
(!JWTAuthConfigurationProperties.JWK_SUPPORTED_KEY_TYPES.contains(keyType)) {
-            throw new DeploymentException("MicroProfile Public Key JWK kty not 
supported: " + keyType);
-        }
-    }
-
-    private byte[] normalizeAndDecodePCKS8(final String publicKey) {
-        if (publicKey.contains("PRIVATE KEY")) {
-            throw new DeploymentException("MicroProfile Public Key is 
Private.");
-        }
-
-        final String normalizedKey =
-                publicKey.replaceAll("-----BEGIN (.*)-----", "")
-                        .replaceAll("-----END (.*)----", "")
-                        .replaceAll("\r\n", "")
-                        .replaceAll("\n", "");
-
-        return Base64.getDecoder().decode(normalizedKey);
-    }
 }
diff --git a/pom.xml b/pom.xml
index 875b11b4b6..9b35512931 100644
--- a/pom.xml
+++ b/pom.xml
@@ -648,7 +648,7 @@
         <activeByDefault>true</activeByDefault>
       </activation>
       <modules>
-        <module>deps</module>
+<!--        <module>deps</module>-->
         <module>boms</module>
         <module>itests</module>
         <module>maven</module>

Reply via email to