From 61510dfca58dd40b4b32c708935126785dcff48c Mon Sep 17 00:00:00 2001 From: Martin Treurnicht Date: Tue, 28 Jun 2016 12:12:40 -0700 Subject: [PATCH] Cleanup as per request of https://github.com/lhazlewood --- pom.xml | 5 - .../io/jsonwebtoken/impl/crypto/ECDSA.java | 177 ------------------ .../impl/crypto/EllipticCurveProvider.java | 169 ++++++++++++++++- .../EllipticCurveSignatureValidator.java | 4 +- .../impl/crypto/EllipticCurveSigner.java | 8 +- .../impl/crypto/SignatureProvider.java | 10 +- .../io/jsonwebtoken/lang/JOSEException.java | 22 --- ...EllipticCurveSignatureValidatorTest.groovy | 110 +++++++++++ .../crypto/EllipticCurveSignerTest.groovy | 8 +- 9 files changed, 292 insertions(+), 221 deletions(-) delete mode 100644 src/main/java/io/jsonwebtoken/impl/crypto/ECDSA.java delete mode 100644 src/main/java/io/jsonwebtoken/lang/JOSEException.java diff --git a/pom.xml b/pom.xml index 585e4700..0d11dbac 100644 --- a/pom.xml +++ b/pom.xml @@ -307,11 +307,6 @@ 100 90 - - io.jsonwebtoken.impl.crypto.ECDSA - 80 - 60 - diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/ECDSA.java b/src/main/java/io/jsonwebtoken/impl/crypto/ECDSA.java deleted file mode 100644 index d7d6a76e..00000000 --- a/src/main/java/io/jsonwebtoken/impl/crypto/ECDSA.java +++ /dev/null @@ -1,177 +0,0 @@ -package io.jsonwebtoken.impl.crypto; - - -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.lang.JOSEException; - - -/** - * Elliptic Curve Digital Signature Algorithm (ECDSA) functions and utilities. - */ -public class ECDSA { - - - /** - * Returns the expected signature byte array length (R + S parts) for - * the specified ECDSA algorithm. - * - * @param alg The ECDSA algorithm. Must be supported and not - * {@code null}. - * - * @return The expected byte array length for the signature. - * - * @throws JOSEException If the algorithm is not supported. - */ - public static int getSignatureByteArrayLength(final SignatureAlgorithm alg) - throws JOSEException { - - switch (alg) { - case ES256: return 64; - case ES384: return 96; - case ES512: return 132; - default: - throw new JOSEException("unsupported algorithm: " + alg.name()); - } - } - - - /** - * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated - * R + S format expected by ECDSA JWS. - * - * @param derSignature The ASN1./DER-encoded. Must not be {@code null}. - * @param outputLength The expected length of the ECDSA JWS signature. - * - * @return The ECDSA JWS encoded signature. - * - * @throws JOSEException If the ASN.1/DER signature format is invalid. - */ - public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) - throws JOSEException { - - if (derSignature.length < 8 || derSignature[0] != 48) { - throw new JOSEException("Invalid ECDSA signature format"); - } - - int offset; - if (derSignature[1] > 0) { - offset = 2; - } else if (derSignature[1] == (byte) 0x81) { - offset = 3; - } else { - throw new JOSEException("Invalid ECDSA signature format"); - } - - byte rLength = derSignature[offset + 1]; - - int i; - for (i = rLength; (i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0); i--) { - // do nothing - } - - byte sLength = derSignature[offset + 2 + rLength + 1]; - - int j; - for (j = sLength; (j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0); j--) { - // do nothing - } - - int rawLen = Math.max(i, j); - 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) { - throw new JOSEException("Invalid ECDSA signature format"); - } - - final byte[] concatSignature = new byte[2 * rawLen]; - - System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); - System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); - - return concatSignature; - } - - - - /** - * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by - * the JCA verifier. - * - * @param jwsSignature The JWS signature, consisting of the - * concatenated R and S values. Must not be - * {@code null}. - * - * @return The ASN.1/DER encoded signature. - * - * @throws JOSEException If the ECDSA JWS signature format is invalid. - */ - public static byte[] transcodeSignatureToDER(byte[] jwsSignature) - throws JOSEException { - - int rawLen = jwsSignature.length / 2; - - int i = rawLen; - - while((i > 0) && (jwsSignature[rawLen - i] == 0)) i--; - - int j = i; - - if (jwsSignature[rawLen - i] < 0) { - j += 1; - } - - int k = rawLen; - - while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) k--; - - int l = k; - - if (jwsSignature[2 * rawLen - k] < 0) { - l += 1; - } - - int len = 2 + j + 2 + l; - - if (len > 255) { - throw new JOSEException("Invalid ECDSA signature format"); - } - - int offset; - - final byte derSignature[]; - - if (len < 128) { - derSignature = new byte[2 + 2 + j + 2 + l]; - offset = 1; - } else { - derSignature = new byte[3 + 2 + j + 2 + l]; - derSignature[1] = (byte) 0x81; - offset = 2; - } - - derSignature[0] = 48; - derSignature[offset++] = (byte) len; - derSignature[offset++] = 2; - derSignature[offset++] = (byte) j; - - System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i); - - offset += j; - - derSignature[offset++] = 2; - derSignature[offset++] = (byte) l; - - System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k); - - return derSignature; - } - - - /** - * Prevents public instantiation. - */ - private ECDSA() {} -} diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java index 1c215e22..f388c8d9 100644 --- a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java +++ b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java @@ -15,9 +15,6 @@ */ package io.jsonwebtoken.impl.crypto; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.lang.Assert; - import java.security.Key; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -25,6 +22,10 @@ import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.lang.Assert; + /** * ElliptiCurve crypto provider. * @@ -135,4 +136,166 @@ public abstract class EllipticCurveProvider extends SignatureProvider { throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e); } } + + /** + * Returns the expected signature byte array length (R + S parts) for + * the specified ECDSA algorithm. + * + * @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 { + + switch (alg) { + case ES256: return 64; + case ES384: return 96; + case ES512: return 132; + default: + throw new JwtException("Unsupported Algorithm: " + alg.name()); + } + } + + + /** + * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated + * R + S format expected by ECDSA JWS. + * + * @param derSignature The ASN1./DER-encoded. Must not be {@code null}. + * @param outputLength The expected length of the ECDSA JWS signature. + * + * @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 { + + if (derSignature.length < 8 || derSignature[0] != 48) { + throw new JwtException("Invalid ECDSA signature format"); + } + + int offset; + if (derSignature[1] > 0) { + offset = 2; + } else if (derSignature[1] == (byte) 0x81) { + offset = 3; + } else { + throw new JwtException("Invalid ECDSA signature format"); + } + + byte rLength = derSignature[offset + 1]; + + int i = rLength; + while ((i > 0) + && (derSignature[(offset + 2 + rLength) - i] == 0)) + i--; + + byte sLength = derSignature[offset + 2 + rLength + 1]; + + int j = sLength; + while ((j > 0) + && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) + j--; + + int rawLen = Math.max(i, j); + 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) { + throw new JwtException("Invalid ECDSA signature format"); + } + + final byte[] concatSignature = new byte[2 * rawLen]; + + System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); + System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); + + return concatSignature; + } + + + + /** + * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by + * the JCA verifier. + * + * @param jwsSignature The JWS signature, consisting of the + * concatenated R and S values. Must not be + * {@code null}. + * + * @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 { + + int rawLen = jwsSignature.length / 2; + + int i = rawLen; + + while((i > 0) + && (jwsSignature[rawLen - i] == 0)) + i--; + + int j = i; + + if (jwsSignature[rawLen - i] < 0) { + j += 1; + } + + int k = rawLen; + + while ((k > 0) + && (jwsSignature[2 * rawLen - k] == 0)) + k--; + + int l = k; + + if (jwsSignature[2 * rawLen - k] < 0) { + l += 1; + } + + int len = 2 + j + 2 + l; + + if (len > 255) { + throw new JwtException("Invalid ECDSA signature format"); + } + + int offset; + + final byte derSignature[]; + + if (len < 128) { + derSignature = new byte[2 + 2 + j + 2 + l]; + offset = 1; + } else { + derSignature = new byte[3 + 2 + j + 2 + l]; + derSignature[1] = (byte) 0x81; + offset = 2; + } + + derSignature[0] = 48; + derSignature[offset++] = (byte) len; + derSignature[offset++] = 2; + derSignature[offset++] = (byte) j; + + System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i); + + offset += j; + + derSignature[offset++] = 2; + derSignature[offset++] = (byte) l; + + System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k); + + return derSignature; + } } diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java index 172521af..7d9435ec 100644 --- a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java +++ b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java @@ -40,7 +40,9 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple Signature sig = createSignatureInstance(); PublicKey publicKey = (PublicKey) key; try { - byte[] derSignature = ECDSA.transcodeSignatureToDER(signature); + int expectedSize = getSignatureByteArrayLength(alg); + //if the expected size is not valid for JOSE, fall back to ASN.1 DER signature + byte[] derSignature = expectedSize != signature.length && signature[0] == 0x30 ? signature : EllipticCurveProvider.transcodeSignatureToDER(signature); return doVerify(sig, publicKey, data, derSignature); } catch (Exception e) { String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. " + e.getMessage(); diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java index 41ce6c9b..14913f18 100644 --- a/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java +++ b/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java @@ -21,9 +21,9 @@ import java.security.PrivateKey; import java.security.Signature; import java.security.interfaces.ECPrivateKey; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; -import io.jsonwebtoken.lang.JOSEException; public class EllipticCurveSigner extends EllipticCurveProvider implements Signer { @@ -44,16 +44,16 @@ public class EllipticCurveSigner extends EllipticCurveProvider implements Signer throw new SignatureException("Invalid Elliptic Curve PrivateKey. " + e.getMessage(), e); } catch (java.security.SignatureException e) { throw new SignatureException("Unable to calculate signature using Elliptic Curve PrivateKey. " + e.getMessage(), e); - } catch (JOSEException e) { + } catch (JwtException e) { throw new SignatureException("Unable to convert signature to JOSE format. " + e.getMessage(), e); } } - protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JOSEException { + protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException { PrivateKey privateKey = (PrivateKey)key; Signature sig = createSignatureInstance(); sig.initSign(privateKey); sig.update(data); - return ECDSA.transcodeSignatureToConcat(sig.sign(), ECDSA.getSignatureByteArrayLength(alg)); + return transcodeSignatureToConcat(sig.sign(), getSignatureByteArrayLength(alg)); } } diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java b/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java index 4fb4f8a0..e10c7a77 100644 --- a/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java +++ b/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java @@ -15,16 +15,16 @@ */ package io.jsonwebtoken.impl.crypto; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.SignatureException; -import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.RuntimeEnvironment; - import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Signature; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.RuntimeEnvironment; + abstract class SignatureProvider { /** diff --git a/src/main/java/io/jsonwebtoken/lang/JOSEException.java b/src/main/java/io/jsonwebtoken/lang/JOSEException.java deleted file mode 100644 index 5b611909..00000000 --- a/src/main/java/io/jsonwebtoken/lang/JOSEException.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.jsonwebtoken.lang; - - -/** - * Javascript Object Signing and Encryption (JOSE) exception. - */ -public class JOSEException extends Exception { - - - private static final long serialVersionUID = 1L; - - - /** - * Creates a new JOSE exception with the specified message. - * - * @param message The exception message. - */ - public JOSEException(final String message) { - - super(message); - } -} diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy index bc6dea44..364b1c57 100644 --- a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.crypto +import io.jsonwebtoken.JwtException import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureException import io.jsonwebtoken.impl.TextCodec @@ -76,4 +77,113 @@ class EllipticCurveSignatureValidatorTest { //Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4 verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn") } + + @Test + void legacySignatureCompatTest() { + def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30" + def keypair = EllipticCurveProvider.generateKeyPair() + def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName, "BC") + def data = withoutSignature.getBytes("US-ASCII") + signature.initSign(keypair.private) + signature.update(data) + def signed = signature.sign() + assert new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, keypair.public).isValid(data, signed) + } + + @Test + void invalidAlgorithmTest() { + def invalidAlgorithm = SignatureAlgorithm.HS256 + try { + EllipticCurveProvider.getSignatureByteArrayLength(invalidAlgorithm) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Unsupported Algorithm: ' + invalidAlgorithm.name() + } + } + + @Test + void invalidECDSASignatureFormatTest() { + try { + def signature = new byte[257] + SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature) + EllipticCurveProvider.transcodeSignatureToDER(signature) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Invalid ECDSA signature format' + } + } + + @Test + void invalidDERSignatureToJoseFormatTest() { + def verify = { signature -> + try { + EllipticCurveProvider.transcodeSignatureToConcat(signature, 132) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Invalid ECDSA signature format' + } + } + def signature = new byte[257] + SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature) + //invalid type + signature[0] = 34 + verify(signature) + def shortSignature = new byte[7] + SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(shortSignature) + verify(shortSignature) + signature[0] = 48 +// signature[1] = 0x81 + signature[1] = -10 + verify(signature) + } + + @Test + void edgeCaseSignatureLengthTest() { + def signature = new byte[1] + EllipticCurveProvider.transcodeSignatureToDER(signature) + } + + @Test + void edgeCaseSignatureToConcatLengthTest() { + try { + def signature = TextCodec.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE") + EllipticCurveProvider.transcodeSignatureToConcat(signature, 132) + fail() + } catch (JwtException e) { + + } + } + + @Test + void edgeCaseSignatureToConcatInvalidSignatureTest() { + try { + def signature = TextCodec.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + EllipticCurveProvider.transcodeSignatureToConcat(signature, 132) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Invalid ECDSA signature format' + } + } + + @Test + void edgeCaseSignatureToConcatInvalidSignatureBranchTest() { + try { + def signature = TextCodec.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + EllipticCurveProvider.transcodeSignatureToConcat(signature, 132) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Invalid ECDSA signature format' + } + } + + @Test + void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() { + try { + def signature = TextCodec.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + EllipticCurveProvider.transcodeSignatureToConcat(signature, 132) + fail() + } catch (JwtException e) { + assertEquals e.message, 'Invalid ECDSA signature format' + } + } } diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy index c353a7dd..02067309 100644 --- a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy @@ -15,16 +15,16 @@ */ package io.jsonwebtoken.impl.crypto +import io.jsonwebtoken.JwtException import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureException -import io.jsonwebtoken.lang.JOSEException +import org.junit.Test import java.security.InvalidKeyException import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey -import org.junit.Test import static org.junit.Assert.* class EllipticCurveSignerTest { @@ -88,11 +88,11 @@ class EllipticCurveSignerTest { PrivateKey privateKey = kp.getPrivate(); String msg = 'foo' - final JOSEException ex = new JOSEException(msg) + final JwtException ex = new JwtException(msg) def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) { @Override - protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JOSEException { + protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException { throw ex } }