Skip to content

Commit 05b5449

Browse files
committed
Added protection against CVE-2022-21449
1 parent dd40410 commit 05b5449

File tree

3 files changed

+221
-7
lines changed

3 files changed

+221
-7
lines changed

lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java

+66-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.auth0.jwt.interfaces.DecodedJWT;
66
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
77

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

5153
if (!valid) {
@@ -140,13 +142,42 @@ byte[] DERToJOSE(byte[] derSignature) throws SignatureException {
140142
return joseSignature;
141143
}
142144

143-
//Visible for testing
144-
byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
145+
/**
146+
* Added check for extra protection against CVE-2022-21449.
147+
* This method ensures the signature's structure is as expected.
148+
*
149+
* @param joseSignature is the signature from the JWT
150+
* @param publicKey public key used to verify the JWT
151+
* @throws SignatureException if the signature's structure is not as per expectation
152+
*/
153+
// Visible for testing
154+
void validateSignatureStructure(byte[] joseSignature, ECPublicKey publicKey) throws SignatureException {
155+
// check signature length, moved this check from JOSEToDER method
145156
if (joseSignature.length != ecNumberSize * 2) {
146157
throw new SignatureException("Invalid JOSE signature format.");
147158
}
148159

149-
// Retrieve R and S number's length and padding.
160+
if (isAllZeros(joseSignature)) {
161+
throw new SignatureException("Invalid Signature: All Zeros.");
162+
}
163+
164+
// get R
165+
byte[] rBytes = new byte[ecNumberSize];
166+
System.arraycopy(joseSignature, 0, rBytes, 0, ecNumberSize);
167+
BigInteger r = new BigInteger(1, rBytes);
168+
if(isAllZeros(rBytes)) {
169+
throw new SignatureException("Invalid Signature: All Zeros for R value.");
170+
}
171+
172+
// get S
173+
byte[] sBytes = new byte[ecNumberSize];
174+
System.arraycopy(joseSignature, ecNumberSize, sBytes, 0, ecNumberSize);
175+
BigInteger s = new BigInteger(1, sBytes);
176+
if(isAllZeros(sBytes)) {
177+
throw new SignatureException("Invalid Signature: All Zeros for S value.");
178+
}
179+
180+
//moved this check from JOSEToDER method
150181
int rPadding = countPadding(joseSignature, 0, ecNumberSize);
151182
int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length);
152183
int rLength = ecNumberSize - rPadding;
@@ -157,6 +188,29 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
157188
throw new SignatureException("Invalid JOSE signature format.");
158189
}
159190

191+
// check for 0 r or s here since we have the values
192+
if (BigInteger.ZERO.equals(r) || BigInteger.ZERO.equals(s)) {
193+
throw new SignatureException("R or S value cannot be zero.");
194+
}
195+
196+
BigInteger order = publicKey.getParams().getOrder();
197+
198+
// R and S must be less than N
199+
if (order.compareTo(r) < 1 || order.compareTo(s) < 1) {
200+
throw new SignatureException("The difference between R or S value and order should be greater than one.");
201+
}
202+
}
203+
204+
//Visible for testing
205+
byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
206+
// Retrieve R and S number's length and padding.
207+
int rPadding = countPadding(joseSignature, 0, ecNumberSize);
208+
int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length);
209+
int rLength = ecNumberSize - rPadding;
210+
int sLength = ecNumberSize - sPadding;
211+
212+
int length = 2 + rLength + 2 + sLength;
213+
160214
final byte[] derSignature;
161215
int offset;
162216
if (length > 0x7f) {
@@ -205,6 +259,15 @@ byte[] JOSEToDER(byte[] joseSignature) throws SignatureException {
205259
return derSignature;
206260
}
207261

262+
private boolean isAllZeros(byte[] bytes) {
263+
for (byte b : bytes) {
264+
if (b != 0) {
265+
return false;
266+
}
267+
}
268+
return true;
269+
}
270+
208271
private int countPadding(byte[] bytes, int fromIndex, int toIndex) {
209272
int padding = 0;
210273
while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) {

lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java

+137-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.auth0.jwt.exceptions.SignatureGenerationException;
55
import com.auth0.jwt.exceptions.SignatureVerificationException;
66
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
7+
import com.auth0.jwt.interfaces.JWTVerifier;
78
import org.hamcrest.Matchers;
89
import org.hamcrest.collection.IsIn;
910
import org.junit.Assert;
@@ -12,11 +13,13 @@
1213
import org.junit.rules.ExpectedException;
1314

1415
import java.io.ByteArrayOutputStream;
16+
import java.math.BigInteger;
1517
import java.nio.charset.StandardCharsets;
1618
import java.security.*;
1719
import java.security.interfaces.ECKey;
1820
import java.security.interfaces.ECPrivateKey;
1921
import java.security.interfaces.ECPublicKey;
22+
import java.security.spec.ECParameterSpec;
2023
import java.util.Arrays;
2124
import java.util.Base64;
2225

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

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

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

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

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

947-
algorithm256.JOSEToDER(joseSignature);
963+
algorithm256.validateSignatureStructure(joseSignature, publicKey);
948964
}
949965

950966
@Test
@@ -1309,4 +1325,123 @@ public void shouldFailOnECDSA256SigningWithDeprecatedMethodWhenProvidedPrivateKe
13091325
algorithm.sign(new byte[0]);
13101326
}
13111327

