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

Allow to relax the JWT build keys verification #553

Merged
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
2 changes: 2 additions & 0 deletions doc/modules/ROOT/pages/generate-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ Smallrye JWT supports the following properties which can be used to customize th
|smallrye.jwt.encrypt.key.location|none|Location of a key which will be used to encrypt the claims or inner JWT when a no-argument encrypt() method is called.
|smallrye.jwt.encrypt.key|none|Key value which will be used to encrypt the claims or inner JWT when a no-argument encrypt() method is called.
|smallrye.jwt.encrypt.key.id|none|Encryption key identifier which is checked only when JWK keys are used.
|smallrye.jwt.encrypt.relax-key-validation|false|Relax the validation of the encryption keys
|smallrye.jwt.sign.key.location|none|Location of a key which will be used to sign the claims when either a no-argument sign() or innerSign() method is called.
|smallrye.jwt.sign.key|none|Key value which will be used to sign the claims when either a no-argument sign() or innerSign() method is called.
|smallrye.jwt.sign.key.id|none|Signing key identifier which is checked only when JWK keys are used.
|smallrye.jwt.sign.relax-key-validation|false|Relax the validation of the signing keys
|smallrye.jwt.new-token.signature-algorithm|RS256|Signature algorithm. This property will be checked if the JWT signature builder has not already set the signature algorithm.
|smallrye.jwt.new-token.key-encryption-algorithm|RSA-OAEP|Key encryption algorithm. This property will be checked if the JWT encryption builder has not already set the key encryption algorithm.
|smallrye.jwt.new-token.content-encryption-algorithm|A256GCM|Content encryption algorithm. This property will be checked if the JWT encryption builder has not already set the content encryption algorithm.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ JwtSignatureException unsupportedSignatureAlgorithm(String algorithmName,

JwtSignatureException unsupportedSignatureAlgorithm(String algorithmName);

@Message(id = 5001, value = "A key of size 2048 bits or larger MUST be used with " +
"the '%s' algorithm")
JwtEncryptionException encryptionKeySizeMustBeHigher(String algorithmName);

@Message(id = 5003, value = "%s")
JwtEncryptionException joseSerializationError(String errorMessage, @Cause Throwable t);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ public class JwtBuildUtils {
public static final String SIGN_KEY_LOCATION_PROPERTY = "smallrye.jwt.sign.key.location";
public static final String SIGN_KEY_PROPERTY = "smallrye.jwt.sign.key";
public static final String SIGN_KEY_ID_PROPERTY = "smallrye.jwt.sign.key.id";
public static final String SIGN_KEY_RELAX_VALIDATION_PROPERTY = "smallrye.jwt.sign.relax-key-validation";
public static final String ENC_KEY_LOCATION_PROPERTY = "smallrye.jwt.encrypt.key.location";
public static final String ENC_KEY_PROPERTY = "smallrye.jwt.encrypt.key";
public static final String ENC_KEY_ID_PROPERTY = "smallrye.jwt.encrypt.key.id";
public static final String ENC_KEY_RELAX_VALIDATION_PROPERTY = "smallrye.jwt.encrypt.relax-key-validation";

public static final String NEW_TOKEN_ISSUER_PROPERTY = "smallrye.jwt.new-token.issuer";
public static final String NEW_TOKEN_AUDIENCE_PROPERTY = "smallrye.jwt.new-token.audience";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,21 @@ private String encryptInternal(Key key) {
String keyAlgorithm = getKeyEncryptionAlgorithm(key);
jwe.setAlgorithmHeaderValue(keyAlgorithm);
jwe.setEncryptionMethodHeaderParameter(getContentEncryptionAlgorithm());

if (key instanceof RSAPublicKey && keyAlgorithm.startsWith(KeyEncryptionAlgorithm.RSA_OAEP.getAlgorithm())
&& ((RSAPublicKey) key).getModulus().bitLength() < 2048) {
throw ImplMessages.msg.encryptionKeySizeMustBeHigher(keyAlgorithm);
}
jwe.setKey(key);
if (isRelaxKeyValidation()) {
jwe.setDoKeyValidation(false);
}
try {
return jwe.getCompactSerialization();
} catch (org.jose4j.lang.JoseException ex) {
throw ImplMessages.msg.joseSerializationError(ex.getMessage(), ex);
}
}

private boolean isRelaxKeyValidation() {
return JwtBuildUtils.getConfigProperty(JwtBuildUtils.ENC_KEY_RELAX_VALIDATION_PROPERTY, Boolean.class, false);
}

private String getKeyEncryptionAlgorithm(Key keyEncryptionKey) {
String alg = (String) headers.get("alg");
if (alg == null) {
Expand Down Expand Up @@ -220,7 +222,7 @@ private static String getKeyContentFromConfig() {

String keyLocation = JwtBuildUtils.getConfigProperty(JwtBuildUtils.ENC_KEY_LOCATION_PROPERTY, String.class);
if (keyLocation != null) {
return getKeyContentFromLocation(keyLocation);
return getKeyContentFromLocation(keyLocation.trim());
}
String keyContent = JwtBuildUtils.getConfigProperty(JwtBuildUtils.ENC_KEY_PROPERTY, String.class);
if (keyContent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,20 @@ private String signInternal(Key signingKey) {

jws.setPayload(claims.toJson());
jws.setKey(signingKey);
if (isRelaxKeyValidation()) {
jws.setDoKeyValidation(false);
}
try {
return jws.getCompactSerialization();
} catch (Exception ex) {
throw ImplMessages.msg.signJwtTokenFailed(ex.getMessage(), ex);
}
}

private boolean isRelaxKeyValidation() {
return JwtBuildUtils.getConfigProperty(JwtBuildUtils.SIGN_KEY_RELAX_VALIDATION_PROPERTY, Boolean.class, false);
}

private String getSignatureAlgorithm(Key signingKey) {
String alg = (String) headers.get("alg");
if (alg == null) {
Expand Down Expand Up @@ -200,7 +207,7 @@ static String getKeyContentFromConfig() {

String keyLocation = JwtBuildUtils.getConfigProperty(JwtBuildUtils.SIGN_KEY_LOCATION_PROPERTY, String.class);
if (keyLocation != null) {
return getKeyContentFromLocation(keyLocation);
return getKeyContentFromLocation(keyLocation.trim());
}
String keyContent = JwtBuildUtils.getConfigProperty(JwtBuildUtils.SIGN_KEY_PROPERTY, String.class);
if (keyContent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class JwtBuildConfigSource implements ConfigSource {

private static final Set<String> PROPERTY_NAMES = new HashSet<>(Arrays.asList(
JwtBuildUtils.SIGN_KEY_ID_PROPERTY,
JwtBuildUtils.SIGN_KEY_RELAX_VALIDATION_PROPERTY,
JwtBuildUtils.ENC_KEY_RELAX_VALIDATION_PROPERTY,
JwtBuildUtils.ENC_KEY_ID_PROPERTY,
JwtBuildUtils.NEW_TOKEN_ISSUER_PROPERTY,
JwtBuildUtils.NEW_TOKEN_AUDIENCE_PROPERTY,
Expand All @@ -40,6 +42,9 @@ public class JwtBuildConfigSource implements ConfigSource {
private boolean useSignKeyProperty;
private boolean useEncryptionKeyProperty;

private boolean relaxSignatureKeyValidation;
private boolean relaxEncryptionKeyValidation;

@Override
public Map<String, String> getProperties() {
Map<String, String> map = new HashMap<>();
Expand Down Expand Up @@ -88,6 +93,14 @@ public Map<String, String> getProperties() {
map.put(JwtBuildUtils.NEW_TOKEN_AUDIENCE_PROPERTY, "https://custom-audience");
}

if (relaxSignatureKeyValidation) {
map.put(JwtBuildUtils.SIGN_KEY_RELAX_VALIDATION_PROPERTY, String.valueOf(relaxSignatureKeyValidation));
}

if (relaxEncryptionKeyValidation) {
map.put(JwtBuildUtils.ENC_KEY_RELAX_VALIDATION_PROPERTY, String.valueOf(relaxEncryptionKeyValidation));
}

map.put(JwtBuildUtils.NEW_TOKEN_OVERRIDE_CLAIMS_PROPERTY, String.valueOf(overrideMatchingClaims));
return map;
}
Expand Down Expand Up @@ -182,4 +195,13 @@ public void setUseEncryptionKeyProperty(boolean useEncryptionKeyProperty) {
this.useEncryptionKeyProperty = useEncryptionKeyProperty;
}

public void setRelaxSignatureKeyValidation(boolean relax) {
this.relaxSignatureKeyValidation = relax;

}

public void setRelaxEncryptionKeyValidation(boolean relax) {
this.relaxEncryptionKeyValidation = relax;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
Expand Down Expand Up @@ -192,19 +193,38 @@ public void testEncryptWithRsaOaep256Configured() throws Exception {
}

@Test
public void testEncryptWithInvalidRSAKey() throws Exception {
public void testEncryptWithShortRSAKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
PublicKey key = keyPairGenerator.generateKeyPair().getPublic();
try {
Jwt.claims().jwe().encrypt(key);
Assert.fail("JwtEncryptionException is expected due to the invalid key size");
} catch (JwtEncryptionException ex) {
Assert.assertEquals("SRJWT05001: A key of size 2048 bits or larger MUST be used with the 'RSA-OAEP' algorithm",
Assert.assertEquals(
"SRJWT05003: An RSA key of size 2048 bits or larger MUST be used with the all JOSE RSA algorithms (given key was only 1024 bits).",
ex.getMessage());
}
}

@Test
public void testEncryptWithShortRSAKeyAndRelaxedValidation() throws Exception {
KeyPair keyPair = KeyUtils.generateKeyPair(1024);

JwtBuildConfigSource configSource = JwtSignTest.getConfigSource();
configSource.setRelaxEncryptionKeyValidation(true);
try {
String jwt = Jwt.claims(Collections.singletonMap("customClaim", "custom-value"))
.jwe().encrypt(keyPair.getPublic());

JsonWebEncryption jwe = getJsonWebEncryption(jwt, keyPair.getPrivate(), true);
JwtClaims claims = JwtClaims.parse(jwe.getPlaintextString());
checkJwtClaims(claims);
} finally {
configSource.setRelaxEncryptionKeyValidation(false);
}
}

@Test
public void testEncryptWithEcKey() throws Exception {
EllipticCurveJsonWebKey jwk = createECJwk();
Expand Down Expand Up @@ -402,9 +422,17 @@ private static JsonWebEncryption getJsonWebEncryption(String compactJwe) throws
}

private static JsonWebEncryption getJsonWebEncryption(String compactJwe, Key decryptionKey) throws Exception {
return getJsonWebEncryption(compactJwe, decryptionKey, false);
}

private static JsonWebEncryption getJsonWebEncryption(String compactJwe, Key decryptionKey, boolean relaxKeyValidation)
throws Exception {
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setCompactSerialization(compactJwe);
jwe.setKey(decryptionKey);
if (relaxKeyValidation) {
jwe.setDoKeyValidation(false);
}
return jwe;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.Duration;
Expand Down Expand Up @@ -279,12 +279,10 @@ private void verifySignedJsonObject(String jwt) throws Exception {
}

@Test
public void testSignWithInvalidRSAKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
PrivateKey key = keyPairGenerator.generateKeyPair().getPrivate();
public void testSignWithShortRSAKey() throws Exception {
KeyPair keyPair = KeyUtils.generateKeyPair(1024);
try {
Jwt.claims().sign(key);
Jwt.claims().sign(keyPair.getPrivate());
Assert.fail("JwtSignatureException is expected due to the invalid key size");
} catch (JwtSignatureException ex) {
Assert.assertEquals(
Expand All @@ -293,6 +291,28 @@ public void testSignWithInvalidRSAKey() throws Exception {
}
}

@Test
public void testSignWithShortRSAKeyAndRelaxedValidation() throws Exception {
KeyPair keyPair = KeyUtils.generateKeyPair(1024);

JwtBuildConfigSource configSource = getConfigSource();
configSource.setRelaxSignatureKeyValidation(true);
try {
String jwt = Jwt.claims(Collections.singletonMap("customClaim", "custom-value"))
.sign(keyPair.getPrivate());

JsonWebSignature jws = getVerifiedJws(jwt, keyPair.getPublic(), true);
JwtClaims claims = JwtClaims.parse(jws.getPayload());

Assert.assertEquals(4, claims.getClaimsMap().size());
checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims);

Assert.assertEquals("custom-value", claims.getClaimValue("customClaim"));
} finally {
configSource.setRelaxSignatureKeyValidation(false);
}
}

@Test
public void testSignClaimsConfiguredKeyLocation() throws Exception {
JwtBuildConfigSource configSource = getConfigSource();
Expand Down Expand Up @@ -389,9 +409,16 @@ private static JsonWebSignature getVerifiedJws(String jwt) throws Exception {
}

static JsonWebSignature getVerifiedJws(String jwt, Key key) throws Exception {
return getVerifiedJws(jwt, key, false);
}

static JsonWebSignature getVerifiedJws(String jwt, Key key, boolean relaxKeyValidation) throws Exception {
JsonWebSignature jws = new JsonWebSignature();
jws.setKey(key);
jws.setCompactSerialization(jwt);
if (relaxKeyValidation) {
jws.setDoKeyValidation(false);
}
Assert.assertTrue(jws.verifySignature());
return jws;
}
Expand Down Expand Up @@ -564,6 +591,42 @@ public void testSignClaimsWithSecret() throws Exception {
Assert.assertEquals("custom-value", claims.getClaimValue("customClaim"));
}

@Test
public void testSignClaimsWithShortSecret() throws Exception {
String secret = "AyM1SysPpbyDfgZld3umj1qzKObw";

JwtSignatureException thrown = assertThrows("JwtSignatureException is expected",
JwtSignatureException.class, () -> Jwt.claims().claim("customClaim", "custom-value").signWithSecret(secret));
Assert.assertEquals(
"A key of the same size as the hash output (i.e. 256 bits for HS256) or larger MUST be used with the HMAC SHA"
+ " algorithms but this key is only 224 bits",
thrown.getCause().getMessage());
}

@Test
public void testSignClaimsWithShortSecretAndRelaxedValidation() throws Exception {
String secret = "AyM1SysPpbyDfgZld3umj1qzKObw";

JwtBuildConfigSource configSource = getConfigSource();
configSource.setRelaxSignatureKeyValidation(true);
try {
String jwt = Jwt.claims()
.claim("customClaim", "custom-value")
.signWithSecret(secret);

SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES");
JsonWebSignature jws = getVerifiedJws(jwt, secretKey, true);
JwtClaims claims = JwtClaims.parse(jws.getPayload());

Assert.assertEquals(4, claims.getClaimsMap().size());
checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "HS256", 300);

Assert.assertEquals("custom-value", claims.getClaimValue("customClaim"));
} finally {
configSource.setRelaxSignatureKeyValidation(false);
}
}

@Test
public void testSignClaimsJwkSymmetricKey() throws Exception {
JwtBuildConfigSource configSource = getConfigSource();
Expand Down