Added additional guards for JVM CVE-2022-21449 per review, accompanied by corresponding regression tests (#733)

This commit is contained in:
Les Hazlewood 2022-04-28 12:11:36 -04:00 committed by GitHub
parent 9c0ea0d0eb
commit 877960fe04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 310 additions and 155 deletions

View File

@ -1,5 +1,32 @@
## Release Notes
### 0.11.5
This patch release adds additional security guards against an ECDSA bug in Java SE versions 15-15.0.6, 17-17.0.2, and 18
([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)) in addition to the guards added in the JJWT 0.11.3
release. This patch allows JJWT users using those JVM versions to upgrade to JJWT 0.11.5, even if they are unable to
upgrade their JVM to patched/fixed JVM version in a timely manner. Note: if your application does not use these JVM
versions, you are not exposed to the JVM vulnerability.
Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions, and the
JJWT 0.11.5 release adds additional precautions within JJWT in case an application team is not able to upgrade
their JVM in a timely manner.
However, even with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly
recommended** to upgrade your JVM to version
15.0.7, 17.0.3, or 18.0.1 or later to ensure the bug does not surface elsewhere in your application code or any other
third party library in your application that may not contain similar security guards.
Issues included in this patch are listed in the [JJWT 0.11.5 milestone](https://github.com/jwtk/jjwt/milestone/26?closed=1).
#### Credits
Thank you to [Neil Madden](https://neilmadden.blog), the security researcher that first discovered the JVM
vulnerability as covered in his [Psychic Signatures in Java](https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/)
blog post. Neil worked directly with the JJWT team to provide these additional guards, beyond what was in the JJWT 0.11.3
release, and we're grateful for his help and collaboration in reviewing our fixes and for the additional tests he
provided the JJWT team.
### 0.11.4
This patch release:
@ -20,18 +47,14 @@ Issues included in this patch are listed in the [JJWT 0.11.4 milestone](https://
### 0.11.3
This patch release adds security guards against an ECDSA bug in Java SE versions 15-15.0.6, 17-17.0.2, and 18
([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)). This patch allows JJWT users using those JVM
versions to upgrade to JJWT 0.11.3, even if they are unable to upgrade their JVM to patched/fixed JVM version in a
timely manner. Note: if your application does not use these JVM versions, you are not exposed to the JVM vulnerability.
([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)). Note: if your application does not use these
JVM versions, you are not exposed to the JVM vulnerability.
Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions, and the
JJWT 0.11.3 release adds additional precautions within JJWT in case an application team is not able to upgrade
their JVM in a timely manner.
However, even with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly
recommended** to upgrade your JVM to version
15.0.7, 17.0.3, or 18.0.1 or later to ensure the bug does not surface elsewhere in your application code or any other
third party library in your application that may not contain similar security guards.
Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions. However, even
with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly
recommended** to upgrade your JVM to version 15.0.7, 17.0.3, or 18.0.1 or later to ensure the bug does not surface
elsewhere in your application code or any other third party library in your application that may not contain similar
security guards.
Issues included in this patch are listed in the [JJWT 0.11.3 milestone](https://github.com/jwtk/jjwt/milestone/24).

View File

@ -580,15 +580,15 @@ for any RSA key, the requirement is the RSA key (modulus) length in bits MUST be
<a name="jws-key-ecdsa"></a>
#### Elliptic Curve
JWT Elliptic Curve signature algorithms `ES256`, `ES384`, and `ES512` all require a minimum key length
(aka an Elliptic Curve order bit length) that is _at least_ as many bits as the algorithm signature's individual
JWT Elliptic Curve signature algorithms `ES256`, `ES384`, and `ES512` all require a key length
(aka an Elliptic Curve order bit length) equal to the algorithm signature's individual
`R` and `S` components per [RFC 7512 Section 3.4](https://tools.ietf.org/html/rfc7518#section-3.4). This means:
* `ES256` requires that you use a private key that is at least 256 bits (32 bytes) long.
* `ES256` requires that you use a private key that is exactly 256 bits (32 bytes) long.
* `ES384` requires that you use a private key that is at least 384 bits (48 bytes) long.
* `ES384` requires that you use a private key that is exactly 384 bits (48 bytes) long.
* `ES512` requires that you use a private key that is at least 512 bits (64 bytes) long.
* `ES512` requires that you use a private key that is exactly 521 bits (65 or 66 bytes) long (depending on format).
<a name="jws-key-create"></a>
#### Creating Safe Keys

View File

@ -15,21 +15,11 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.io.*;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
@ -120,6 +110,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
Assert.notNull(key, "Key argument cannot be null.");
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
createSigner(alg, key); // since 0.11.5: fail fast if key cannot be used for alg.
this.algorithm = alg;
this.key = key;
return this;

View File

@ -15,27 +15,7 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.MissingClaimException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
@ -403,13 +383,13 @@ public class DefaultJwtParser implements JwtParser {
throw e;
} catch (InvalidKeyException | IllegalArgumentException e) {
String algName = algorithm.getValue();
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
"algorithm, but the specified signing key of type " + key.getClass().getName() +
" may not be used to validate " + algName + " signatures. Because the specified " +
"signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
String msg = "The parsed JWT indicates it was signed with the '" + algName + "' signature " +
"algorithm, but the provided " + key.getClass().getName() + " key may " +
"not be used to verify " + algName + " signatures. Because the specified " +
"key reflects a specific and expected algorithm, and the JWT does not reflect " +
"this algorithm, it is likely that the JWT was not expected and therefore should not be " +
"trusted. Another possibility is that the parser was configured with the incorrect " +
"signing key, but this cannot be assumed for security reasons.";
"trusted. Another possibility is that the parser was provided the incorrect " +
"signature verification key, but this cannot be assumed for security reasons.";
throw new UnsupportedJwtException(msg, e);
}

View File

@ -19,12 +19,15 @@ import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.SignatureException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap;
import java.util.Map;
@ -39,16 +42,43 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
private static final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames();
private static Map<SignatureAlgorithm, String> createEcCurveNames() {
Map<SignatureAlgorithm, String> m = new HashMap<SignatureAlgorithm, String>(); //alg to ASN1 OID name
Map<SignatureAlgorithm, String> m = new HashMap<>(); //alg to ASN1 OID name
m.put(SignatureAlgorithm.ES256, "secp256r1");
m.put(SignatureAlgorithm.ES384, "secp384r1");
m.put(SignatureAlgorithm.ES512, "secp521r1");
return m;
}
protected static String byteSizeString(int bytesLength) {
return bytesLength + " bytes (" + (bytesLength * Byte.SIZE) + " bits)";
}
protected final int requiredSignatureByteLength;
protected final int fieldByteLength;
protected EllipticCurveProvider(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
if (!(key instanceof ECKey)) {
String msg = "Elliptic Curve signatures require an ECKey. The provided key of type " +
key.getClass().getName() + " is not a " + ECKey.class.getName() + " instance.";
throw new InvalidKeyException(msg);
}
this.requiredSignatureByteLength = getSignatureByteArrayLength(alg);
this.fieldByteLength = this.requiredSignatureByteLength / 2;
ECKey ecKey = (ECKey) key; // can cast here because of the Assert.isTrue assertion above
BigInteger order = ecKey.getParams().getOrder();
int keyFieldByteLength = (order.bitLength() + 7) / Byte.SIZE; //for ES512 (can be 65 or 66, this ensures 66)
int concatByteLength = keyFieldByteLength * 2;
if (concatByteLength != this.requiredSignatureByteLength) {
String msg = "EllipticCurve key has a field size of " +
byteSizeString(keyFieldByteLength) + ", but " + alg.name() + " requires a field size of " +
byteSizeString(this.fieldByteLength) + " per [RFC 7518, Section 3.4 (validation)]" +
"(https://datatracker.ietf.org/doc/html/rfc7518#section-3.4).";
throw new InvalidKeyException(msg);
}
}
/**
@ -148,16 +178,15 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
/**
* Returns the expected signature byte array length (R + S parts) for
* the specified ECDSA algorithm.
* the specified ECDSA algorithm. Expected lengths are mandated by
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-3.4">JWA RFC 7518, Section 3.4</a>.
*
* @param alg The ECDSA algorithm. Must be supported and not
* {@code null}.
* @return The expected byte array length for the signature.
* @throws JwtException If the algorithm is not supported.
*/
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
throws JwtException {
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg) throws JwtException {
switch (alg) {
case ES256:
return 64;
@ -180,7 +209,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* @return The ECDSA JWS encoded signature.
* @throws JwtException If the ASN.1/DER signature format is invalid.
*/
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) throws JwtException {
public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException {
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new JwtException("Invalid ECDSA signature format");
@ -213,9 +242,9 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
rawLen = Math.max(rawLen, outputLength / 2);
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|| derSignature[offset] != 2
|| derSignature[offset + 2 + rLength] != 2) {
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|| derSignature[offset] != 2
|| derSignature[offset + 2 + rLength] != 2) {
throw new JwtException("Invalid ECDSA signature format");
}
@ -238,7 +267,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
* @return The ASN.1/DER encoded signature.
* @throws JwtException If the ECDSA JWS signature format is invalid.
*/
public static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws JwtException {
public static byte[] transcodeConcatToDER(byte[] jwsSignature) throws JwtException {
try {
return concatToDER(jwsSignature);
} catch (Exception e) { // CVE-2022-21449 guard
@ -257,10 +286,6 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
i--;
}
if (i == 0) { // r == 0, JVM bug CVE-2202-21449 guard:
throw new JwtException("Invalid ECDSA Signature format");
}
int j = i;
if (jwsSignature[rawLen - i] < 0) {

View File

@ -19,16 +19,18 @@ import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.SignatureException;
import java.security.InvalidKeyException;
import java.math.BigInteger;
import java.security.Key;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
private static final String EC_PUBLIC_KEY_REQD_MSG =
"Elliptic Curve signature validation requires an ECPublicKey instance.";
"Elliptic Curve signature validation requires an ECPublicKey instance.";
private static final String DER_ENCODING_SYS_PROPERTY_NAME =
"io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported";
@ -39,24 +41,48 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
}
@Override
public boolean isValid(byte[] data, byte[] signature) {
public boolean isValid(byte[] data, byte[] concatSignature) {
Signature sig = createSignatureInstance();
PublicKey publicKey = (PublicKey) key;
try {
int expectedSize = getSignatureByteArrayLength(alg);
/*
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application
* is configured to do so. This fallback is for backwards compatibility ONLY (to support tokens
* generated by early versions of jjwt) and backwards compatibility will be removed in a future
* version of this library. This fallback is only enabled if the system property is set to 'true' due to
* the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18.
*/
//TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs)
// mandated per https://datatracker.ietf.org/doc/html/rfc7518#section-3.4 :
int requiredConcatByteLength = getSignatureByteArrayLength(alg);
byte[] derSignature;
if (expectedSize != signature.length && signature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
derSignature = signature;
if (requiredConcatByteLength != concatSignature.length) {
/*
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application
* is configured to do so. This fallback is for backwards compatibility ONLY (to support tokens
* generated by early versions of jjwt) and backwards compatibility will be removed in a future
* version of this library. This fallback is only enabled if the system property is set to 'true' due to
* the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18.
*/
// TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs)
if (concatSignature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
derSignature = concatSignature;
} else {
String msg = "Provided signature is " + byteSizeString(concatSignature.length) + " but " +
alg.name() + " signatures must be exactly " + byteSizeString(requiredConcatByteLength) + " per " +
"[RFC 7518, Section 3.4 (validation)](https://datatracker.ietf.org/doc/html/rfc7518#section-3.4).";
throw new SignatureException(msg);
}
} else {
derSignature = EllipticCurveProvider.transcodeSignatureToDER(signature);
//guard for JVM security bug CVE-2022-21449:
ECKey ecKey = (ECKey) publicKey; // we can cast here because of the assertions made in the constructor
BigInteger order = ecKey.getParams().getOrder();
BigInteger r = new BigInteger(1, Arrays.copyOfRange(concatSignature, 0, this.fieldByteLength));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(concatSignature, this.fieldByteLength, concatSignature.length));
if (r.signum() < 1 || s.signum() < 1 || r.compareTo(order) >= 0 || s.compareTo(order) >= 0) {
return false;
}
// Convert from concat to DER encoding since
// 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and
// 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting)
derSignature = transcodeConcatToDER(concatSignature);
}
return doVerify(sig, publicKey, data, derSignature);
} catch (Exception e) {
@ -66,7 +92,7 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
}
protected boolean doVerify(Signature sig, PublicKey publicKey, byte[] data, byte[] signature)
throws InvalidKeyException, java.security.SignatureException {
throws java.security.InvalidKeyException, java.security.SignatureException {
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);

View File

@ -23,16 +23,15 @@ import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.ECKey;
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
if (!(key instanceof PrivateKey && key instanceof ECKey)) {
String msg = "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
if (!(key instanceof PrivateKey)) {
String msg = "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
"type " + key.getClass().getName() + " is not an EC PrivateKey.";
throw new IllegalArgumentException(msg);
throw new io.jsonwebtoken.security.InvalidKeyException(msg);
}
}
@ -54,6 +53,6 @@ public class EllipticCurveSigner extends EllipticCurveProvider implements Signer
Signature sig = createSignatureInstance();
sig.initSign(privateKey);
sig.update(data);
return transcodeSignatureToConcat(sig.sign(), getSignatureByteArrayLength(alg));
return transcodeDERToConcat(sig.sign(), getSignatureByteArrayLength(alg));
}
}

View File

@ -19,10 +19,12 @@ import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.lang.Services
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.io.Serializer
import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.security.WeakKeyException
import org.junit.Test
@ -576,6 +578,71 @@ class JwtsTest {
}
}
/**
* @since 0.11.5
*/
@Test
void testBuilderWithEcdsaPublicKey() {
def builder = Jwts.builder().setSubject('foo')
def pair = Keys.keyPairFor(SignatureAlgorithm.ES256)
try {
builder.signWith(pair.public, SignatureAlgorithm.ES256) //public keys can't be used to create signatures
} catch (InvalidKeyException expected) {
String msg = "ECDSA signing keys must be PrivateKey instances."
assertEquals msg, expected.getMessage()
}
}
/**
* @since 0.11.5 as part of testing guards against JVM CVE-2022-21449
*/
@Test
void testBuilderWithMismatchedEllipticCurveKeyAndAlgorithm() {
def builder = Jwts.builder().setSubject('foo')
def pair = Keys.keyPairFor(SignatureAlgorithm.ES384)
try {
builder.signWith(pair.private, SignatureAlgorithm.ES256) //ES384 keys can't be used to create ES256 signatures
} catch (InvalidKeyException expected) {
String msg = "EllipticCurve key has a field size of 48 bytes (384 bits), but ES256 requires a " +
"field size of 32 bytes (256 bits) per [RFC 7518, Section 3.4 (validation)]" +
"(https://datatracker.ietf.org/doc/html/rfc7518#section-3.4)."
assertEquals msg, expected.getMessage()
}
}
/**
* @since 0.11.5 as part of testing guards against JVM CVE-2022-21449
*/
@Test
void testParserWithMismatchedEllipticCurveKeyAndAlgorithm() {
def pair = Keys.keyPairFor(SignatureAlgorithm.ES256)
def jws = Jwts.builder().setSubject('foo').signWith(pair.private).compact()
def parser = Jwts.parserBuilder().setSigningKey(Keys.keyPairFor(SignatureAlgorithm.ES384).public).build()
try {
parser.parseClaimsJws(jws)
} catch (UnsupportedJwtException expected) {
String msg = 'The parsed JWT indicates it was signed with the \'ES256\' signature algorithm, but ' +
'the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify ES256 signatures. ' +
'Because the specified key reflects a specific and expected algorithm, and the JWT does not ' +
'reflect this algorithm, it is likely that the JWT was not expected and therefore should not ' +
'be trusted. Another possibility is that the parser was provided the incorrect signature ' +
'verification key, but this cannot be assumed for security reasons.'
assertEquals msg, expected.getMessage()
}
}
/**
* @since 0.11.5 as part of testing guards against JVM CVE-2022-21449
*/
@Test(expected=io.jsonwebtoken.security.SignatureException)
void testEcdsaInvalidSignatureValue() {
def withoutSignature = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def invalidEncodedSignature = "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"
String jws = withoutSignature + '.' + invalidEncodedSignature
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
Jwts.parserBuilder().setSigningKey(keypair.public).build().parseClaimsJws(jws)
}
//Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20
@Test
void testParseClaimsJwsWithUnsignedJwt() {

View File

@ -15,7 +15,10 @@
*/
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.Keys
import org.junit.Test
import java.security.KeyPair
@ -65,4 +68,29 @@ class EllipticCurveProviderTest {
assertEquals ise.message, "SignatureAlgorithm argument must represent an Elliptic Curve algorithm."
}
}
@Test
void testConstructorWithNonEcKey() {
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
try {
new EllipticCurveProvider(SignatureAlgorithm.ES256, key) {}
} catch (InvalidKeyException expected) {
String msg = 'Elliptic Curve signatures require an ECKey. The provided key of type ' +
'javax.crypto.spec.SecretKeySpec is not a java.security.interfaces.ECKey instance.'
assertEquals msg, expected.getMessage()
}
}
@Test
void testConstructorWithInvalidKeyFieldLength() {
def keypair = Keys.keyPairFor(SignatureAlgorithm.ES256)
try {
new EllipticCurveProvider(SignatureAlgorithm.ES384, keypair.public){}
} catch (InvalidKeyException expected) {
String msg = "EllipticCurve key has a field size of 32 bytes (256 bits), but ES384 requires a " +
"field size of 48 bytes (384 bits) per [RFC 7518, Section 3.4 (validation)]" +
"(https://datatracker.ietf.org/doc/html/rfc7518#section-3.4)."
assertEquals msg, expected.getMessage()
}
}
}

View File

@ -36,21 +36,23 @@ class EllipticCurveSignatureValidatorTest {
String msg = 'foo'
final InvalidKeyException ex = new InvalidKeyException(msg)
def alg = SignatureAlgorithm.ES512
def keypair = EllipticCurveProvider.generateKeyPair(alg)
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().public) {
def v = new EllipticCurveSignatureValidator(alg, EllipticCurveProvider.generateKeyPair(alg).public) {
@Override
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
throw ex;
}
}
byte[] bytes = new byte[16]
byte[] signature = new byte[16]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
byte[] data = new byte[32]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(data)
byte[] signature = new EllipticCurveSigner(alg, keypair.getPrivate()).sign(data)
try {
v.isValid(bytes, signature)
v.isValid(data, signature)
fail();
} catch (SignatureException se) {
assertEquals se.message, 'Unable to verify Elliptic Curve signature using configured ECPublicKey. ' + msg
@ -80,12 +82,24 @@ class EllipticCurveSignatureValidatorTest {
void legacySignatureCompatDefaultTest() {
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName)
def alg = SignatureAlgorithm.ES512
def signature = Signature.getInstance(alg.jcaName)
def data = withoutSignature.getBytes("US-ASCII")
signature.initSign(keypair.private)
signature.update(data)
def signed = signature.sign()
assertFalse new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, keypair.public).isValid(data, signed)
def validator = new EllipticCurveSignatureValidator(alg, keypair.public)
try {
validator.isValid(data, signed)
fail()
} catch (SignatureException expected) {
String signedBytesString = EllipticCurveProvider.byteSizeString(signed.length)
String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. Provided " +
"signature is $signedBytesString but ES512 signatures must be exactly 132 bytes (1056 bits) " +
"per [RFC 7518, Section 3.4 (validation)]" +
"(https://datatracker.ietf.org/doc/html/rfc7518#section-3.4)." as String
assertEquals msg, expected.getMessage()
}
}
@Test
@ -107,54 +121,54 @@ class EllipticCurveSignatureValidatorTest {
@Test // asserts guard for JVM security bug CVE-2022-21449:
void testSignatureAllZeros() {
try {
byte[] forgedSig = new byte[64]
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def data = withoutSignature.getBytes("US-ASCII")
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, forgedSig)
fail("SignatureException expected")
} catch(SignatureException expected) {
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
}
byte[] forgedSig = new byte[64]
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def alg = SignatureAlgorithm.ES256
def keypair = EllipticCurveProvider.generateKeyPair(alg)
def data = withoutSignature.getBytes("US-ASCII")
def validator = new EllipticCurveSignatureValidator(alg, keypair.public)
assertFalse validator.isValid(data, forgedSig)
}
@Test // asserts guard for JVM security bug CVE-2022-21449:
void testSignatureRZero() {
try {
byte[] r = new byte[32]
byte[] s = new byte[32]; Arrays.fill(s, Byte.MAX_VALUE)
byte[] sig = new byte[r.length + s.length]
System.arraycopy(r, 0, sig, 0, r.length)
System.arraycopy(s, 0, sig, r.length, s.length)
byte[] r = new byte[32]
byte[] s = new byte[32]; Arrays.fill(s, Byte.MAX_VALUE)
byte[] sig = new byte[r.length + s.length]
System.arraycopy(r, 0, sig, 0, r.length)
System.arraycopy(s, 0, sig, r.length, s.length)
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def data = withoutSignature.getBytes("US-ASCII")
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, sig)
fail("SignatureException expected")
} catch(SignatureException expected) {
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
}
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
def data = withoutSignature.getBytes("US-ASCII")
def validator = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public)
assertFalse validator.isValid(data, sig)
}
@Test // asserts guard for JVM security bug CVE-2022-21449:
void testSignatureSZero() {
try {
byte[] r = new byte[32]; Arrays.fill(r, Byte.MAX_VALUE);
byte[] s = new byte[32]
byte[] sig = new byte[r.length + s.length]
System.arraycopy(r, 0, sig, 0, r.length)
System.arraycopy(s, 0, sig, r.length, s.length)
byte[] r = new byte[32]; Arrays.fill(r, Byte.MAX_VALUE);
byte[] s = new byte[32]
byte[] sig = new byte[r.length + s.length]
System.arraycopy(r, 0, sig, 0, r.length)
System.arraycopy(s, 0, sig, r.length, s.length)
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def data = withoutSignature.getBytes("US-ASCII")
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, sig)
fail("SignatureException expected")
} catch(SignatureException expected) {
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
}
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
def data = withoutSignature.getBytes("US-ASCII")
def validator = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public)
assertFalse validator.isValid(data, sig)
}
@Test // asserts guard for JVM security bug CVE-2022-21449:
void ecdsaInvalidSignatureValuesTest() {
def withoutSignature = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def invalidEncodedSignature = "_____wAAAAD__________7zm-q2nF56E87nKwvxjJVH_____AAAAAP__________vOb6racXnoTzucrC_GMlUQ"
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
def data = withoutSignature.getBytes("US-ASCII")
def invalidSignature = Decoders.BASE64URL.decode(invalidEncodedSignature)
def validator = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public)
assertFalse("Forged signature must not be considered valid.", validator.isValid(data, invalidSignature))
}
@Test
@ -173,7 +187,7 @@ class EllipticCurveSignatureValidatorTest {
try {
def signature = new byte[257]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
EllipticCurveProvider.transcodeSignatureToDER(signature)
EllipticCurveProvider.transcodeConcatToDER(signature)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format.'
@ -184,7 +198,7 @@ class EllipticCurveSignatureValidatorTest {
void invalidDERSignatureToJoseFormatTest() {
def verify = { signature ->
try {
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
@ -209,14 +223,14 @@ class EllipticCurveSignatureValidatorTest {
def signature = new byte[32]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
signature[0] = 0 as byte
EllipticCurveProvider.transcodeSignatureToDER(signature) //no exception
EllipticCurveProvider.transcodeConcatToDER(signature) //no exception
}
@Test
void edgeCaseSignatureToConcatLengthTest() {
try {
def signature = Decoders.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
fail()
} catch (JwtException e) {
@ -227,7 +241,7 @@ class EllipticCurveSignatureValidatorTest {
void edgeCaseSignatureToConcatInvalidSignatureTest() {
try {
def signature = Decoders.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
@ -238,7 +252,7 @@ class EllipticCurveSignatureValidatorTest {
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
try {
def signature = Decoders.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
@ -249,7 +263,7 @@ class EllipticCurveSignatureValidatorTest {
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
try {
def signature = Decoders.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
@ -260,7 +274,7 @@ class EllipticCurveSignatureValidatorTest {
void verifySwarmTest() {
for (SignatureAlgorithm algorithm : [SignatureAlgorithm.ES256, SignatureAlgorithm.ES384, SignatureAlgorithm.ES512]) {
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def keypair = EllipticCurveProvider.generateKeyPair(algorithm)
def data = withoutSignature.getBytes("US-ASCII")
def signature = new EllipticCurveSigner(algorithm, keypair.private).sign(data)
assert new EllipticCurveSignatureValidator(algorithm, keypair.public).isValid(data, signature)

View File

@ -43,13 +43,14 @@ class EllipticCurveSignerTest {
@Test
void testConstructorWithoutECPrivateKey() {
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
def pair = Keys.keyPairFor(SignatureAlgorithm.ES256)
try {
new EllipticCurveSigner(SignatureAlgorithm.ES256, key)
fail('EllipticCurveSigner should reject non ECPrivateKey instances.')
} catch (IllegalArgumentException expected) {
assertEquals expected.message, "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
"type " + key.getClass().getName() + " is not an EC PrivateKey."
new EllipticCurveSigner(SignatureAlgorithm.ES256, pair.public)
fail('EllipticCurveSigner should reject public ECKey instances.')
} catch (io.jsonwebtoken.security.InvalidKeyException expected) {
String msg = 'Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of ' +
'type sun.security.ec.ECPublicKeyImpl is not an EC PrivateKey.'
assertEquals(msg, expected.getMessage())
}
}
@ -59,7 +60,6 @@ class EllipticCurveSignerTest {
SignatureAlgorithm alg = SignatureAlgorithm.ES256
KeyPair kp = Keys.keyPairFor(alg)
PublicKey publicKey = kp.getPublic()
PrivateKey privateKey = kp.getPrivate()
String msg = 'foo'
@ -87,14 +87,15 @@ class EllipticCurveSignerTest {
@Test
void testDoSignWithJoseSignatureFormatException() {
KeyPair kp = EllipticCurveProvider.generateKeyPair()
SignatureAlgorithm alg = SignatureAlgorithm.ES256
KeyPair kp = EllipticCurveProvider.generateKeyPair(alg)
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
String msg = 'foo'
final JwtException ex = new JwtException(msg)
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
def signer = new EllipticCurveSigner(alg, privateKey) {
@Override
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
throw ex
@ -116,14 +117,15 @@ class EllipticCurveSignerTest {
@Test
void testDoSignWithJdkSignatureException() {
KeyPair kp = EllipticCurveProvider.generateKeyPair()
SignatureAlgorithm alg = SignatureAlgorithm.ES256
KeyPair kp = EllipticCurveProvider.generateKeyPair(alg)
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
String msg = 'foo'
final java.security.SignatureException ex = new java.security.SignatureException(msg)
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
def signer = new EllipticCurveSigner(alg, privateKey) {
@Override
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
throw ex