1328+
@Test
1329+
public void invalidECDSA256SignatureShouldFailTokenVerification() throws Exception {
1330+
exception.expect(SignatureVerificationException.class);
1331+
exception.expectCause(isA(SignatureException.class));
1332+
1333+
String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0._____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ";
1334+
1335+
ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1336+
ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC");
1337+
ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC");
1338+
ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC");
1339+
JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build();
1340+
JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build();
1341+
JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build();
1342+
JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build();
1343+
verifier256.verify(jwtWithInvalidSig);
1344+
verifier384.verify(jwtWithInvalidSig);
1345+
verifier512.verify(jwtWithInvalidSig);
1346+
verifier256k.verify(jwtWithInvalidSig);
1347+
}
1348+
1349+
@Test
1350+
public void emptyECDSA256SignatureShouldFailTokenVerification() throws Exception {
1351+
exception.expect(SignatureVerificationException.class);
1352+
exception.expectCause(isA(SignatureException.class));
1353+
1354+
String jwtWithInvalidSig = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
1355+
1356+
ECKey key256 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1357+
ECKey key384 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC");
1358+
ECKey key512 = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC");
1359+
ECKey key256k = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256K, "EC");
1360+
JWTVerifier verifier256 = JWT.require(Algorithm.ECDSA256(key256)).build();
1361+
JWTVerifier verifier384 = JWT.require(Algorithm.ECDSA256(key384)).build();
1362+
JWTVerifier verifier512 = JWT.require(Algorithm.ECDSA256(key512)).build();
1363+
JWTVerifier verifier256k = JWT.require(Algorithm.ECDSA256(key256k)).build();
1364+
verifier256.verify(jwtWithInvalidSig);
1365+
verifier384.verify(jwtWithInvalidSig);
1366+
verifier512.verify(jwtWithInvalidSig);
1367+
verifier256k.verify(jwtWithInvalidSig);
1368+
}
1369+
1370+
@Test
1371+
public void signatureWithAllZerosShouldFail() throws Exception {
1372+
exception.expect(SignatureException.class);
1373+
1374+
ECPublicKey pubKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1375+
1376+
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(pubKey, (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"));
1377+
byte[] signatureBytes = new byte[64];
1378+
algorithm256.validateSignatureStructure(signatureBytes, pubKey);
1379+
}
1380+
1381+
@Test
1382+
public void signatureWithRZeroShouldFail() throws Exception {
1383+
exception.expect(SignatureException.class);
1384+
1385+
ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1386+
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");
1387+
1388+
String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));
1389+
1390+
String[] chunks = signedJwt.split("\\.");
1391+
byte[] signature = Base64.getUrlDecoder().decode(chunks[2]);
1392+
1393+
byte[] sigWithBlankR = new byte[signature.length];
1394+
for (int i = 0; i < signature.length; i++) {
1395+
if (i < signature.length / 2) {
1396+
sigWithBlankR[i] = 0;
1397+
} else {
1398+
sigWithBlankR[i] = signature[i];
1399+
}
1400+
}
1401+
1402+
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
1403+
algorithm256.validateSignatureStructure(sigWithBlankR, publicKey);
1404+
}
1405+
1406+
@Test
1407+
public void signatureWithSZeroShouldFail() throws Exception {
1408+
exception.expect(SignatureException.class);
1409+
1410+
ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1411+
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");
1412+
1413+
String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));
1414+
1415+
String[] chunks = signedJwt.split("\\.");
1416+
byte[] signature = Base64.getUrlDecoder().decode(chunks[2]);
1417+
1418+
byte[] sigWithBlankS = new byte[signature.length];
1419+
for (int i = 0; i < signature.length; i++) {
1420+
if (i < signature.length / 2) {
1421+
sigWithBlankS[i] = signature[i];
1422+
} else {
1423+
sigWithBlankS[i] = 0;
1424+
}
1425+
}
1426+
1427+
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
1428+
algorithm256.validateSignatureStructure(sigWithBlankS, publicKey);
1429+
}
1430+
1431+
@Test
1432+
public void signatureWithRSValueNotLessThanOrderShouldFail() throws Exception {
1433+
exception.expect(SignatureException.class);
1434+
1435+
ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC");
1436+
ECPrivateKey privateKey = (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC");
1437+
1438+
String signedJwt = JWT.create().sign(Algorithm.ECDSA256(publicKey, privateKey));
1439+
String jwtWithInvalidSig = signedJwt.substring(0, signedJwt.lastIndexOf('.') + 1) + "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ";
1440+
1441+
String[] chunks = jwtWithInvalidSig.split("\\.");
1442+
byte[] invalidSignature = Base64.getUrlDecoder().decode(chunks[2]);
1443+
1444+
ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256(publicKey, privateKey);
1445+
algorithm256.validateSignatureStructure(invalidSignature, publicKey);
1446+
}
13121447
}

lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111
import org.junit.Test;
1212
import org.junit.rules.ExpectedException;
1313

14+
import java.math.BigInteger;
1415
import java.nio.charset.StandardCharsets;
1516
import java.security.*;
1617
import java.security.interfaces.ECKey;
1718
import java.security.interfaces.ECPrivateKey;
1819
import java.security.interfaces.ECPublicKey;
20+
import java.security.spec.ECParameterSpec;
21+
import java.util.Arrays;
1922
import java.util.Base64;
2023

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

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

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

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

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

943-
algorithm256.JOSEToDER(joseSignature);
959+
algorithm256.validateSignatureStructure(joseSignature, publicKey);
944960
}
945961

946962
@Test

0 commit comments

Comments
 (0)