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/09 16:23:16 UTC

[tomee] branch main updated (28e9a390cd -> ab6e17dc8f)

This is an automated email from the ASF dual-hosted git repository.

dblevins pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git


    from 28e9a390cd TOMEE-4001 - Upgrade Tomcat to 10.0.23
     new 9f1f46b61e Tests for public key resolution
     new b682e9c48f Parsing for private decrypt keys
     new ab6e17dc8f TOMEE-3948 Decryption of JWTs using RSA-OAEP and A256GCM algorithms

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../microprofile/jwt/JsonWebTokenValidator.java    |   6 +-
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java |  36 ++++-
 .../jwt/config/JWTAuthConfiguration.java           |  40 ++++--
 .../jwt/config/JWTAuthConfigurationProperties.java |  10 +-
 .../{PublicKeyResolver.java => KeyResolver.java}   |  40 ++++--
 .../tomee/microprofile/jwt/CurveAsserts.java       |  52 +++++++
 .../apache/tomee/microprofile/jwt/KeyAsserts.java  |  54 ++++++++
 .../microprofile/jwt/config/KeyResolverTest.java   | 153 +++++++++++++++++++++
 8 files changed, 358 insertions(+), 33 deletions(-)
 rename mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/{PublicKeyResolver.java => KeyResolver.java} (79%)
 create mode 100644 mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/CurveAsserts.java
 create mode 100644 mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/KeyAsserts.java
 create mode 100644 mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/KeyResolverTest.java


[tomee] 01/03: Tests for public key resolution

Posted by db...@apache.org.
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 9f1f46b61edb5710e6e93afc9ed09b912146f567
Author: David Blevins <db...@tomitribe.com>
AuthorDate: Thu Sep 8 19:18:27 2022 -0700

    Tests for public key resolution
---
 .../tomee/microprofile/jwt/CurveAsserts.java       | 52 +++++++++++++++
 .../apache/tomee/microprofile/jwt/KeyAsserts.java  | 54 +++++++++++++++
 .../jwt/config/PublicKeyResolverTest.java          | 78 ++++++++++++++++++++++
 3 files changed, 184 insertions(+)

diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/CurveAsserts.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/CurveAsserts.java
new file mode 100644
index 0000000000..9de1e8734f
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/CurveAsserts.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 Tomitribe and community
+ *
+ * Licensed 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
+ *
+ *     https://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;
+
+
+import io.churchkey.shade.util.Hex;
+
+import java.math.BigInteger;
+import java.security.spec.ECFieldF2m;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+
+import static org.junit.Assert.assertEquals;
+
+public class CurveAsserts {
+
+    public static void assertParamSpec(final ECParameterSpec expected, final ECParameterSpec actual) {
+        assertEquals(expected.getCofactor(), actual.getCofactor());
+        assertBigInt(expected.getOrder(), actual.getOrder());
+        assertBigInt(expected.getCurve().getA(), actual.getCurve().getA());
+        assertBigInt(expected.getCurve().getB(), actual.getCurve().getB());
+        assertBigInt(expected.getGenerator().getAffineX(), actual.getGenerator().getAffineX());
+        assertBigInt(expected.getGenerator().getAffineY(), actual.getGenerator().getAffineY());
+
+        if (expected.getCurve().getField() instanceof ECFieldFp) {
+            assertBigInt(((ECFieldFp) expected.getCurve().getField()).getP(), ((ECFieldFp) actual.getCurve().getField()).getP());
+        }
+        if (expected.getCurve().getField() instanceof ECFieldF2m) {
+            assertBigInt(((ECFieldF2m) expected.getCurve().getField()).getReductionPolynomial(), ((ECFieldF2m) actual.getCurve().getField()).getReductionPolynomial());
+        }
+
+    }
+
+    public static void assertBigInt(final BigInteger expected, final BigInteger actual) {
+        final String e1 = Hex.toString(expected.toByteArray()).replaceFirst("^00", "");
+        final String a1 = Hex.toString(actual.toByteArray()).replaceFirst("^00", "");
+        assertEquals(e1, a1);
+    }
+}
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/KeyAsserts.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/KeyAsserts.java
new file mode 100644
index 0000000000..5ff253a457
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/KeyAsserts.java
@@ -0,0 +1,54 @@
+package org.apache.tomee.microprofile.jwt;
+
+
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+
+import static org.junit.Assert.assertEquals;
+
+public class KeyAsserts {
+    public static void assertRsaPrivateKey(final RSAPrivateCrtKey expected, final RSAPrivateCrtKey actual) {
+        assertEquals(expected.getPublicExponent(), actual.getPublicExponent());
+        assertEquals(expected.getCrtCoefficient(), actual.getCrtCoefficient());
+        assertEquals(expected.getPrimeExponentP(), actual.getPrimeExponentP());
+        assertEquals(expected.getPrimeExponentQ(), actual.getPrimeExponentQ());
+        assertEquals(expected.getPrimeP(), actual.getPrimeP());
+        assertEquals(expected.getPrimeQ(), actual.getPrimeQ());
+        assertEquals(expected.getPrivateExponent(), actual.getPrivateExponent());
+        assertEquals(expected.getModulus(), actual.getModulus());
+    }
+
+    public static void assertRsaPublicKey(final RSAPublicKey expected, final RSAPublicKey actual) {
+        assertEquals(expected.getPublicExponent(), actual.getPublicExponent());
+        assertEquals(expected.getModulus(), actual.getModulus());
+    }
+
+    public static void assertDsaPrivateKey(final DSAPrivateKey expected, final DSAPrivateKey actual) {
+        assertEquals(expected.getParams().getG(), actual.getParams().getG());
+        assertEquals(expected.getParams().getQ(), actual.getParams().getQ());
+        assertEquals(expected.getParams().getP(), actual.getParams().getP());
+        assertEquals(expected.getX(), actual.getX());
+    }
+
+    public static void assertDsaPublicKey(final DSAPublicKey expected, final DSAPublicKey actual) {
+        assertEquals(expected.getParams().getG(), actual.getParams().getG());
+        assertEquals(expected.getParams().getQ(), actual.getParams().getQ());
+        assertEquals(expected.getParams().getP(), actual.getParams().getP());
+        assertEquals(expected.getY(), actual.getY());
+    }
+
+    public static void assertEcPrivateKey(final ECPrivateKey expected, final ECPrivateKey actual) {
+        assertEquals("d", expected.getS(), actual.getS());
+        CurveAsserts.assertParamSpec(expected.getParams(), actual.getParams());
+    }
+
+    public static void assertEcPublicKey(final ECPublicKey expected, final ECPublicKey actual) {
+        assertEquals("x", expected.getW().getAffineX(), actual.getW().getAffineX());
+        assertEquals("y", expected.getW().getAffineY(), actual.getW().getAffineY());
+        CurveAsserts.assertParamSpec(expected.getParams(), actual.getParams());
+    }
+}
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java
new file mode 100644
index 0000000000..e792870ee9
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.config;
+
+import io.churchkey.Key;
+import io.churchkey.Keys;
+import org.apache.openejb.loader.Files;
+import org.apache.openejb.loader.IO;
+import org.apache.tomee.microprofile.jwt.KeyAsserts;
+import org.junit.Test;
+
+import java.io.File;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+
+public class PublicKeyResolverTest {
+
+    @Test
+    public void publicKeyPemFromFileUrl() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key privateKey = generate(Key.Algorithm.RSA);
+        final Key expected = privateKey.getPublicKey();
+
+        final File file = new File(dir, "publicKey.pem");
+        IO.copy(expected.encode(Key.Format.PEM), file);
+
+        final Map<String, java.security.Key> keys = new PublicKeyResolver().resolve(
+                Optional.empty(),
+                Optional.of(file.toURI().toASCIIString())).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
+    }
+
+    @Test
+    public void publicKeyPemContents() throws Exception {
+        final Key privateKey = generate(Key.Algorithm.RSA);
+        final Key expected = privateKey.getPublicKey();
+
+        final Map<String, java.security.Key> keys = new PublicKeyResolver().resolve(
+                Optional.of(expected.toPem()),
+                Optional.empty()).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
+    }
+
+    private Key generate(final Key.Algorithm algorithm) throws NoSuchAlgorithmException {
+        final KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.name());
+        final KeyPair pair = generator.generateKeyPair();
+        return Keys.of(pair);
+    }
+
+}


