Skip to content

Commit

Permalink
Patch for CVE-2022-21449. Initial report indicated that only Java 15,…
Browse files Browse the repository at this point in the history
… 16, 17 or 18 were vulnerable. Unable to replicate the vulnerability on Java 8 in my tests.
  • Loading branch information
robotdan committed Apr 20, 2022
1 parent 78f9f0f commit 4d89460
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 4 deletions.
2 changes: 1 addition & 1 deletion build.savant
Expand Up @@ -17,7 +17,7 @@
savantVersion = "1.0.0"
jacksonVersion = "2.13.2"

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

workflow {
fetch {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -6,7 +6,7 @@

<groupId>io.fusionauth</groupId>
<artifactId>fusionauth-jwt</artifactId>
<version>5.1.1</version>
<version>5.1.2</version>
<packaging>jar</packaging>

<name>FusionAuth JWT</name>
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/io/fusionauth/jwt/ec/ECVerifier.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020, FusionAuth, All Rights Reserved
* Copyright (c) 2018-2022, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -178,11 +178,36 @@ public boolean canVerify(Algorithm algorithm) {
}
}

private void checkFor_CVE_2022_21449(byte[] signature) {
int half = signature.length / 2;

boolean rOk = false;
boolean sOk = false;
for (int i = 0; i < signature.length; i++) {
if (i < half) {
rOk = signature[i] != 0;
if (rOk) {
i = half - 1;
}
} else {
sOk = signature[i] != 0;
if (sOk) {
break;
}
}
}

if (!rOk || !sOk) {
throw new InvalidJWTSignatureException();
}
}

@Override
public void verify(Algorithm algorithm, byte[] message, byte[] signature) {
Objects.requireNonNull(algorithm);
Objects.requireNonNull(message);
Objects.requireNonNull(signature);
checkFor_CVE_2022_21449(signature);

try {
Signature verifier = cryptoProvider.getSignatureInstance(algorithm.getName());
Expand Down
29 changes: 28 additions & 1 deletion src/test/java/io/fusionauth/jwt/VulnerabilityTest.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2019, FusionAuth, All Rights Reserved
* Copyright (c) 2018-2022, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,8 @@
package io.fusionauth.jwt;

import io.fusionauth.jwt.domain.JWT;
import io.fusionauth.jwt.ec.ECSigner;
import io.fusionauth.jwt.ec.ECVerifier;
import io.fusionauth.jwt.hmac.HMACSigner;
import io.fusionauth.jwt.hmac.HMACVerifier;
import io.fusionauth.jwt.rsa.RSAVerifier;
Expand All @@ -26,6 +28,8 @@
import java.nio.file.Paths;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

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

@Test
public void test_ECDSA_CVE_2022_21449() {
// Note this test will always fail when run on Java 8, it will fail on Java 15, 16 and 17.
JWT inputJwt = new JWT()
.setSubject("123456789")
.setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC))
.setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusHours(2));

// Sign it using ECDSA 256
for (String alg : Arrays.asList("256", "384", "521")) {
System.out.println("\n\n\n\n");
Signer signer = alg.equals("256")
? ECSigner.newSHA256Signer(readFile("ec_private_key_p_" + alg + ".pem"))
: alg.equals("384")
? ECSigner.newSHA384Signer(readFile("ec_private_key_p_" + alg + ".pem"))
: ECSigner.newSHA512Signer(readFile("ec_private_key_p_" + alg + ".pem"));

String encodedJWT = JWT.getEncoder().encode(inputJwt, signer);
String hackedEncodedJWT = encodedJWT.substring(0, encodedJWT.lastIndexOf('.') + 1) + Base64.getUrlEncoder().encodeToString(new byte[64]);
expectException(InvalidJWTSignatureException.class, () -> JWT.getDecoder().decode(hackedEncodedJWT, ECVerifier.newVerifier(readFile("ec_public_key_p_" + alg + ".pem"))));
}
}

@Test
public void test_encodedJwtWithSignatureRemoved() {
// Sign a JWT and then attempt to verify it using None.
Expand Down

0 comments on commit 4d89460

Please sign in to comment.