Skip to content

Commit

Permalink
Add smallrye.jwt.verify.secretkey property for inlining secret keys (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin authored Aug 15, 2024
1 parent c927220 commit d4c2714
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 9 deletions.
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ SmallRye JWT supports many properties which can be used to customize the token p
[cols="<m,<m,<2",options="header"]
|===
|Property Name|Default|Description
|smallrye.jwt.verify.secretkey|none|Secret key supplied as a string.
|smallrye.jwt.verify.key.location|NONE|Location of the verification key which can point to both public and secret keys. Secret keys can only be in the JWK format. Note that 'mp.jwt.verify.publickey.location' will be ignored if this property is set.
|smallrye.jwt.verify.algorithm|`RS256`|Signature algorithm. Set it to `ES256` to support the Elliptic Curve signature algorithm. This property is deprecated, use `mp.jwt.verify.publickey.algorithm`.
|smallrye.jwt.verify.key-format|`ANY`|Set this property to a specific key format such as `PEM_KEY`, `PEM_CERTIFICATE`, `JWK` or `JWK_BASE64URL` to optimize the way the verification key is loaded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class JWTAuthContextInfo {
private int clockSkew = 60;
private String publicKeyLocation;
private String publicKeyContent;
private String secretKeyContent;
private String decryptionKeyLocation;
private String decryptionKeyContent;
private Integer jwksRefreshInterval = 60;
Expand Down Expand Up @@ -115,6 +116,7 @@ public JWTAuthContextInfo(JWTAuthContextInfo orig) {
this.clockSkew = orig.getClockSkew();
this.publicKeyLocation = orig.getPublicKeyLocation();
this.publicKeyContent = orig.getPublicKeyContent();
this.secretKeyContent = orig.getSecretKeyContent();
this.decryptionKeyLocation = orig.getDecryptionKeyLocation();
this.decryptionKeyContent = orig.getDecryptionKeyContent();
this.jwksRefreshInterval = orig.getJwksRefreshInterval();
Expand Down Expand Up @@ -249,6 +251,14 @@ public void setPublicKeyContent(String publicKeyContent) {
this.publicKeyContent = publicKeyContent;
}

public String getSecretKeyContent() {
return this.secretKeyContent;
}

public void setSecretKeyContent(String secretKeyContent) {
this.secretKeyContent = secretKeyContent;
}

public String getDecryptionKeyContent() {
return this.decryptionKeyContent;
}
Expand Down Expand Up @@ -422,6 +432,7 @@ public String toString() {
", tokenAge=" + tokenAge +
", publicKeyLocation='" + publicKeyLocation + '\'' +
", publicKeyContent='" + publicKeyContent + '\'' +
", secretKeyContent='" + secretKeyContent + '\'' +
", decryptionKeyLocation='" + decryptionKeyLocation + '\'' +
", decryptionKeyContent='" + decryptionKeyContent + '\'' +
", jwksRefreshInterval=" + jwksRefreshInterval +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,14 @@ protected void initializeKeyContent() throws Exception {
return;
}

String content = authContextInfo.getPublicKeyContent() != null
? authContextInfo.getPublicKeyContent()
: readKeyContent(authContextInfo.getPublicKeyLocation());
String content = null;
if (authContextInfo.getPublicKeyContent() != null) {
content = authContextInfo.getPublicKeyContent();
} else if (authContextInfo.getSecretKeyContent() != null) {
content = authContextInfo.getSecretKeyContent();
} else {
content = readKeyContent(authContextInfo.getPublicKeyLocation());
}

// Try to init the verification key from the local PEM or JWK(S) content
if (mayBeFormat(KeyFormat.PEM_KEY)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@ public interface ConfigLogging extends BasicLogger {
@Message(id = 3006, value = "'%s' property is deprecated and will be removed in a future version. " +
"Use '%s ' property instead")
void replacedConfig(String originalConfig, String newConfig);

@LogMessage(level = Logger.Level.WARN)
@Message(id = 3007, value = "Public key is configured but either the secret key or key location are also configured and will be ignored")
void publicKeyConfiguredButOtherKeyPropertiesAreAlsoUsed();

@LogMessage(level = Logger.Level.WARN)
@Message(id = 3008, value = "Secret key is configured but the key location is also configured and will be ignored")
void secretKeyConfiguredButKeyLocationIsAlsoUsed();
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,17 @@ public static JWTAuthContextInfoProvider createWithKeyStoreLocation(String keyLo
theKeyStoreDecryptKeyAlias, false, false, issuer, Optional.empty());
}

private static JWTAuthContextInfoProvider create(String publicKey,
public static JWTAuthContextInfoProvider create(String key,
String keyLocation,
boolean secretKey,
boolean verifyCertificateThumbprint,
String issuer,
Optional<String> 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<String> theKeyStoreType,
Optional<String> theKeyStoreProvider,
Expand All @@ -158,7 +158,8 @@ private static JWTAuthContextInfoProvider create(String publicKey,
String issuer,
Optional<String> 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;
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -668,9 +676,20 @@ Optional<JWTAuthContextInfo> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit d4c2714

Please sign in to comment.