[tomee] 02/03: Parsing for private decrypt keys

Posted by db...@apache.org.
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 b682e9c48f0702e640bfd72bdc130fb449a65e9e
Author: David Blevins <db...@tomitribe.com>
AuthorDate: Thu Sep 8 20:08:25 2022 -0700

    Parsing for private decrypt keys
---
 .../microprofile/jwt/JsonWebTokenValidator.java    |   4 +-
 .../jwt/config/JWTAuthConfigurationProperties.java |   2 +-
 .../{PublicKeyResolver.java => KeyResolver.java}   |  40 ++++--
 .../microprofile/jwt/config/KeyResolverTest.java   | 153 +++++++++++++++++++++
 .../jwt/config/PublicKeyResolverTest.java          |  78 -----------
 5 files changed, 187 insertions(+), 90 deletions(-)

diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
index 40d2eec817..a0caf6d096 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
@@ -18,7 +18,7 @@ package org.apache.tomee.microprofile.jwt;
 
 import org.apache.openejb.util.Logger;
 import org.apache.tomee.microprofile.jwt.config.JWTAuthConfiguration;
-import org.apache.tomee.microprofile.jwt.config.PublicKeyResolver;
+import org.apache.tomee.microprofile.jwt.config.KeyResolver;
 import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipal;
 import org.eclipse.microprofile.jwt.Claims;
 import org.eclipse.microprofile.jwt.JsonWebToken;
