mirror of https://github.com/jwtk/jjwt.git
Added additional guards for JVM CVE-2022-21449 per review, accompanied by corresponding regression tests (#733)
This commit is contained in:
parent
9c0ea0d0eb
commit
877960fe04
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -1,5 +1,32 @@
|
||||||
## Release Notes
|
## 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
|
### 0.11.4
|
||||||
|
|
||||||
This patch release:
|
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
|
### 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
|
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
|
([CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449)). Note: if your application does not use these
|
||||||
versions to upgrade to JJWT 0.11.3, even if they are unable to upgrade their JVM to patched/fixed JVM version in a
|
JVM versions, you are not exposed to the JVM vulnerability.
|
||||||
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
|
Note that the CVE is not a bug within JJWT itself - it is a bug within the above listed JVM versions. However, even
|
||||||
JJWT 0.11.3 release adds additional precautions within JJWT in case an application team is not able to upgrade
|
with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly
|
||||||
their JVM in a timely manner.
|
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
|
||||||
However, even with these additional JJWT security guards, the root cause of the issue is the JVM, so it **strongly
|
security guards.
|
||||||
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).
|
Issues included in this patch are listed in the [JJWT 0.11.3 milestone](https://github.com/jwtk/jjwt/milestone/24).
|
||||||
|
|
||||||
|
|
10
README.md
10
README.md
|
@ -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>
|
<a name="jws-key-ecdsa"></a>
|
||||||
#### Elliptic Curve
|
#### Elliptic Curve
|
||||||
|
|
||||||
JWT Elliptic Curve signature algorithms `ES256`, `ES384`, and `ES512` all require a minimum key length
|
JWT Elliptic Curve signature algorithms `ES256`, `ES384`, and `ES512` all require a key length
|
||||||
(aka an Elliptic Curve order bit length) that is _at least_ as many bits as the algorithm signature's individual
|
(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:
|
`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>
|
<a name="jws-key-create"></a>
|
||||||
#### Creating Safe Keys
|
#### Creating Safe Keys
|
||||||
|
|
|
@ -15,21 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.*;
|
||||||
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.impl.crypto.DefaultJwtSigner;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
||||||
import io.jsonwebtoken.impl.lang.LegacyServices;
|
import io.jsonwebtoken.impl.lang.LegacyServices;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.io.*;
|
||||||
import io.jsonwebtoken.io.Encoder;
|
|
||||||
import io.jsonwebtoken.io.Encoders;
|
|
||||||
import io.jsonwebtoken.io.SerializationException;
|
|
||||||
import io.jsonwebtoken.io.Serializer;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
@ -120,6 +110,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
Assert.notNull(key, "Key argument cannot be null.");
|
Assert.notNull(key, "Key argument cannot be null.");
|
||||||
Assert.notNull(alg, "SignatureAlgorithm 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
|
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.algorithm = alg;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -15,27 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import io.jsonwebtoken.ClaimJwtException;
|
import io.jsonwebtoken.*;
|
||||||
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.impl.compression.DefaultCompressionCodecResolver;
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
||||||
|
@ -403,13 +383,13 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (InvalidKeyException | IllegalArgumentException e) {
|
} catch (InvalidKeyException | IllegalArgumentException e) {
|
||||||
String algName = algorithm.getValue();
|
String algName = algorithm.getValue();
|
||||||
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
|
String msg = "The parsed JWT indicates it was signed with the '" + algName + "' signature " +
|
||||||
"algorithm, but the specified signing key of type " + key.getClass().getName() +
|
"algorithm, but the provided " + key.getClass().getName() + " key may " +
|
||||||
" may not be used to validate " + algName + " signatures. Because the specified " +
|
"not be used to verify " + algName + " signatures. Because the specified " +
|
||||||
"signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
|
"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 " +
|
"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 " +
|
"trusted. Another possibility is that the parser was provided the incorrect " +
|
||||||
"signing key, but this cannot be assumed for security reasons.";
|
"signature verification key, but this cannot be assumed for security reasons.";
|
||||||
throw new UnsupportedJwtException(msg, e);
|
throw new UnsupportedJwtException(msg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,15 @@ import io.jsonwebtoken.JwtException;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
import io.jsonwebtoken.security.SignatureException;
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.security.interfaces.ECKey;
|
||||||
import java.security.spec.ECGenParameterSpec;
|
import java.security.spec.ECGenParameterSpec;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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 final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames();
|
||||||
|
|
||||||
private static Map<SignatureAlgorithm, String> 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.ES256, "secp256r1");
|
||||||
m.put(SignatureAlgorithm.ES384, "secp384r1");
|
m.put(SignatureAlgorithm.ES384, "secp384r1");
|
||||||
m.put(SignatureAlgorithm.ES512, "secp521r1");
|
m.put(SignatureAlgorithm.ES512, "secp521r1");
|
||||||
return m;
|
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) {
|
protected EllipticCurveProvider(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
|
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
|
* 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
|
* @param alg The ECDSA algorithm. Must be supported and not
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
* @return The expected byte array length for the signature.
|
* @return The expected byte array length for the signature.
|
||||||
* @throws JwtException If the algorithm is not supported.
|
* @throws JwtException If the algorithm is not supported.
|
||||||
*/
|
*/
|
||||||
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
|
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg) throws JwtException {
|
||||||
throws JwtException {
|
|
||||||
|
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case ES256:
|
case ES256:
|
||||||
return 64;
|
return 64;
|
||||||
|
@ -180,7 +209,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
* @return The ECDSA JWS encoded signature.
|
* @return The ECDSA JWS encoded signature.
|
||||||
* @throws JwtException If the ASN.1/DER signature format is invalid.
|
* @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) {
|
if (derSignature.length < 8 || derSignature[0] != 48) {
|
||||||
throw new JwtException("Invalid ECDSA signature format");
|
throw new JwtException("Invalid ECDSA signature format");
|
||||||
|
@ -238,7 +267,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
* @return The ASN.1/DER encoded signature.
|
* @return The ASN.1/DER encoded signature.
|
||||||
* @throws JwtException If the ECDSA JWS signature format is invalid.
|
* @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 {
|
try {
|
||||||
return concatToDER(jwsSignature);
|
return concatToDER(jwsSignature);
|
||||||
} catch (Exception e) { // CVE-2022-21449 guard
|
} catch (Exception e) { // CVE-2022-21449 guard
|
||||||
|
@ -257,10 +286,6 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0) { // r == 0, JVM bug CVE-2202-21449 guard:
|
|
||||||
throw new JwtException("Invalid ECDSA Signature format");
|
|
||||||
}
|
|
||||||
|
|
||||||
int j = i;
|
int j = i;
|
||||||
|
|
||||||
if (jwsSignature[rawLen - i] < 0) {
|
if (jwsSignature[rawLen - i] < 0) {
|
||||||
|
|
|
@ -19,11 +19,13 @@ import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.SignatureException;
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.math.BigInteger;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
import java.security.interfaces.ECKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
||||||
|
|
||||||
|
@ -39,11 +41,17 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(byte[] data, byte[] signature) {
|
public boolean isValid(byte[] data, byte[] concatSignature) {
|
||||||
Signature sig = createSignatureInstance();
|
Signature sig = createSignatureInstance();
|
||||||
PublicKey publicKey = (PublicKey) key;
|
PublicKey publicKey = (PublicKey) key;
|
||||||
try {
|
try {
|
||||||
int expectedSize = getSignatureByteArrayLength(alg);
|
// mandated per https://datatracker.ietf.org/doc/html/rfc7518#section-3.4 :
|
||||||
|
int requiredConcatByteLength = getSignatureByteArrayLength(alg);
|
||||||
|
|
||||||
|
byte[] derSignature;
|
||||||
|
|
||||||
|
if (requiredConcatByteLength != concatSignature.length) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application
|
* 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
|
* is configured to do so. This fallback is for backwards compatibility ONLY (to support tokens
|
||||||
|
@ -51,12 +59,30 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
|
||||||
* version of this library. This fallback is only enabled if the system property is set to 'true' due to
|
* 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.
|
* 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)
|
// TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs)
|
||||||
byte[] derSignature;
|
if (concatSignature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
|
||||||
if (expectedSize != signature.length && signature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
|
derSignature = concatSignature;
|
||||||
derSignature = signature;
|
|
||||||
} else {
|
} else {
|
||||||
derSignature = EllipticCurveProvider.transcodeSignatureToDER(signature);
|
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 {
|
||||||
|
|
||||||
|
//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);
|
return doVerify(sig, publicKey, data, derSignature);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -66,7 +92,7 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doVerify(Signature sig, PublicKey publicKey, byte[] data, byte[] signature)
|
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.initVerify(publicKey);
|
||||||
sig.update(data);
|
sig.update(data);
|
||||||
return sig.verify(signature);
|
return sig.verify(signature);
|
||||||
|
|
|
@ -23,16 +23,15 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.interfaces.ECKey;
|
|
||||||
|
|
||||||
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
|
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
|
||||||
|
|
||||||
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
|
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
if (!(key instanceof PrivateKey && key instanceof ECKey)) {
|
if (!(key instanceof PrivateKey)) {
|
||||||
String msg = "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
|
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.";
|
"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();
|
Signature sig = createSignatureInstance();
|
||||||
sig.initSign(privateKey);
|
sig.initSign(privateKey);
|
||||||
sig.update(data);
|
sig.update(data);
|
||||||
return transcodeSignatureToConcat(sig.sign(), getSignatureByteArrayLength(alg));
|
return transcodeDERToConcat(sig.sign(), getSignatureByteArrayLength(alg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,12 @@ import io.jsonwebtoken.impl.DefaultHeader
|
||||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
|
||||||
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
||||||
|
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
|
||||||
import io.jsonwebtoken.impl.lang.Services
|
import io.jsonwebtoken.impl.lang.Services
|
||||||
import io.jsonwebtoken.io.Encoders
|
import io.jsonwebtoken.io.Encoders
|
||||||
import io.jsonwebtoken.io.Serializer
|
import io.jsonwebtoken.io.Serializer
|
||||||
import io.jsonwebtoken.lang.Strings
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException
|
||||||
import io.jsonwebtoken.security.Keys
|
import io.jsonwebtoken.security.Keys
|
||||||
import io.jsonwebtoken.security.WeakKeyException
|
import io.jsonwebtoken.security.WeakKeyException
|
||||||
import org.junit.Test
|
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
|
//Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20
|
||||||
@Test
|
@Test
|
||||||
void testParseClaimsJwsWithUnsignedJwt() {
|
void testParseClaimsJwsWithUnsignedJwt() {
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
@ -65,4 +68,29 @@ class EllipticCurveProviderTest {
|
||||||
assertEquals ise.message, "SignatureAlgorithm argument must represent an Elliptic Curve algorithm."
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,21 +36,23 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
final InvalidKeyException ex = new InvalidKeyException(msg)
|
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
|
@Override
|
||||||
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
|
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[16]
|
byte[] data = new byte[32]
|
||||||
byte[] signature = new byte[16]
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(data)
|
||||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
|
|
||||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
byte[] signature = new EllipticCurveSigner(alg, keypair.getPrivate()).sign(data)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
v.isValid(bytes, signature)
|
v.isValid(data, signature)
|
||||||
fail();
|
fail();
|
||||||
} catch (SignatureException se) {
|
} catch (SignatureException se) {
|
||||||
assertEquals se.message, 'Unable to verify Elliptic Curve signature using configured ECPublicKey. ' + msg
|
assertEquals se.message, 'Unable to verify Elliptic Curve signature using configured ECPublicKey. ' + msg
|
||||||
|
@ -80,12 +82,24 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void legacySignatureCompatDefaultTest() {
|
void legacySignatureCompatDefaultTest() {
|
||||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
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")
|
def data = withoutSignature.getBytes("US-ASCII")
|
||||||
signature.initSign(keypair.private)
|
signature.initSign(keypair.private)
|
||||||
signature.update(data)
|
signature.update(data)
|
||||||
def signed = signature.sign()
|
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
|
@Test
|
||||||
|
@ -107,21 +121,17 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
|
|
||||||
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
||||||
void testSignatureAllZeros() {
|
void testSignatureAllZeros() {
|
||||||
try {
|
|
||||||
byte[] forgedSig = new byte[64]
|
byte[] forgedSig = new byte[64]
|
||||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
def alg = SignatureAlgorithm.ES256
|
||||||
|
def keypair = EllipticCurveProvider.generateKeyPair(alg)
|
||||||
def data = withoutSignature.getBytes("US-ASCII")
|
def data = withoutSignature.getBytes("US-ASCII")
|
||||||
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, forgedSig)
|
def validator = new EllipticCurveSignatureValidator(alg, keypair.public)
|
||||||
fail("SignatureException expected")
|
assertFalse validator.isValid(data, forgedSig)
|
||||||
} catch(SignatureException expected) {
|
|
||||||
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
||||||
void testSignatureRZero() {
|
void testSignatureRZero() {
|
||||||
try {
|
|
||||||
byte[] r = new byte[32]
|
byte[] r = new byte[32]
|
||||||
byte[] s = new byte[32]; Arrays.fill(s, Byte.MAX_VALUE)
|
byte[] s = new byte[32]; Arrays.fill(s, Byte.MAX_VALUE)
|
||||||
byte[] sig = new byte[r.length + s.length]
|
byte[] sig = new byte[r.length + s.length]
|
||||||
|
@ -129,18 +139,14 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
System.arraycopy(s, 0, sig, r.length, s.length)
|
System.arraycopy(s, 0, sig, r.length, s.length)
|
||||||
|
|
||||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
|
||||||
def data = withoutSignature.getBytes("US-ASCII")
|
def data = withoutSignature.getBytes("US-ASCII")
|
||||||
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, sig)
|
def validator = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public)
|
||||||
fail("SignatureException expected")
|
assertFalse validator.isValid(data, sig)
|
||||||
} catch(SignatureException expected) {
|
|
||||||
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
@Test // asserts guard for JVM security bug CVE-2022-21449:
|
||||||
void testSignatureSZero() {
|
void testSignatureSZero() {
|
||||||
try {
|
|
||||||
byte[] r = new byte[32]; Arrays.fill(r, Byte.MAX_VALUE);
|
byte[] r = new byte[32]; Arrays.fill(r, Byte.MAX_VALUE);
|
||||||
byte[] s = new byte[32]
|
byte[] s = new byte[32]
|
||||||
byte[] sig = new byte[r.length + s.length]
|
byte[] sig = new byte[r.length + s.length]
|
||||||
|
@ -148,13 +154,21 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
System.arraycopy(s, 0, sig, r.length, s.length)
|
System.arraycopy(s, 0, sig, r.length, s.length)
|
||||||
|
|
||||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
def keypair = EllipticCurveProvider.generateKeyPair(SignatureAlgorithm.ES256)
|
||||||
def data = withoutSignature.getBytes("US-ASCII")
|
def data = withoutSignature.getBytes("US-ASCII")
|
||||||
new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public).isValid(data, sig)
|
def validator = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES256, keypair.public)
|
||||||
fail("SignatureException expected")
|
assertFalse validator.isValid(data, sig)
|
||||||
} catch(SignatureException expected) {
|
|
||||||
assertEquals 'Unable to verify Elliptic Curve signature using configured ECPublicKey. Invalid ECDSA signature format.', expected.getMessage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
|
@ -173,7 +187,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
try {
|
try {
|
||||||
def signature = new byte[257]
|
def signature = new byte[257]
|
||||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||||
EllipticCurveProvider.transcodeSignatureToDER(signature)
|
EllipticCurveProvider.transcodeConcatToDER(signature)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
assertEquals e.message, 'Invalid ECDSA signature format.'
|
assertEquals e.message, 'Invalid ECDSA signature format.'
|
||||||
|
@ -184,7 +198,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void invalidDERSignatureToJoseFormatTest() {
|
void invalidDERSignatureToJoseFormatTest() {
|
||||||
def verify = { signature ->
|
def verify = { signature ->
|
||||||
try {
|
try {
|
||||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||||
|
@ -209,14 +223,14 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
def signature = new byte[32]
|
def signature = new byte[32]
|
||||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||||
signature[0] = 0 as byte
|
signature[0] = 0 as byte
|
||||||
EllipticCurveProvider.transcodeSignatureToDER(signature) //no exception
|
EllipticCurveProvider.transcodeConcatToDER(signature) //no exception
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void edgeCaseSignatureToConcatLengthTest() {
|
void edgeCaseSignatureToConcatLengthTest() {
|
||||||
try {
|
try {
|
||||||
def signature = Decoders.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
|
def signature = Decoders.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
|
||||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
|
|
||||||
|
@ -227,7 +241,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void edgeCaseSignatureToConcatInvalidSignatureTest() {
|
void edgeCaseSignatureToConcatInvalidSignatureTest() {
|
||||||
try {
|
try {
|
||||||
def signature = Decoders.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
def signature = Decoders.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||||
|
@ -238,7 +252,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
|
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
|
||||||
try {
|
try {
|
||||||
def signature = Decoders.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
def signature = Decoders.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||||
|
@ -249,7 +263,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
|
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
|
||||||
try {
|
try {
|
||||||
def signature = Decoders.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
def signature = Decoders.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
EllipticCurveProvider.transcodeDERToConcat(signature, 132)
|
||||||
fail()
|
fail()
|
||||||
} catch (JwtException e) {
|
} catch (JwtException e) {
|
||||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||||
|
@ -260,7 +274,7 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
void verifySwarmTest() {
|
void verifySwarmTest() {
|
||||||
for (SignatureAlgorithm algorithm : [SignatureAlgorithm.ES256, SignatureAlgorithm.ES384, SignatureAlgorithm.ES512]) {
|
for (SignatureAlgorithm algorithm : [SignatureAlgorithm.ES256, SignatureAlgorithm.ES384, SignatureAlgorithm.ES512]) {
|
||||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
def keypair = EllipticCurveProvider.generateKeyPair(algorithm)
|
||||||
def data = withoutSignature.getBytes("US-ASCII")
|
def data = withoutSignature.getBytes("US-ASCII")
|
||||||
def signature = new EllipticCurveSigner(algorithm, keypair.private).sign(data)
|
def signature = new EllipticCurveSigner(algorithm, keypair.private).sign(data)
|
||||||
assert new EllipticCurveSignatureValidator(algorithm, keypair.public).isValid(data, signature)
|
assert new EllipticCurveSignatureValidator(algorithm, keypair.public).isValid(data, signature)
|
||||||
|
|
|
@ -43,13 +43,14 @@ class EllipticCurveSignerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithoutECPrivateKey() {
|
void testConstructorWithoutECPrivateKey() {
|
||||||
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
def pair = Keys.keyPairFor(SignatureAlgorithm.ES256)
|
||||||
try {
|
try {
|
||||||
new EllipticCurveSigner(SignatureAlgorithm.ES256, key)
|
new EllipticCurveSigner(SignatureAlgorithm.ES256, pair.public)
|
||||||
fail('EllipticCurveSigner should reject non ECPrivateKey instances.')
|
fail('EllipticCurveSigner should reject public ECKey instances.')
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (io.jsonwebtoken.security.InvalidKeyException expected) {
|
||||||
assertEquals expected.message, "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
|
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."
|
'type sun.security.ec.ECPublicKeyImpl is not an EC PrivateKey.'
|
||||||
|
assertEquals(msg, expected.getMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +60,6 @@ class EllipticCurveSignerTest {
|
||||||
SignatureAlgorithm alg = SignatureAlgorithm.ES256
|
SignatureAlgorithm alg = SignatureAlgorithm.ES256
|
||||||
|
|
||||||
KeyPair kp = Keys.keyPairFor(alg)
|
KeyPair kp = Keys.keyPairFor(alg)
|
||||||
PublicKey publicKey = kp.getPublic()
|
|
||||||
PrivateKey privateKey = kp.getPrivate()
|
PrivateKey privateKey = kp.getPrivate()
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
|
@ -87,14 +87,15 @@ class EllipticCurveSignerTest {
|
||||||
@Test
|
@Test
|
||||||
void testDoSignWithJoseSignatureFormatException() {
|
void testDoSignWithJoseSignatureFormatException() {
|
||||||
|
|
||||||
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
SignatureAlgorithm alg = SignatureAlgorithm.ES256
|
||||||
|
KeyPair kp = EllipticCurveProvider.generateKeyPair(alg)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
final JwtException ex = new JwtException(msg)
|
final JwtException ex = new JwtException(msg)
|
||||||
|
|
||||||
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
def signer = new EllipticCurveSigner(alg, privateKey) {
|
||||||
@Override
|
@Override
|
||||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
|
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
|
||||||
throw ex
|
throw ex
|
||||||
|
@ -116,14 +117,15 @@ class EllipticCurveSignerTest {
|
||||||
@Test
|
@Test
|
||||||
void testDoSignWithJdkSignatureException() {
|
void testDoSignWithJdkSignatureException() {
|
||||||
|
|
||||||
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
SignatureAlgorithm alg = SignatureAlgorithm.ES256
|
||||||
|
KeyPair kp = EllipticCurveProvider.generateKeyPair(alg)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
final java.security.SignatureException ex = new java.security.SignatureException(msg)
|
final java.security.SignatureException ex = new java.security.SignatureException(msg)
|
||||||
|
|
||||||
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
def signer = new EllipticCurveSigner(alg, privateKey) {
|
||||||
@Override
|
@Override
|
||||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
||||||
throw ex
|
throw ex
|
||||||
|
|
Loading…
Reference in New Issue