From d9b2f96176477a05f42bfea3cfc9d6c8e3a84314 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 15 Aug 2024 16:24:38 +0100 Subject: [PATCH] Add smallrye.jwt.verify.secretkey property for inlining secret keys --- doc/modules/ROOT/pages/configuration.adoc | 1 + .../auth/principal/JWTAuthContextInfo.java | 11 +++++ .../auth/principal/KeyLocationResolver.java | 11 +++-- .../io/smallrye/jwt/config/ConfigLogging.java | 8 ++++ .../config/JWTAuthContextInfoProvider.java | 31 +++++++++++--- .../principal/KeyLocationResolverTest.java | 42 +++++++++++++++++++ 6 files changed, 95 insertions(+), 9 deletions(-) diff --git a/doc/modules/ROOT/pages/configuration.adoc b/doc/modules/ROOT/pages/configuration.adoc index 9043583d..6d5a122d 100644 --- a/doc/modules/ROOT/pages/configuration.adoc +++ b/doc/modules/ROOT/pages/configuration.adoc @@ -39,6 +39,7 @@ SmallRye JWT supports many properties which can be used to customize the token p [cols=" decryptionKey) { - return create(publicKey, keyLocation, Optional.empty(), Optional.empty(), Optional.empty(), + return create(key, keyLocation, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), secretKey, verifyCertificateThumbprint, issuer, decryptionKey); } - private static JWTAuthContextInfoProvider create(String publicKey, + private static JWTAuthContextInfoProvider create(String key, String keyLocation, Optional theKeyStoreType, Optional theKeyStoreProvider, @@ -158,7 +158,8 @@ private static JWTAuthContextInfoProvider create(String publicKey, String issuer, Optional decryptionKey) { JWTAuthContextInfoProvider provider = new JWTAuthContextInfoProvider(); - provider.mpJwtPublicKey = publicKey; + provider.mpJwtPublicKey = !secretKey ? key : NONE; + provider.jwtSecretKey = secretKey ? key : NONE; provider.mpJwtPublicKeyAlgorithm = Optional.of(SignatureAlgorithm.RS256); provider.mpJwtLocation = !secretKey && !theKeyStoreDecryptKeyAlias.isPresent() ? keyLocation : NONE; provider.verifyKeyLocation = secretKey ? keyLocation : NONE; @@ -217,6 +218,13 @@ private static JWTAuthContextInfoProvider create(String publicKey, @Inject @ConfigProperty(name = "mp.jwt.verify.publickey", defaultValue = NONE) private String mpJwtPublicKey; + + /** + * @since 4.5.4 + */ + @Inject + @ConfigProperty(name = "smallrye.jwt.verify.secretkey", defaultValue = NONE) + private String jwtSecretKey; /** * @since 1.2 */ @@ -668,9 +676,20 @@ Optional getOptionalContextInfo() { contextInfo.setIssuedBy(mpJwtIssuer.trim()); } - if (!NONE.equals(mpJwtPublicKey)) { + final boolean verificationPublicKeySet = !NONE.equals(mpJwtPublicKey); + final boolean verificationSecretKeySet = !NONE.equals(jwtSecretKey); + final boolean verificationKeyLocationSet = !NONE.equals(resolvedVerifyKeyLocation); + if (verificationPublicKeySet) { contextInfo.setPublicKeyContent(mpJwtPublicKey); - } else if (!NONE.equals(resolvedVerifyKeyLocation)) { + if (verificationKeyLocationSet || verificationSecretKeySet) { + ConfigLogging.log.publicKeyConfiguredButOtherKeyPropertiesAreAlsoUsed(); + } + } else if (verificationSecretKeySet) { + contextInfo.setSecretKeyContent(jwtSecretKey); + if (verificationKeyLocationSet) { + ConfigLogging.log.secretKeyConfiguredButKeyLocationIsAlsoUsed(); + } + } else if (verificationKeyLocationSet) { String resolvedVerifyKeyLocationTrimmed = resolvedVerifyKeyLocation.trim(); if (resolvedVerifyKeyLocationTrimmed.startsWith("http")) { if (fetchRemoteKeysOnStartup) { diff --git a/testsuite/basic/src/test/java/io/smallrye/jwt/auth/principal/KeyLocationResolverTest.java b/testsuite/basic/src/test/java/io/smallrye/jwt/auth/principal/KeyLocationResolverTest.java index 4ab85065..23c5bfe5 100644 --- a/testsuite/basic/src/test/java/io/smallrye/jwt/auth/principal/KeyLocationResolverTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/jwt/auth/principal/KeyLocationResolverTest.java @@ -21,8 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.time.Instant; +import java.util.Base64; +import java.util.Optional; import org.eclipse.microprofile.jwt.tck.util.TokenUtils; import org.jose4j.jwt.JwtClaims; @@ -190,6 +193,45 @@ void verifyTokenSignedWithSecretKey() throws Exception { assertEquals("Alice", jwt.getClaimValueAsString("upn")); } + @Test + void verifyTokenSignedWithInlinedSecretKey() throws Exception { + String jwtString = Jwt.issuer("https://server.example.com").upn("Alice").sign("secretKey.jwk"); + JWTAuthContextInfoProvider provider = JWTAuthContextInfoProvider + .create("{\n" + + " \"kty\":\"oct\",\n" + + " \"k\":\"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I\"\n" + + " }", + null, + true, + false, + "https://server.example.com", + Optional.empty()); + JWTAuthContextInfo contextInfo = provider.getContextInfo(); + contextInfo.setSignatureAlgorithm(SignatureAlgorithm.HS256); + JwtClaims jwt = new DefaultJWTTokenParser().parse(jwtString, contextInfo).getJwtClaims(); + assertEquals("Alice", jwt.getClaimValueAsString("upn")); + } + + @Test + void verifyTokenSignedWithInlinedBase64UrlEncodedSecretKey() throws Exception { + String jwtString = Jwt.issuer("https://server.example.com").upn("Alice").sign("secretKey.jwk"); + byte[] bytes = ("{\n" + + " \"kty\":\"oct\",\n" + + " \"k\":\"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I\"\n" + + " }").getBytes(StandardCharsets.UTF_8); + JWTAuthContextInfoProvider provider = JWTAuthContextInfoProvider + .create(Base64.getUrlEncoder().withoutPadding().encodeToString(bytes), + null, + true, + false, + "https://server.example.com", + Optional.empty()); + JWTAuthContextInfo contextInfo = provider.getContextInfo(); + contextInfo.setSignatureAlgorithm(SignatureAlgorithm.HS256); + JwtClaims jwt = new DefaultJWTTokenParser().parse(jwtString, contextInfo).getJwtClaims(); + assertEquals("Alice", jwt.getClaimValueAsString("upn")); + } + @Test void decryptToken() throws Exception { String jwtString = Jwt.issuer("https://server.example.com").upn("Alice").jwe().encrypt("publicKey.pem");