@@ -136,7 +136,7 @@ public class JsonWebTokenValidator {
         }
 
         public Builder publicKey(final String keyContent) {
-            final Map<String, Key> keys = new PublicKeyResolver().readPublicKeys(keyContent);
+            final Map<String, Key> keys = new KeyResolver().readPublicKeys(keyContent);
             final Map.Entry<String, Key> key = keys.entrySet().iterator().next();
             return verificationKey(key.getValue());
         }
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 f5255bb428..1697c92c7e 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
@@ -92,7 +92,7 @@ public class JWTAuthConfigurationProperties {
         final Optional<String> publicKeyLocation = getPublicKeyLocation();
         final List<String> audiences = getAudiences();
 
-        final Map<String, Key> keys = new PublicKeyResolver().resolve(publicKeyContents, publicKeyLocation).orElse(null);
+        final Map<String, Key> keys = new KeyResolver().resolvePublicKey(publicKeyContents, publicKeyLocation).orElse(null);
         final Boolean allowNoExp = config.getOptionalValue("mp.jwt.tomee.allow.no-exp", Boolean.class).orElse(false);
 
         return JWTAuthConfiguration.authConfiguration(keys, getIssuer().orElse(null), allowNoExp, audiences.toArray(new String[0]));
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/KeyResolver.java
similarity index 79%
rename from mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolver.java
rename to mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/KeyResolver.java
index a32e669473..d9e73bfd7b 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/KeyResolver.java
@@ -17,7 +17,6 @@
 package org.apache.tomee.microprofile.jwt.config;
 
 import io.churchkey.Keys;
-import io.churchkey.util.Pem;
 import jakarta.enterprise.inject.spi.DeploymentException;
 import org.apache.openejb.loader.IO;
 
@@ -31,18 +30,28 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
 import static io.churchkey.Key.Type.PRIVATE;
+import static io.churchkey.Key.Type.PUBLIC;
 import static io.churchkey.Key.Type.SECRET;
 
-public class PublicKeyResolver {
+public class KeyResolver {
 
-    public Optional<Map<String, Key>> resolve(final Optional<String> publicKeyContents, final Optional<String> publicKeyLocation) {
+    public Optional<Map<String, Key>> resolvePublicKey(final Optional<String> keyContents, final Optional<String> keyLocation) {
+        return resolve(keyContents, keyLocation, this::validatePublicKeys);
+    }
+
+    public Optional<Map<String, Key>> resolveDecryptKey(final Optional<String> keyContents, final Optional<String> keyLocation) {
+        return resolve(keyContents, keyLocation, this::validateDecryptKeys);
+    }
+
+    private Optional<Map<String, Key>> resolve(final Optional<String> publicKeyContents, final Optional<String> publicKeyLocation, final Consumer<List<io.churchkey.Key>> validation) {
         final Stream<Supplier<Optional<Map<String, Key>>>> possiblePublicKeys =
-                Stream.of(() -> publicKeyContents.map(this::readPublicKeys),
-                        () -> publicKeyLocation.map(this::readPublicKeysFromLocation));
+                Stream.of(() -> publicKeyContents.map(publicKey -> readPublicKeys(publicKey, validation)),
+                        () -> publicKeyLocation.map(publicKeyLocation1 -> readPublicKeysFromLocation(publicKeyLocation1, validation)));
 
         return (Optional<Map<String, Key>>) possiblePublicKeys
                 .map(Supplier::get)
@@ -52,6 +61,10 @@ public class PublicKeyResolver {
     }
 
     public Map<String, Key> readPublicKeys(final String publicKey) {
+        return readPublicKeys(publicKey, this::validatePublicKeys);
+    }
+
+    public Map<String, Key> readPublicKeys(final String publicKey, final Consumer<List<io.churchkey.Key>> validation) {
         final List<io.churchkey.Key> keys;
         try {
             keys = Keys.decodeSet(publicKey);
@@ -63,8 +76,7 @@ public class PublicKeyResolver {
             throw new DeploymentException("No keys found in key contents: " + publicKey);
         }
 
-        checkInvalidTypes(keys, PRIVATE);
-        checkInvalidTypes(keys, SECRET);
+        validation.accept(keys);
 
         int unknown = 0;
 
@@ -85,6 +97,15 @@ public class PublicKeyResolver {
         return map;
     }
 
+    private void validatePublicKeys(final List<io.churchkey.Key> keys) {
+        checkInvalidTypes(keys, PRIVATE);
+        checkInvalidTypes(keys, SECRET);
+    }
+
+    private void validateDecryptKeys(final List<io.churchkey.Key> keys) {
+        checkInvalidTypes(keys, PUBLIC);
+    }
+
     private boolean defined(final io.churchkey.Key key, final String kid) {
         final String attribute = key.getAttribute(kid);
         return attribute != null && attribute.length() > 0;
@@ -102,7 +123,7 @@ public class PublicKeyResolver {
         }
     }
 
-    private Map<String, Key> readPublicKeysFromLocation(final String publicKeyLocation) {
+    private Map<String, Key> readPublicKeysFromLocation(final String publicKeyLocation, final Consumer<List<io.churchkey.Key>> validatePublicKeys) {
         final Stream<Supplier<Optional<String>>> possiblePublicKeysLocations =
                 Stream.of(() -> readPublicKeysFromClasspath(publicKeyLocation),
                         () -> readPublicKeysFromFile(publicKeyLocation),
@@ -113,7 +134,7 @@ public class PublicKeyResolver {
                 .filter(Optional::isPresent)
                 .map(Optional::get)
                 .findFirst()
-                .map(this::readPublicKeys)
+                .map(publicKey -> readPublicKeys(publicKey, validatePublicKeys))
                 .orElseThrow(() -> new DeploymentException(JWTAuthConfigurationProperties.PUBLIC_KEY_ERROR_LOCATION +
                         publicKeyLocation));
     }
@@ -164,4 +185,5 @@ public class PublicKeyResolver {
         }
     }
 
+
 }
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/KeyResolverTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/KeyResolverTest.java
new file mode 100644
index 0000000000..28614c7290
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/KeyResolverTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.config;
+
+import io.churchkey.Key;
+import io.churchkey.Keys;
+import jakarta.enterprise.inject.spi.DeploymentException;
+import org.apache.openejb.loader.Files;
+import org.apache.openejb.loader.IO;
+import org.apache.tomee.microprofile.jwt.KeyAsserts;
+import org.junit.Test;
+
+import java.io.File;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class KeyResolverTest {
+
+    @Test
+    public void publicKeyPemFromFileUrl() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key privateKey = generate(Key.Algorithm.RSA);
+        final Key expected = privateKey.getPublicKey();
+
+        final File file = new File(dir, "publicKey.pem");
+        IO.copy(expected.encode(Key.Format.PEM), file);
+
+        final Map<String, java.security.Key> keys = new KeyResolver().resolvePublicKey(
+                Optional.empty(),
+                Optional.of(file.toURI().toASCIIString())).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
+    }
+
+    @Test
+    public void publicKeyPemFromFileUrlInvalidKey() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key expected = generate(Key.Algorithm.RSA);
+
+        final File file = new File(dir, "publicKey.pem");
+        IO.copy(expected.encode(Key.Format.PEM), file);
+
+        try {
+            new KeyResolver().resolvePublicKey(
+                    Optional.empty(),
+                    Optional.of(file.toURI().toASCIIString())).get();
+            fail("Expected DeploymentException");
+        } catch (DeploymentException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void publicKeyPemContents() throws Exception {
+        final Key privateKey = generate(Key.Algorithm.RSA);
+        final Key expected = privateKey.getPublicKey();
+
+        final Map<String, java.security.Key> keys = new KeyResolver().resolvePublicKey(
+                Optional.of(expected.toPem()),
+                Optional.empty()).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
+    }
+
+    @Test
+    public void privateKeyPemFromFileUrl() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key expected = generate(Key.Algorithm.RSA);
+
+        final File file = new File(dir, "privateKey.pem");
+        IO.copy(expected.encode(Key.Format.PEM), file);
+
+        final Map<String, java.security.Key> keys = new KeyResolver().resolveDecryptKey(
+                Optional.empty(),
+                Optional.of(file.toURI().toASCIIString())).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPrivateKey((RSAPrivateCrtKey) expected.getKey(), (RSAPrivateCrtKey) actual);
+    }
+
+    @Test
+    public void privateKeyPemFromFileUrlInvalidKey() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key expected = generate(Key.Algorithm.RSA).getPublicKey();
+
+        final File file = new File(dir, "publicKey.pem");
+        IO.copy(expected.encode(Key.Format.PEM), file);
+
+        try {
+            new KeyResolver().resolveDecryptKey(
+                    Optional.empty(),
+                    Optional.of(file.toURI().toASCIIString())).get();
+            fail("Expected DeploymentException");
+        } catch (DeploymentException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void privateKeyJwkFromFileUrl() throws Exception {
+        final File dir = Files.tmpdir();
+        final Key expected = generate(Key.Algorithm.RSA);
+
+        final File file = new File(dir, "privateKey.jwk");
+        IO.copy(expected.encode(Key.Format.JWK), file);
+
+        final Map<String, java.security.Key> keys = new KeyResolver().resolveDecryptKey(
+                Optional.empty(),
+                Optional.of(file.toURI().toASCIIString())).get();
+
+        assertEquals(1, keys.size());
+        final java.security.Key actual = keys.values().iterator().next();
+
+        KeyAsserts.assertRsaPrivateKey((RSAPrivateCrtKey) expected.getKey(), (RSAPrivateCrtKey) actual);
+    }
+
+    private Key generate(final Key.Algorithm algorithm) throws NoSuchAlgorithmException {
+        final KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.name());
+        final KeyPair pair = generator.generateKeyPair();
+        return Keys.of(pair);
+    }
+
+}
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java
deleted file mode 100644
index e792870ee9..0000000000
--- a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/PublicKeyResolverTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.config;
-
-import io.churchkey.Key;
-import io.churchkey.Keys;
-import org.apache.openejb.loader.Files;
-import org.apache.openejb.loader.IO;
-import org.apache.tomee.microprofile.jwt.KeyAsserts;
-import org.junit.Test;
-
-import java.io.File;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Map;
-import java.util.Optional;
-
-import static org.junit.Assert.assertEquals;
-
-public class PublicKeyResolverTest {
-
-    @Test
-    public void publicKeyPemFromFileUrl() throws Exception {
-        final File dir = Files.tmpdir();
-        final Key privateKey = generate(Key.Algorithm.RSA);
-        final Key expected = privateKey.getPublicKey();
-
-        final File file = new File(dir, "publicKey.pem");
-        IO.copy(expected.encode(Key.Format.PEM), file);
-
-        final Map<String, java.security.Key> keys = new PublicKeyResolver().resolve(
-                Optional.empty(),
-                Optional.of(file.toURI().toASCIIString())).get();
-
-        assertEquals(1, keys.size());
-        final java.security.Key actual = keys.values().iterator().next();
-
-        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
-    }
-
-    @Test
-    public void publicKeyPemContents() throws Exception {
-        final Key privateKey = generate(Key.Algorithm.RSA);
-        final Key expected = privateKey.getPublicKey();
-
-        final Map<String, java.security.Key> keys = new PublicKeyResolver().resolve(
-                Optional.of(expected.toPem()),
-                Optional.empty()).get();
-
-        assertEquals(1, keys.size());
-        final java.security.Key actual = keys.values().iterator().next();
-
-        KeyAsserts.assertRsaPublicKey((RSAPublicKey) expected.getKey(), (RSAPublicKey) actual);
-    }
-
-    private Key generate(final Key.Algorithm algorithm) throws NoSuchAlgorithmException {
-        final KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm.name());
-        final KeyPair pair = generator.generateKeyPair();
-        return Keys.of(pair);
-    }
-
-}


[tomee] 03/03: TOMEE-3948 Decryption of JWTs using RSA-OAEP and A256GCM algorithms

Posted by db...@apache.org.
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 ab6e17dc8f0ea6d875c7107e1f39861b9dabf853
Author: David Blevins <db...@tomitribe.com>
AuthorDate: Fri Sep 9 09:20:39 2022 -0700

    TOMEE-3948 Decryption of JWTs using RSA-OAEP and A256GCM algorithms
---
 .../microprofile/jwt/JsonWebTokenValidator.java    |  2 +-
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java | 36 ++++++++++++++++---
 .../jwt/config/JWTAuthConfiguration.java           | 40 ++++++++++++++--------
 .../jwt/config/JWTAuthConfigurationProperties.java | 10 ++++--
 4 files changed, 66 insertions(+), 22 deletions(-)

diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
index a0caf6d096..2178832bc2 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/JsonWebTokenValidator.java
@@ -85,7 +85,7 @@ public class JsonWebTokenValidator {
             if (authConfiguration.isSingleKey()) {
                 builder.setVerificationKey(authConfiguration.getPublicKey());
             } else {
-                builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(authConfiguration.getPublicKeys()));
+                builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(authConfiguration.getPublicKeysJwk()));
             }
 
             final JwtConsumer jwtConsumer = builder.build();
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 c9f7c5049c..adee42c135 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
@@ -17,6 +17,7 @@
 package org.apache.tomee.microprofile.jwt;
 
 import jakarta.enterprise.inject.Instance;
+import jakarta.enterprise.inject.spi.DeploymentException;
 import jakarta.inject.Inject;
 import jakarta.servlet.Filter;
 import jakarta.servlet.FilterChain;
@@ -42,6 +43,7 @@ import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipal;
 import org.eclipse.microprofile.jwt.Claims;
 import org.eclipse.microprofile.jwt.JsonWebToken;
 import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwk.JsonWebKey;
 import org.jose4j.jws.AlgorithmIdentifiers;
 import org.jose4j.jwt.JwtClaims;
 import org.jose4j.jwt.MalformedClaimException;
@@ -50,14 +52,19 @@ import org.jose4j.jwt.consumer.InvalidJwtException;
 import org.jose4j.jwt.consumer.JwtConsumer;
 import org.jose4j.jwt.consumer.JwtConsumerBuilder;
 import org.jose4j.jwt.consumer.JwtContext;
+import org.jose4j.keys.resolvers.JwksDecryptionKeyResolver;
 import org.jose4j.keys.resolvers.JwksVerificationKeyResolver;
+import org.jose4j.lang.JoseException;
 
 import javax.security.auth.Subject;
 import java.io.IOException;
+import java.security.Key;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -277,7 +284,6 @@ public class MPJWTFilter implements Filter {
             this.jwtAuthConfiguration = authContextInfo;
         }
 
-
         public JsonWebToken validate(final HttpServletRequest request) {
 
             // not sure it's worth having synchronization inside a single request
@@ -357,12 +363,20 @@ public class MPJWTFilter implements Filter {
                     builder.setEvaluationTime(NumericDate.fromSeconds(0));
                 }
 
-                if (authContextInfo.isSingleKey()) {
+                if (authContextInfo.getPublicKeys().size() == 1) {
                     builder.setVerificationKey(authContextInfo.getPublicKey());
-                } else {
-                    builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(authContextInfo.getPublicKeys()));
+                } else if (authContextInfo.getPublicKeys().size() > 1) {
+                    builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(asJwks(authContextInfo.getPublicKeys())));
                 }
 
+                if (authContextInfo.getDecryptKeys().size() == 1){
+                    final Key decryptionKey = authContextInfo.getDecryptKeys().values().iterator().next();
+                    builder.setDecryptionKey(decryptionKey);
+                } else if (authContextInfo.getDecryptKeys().size() > 1) {
+                    builder.setDecryptionKeyResolver(new JwksDecryptionKeyResolver(asJwks(authContextInfo.getDecryptKeys())));
+                }
+                
+
                 final JwtConsumer jwtConsumer = builder.build();
                 final JwtContext jwtContext = jwtConsumer.process(token);
                 final String type = jwtContext.getJoseObjects().get(0).getHeader("typ");
@@ -392,5 +406,19 @@ public class MPJWTFilter implements Filter {
 
             return principal;
         }
+
+        public static List<JsonWebKey> asJwks(final Map<String, Key> keys) {
+            return keys.entrySet().stream().map(key -> {
+                try {
+                    final JsonWebKey jsonWebKey = JsonWebKey.Factory.newJwk(key.getValue());
+                    jsonWebKey.setKeyId(key.getKey());
+                    return jsonWebKey;
+                } catch (final JoseException e) {
+                    throw new DeploymentException(e);
+                }
+            }).collect(Collectors.toList());
+        }
     }
+
+
 }
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
index 930e9d7f23..cd66709355 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
@@ -16,15 +16,14 @@
  */
 package org.apache.tomee.microprofile.jwt.config;
 
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
 import org.jose4j.jwk.JsonWebKey;
-import org.jose4j.lang.JoseException;
 
 import java.security.Key;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Logger;
-import java.util.stream.Collectors;
 
 /**
  * The public key and expected issuer needed to validate a token.
@@ -34,6 +33,7 @@ public class JWTAuthConfiguration {
     public static final String DEFAULT_KEY = "DEFAULT";
 
     private Map<String, Key> publicKeys;
+    private Map<String, Key> decryptKeys;
     private String[] audiences;
     private String issuer;
     private int expGracePeriodSecs = 60;
@@ -48,7 +48,7 @@ public class JWTAuthConfiguration {
         this.audiences = audiences;
     }
 
-    private JWTAuthConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences) {
+    private JWTAuthConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences, final Map<String, Key> decryptKeys) {
         if (publicKeys == null) {
             this.publicKeys = Collections.EMPTY_MAP;
         } else if (publicKeys.size() == 1) {
@@ -57,6 +57,13 @@ public class JWTAuthConfiguration {
         } else {
             this.publicKeys = Collections.unmodifiableMap(publicKeys);
         }
+
+        if (decryptKeys == null) {
+            this.decryptKeys = Collections.EMPTY_MAP;
+        } else {
+            this.decryptKeys = Collections.unmodifiableMap(decryptKeys);
+        }
+        
         this.issuer = issuer;
         this.allowNoExpiryClaim = allowNoExpiryClaim;
         this.audiences = audiences;
@@ -71,7 +78,11 @@ public class JWTAuthConfiguration {
     }
 
     public static JWTAuthConfiguration authConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences) {
-        return new JWTAuthConfiguration(publicKeys, issuer, allowNoExpiryClaim, audiences);
+        return authConfiguration(publicKeys, issuer, allowNoExpiryClaim, audiences, null);
+    }
+
+    public static JWTAuthConfiguration authConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences, final Map<String, Key> decryptKeys) {
+        return new JWTAuthConfiguration(publicKeys, issuer, allowNoExpiryClaim, audiences, decryptKeys);
     }
 
     public String[] getAudiences() {
@@ -86,17 +97,16 @@ public class JWTAuthConfiguration {
         return publicKeys.get(DEFAULT_KEY);
     }
 
-    public List<JsonWebKey> getPublicKeys() {
-        return publicKeys.entrySet().stream().map(key -> {
-            try {
-                final JsonWebKey jsonWebKey = JsonWebKey.Factory.newJwk(key.getValue());
-                jsonWebKey.setKeyId(key.getKey());
-                return jsonWebKey;
-            } catch (final JoseException e) {
-                logger.warning(e.getMessage());
-                return null;
-            }
-        }).collect(Collectors.toList());
+    public Map<String, Key> getPublicKeys() {
+        return publicKeys;
+    }
+
+    public Map<String, Key> getDecryptKeys() {
+        return decryptKeys;
+    }
+
+    public List<JsonWebKey> getPublicKeysJwk() {
+        return MPJWTFilter.ValidateJSonWebToken.asJwks(publicKeys);
     }
 
     public String getIssuer() {
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 1697c92c7e..76ad9c9003 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
@@ -32,6 +32,7 @@ import java.util.Map;
 import java.util.Optional;
 
 import static org.eclipse.microprofile.jwt.config.Names.AUDIENCES;
+import static org.eclipse.microprofile.jwt.config.Names.DECRYPTOR_KEY_LOCATION;
 import static org.eclipse.microprofile.jwt.config.Names.ISSUER;
 import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY;
 import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY_LOCATION;
@@ -92,10 +93,15 @@ public class JWTAuthConfigurationProperties {
         final Optional<String> publicKeyLocation = getPublicKeyLocation();
         final List<String> audiences = getAudiences();
 
-        final Map<String, Key> keys = new KeyResolver().resolvePublicKey(publicKeyContents, publicKeyLocation).orElse(null);
+        final Optional<String> decryptorKeyLocation = config.getOptionalValue(DECRYPTOR_KEY_LOCATION, String.class);
+
+        final KeyResolver resolver = new KeyResolver();
+        final Map<String, Key> publicKeys = resolver.resolvePublicKey(publicKeyContents, publicKeyLocation).orElse(null);
+        final Map<String, Key> decryptkeys = resolver.resolveDecryptKey(Optional.empty(), decryptorKeyLocation).orElse(null);
+
         final Boolean allowNoExp = config.getOptionalValue("mp.jwt.tomee.allow.no-exp", Boolean.class).orElse(false);
 
-        return JWTAuthConfiguration.authConfiguration(keys, getIssuer().orElse(null), allowNoExp, audiences.toArray(new String[0]));
+        return JWTAuthConfiguration.authConfiguration(publicKeys, getIssuer().orElse(null), allowNoExp, audiences.toArray(new String[0]), decryptkeys);
     }
 
 }