Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smallrye.jwt.verify.secretkey property for inlining secret keys #815

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
MikeEdgar marked this conversation as resolved.
Show resolved Hide resolved
|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
Loading