Skip to content

Commit

Permalink
Added protection against CVE-2022-21449
Browse files Browse the repository at this point in the history
  • Loading branch information
poovamraj committed May 3, 2022
1 parent dd40410 commit 05b5449
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 7 deletions.
69 changes: 66 additions & 3 deletions lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java
Original file line number Diff line number Diff line change
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: All Zeros.");
}

// 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: All Zeros for R value.");
}

// 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: All Zeros for S value.");
}

//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,29 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
throw new SignatureException("Invalid JOSE signature format.");
}

// check for 0 r or s here since we have the values
if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) {
throw new SignatureException("R or S value cannot be zero.");
}

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

// R and S must be less than N
if (order.compareTo(r) < 1 || order.compareTo(s) < 1) {
throw new SignatureException("The difference between R or S value and order should be greater than one.");
}
}

//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 +259,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
139 changes: 137 additions & 2 deletions lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java
Original file line number Diff line number Diff line change
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,123 @@ 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);

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);

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);

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 signatureWithRSValueNotLessThanOrderShouldFail() throws Exception {
exception.expect(SignatureException.class);

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);
}
}
Original file line number Diff line number Diff line change
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 05b5449

Please sign in to comment.