diff --git a/build.savant b/build.savant index e702bb5..a8060cc 100644 --- a/build.savant +++ b/build.savant @@ -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 { diff --git a/pom.xml b/pom.xml index 1a8743d..3c83562 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.fusionauth fusionauth-jwt - 5.1.1 + 5.1.2 jar FusionAuth JWT diff --git a/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java b/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java index 0d91106..4fcff65 100644 --- a/src/main/java/io/fusionauth/jwt/ec/ECVerifier.java +++ b/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. @@ -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()); diff --git a/src/test/java/io/fusionauth/jwt/VulnerabilityTest.java b/src/test/java/io/fusionauth/jwt/VulnerabilityTest.java index 7a24967..b32bb06 100644 --- a/src/test/java/io/fusionauth/jwt/VulnerabilityTest.java +++ b/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. @@ -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; @@ -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; @@ -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.