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

[SDK-3311] Added protection against CVE-2022-21449 #579

Merged
merged 6 commits into from May 5, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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