Skip to content

Commit

Permalink
Merge pull request #579 from auth0/psychic-signature-vulnerability-pr…
Browse files Browse the repository at this point in the history
…otection

[SDK-3311] Added protection against CVE-2022-21449
  • Loading branch information
poovamraj committed May 5, 2022
2 parents dd40410 + 9d471d2 commit 34caf1f
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -9,6 +9,8 @@

A Java implementation of [JSON Web Token (JWT) - RFC 7519](https://tools.ietf.org/html/rfc7519).

> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment.
If you're looking for an **Android** version of the JWT Decoder take a look at our [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) library.

> This library requires Java 8 or higher. The last version that supported Java 7 was 3.11.0.
Expand Down
68 changes: 65 additions & 3 deletions lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java
Expand Up @@ -5,6 +5,7 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
Expand Down Expand Up @@ -46,6 +47,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
if (publicKey == null) {
throw new IllegalStateException("The given Public Key is null.");
}
validateSignatureStructure(signatureBytes, publicKey);
boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), JOSEToDER(signatureBytes));

if (!valid) {
Expand Down Expand Up @@ -140,13 +142,42 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException {
return joseSignature;
}

//Visible for testing
byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
/**
* Added check for extra protection against CVE-2022-21449.
* This method ensures the signature's structure is as expected.
*
* @param joseSignature is the signature from the JWT
* @param publicKey public key used to verify the JWT
* @throws SignatureException if the signature's structure is not as per expectation
*/
// Visible for testing
void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) throws SignatureException {
// check signature length, moved this check from JOSEToDER method
if (joseSignature.length != ecNumberSize * 2) {
throw new SignatureException("Invalid JOSE signature format.");
}

// Retrieve R and S number's length and padding.
if (isAllZeros(joseSignature)) {
throw new SignatureException("Invalid signature format.");
}

// get R
byte[] rBytes = new byte[ecNumberSize];
System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize);
BigInteger r = new BigInteger(1, rBytes);
if(isAllZeros(rBytes)) {
throw new SignatureException("Invalid signature format.");
}

// get S
byte[] sBytes = new byte[ecNumberSize];
System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize);
BigInteger s = new BigInteger(1, sBytes);
if(isAllZeros(sBytes)) {
throw new SignatureException("Invalid signature format.");
}

//moved this check from JOSEToDER method
int rPadding = countPadding(joseSignature, 0, ecNumberSize);
int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length);
int rLength = ecNumberSize - rPadding;
Expand All @@ -157,6 +188,28 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
throw new SignatureException("Invalid JOSE signature format.");
}

BigInteger order = publicKey.getParams().getOrder();

// R and S must be less than N
if (order.compareTo(r) < 1) {
throw new SignatureException("Invalid signature format.");
}

if (order.compareTo(s) < 1){
throw new SignatureException("Invalid signature format.");
}
}

//Visible for testing
byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
// Retrieve R and S number's length and padding.
int rPadding = countPadding(joseSignature, 0, ecNumberSize);
int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length);
int rLength = ecNumberSize - rPadding;
int sLength = ecNumberSize - sPadding;

int length = 2 + rLength + 2 + sLength;

final byte[] derSignature;
int offset;
if (length > 0x7f) {
Expand Down Expand Up @@ -205,6 +258,15 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
return derSignature;
}

private boolean isAllZeros(byte[] bytes) {
for (byte b : bytes) {
if (b != 0) {
return false;
}
}
return true;
}

