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