Skip to content

Commit 4d89460

Browse files
committed
Patch for CVE-2022-21449. Initial report indicated that only Java 15, 16, 17 or 18 were vulnerable. Unable to replicate the vulnerability on Java 8 in my tests.
1 parent 78f9f0f commit 4d89460

File tree

4 files changed

+56
-4
lines changed

4 files changed

+56
-4
lines changed

build.savant

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
savantVersion = "1.0.0"
1818
jacksonVersion = "2.13.2"
1919

20-
project(group: "io.fusionauth", name: "fusionauth-jwt", version: "5.1.1", licenses: ["ApacheV2_0"]) {
20+
project(group: "io.fusionauth", name: "fusionauth-jwt", version: "5.1.2", licenses: ["ApacheV2_0"]) {
2121

2222
workflow {
2323
fetch {

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.fusionauth</groupId>
88
<artifactId>fusionauth-jwt</artifactId>
9-
<version>5.1.1</version>
9+
<version>5.1.2</version>
1010
<packaging>jar</packaging>
1111

1212
<name>FusionAuth JWT</name>

src/main/java/io/fusionauth/jwt/ec/ECVerifier.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018-2020, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2018-2022, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -178,11 +178,36 @@ public boolean canVerify(Algorithm algorithm) {
178178
}
179179
}
180180

181+
private void checkFor_CVE_2022_21449(byte[] signature) {
182+
int half = signature.length / 2;
183+
184+
boolean rOk = false;
185+
boolean sOk = false;
186+
for (int i = 0; i < signature.length; i++) {
187+
if (i < half) {
188+
rOk = signature[i] != 0;
189+
if (rOk) {
190+
i = half - 1;
191+
}
192+
} else {
193+
sOk = signature[i] != 0;
194+
if (sOk) {
195+
break;
196+
}
197+
}
198+
}
199+
200+
if (!rOk || !sOk) {
201+
throw new InvalidJWTSignatureException();
202+
}
203+
}
204+
181205
@Override
182206
public void verify(Algorithm algorithm, byte[] message, byte[] signature) {
183207
Objects.requireNonNull(algorithm);
184208
Objects.requireNonNull(message);
185209
Objects.requireNonNull(signature);
210+
checkFor_CVE_2022_21449(signature);
186211

187212
try {
188213
Signature verifier = cryptoProvider.getSignatureInstance(algorithm.getName());

src/test/java/io/fusionauth/jwt/VulnerabilityTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018-2019, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2018-2022, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
1717
package io.fusionauth.jwt;
1818

1919
import io.fusionauth.jwt.domain.JWT;
20+
import io.fusionauth.jwt.ec.ECSigner;
21+
import io.fusionauth.jwt.ec.ECVerifier;
2022
import io.fusionauth.jwt.hmac.HMACSigner;
2123
import io.fusionauth.jwt.hmac.HMACVerifier;
2224
import io.fusionauth.jwt.rsa.RSAVerifier;
@@ -26,6 +28,8 @@
2628
import java.nio.file.Paths;
2729
import java.time.ZoneOffset;
2830
import java.time.ZonedDateTime;
31+
import java.util.Arrays;
32+
import java.util.Base64;
2933
import java.util.HashMap;
3034
import java.util.Map;
3135

@@ -49,6 +53,29 @@ public void test_SignedWithoutSignature() {
4953
expectException(MissingSignatureException.class, () -> JWT.getDecoder().decode(encodedJWTNoSignature));
5054
}
5155

56+
@Test
57+
public void test_ECDSA_CVE_2022_21449() {
58+
// Note this test will always fail when run on Java 8, it will fail on Java 15, 16 and 17.
59+
JWT inputJwt = new JWT()
60+
.setSubject("123456789")
61+
.setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC))
62+
.setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusHours(2));
63+
64+
// Sign it using ECDSA 256
65+
for (String alg : Arrays.asList("256", "384", "521")) {
66+
System.out.println("\n\n\n\n");
67+
Signer signer = alg.equals("256")
68+
? ECSigner.newSHA256Signer(readFile("ec_private_key_p_" + alg + ".pem"))
69+
: alg.equals("384")
70+
? ECSigner.newSHA384Signer(readFile("ec_private_key_p_" + alg + ".pem"))
71+
: ECSigner.newSHA512Signer(readFile("ec_private_key_p_" + alg + ".pem"));
72+
73+
String encodedJWT = JWT.getEncoder().encode(inputJwt, signer);
74+
String hackedEncodedJWT = encodedJWT.substring(0, encodedJWT.lastIndexOf('.') + 1) + Base64.getUrlEncoder().encodeToString(new byte[64]);
75+
expectException(InvalidJWTSignatureException.class, () -> JWT.getDecoder().decode(hackedEncodedJWT, ECVerifier.newVerifier(readFile("ec_public_key_p_" + alg + ".pem"))));
76+
}
77+
}
78+
5279
@Test
5380
public void test_encodedJwtWithSignatureRemoved() {
5481
// Sign a JWT and then attempt to verify it using None.

0 commit comments

Comments
 (0)