private int countPadding(byte[] bytes, int fromIndex, int toIndex) {
int padding = 0;
while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) {
Expand Down
162 changes: 160 additions & 2 deletions lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java
Expand Up @@ -4,6 +4,7 @@
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.JWTVerifier;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsIn;
import org.junit.Assert;
Expand All @@ -12,11 +13,13 @@
import org.junit.rules.ExpectedException;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.Base64;

Expand Down Expand Up @@ -574,6 +577,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce
.thenThrow(NoSuchAlgorithmException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand All @@ -592,6 +599,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception {
.thenThrow(InvalidKeyException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand All @@ -610,6 +621,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception
.thenThrow(SignatureException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand Down Expand Up @@ -939,12 +954,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt

@Test
public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception {
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
byte[] joseSignature = new byte[32 * 2 - 1];
exception.expect(SignatureException.class);
exception.expectMessage("Invalid JOSE signature format.");

algorithm256.JOSEToDER(joseSignature);
algorithm256.validateSignatureStructure(joseSignature, publicKey);
}

@Test
Expand Down Expand Up @@ -1309,4 +1325,146 @@ public void shouldFailOnECDSA256SigningWithDeprecatedMethodWhenProvidedPrivateKe
algorithm.sign(new byte[0]);
}

@Test
public void invalidECDSA256SignatureShouldFailTokenVerification() throws Exception {
exception.expect(SignatureVerificationException.class);
exception.expectCause(isA(SignatureException.class));

String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0._____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ";

ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC");
ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC");
ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC");
JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build();
JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build();
JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build();
JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build();
verifier256.verify(jwtWithInvalidSig);
verifier384.verify(jwtWithInvalidSig);
verifier512.verify(jwtWithInvalidSig);
verifier256k.verify(jwtWithInvalidSig);
}

@Test
public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception {
exception.expect(SignatureVerificationException.class);
exception.expectCause(isA(SignatureException.class));

String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC");
ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC");
ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC");
JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build();
JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build();
JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build();
JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build();
verifier256.verify(jwtWithInvalidSig);
verifier384.verify(jwtWithInvalidSig);
verifier512.verify(jwtWithInvalidSig);
verifier256k.verify(jwtWithInvalidSig);
}

@Test
public void signatureWithAllZerosShouldFail() throws Exception {
exception.expect(SignatureException.class);
exception.expectMessage("Invalid signature format.");

ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");

ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(pubKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
byte[] signatureBytes = new byte[64];
algorithm256.validateSignatureStructure(signatureBytes, pubKey);
}

@Test
public void signatureWithRZeroShouldFail() throws Exception {
exception.expect(SignatureException.class);
exception.expectMessage("Invalid signature format.");

ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");

String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));

String[] chunks = signedJwt.split("\\.");
byte[] signature = Base64.getUrlDecoder().decode(chunks[2]);

byte[] sigWithBlankR = new byte[signature.length];
for (int i = 0; i < signature.length; i++) {
if (i < signature.length / 2) {
sigWithBlankR[i] = 0;
} else {
sigWithBlankR[i] = signature[i];
}
}

ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
algorithm256.validateSignatureStructure(sigWithBlankR, publicKey);
}

@Test
public void signatureWithSZeroShouldFail() throws Exception {
exception.expect(SignatureException.class);
exception.expectMessage("Invalid signature format.");

ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");

String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));

String[] chunks = signedJwt.split("\\.");
byte[] signature = Base64.getUrlDecoder().decode(chunks[2]);

byte[] sigWithBlankS = new byte[signature.length];
for (int i = 0; i < signature.length; i++) {
if (i < signature.length / 2) {
sigWithBlankS[i] = signature[i];
} else {
sigWithBlankS[i] = 0;
}
}

ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
algorithm256.validateSignatureStructure(sigWithBlankS, publicKey);
}

@Test
public void signatureWithRValueNotLessThanOrderShouldFail() throws Exception {
exception.expect(SignatureException.class);
exception.expectMessage("Invalid signature format.");

ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");

String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));
String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ";

String[] chunks = jwtWithInvalidSig.split("\\.");
byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]);

ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
algorithm256.validateSignatureStructure(invalidSignature, publicKey);
}

@Test
public void signatureWithSValueNotLessThanOrderShouldFail() throws Exception {
exception.expect(SignatureException.class);
exception.expectMessage("Invalid signature format.");

ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");

String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));
String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ";

String[] chunks = jwtWithInvalidSig.split("\\.");
byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]);
invalidSignature[0] = Byte.MAX_VALUE;

ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
algorithm256.validateSignatureStructure(invalidSignature, publicKey);
}
}
Expand Up @@ -11,11 +11,14 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.Base64;

import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile;
Expand Down Expand Up @@ -591,6 +594,10 @@ public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exce
.thenThrow(NoSuchAlgorithmException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand All @@ -609,6 +616,10 @@ public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception {
.thenThrow(InvalidKeyException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand All @@ -627,6 +638,10 @@ public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception
.thenThrow(SignatureException.class);

ECPublicKey publicKey = mock(ECPublicKey.class);
when(publicKey.getParams()).thenReturn(mock(ECParameterSpec.class));
byte[] a = new byte[64];
Arrays.fill(a, Byte.MAX_VALUE);
when(publicKey.getParams().getOrder()).thenReturn(new BigInteger(a));
ECPrivateKey privateKey = mock(ECPrivateKey.class);
ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey);
Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider);
Expand Down Expand Up @@ -935,12 +950,13 @@ public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLengt

@Test
public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception {
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
byte[] joseSignature = new byte[32 * 2 - 1];
exception.expect(SignatureException.class);
exception.expectMessage("Invalid JOSE signature format.");

algorithm256.JOSEToDER(joseSignature);
algorithm256.validateSignatureStructure(joseSignature, publicKey);
}

@Test
Expand Down

0 comments on commit 34caf1f

Please sign in to comment.