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