From 064a01ebfa5b832c260798dc777f5d45f94e6c1f Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Wed, 18 Oct 2017 15:43:24 -0300 Subject: [PATCH 1/2] fix ECDSA signature encoding Use a copy of the ECDSA tests with BouncyCastle provider --- .gitignore | 1 + lib/build.gradle | 4 +- .../auth0/jwt/algorithms/ECDSAAlgorithm.java | 97 +- .../com/auth0/jwt/ConcurrentVerifyTest.java | 30 - lib/src/test/java/com/auth0/jwt/PemUtils.java | 10 +- .../jwt/algorithms/ECDSAAlgorithmTest.java | 441 ++++++- .../ECDSABouncyCastleProviderTests.java | 1057 +++++++++++++++++ 7 files changed, 1573 insertions(+), 67 deletions(-) create mode 100644 lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java diff --git a/.gitignore b/.gitignore index 870edff2..ab35b855 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ Temporary Items # IntelliJ /out/ +/lib/out/ # mpeltonen/sbt-idea plugin .idea_modules/ diff --git a/lib/build.gradle b/lib/build.gradle index 75b93a92..54c414ef 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -36,10 +36,10 @@ compileJava { dependencies { compile 'com.fasterxml.jackson.core:jackson-databind:2.8.4' compile 'commons-codec:commons-codec:1.10' - compile 'org.bouncycastle:bcprov-jdk15on:1.55' + testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' testCompile 'junit:junit:4.12' testCompile 'net.jodah:concurrentunit:0.4.2' - testCompile 'org.hamcrest:hamcrest-library:1.3' + testCompile 'org.hamcrest:java-hamcrest:2.0.0.0' testCompile 'org.mockito:mockito-core:2.2.8' } diff --git a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java index 25201132..08ff2885 100644 --- a/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java +++ b/lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java @@ -44,10 +44,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException { if (publicKey == null) { throw new IllegalStateException("The given Public Key is null."); } - if (!isDERSignature(signatureBytes)) { - signatureBytes = JOSEToDER(signatureBytes); - } - boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, signatureBytes); + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, contentBytes, JOSEToDER(signatureBytes)); if (!valid) { throw new SignatureVerificationException(this); @@ -64,7 +61,8 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException { if (privateKey == null) { throw new IllegalStateException("The given Private Key is null."); } - return crypto.createSignatureFor(getDescription(), privateKey, contentBytes); + byte[] signature = crypto.createSignatureFor(getDescription(), privateKey, contentBytes); + return DERToJOSE(signature); } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) { throw new SignatureGenerationException(this, e); } @@ -75,15 +73,60 @@ public String getSigningKeyId() { return keyProvider.getPrivateKeyId(); } - private boolean isDERSignature(byte[] signature) { + //Visible for testing + byte[] DERToJOSE(byte[] derSignature) throws SignatureException { // DER Structure: http://crypto.stackexchange.com/a/1797 - // Should begin with 0x30 and have exactly the expected length - return signature[0] == 0x30 && signature.length != ecNumberSize * 2; + boolean derEncoded = derSignature[0] == 0x30 && derSignature.length != ecNumberSize * 2; + if (!derEncoded) { + throw new SignatureException("Invalid DER signature format."); + } + + final byte[] joseSignature = new byte[ecNumberSize * 2]; + + //Skip 0x30 + int offset = 1; + if (derSignature[1] == (byte) 0x81) { + //Skip sign + offset++; + } + + //Convert to unsigned. Should match DER length - offset + int encodedLength = derSignature[offset++] & 0xff; + if (encodedLength != derSignature.length - offset) { + throw new SignatureException("Invalid DER signature format."); + } + + //Skip 0x02 + offset++; + + //Obtain R number length (Includes padding) and skip it + int rLength = derSignature[offset++]; + if (rLength > ecNumberSize + 1) { + throw new SignatureException("Invalid DER signature format."); + } + int rPadding = ecNumberSize - rLength; + //Retrieve R number + System.arraycopy(derSignature, offset + Math.max(-rPadding, 0), joseSignature, Math.max(rPadding, 0), rLength + Math.min(rPadding, 0)); + + //Skip R number and 0x02 + offset += rLength + 1; + + //Obtain S number length. (Includes padding) + int sLength = derSignature[offset++]; + if (sLength > ecNumberSize + 1) { + throw new SignatureException("Invalid DER signature format."); + } + int sPadding = ecNumberSize - sLength; + //Retrieve R number + System.arraycopy(derSignature, offset + Math.max(-sPadding, 0), joseSignature, ecNumberSize + Math.max(sPadding, 0), sLength + Math.min(sPadding, 0)); + + return joseSignature; } - private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { + //Visible for testing + byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { if (joseSignature.length != ecNumberSize * 2) { - throw new SignatureException(String.format("The signature length was invalid. Expected %d bytes but received %d", ecNumberSize * 2, joseSignature.length)); + throw new SignatureException("Invalid JOSE signature format."); } // Retrieve R and S number's length and padding. @@ -94,10 +137,10 @@ private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { int length = 2 + rLength + 2 + sLength; if (length > 255) { - throw new SignatureException("Invalid ECDSA signature format"); + throw new SignatureException("Invalid JOSE signature format."); } - byte[] derSignature; + final byte[] derSignature; int offset; if (length > 0x7f) { derSignature = new byte[3 + length]; @@ -109,22 +152,38 @@ private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { } // DER Structure: http://crypto.stackexchange.com/a/1797 - // Header with length info + // Header with signature length info derSignature[0] = (byte) 0x30; - derSignature[offset++] = (byte) length; + derSignature[offset++] = (byte) (length & 0xff); + + // Header with "min R" number length derSignature[offset++] = (byte) 0x02; derSignature[offset++] = (byte) rLength; // R number - System.arraycopy(joseSignature, 0, derSignature, offset + (rLength - ecNumberSize), ecNumberSize); - offset += rLength; + if (rPadding < 0) { + //Sign + derSignature[offset++] = (byte) 0x00; + System.arraycopy(joseSignature, 0, derSignature, offset, ecNumberSize); + offset += ecNumberSize; + } else { + int copyLength = Math.min(ecNumberSize, rLength); + System.arraycopy(joseSignature, rPadding, derSignature, offset, copyLength); + offset += copyLength; + } - // S number length + // Header with "min S" number length derSignature[offset++] = (byte) 0x02; derSignature[offset++] = (byte) sLength; // S number - System.arraycopy(joseSignature, ecNumberSize, derSignature, offset + (sLength - ecNumberSize), ecNumberSize); + if (sPadding < 0) { + //Sign + derSignature[offset++] = (byte) 0x00; + System.arraycopy(joseSignature, ecNumberSize, derSignature, offset, ecNumberSize); + } else { + System.arraycopy(joseSignature, ecNumberSize + sPadding, derSignature, offset, Math.min(ecNumberSize, sLength)); + } return derSignature; } @@ -134,7 +193,7 @@ private int countPadding(byte[] bytes, int fromIndex, int toIndex) { while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) { padding++; } - return bytes[fromIndex + padding] > 0x7f ? padding : padding - 1; + return (bytes[fromIndex + padding] & 0xff) > 0x7f ? padding - 1 : padding; } //Visible for testing diff --git a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java index 92968b63..41098c25 100644 --- a/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java +++ b/lib/src/test/java/com/auth0/jwt/ConcurrentVerifyTest.java @@ -140,16 +140,6 @@ public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { concurrentVerify(verifier, token); } - @Test - public void shouldPassECDSA256VerificationWithDERSignature() throws Exception { - String token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; - ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); - Algorithm algorithm = Algorithm.ECDSA256(key); - JWTVerifier verifier = JWTVerifier.init(algorithm).withIssuer("auth0").build(); - - concurrentVerify(verifier, token); - } - @Test public void shouldPassECDSA384VerificationWithJOSESignature() throws Exception { String token = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.50UU5VKNdF1wfykY8jQBKpvuHZoe6IZBJm5NvoB8bR-hnRg6ti-CHbmvoRtlLfnHfwITa_8cJMy6TenMC2g63GQHytc8rYoXqbwtS4R0Ko_AXbLFUmfxnGnMC6v4MS_z"; @@ -160,16 +150,6 @@ public void shouldPassECDSA384VerificationWithJOSESignature() throws Exception { concurrentVerify(verifier, token); } - @Test - public void shouldPassECDSA384VerificationWithDERSignature() throws Exception { - String token = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; - ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); - Algorithm algorithm = Algorithm.ECDSA384(key); - JWTVerifier verifier = JWTVerifier.init(algorithm).withIssuer("auth0").build(); - - concurrentVerify(verifier, token); - } - @Test public void shouldPassECDSA512VerificationWithJOSESignature() throws Exception { String token = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AeCJPDIsSHhwRSGZCY6rspi8zekOw0K9qYMNridP1Fu9uhrA1QrG-EUxXlE06yvmh2R7Rz0aE7kxBwrnq8L8aOBCAYAsqhzPeUvyp8fXjjgs0Eto5I0mndE2QHlgcMSFASyjHbU8wD2Rq7ZNzGQ5b2MZfpv030WGUajT-aZYWFUJHVg2"; @@ -179,14 +159,4 @@ public void shouldPassECDSA512VerificationWithJOSESignature() throws Exception { concurrentVerify(verifier, token); } - - @Test - public void shouldPassECDSA512VerificationWithDERSignature() throws Exception { - String token = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; - ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); - Algorithm algorithm = Algorithm.ECDSA512(key); - JWTVerifier verifier = JWTVerifier.init(algorithm).withIssuer("auth0").build(); - - concurrentVerify(verifier, token); - } } diff --git a/lib/src/test/java/com/auth0/jwt/PemUtils.java b/lib/src/test/java/com/auth0/jwt/PemUtils.java index abfa65c6..5f026b0a 100644 --- a/lib/src/test/java/com/auth0/jwt/PemUtils.java +++ b/lib/src/test/java/com/auth0/jwt/PemUtils.java @@ -3,12 +3,14 @@ import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.interfaces.ECPublicKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; @@ -22,7 +24,9 @@ private static byte[] parsePEMFile(File pemFile) throws IOException { } PemReader reader = new PemReader(new FileReader(pemFile)); PemObject pemObject = reader.readPemObject(); - return pemObject.getContent(); + byte[] content = pemObject.getContent(); + reader.close(); + return content; } private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) { diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java index 09a9e264..86bf3c0b 100644 --- a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSAAlgorithmTest.java @@ -5,6 +5,9 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.ECDSAKeyProvider; import org.apache.commons.codec.binary.Base64; +import org.hamcrest.Matchers; +import org.hamcrest.collection.IsIn; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -14,9 +17,11 @@ import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.util.Arrays; import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; @@ -46,6 +51,8 @@ public class ECDSAAlgorithmTest { //JOSE Signatures obtained using Node 'jwa' lib: https://github.com/brianloveswords/node-jwa //DER Signatures obtained from source JOSE signature using 'ecdsa-sig-formatter' lib: https://github.com/Brightspace/node-ecdsa-sig-formatter + //These tests use the default preferred SecurityProvider to handle ECDSA algorithms + // Verify @Test @@ -57,7 +64,12 @@ public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { } @Test - public void shouldPassECDSA256VerificationWithDERSignature() throws Exception { + public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); Algorithm algorithm = Algorithm.ECDSA256(key); @@ -72,7 +84,12 @@ public void shouldPassECDSA256VerificationWithJOSESignatureWithBothKeys() throws } @Test - public void shouldPassECDSA256VerificationWithDERSignatureWithBothKeys() throws Exception { + public void shouldThrowOnECDSA256VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -122,11 +139,11 @@ public void shouldFailECDSA256VerificationWhenUsingPrivateKey() throws Exception } @Test - public void shouldFailECDSA256VerificationOnInvalidSignatureLength() throws Exception { + public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("The signature length was invalid. Expected 64 bytes but received 63"))); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); byte[] bytes = new byte[63]; new SecureRandom().nextBytes(bytes); @@ -172,7 +189,12 @@ public void shouldPassECDSA384VerificationWithJOSESignature() throws Exception { } @Test - public void shouldPassECDSA384VerificationWithDERSignature() throws Exception { + public void shouldThrowOnECDSA384VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); Algorithm algorithm = Algorithm.ECDSA384(key); @@ -187,7 +209,12 @@ public void shouldPassECDSA384VerificationWithJOSESignatureWithBothKeys() throws } @Test - public void shouldPassECDSA384VerificationWithDERSignatureWithBothKeys() throws Exception { + public void shouldThrowOnECDSA384VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -237,11 +264,11 @@ public void shouldFailECDSA384VerificationWhenUsingPrivateKey() throws Exception } @Test - public void shouldFailECDSA384VerificationOnInvalidSignatureLength() throws Exception { + public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("The signature length was invalid. Expected 96 bytes but received 95"))); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); byte[] bytes = new byte[95]; new SecureRandom().nextBytes(bytes); @@ -287,7 +314,12 @@ public void shouldPassECDSA512VerificationWithJOSESignature() throws Exception { } @Test - public void shouldPassECDSA512VerificationWithDERSignature() throws Exception { + public void shouldThrowOnECDSA512VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); Algorithm algorithm = Algorithm.ECDSA512(key); @@ -302,7 +334,12 @@ public void shouldPassECDSA512VerificationWithJOSESignatureWithBothKeys() throws } @Test - public void shouldPassECDSA512VerificationWithDERSignatureWithBothKeys() throws Exception { + public void shouldThrowECDSA512VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); algorithm.verify(JWT.decode(jwt)); @@ -352,11 +389,11 @@ public void shouldFailECDSA512VerificationWhenUsingPrivateKey() throws Exception } @Test - public void shouldFailECDSA512VerificationOnInvalidSignatureLength() throws Exception { + public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws Exception { exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("The signature length was invalid. Expected 132 bytes but received 131"))); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); byte[] bytes = new byte[131]; new SecureRandom().nextBytes(bytes); @@ -398,7 +435,7 @@ public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws E exception.expect(SignatureVerificationException.class); exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); exception.expectCause(isA(SignatureException.class)); - exception.expectCause(hasMessage(is("Invalid ECDSA signature format"))); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); byte[] bytes = new byte[256]; new SecureRandom().nextBytes(bytes); @@ -749,4 +786,382 @@ public void shouldReturnSigningKeyIdFromProvider() throws Exception { assertThat(algorithm.getSigningKeyId(), is("keyId")); } + + @Test + public void shouldThrowOnDERSignatureConversionIfDoesNotStartWithCorrectSequenceByte() throws Exception { + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + String content256 = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + byte[] signature = algorithm256.sign(content256.getBytes()); + signature[0] = (byte) 0x02; + algorithm256.DERToJOSE(signature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + int received = (int) derSignature[1]; + received--; + derSignature[1] = (byte) received; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfRNumberDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + derSignature[3] = (byte) 34; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + derSignature[4 + 32 + 1] = (byte) 34; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] joseSignature = new byte[32 * 2 - 1]; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid JOSE signature format."); + + algorithm256.JOSEToDER(joseSignature); + } + + @Test + public void shouldSignAndVerifyWithECDSA256() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + String content256 = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm256.sign(content256.getBytes()); + String signature256 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content256 + "." + signature256; + algorithm256.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldSignAndVerifyWithECDSA384() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + String content384 = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm384.sign(content384.getBytes()); + String signature384 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content384 + "." + signature384; + algorithm384.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldSignAndVerifyWithECDSA512() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + String content512 = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm512.sign(content512.getBytes()); + String signature512 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content512 + "." + signature512; + algorithm512.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldDecodeECDSA256JOSE() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(32, false, false); + byte[] derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, false, false); + + //With R padding + joseSignature = createJOSESignature(32, true, false); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, true, false); + + //With S padding + joseSignature = createJOSESignature(32, false, true); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, false, true); + + //With both paddings + joseSignature = createJOSESignature(32, true, true); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, true, true); + } + + @Test + public void shouldDecodeECDSA256DER() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(32, false, false); + byte[] joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, false, false); + + //With R padding + derSignature = createDERSignature(32, true, false); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, true, false); + + //With S padding + derSignature = createDERSignature(32, false, true); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, false, true); + + //With both paddings + derSignature = createDERSignature(32, true, true); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, true, true); + } + + @Test + public void shouldDecodeECDSA384JOSE() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(48, false, false); + byte[] derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, false, false); + + //With R padding + joseSignature = createJOSESignature(48, true, false); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, true, false); + + //With S padding + joseSignature = createJOSESignature(48, false, true); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, false, true); + + //With both paddings + joseSignature = createJOSESignature(48, true, true); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, true, true); + } + + @Test + public void shouldDecodeECDSA384DER() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(48, false, false); + byte[] joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, false, false); + + //With R padding + derSignature = createDERSignature(48, true, false); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, true, false); + + //With S padding + derSignature = createDERSignature(48, false, true); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, false, true); + + //With both paddings + derSignature = createDERSignature(48, true, true); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, true, true); + } + + @Test + public void shouldDecodeECDSA512JOSE() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(66, false, false); + byte[] derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, false, false); + + //With R padding + joseSignature = createJOSESignature(66, true, false); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, true, false); + + //With S padding + joseSignature = createJOSESignature(66, false, true); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, false, true); + + //With both paddings + joseSignature = createJOSESignature(66, true, true); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, true, true); + } + + @Test + public void shouldDecodeECDSA512DER() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(66, false, false); + byte[] joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, false, false); + + //With R padding + derSignature = createDERSignature(66, true, false); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, true, false); + + //With S padding + derSignature = createDERSignature(66, false, true); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, false, true); + + //With both paddings + derSignature = createDERSignature(66, true, true); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, true, true); + } + + + //Test Helpers + static void assertValidJOSESignature(byte[] joseSignature, int numberSize, boolean withRPadding, boolean withSPadding) { + Assert.assertThat(joseSignature, is(Matchers.notNullValue())); + Assert.assertThat(numberSize, is(IsIn.oneOf(32, 48, 66))); + + Assert.assertThat(joseSignature.length, is(numberSize * 2)); + + byte[] rCopy = Arrays.copyOfRange(joseSignature, 0, numberSize); + byte[] sCopy = Arrays.copyOfRange(joseSignature, numberSize, numberSize * 2); + + byte[] rNumber = new byte[numberSize]; + byte[] sNumber = new byte[numberSize]; + Arrays.fill(rNumber, (byte) 0x11); + Arrays.fill(sNumber, (byte) 0x22); + if (withRPadding) { + rNumber[0] = (byte) 0; + } + if (withSPadding) { + sNumber[0] = (byte) 0; + } + Assert.assertThat(Arrays.equals(rNumber, rCopy), is(true)); + Assert.assertThat(Arrays.equals(sNumber, sCopy), is(true)); + } + + static byte[] createDERSignature(int numberSize, boolean withRPadding, boolean withSPadding) { + Assert.assertThat(numberSize, is(IsIn.oneOf(32, 48, 66))); + + int rLength = withRPadding ? numberSize - 1 : numberSize; + int sLength = withSPadding ? numberSize - 1 : numberSize; + int totalLength = 2 + (2 + rLength) + (2 + sLength); + + byte[] rNumber = new byte[rLength]; + byte[] sNumber = new byte[sLength]; + Arrays.fill(rNumber, (byte) 0x11); + Arrays.fill(sNumber, (byte) 0x22); + + byte[] derSignature; + int offset = 0; + if (totalLength > 0x7f) { + totalLength++; + derSignature = new byte[totalLength]; + //Start sequence and sign + derSignature[offset++] = (byte) 0x30; + derSignature[offset++] = (byte) 0x81; + } else { + derSignature = new byte[totalLength]; + //Start sequence + derSignature[offset++] = (byte) 0x30; + } + + //Sequence length + derSignature[offset++] = (byte) (totalLength - offset); + + //R number + derSignature[offset++] = (byte) 0x02; + derSignature[offset++] = (byte) rLength; + System.arraycopy(rNumber, 0, derSignature, offset, rLength); + offset += rLength; + + //S number + derSignature[offset++] = (byte) 0x02; + derSignature[offset++] = (byte) sLength; + System.arraycopy(sNumber, 0, derSignature, offset, sLength); + + return derSignature; + } + + static byte[] createJOSESignature(int numberSize, boolean withRPadding, boolean withSPadding) { + Assert.assertThat(numberSize, is(IsIn.oneOf(32, 48, 66))); + + byte[] rNumber = new byte[numberSize]; + byte[] sNumber = new byte[numberSize]; + Arrays.fill(rNumber, (byte) 0x11); + Arrays.fill(sNumber, (byte) 0x22); + if (withRPadding) { + rNumber[0] = (byte) 0; + } + if (withSPadding) { + sNumber[0] = (byte) 0; + } + byte[] joseSignature = new byte[numberSize * 2]; + System.arraycopy(rNumber, 0, joseSignature, 0, numberSize); + System.arraycopy(sNumber, 0, joseSignature, numberSize, numberSize); + return joseSignature; + } + + static void assertValidDERSignature(byte[] derSignature, int numberSize, boolean withRPadding, boolean withSPadding) { + Assert.assertThat(derSignature, is(Matchers.notNullValue())); + Assert.assertThat(numberSize, is(IsIn.oneOf(32, 48, 66))); + + int rLength = withRPadding ? numberSize - 1 : numberSize; + int sLength = withSPadding ? numberSize - 1 : numberSize; + int totalLength = 2 + (2 + rLength) + (2 + sLength); + int offset = 0; + + //Start sequence + Assert.assertThat(derSignature[offset++], is((byte) 0x30)); + if (totalLength > 0x7f) { + //Add sign before sequence length + totalLength++; + Assert.assertThat(derSignature[offset++], is((byte) 0x81)); + } + //Sequence length + Assert.assertThat(derSignature[offset++], is((byte) (totalLength - offset))); + + //R number + Assert.assertThat(derSignature[offset++], is((byte) 0x02)); + Assert.assertThat(derSignature[offset++], is((byte) rLength)); + byte[] rCopy = Arrays.copyOfRange(derSignature, offset, offset + rLength); + offset += rLength; + + //S number + Assert.assertThat(derSignature[offset++], is((byte) 0x02)); + Assert.assertThat(derSignature[offset++], is((byte) sLength)); + byte[] sCopy = Arrays.copyOfRange(derSignature, offset, offset + sLength); + + + byte[] rNumber = new byte[rLength]; + byte[] sNumber = new byte[sLength]; + Arrays.fill(rNumber, (byte) 0x11); + Arrays.fill(sNumber, (byte) 0x22); + Assert.assertThat(Arrays.equals(rNumber, rCopy), is(true)); + Assert.assertThat(Arrays.equals(sNumber, sCopy), is(true)); + Assert.assertThat(derSignature.length, is(totalLength)); + } + } \ No newline at end of file diff --git a/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java new file mode 100644 index 00000000..4af1c4ec --- /dev/null +++ b/lib/src/test/java/com/auth0/jwt/algorithms/ECDSABouncyCastleProviderTests.java @@ -0,0 +1,1057 @@ +package com.auth0.jwt.algorithms; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.SignatureGenerationException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.ECDSAKeyProvider; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +import static com.auth0.jwt.PemUtils.readPrivateKeyFromFile; +import static com.auth0.jwt.PemUtils.readPublicKeyFromFile; +import static com.auth0.jwt.algorithms.ECDSAAlgorithmTest.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ECDSABouncyCastleProviderTests { + + private static final String PRIVATE_KEY_FILE_256 = "src/test/resources/ec256-key-private.pem"; + private static final String PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public.pem"; + private static final String INVALID_PUBLIC_KEY_FILE_256 = "src/test/resources/ec256-key-public-invalid.pem"; + + private static final String PRIVATE_KEY_FILE_384 = "src/test/resources/ec384-key-private.pem"; + private static final String PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public.pem"; + private static final String INVALID_PUBLIC_KEY_FILE_384 = "src/test/resources/ec384-key-public-invalid.pem"; + + private static final String PRIVATE_KEY_FILE_512 = "src/test/resources/ec512-key-private.pem"; + private static final String PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public.pem"; + private static final String INVALID_PUBLIC_KEY_FILE_512 = "src/test/resources/ec512-key-public-invalid.pem"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + private static final Provider bcProvider = new BouncyCastleProvider(); + + //JOSE Signatures obtained using Node 'jwa' lib: https://github.com/brianloveswords/node-jwa + //DER Signatures obtained from source JOSE signature using 'ecdsa-sig-formatter' lib: https://github.com/Brightspace/node-ecdsa-sig-formatter + + + //These tests add and use the BouncyCastle SecurityProvider to handle ECDSA algorithms + + @BeforeClass + public static void setUp() throws Exception { + //Set BC as the preferred bcProvider + Security.insertProviderAt(bcProvider, 1); + } + + @AfterClass + public static void tearDown() throws Exception { + Security.removeProvider(bcProvider.getName()); + } + + @Test + public void shouldPreferBouncyCastleProvider() throws Exception { + assertThat(Security.getProviders()[0], is(equalTo(bcProvider))); + } + + // Verify + + @Test + public void shouldPassECDSA256VerificationWithJOSESignature() throws Exception { + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + Algorithm algorithm = Algorithm.ECDSA256(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnECDSA256VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + Algorithm algorithm = Algorithm.ECDSA256(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA256VerificationWithJOSESignatureWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnECDSA256VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.MEYCIQDiJWTf5jS/hFPj/0hpCWn7x1n/h+xPMjKWCs9MMusS9AIhAMcFPJVLe2A9uvb8hl8sRO2IpGoKDRpDmyH14ixNPAHW"; + Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA256VerificationWithProvidedPublicKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + when(provider.getPublicKeyById("my-key-id")).thenReturn((ECPublicKey) publicKey); + String jwt = "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.D_oU4CB0ZEsxHOjcWnmS3ZJvlTzm6WcGFx-HASxnvcB2Xu2WjI-axqXH9xKq45aPBDs330JpRhJmqBSc2K8MXQ"; + Algorithm algorithm = Algorithm.ECDSA256(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.D_oU4CB0ZEsxHOjcWnmS3ZJvlTzm6WcGFx-HASxnvcB2Xu2WjI-axqXH9xKq45aPBDs330JpRhJmqBSc2K8MXQ"; + Algorithm algorithm = Algorithm.ECDSA256(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationWithInvalidPublicKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.W9qfN1b80B9hnMo49WL8THrOsf1vEjOhapeFemPMGySzxTcgfyudS5esgeBTO908X5SLdAr5jMwPUPBs9b6nNg"; + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationWhenUsingPrivateKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.W9qfN1b80B9hnMo49WL8THrOsf1vEjOhapeFemPMGySzxTcgfyudS5esgeBTO908X5SLdAr5jMwPUPBs9b6nNg"; + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationOnInvalidJOSESignatureLength() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + byte[] bytes = new byte[63]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationOnInvalidJOSESignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + + byte[] bytes = new byte[64]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA256VerificationOnInvalidDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + + byte[] bytes = new byte[64]; + bytes[0] = 0x30; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_256, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA384VerificationWithJOSESignature() throws Exception { + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.50UU5VKNdF1wfykY8jQBKpvuHZoe6IZBJm5NvoB8bR-hnRg6ti-CHbmvoRtlLfnHfwITa_8cJMy6TenMC2g63GQHytc8rYoXqbwtS4R0Ko_AXbLFUmfxnGnMC6v4MS_z"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + Algorithm algorithm = Algorithm.ECDSA384(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnECDSA384VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + Algorithm algorithm = Algorithm.ECDSA384(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA384VerificationWithJOSESignatureWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.50UU5VKNdF1wfykY8jQBKpvuHZoe6IZBJm5NvoB8bR-hnRg6ti-CHbmvoRtlLfnHfwITa_8cJMy6TenMC2g63GQHytc8rYoXqbwtS4R0Ko_AXbLFUmfxnGnMC6v4MS_z"; + Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnECDSA384VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9.MGUCMQDnRRTlUo10XXB/KRjyNAEqm+4dmh7ohkEmbk2+gHxtH6GdGDq2L4Idua+hG2Ut+ccCMH8CE2v/HCTMuk3pzAtoOtxkB8rXPK2KF6m8LUuEdCqPwF2yxVJn8ZxpzAur+DEv8w=="; + Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA384VerificationWithProvidedPublicKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + when(provider.getPublicKeyById("my-key-id")).thenReturn((ECPublicKey) publicKey); + String jwt = "eyJhbGciOiJFUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.9kjGuFTPx3ylfpqL0eY9H7TGmPepjQOBKI8UPoEvby6N7dDLF5HxLohosNxxFymNT7LzpeSgOPAB0wJEwG2Nl2ukgdUOpZOf492wog_i5ZcZmAykd3g1QH7onrzd69GU"; + Algorithm algorithm = Algorithm.ECDSA384(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJFUzM4NCIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.9kjGuFTPx3ylfpqL0eY9H7TGmPepjQOBKI8UPoEvby6N7dDLF5HxLohosNxxFymNT7LzpeSgOPAB0wJEwG2Nl2ukgdUOpZOf492wog_i5ZcZmAykd3g1QH7onrzd69GU"; + Algorithm algorithm = Algorithm.ECDSA384(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationWithInvalidPublicKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9._k5h1KyO-NE0R2_HAw0-XEc0bGT5atv29SxHhOGC9JDqUHeUdptfCK_ljQ01nLVt2OQWT2SwGs-TuyHDFmhPmPGFZ9wboxvq_ieopmYqhQilNAu-WF-frioiRz9733fU"; + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationWhenUsingPrivateKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + String jwt = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9._k5h1KyO-NE0R2_HAw0-XEc0bGT5atv29SxHhOGC9JDqUHeUdptfCK_ljQ01nLVt2OQWT2SwGs-TuyHDFmhPmPGFZ9wboxvq_ieopmYqhQilNAu-WF-frioiRz9733fU"; + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationOnInvalidJOSESignatureLength() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + byte[] bytes = new byte[95]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationOnInvalidJOSESignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + + byte[] bytes = new byte[96]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA384VerificationOnInvalidDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA384withECDSA"); + + byte[] bytes = new byte[96]; + new SecureRandom().nextBytes(bytes); + bytes[0] = 0x30; + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_384, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA512VerificationWithJOSESignature() throws Exception { + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AeCJPDIsSHhwRSGZCY6rspi8zekOw0K9qYMNridP1Fu9uhrA1QrG-EUxXlE06yvmh2R7Rz0aE7kxBwrnq8L8aOBCAYAsqhzPeUvyp8fXjjgs0Eto5I0mndE2QHlgcMSFASyjHbU8wD2Rq7ZNzGQ5b2MZfpv030WGUajT-aZYWFUJHVg2"; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + Algorithm algorithm = Algorithm.ECDSA512(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnECDSA512VerificationWithDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + ECKey key = (ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + Algorithm algorithm = Algorithm.ECDSA512(key); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA512VerificationWithJOSESignatureWithBothKeys() throws Exception { + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AeCJPDIsSHhwRSGZCY6rspi8zekOw0K9qYMNridP1Fu9uhrA1QrG-EUxXlE06yvmh2R7Rz0aE7kxBwrnq8L8aOBCAYAsqhzPeUvyp8fXjjgs0Eto5I0mndE2QHlgcMSFASyjHbU8wD2Rq7ZNzGQ5b2MZfpv030WGUajT-aZYWFUJHVg2"; + Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowECDSA512VerificationWithDERSignatureWithBothKeys() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.MIGIAkIB4Ik8MixIeHBFIZkJjquymLzN6Q7DQr2pgw2uJ0/UW726GsDVCsb4RTFeUTTrK+aHZHtHPRoTuTEHCuerwvxo4EICQgGALKocz3lL8qfH1444LNBLaOSNJp3RNkB5YHDEhQEsox21PMA9kau2TcxkOW9jGX6b9N9FhlGo0/mmWFhVCR1YNg=="; + Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldPassECDSA512VerificationWithProvidedPublicKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + when(provider.getPublicKeyById("my-key-id")).thenReturn((ECPublicKey) publicKey); + String jwt = "eyJhbGciOiJFUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.AGxEwbsYa2bQ7Y7DAcTQnVD8PmLSlhJ20jg2OfdyPnqdXI8SgBaG6lGciq3_pofFhs1HEoFoJ33Jcluha24oMHIvAfwu8qbv_Wq3L2eI9Q0L0p6ul8Pd_BS8adRa2PgLc36xXGcRc7ID5YH-CYaQfsTp5YIaF0Po3h0QyCoQ6ZiYQkqm"; + Algorithm algorithm = Algorithm.ECDSA512(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationWhenProvidedPublicKeyIsNull() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPublicKeyById("my-key-id")).thenReturn(null); + String jwt = "eyJhbGciOiJFUzUxMiIsImtpZCI6Im15LWtleS1pZCJ9.eyJpc3MiOiJhdXRoMCJ9.AGxEwbsYa2bQ7Y7DAcTQnVD8PmLSlhJ20jg2OfdyPnqdXI8SgBaG6lGciq3_pofFhs1HEoFoJ33Jcluha24oMHIvAfwu8qbv_Wq3L2eI9Q0L0p6ul8Pd_BS8adRa2PgLc36xXGcRc7ID5YH-CYaQfsTp5YIaF0Po3h0QyCoQ6ZiYQkqm"; + Algorithm algorithm = Algorithm.ECDSA512(provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationWithInvalidPublicKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AZgdopFFsN0amCSs2kOucXdpylD31DEm5ChK1PG0_gq5Mf47MrvVph8zHSVuvcrXzcE1U3VxeCg89mYW1H33Y-8iAF0QFkdfTUQIWKNObH543WNMYYssv3OtOj0znPv8atDbaF8DMYAtcT1qdmaSJRhx-egRE9HGZkinPh9CfLLLt58X"; + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationWhenUsingPrivateKey() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Public Key is null."))); + String jwt = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9.AZgdopFFsN0amCSs2kOucXdpylD31DEm5ChK1PG0_gq5Mf47MrvVph8zHSVuvcrXzcE1U3VxeCg89mYW1H33Y-8iAF0QFkdfTUQIWKNObH543WNMYYssv3OtOj0znPv8atDbaF8DMYAtcT1qdmaSJRhx-egRE9HGZkinPh9CfLLLt58X"; + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationOnInvalidJOSESignatureLength() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + byte[] bytes = new byte[131]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationOnInvalidJOSESignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + + byte[] bytes = new byte[132]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailECDSA512VerificationOnInvalidDERSignature() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA512withECDSA"); + + byte[] bytes = new byte[132]; + new SecureRandom().nextBytes(bytes); + bytes[0] = 0x30; + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(INVALID_PUBLIC_KEY_FILE_512, "EC")); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailJOSEToDERConversionOnInvalidJOSESignatureLength() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(SignatureException.class)); + exception.expectCause(hasMessage(is("Invalid JOSE signature format."))); + + byte[] bytes = new byte[256]; + new SecureRandom().nextBytes(bytes); + String signature = Base64.encodeBase64URLSafeString(bytes); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9." + signature; + + ECPublicKey publicKey = (ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm("ES256", "SHA256withECDSA", 128, provider); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnVerifyWhenSignatureAlgorithmDoesNotExists() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: some-alg"); + exception.expectCause(isA(NoSuchAlgorithmException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + .thenThrow(NoSuchAlgorithmException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnVerifyWhenThePublicKeyIsInvalid() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: some-alg"); + exception.expectCause(isA(InvalidKeyException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + .thenThrow(InvalidKeyException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldThrowOnVerifyWhenTheSignatureIsNotPrepared() throws Exception { + exception.expect(SignatureVerificationException.class); + exception.expectMessage("The Token's Signature resulted invalid when verified using the Algorithm: some-alg"); + exception.expectCause(isA(SignatureException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.verifySignatureFor(anyString(), any(PublicKey.class), any(byte[].class), any(byte[].class))) + .thenThrow(SignatureException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + String jwt = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + algorithm.verify(JWT.decode(jwt)); + } + + //Sign + private static final String ES256Header = "eyJhbGciOiJFUzI1NiJ9"; + private static final String ES384Header = "eyJhbGciOiJFUzM4NCJ9"; + private static final String ES512Header = "eyJhbGciOiJFUzUxMiJ9"; + private static final String auth0IssPayload = "eyJpc3MiOiJhdXRoMCJ9"; + + @Test + public void shouldDoECDSA256Signing() throws Exception { + Algorithm algorithmSign = Algorithm.ECDSA256((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + Algorithm algorithmVerify = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC")); + String jwtContent = String.format("%s.%s", ES256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithmSign.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithmVerify.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA256SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + String jwtContent = String.format("%s.%s", ES256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA256SigningWithProvidedPrivateKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"); + when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); + when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); + Algorithm algorithm = Algorithm.ECDSA256(provider); + String jwtContent = String.format("%s.%s", ES256Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailOnECDSA256SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA256(provider); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldFailOnECDSA256SigningWhenUsingPublicKey() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + Algorithm algorithm = Algorithm.ECDSA256((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC")); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldDoECDSA384Signing() throws Exception { + Algorithm algorithmSign = Algorithm.ECDSA384((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + Algorithm algorithmVerify = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC")); + String jwtContent = String.format("%s.%s", ES384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithmSign.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithmVerify.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA384SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + String jwtContent = String.format("%s.%s", ES384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA384SigningWithProvidedPrivateKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"); + when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); + when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); + Algorithm algorithm = Algorithm.ECDSA384(provider); + String jwtContent = String.format("%s.%s", ES384Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailOnECDSA384SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA384(provider); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldFailOnECDSA384SigningWhenUsingPublicKey() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA384withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + Algorithm algorithm = Algorithm.ECDSA384((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC")); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldDoECDSA512Signing() throws Exception { + Algorithm algorithmSign = Algorithm.ECDSA512((ECKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + Algorithm algorithmVerify = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC")); + String jwtContent = String.format("%s.%s", ES512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithmSign.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithmVerify.verify(JWT.decode(jwt)); + } + + @Test + public void shouldDoECDSA512SigningWithBothKeys() throws Exception { + Algorithm algorithm = Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + String jwtContent = String.format("%s.%s", ES512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + + @Test + public void shouldDoECDSA512SigningWithProvidedPrivateKey() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + PrivateKey privateKey = readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC"); + PublicKey publicKey = readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"); + when(provider.getPrivateKey()).thenReturn((ECPrivateKey) privateKey); + when(provider.getPublicKeyById(null)).thenReturn((ECPublicKey) publicKey); + Algorithm algorithm = Algorithm.ECDSA512(provider); + String jwtContent = String.format("%s.%s", ES512Header, auth0IssPayload); + byte[] contentBytes = jwtContent.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = algorithm.sign(contentBytes); + String jwtSignature = Base64.encodeBase64URLSafeString(signatureBytes); + String jwt = String.format("%s.%s", jwtContent, jwtSignature); + + assertThat(signatureBytes, is(notNullValue())); + algorithm.verify(JWT.decode(jwt)); + } + + @Test + public void shouldFailOnECDSA512SigningWhenProvidedPrivateKeyIsNull() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKey()).thenReturn(null); + Algorithm algorithm = Algorithm.ECDSA512(provider); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldFailOnECDSA512SigningWhenUsingPublicKey() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: SHA512withECDSA"); + exception.expectCause(isA(IllegalStateException.class)); + exception.expectCause(hasMessage(is("The given Private Key is null."))); + + Algorithm algorithm = Algorithm.ECDSA512((ECKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC")); + algorithm.sign(new byte[0]); + } + + @Test + public void shouldThrowOnSignWhenSignatureAlgorithmDoesNotExists() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: some-algorithm"); + exception.expectCause(isA(NoSuchAlgorithmException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + .thenThrow(NoSuchAlgorithmException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void shouldThrowOnSignWhenThePrivateKeyIsInvalid() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: some-algorithm"); + exception.expectCause(isA(InvalidKeyException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + .thenThrow(InvalidKeyException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void shouldThrowOnSignWhenTheSignatureIsNotPrepared() throws Exception { + exception.expect(SignatureGenerationException.class); + exception.expectMessage("The Token's Signature couldn't be generated when signing using the Algorithm: some-algorithm"); + exception.expectCause(isA(SignatureException.class)); + + CryptoHelper crypto = mock(CryptoHelper.class); + when(crypto.createSignatureFor(anyString(), any(PrivateKey.class), any(byte[].class))) + .thenThrow(SignatureException.class); + + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm(crypto, "some-alg", "some-algorithm", 32, provider); + algorithm.sign(ES256Header.getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void shouldReturnNullSigningKeyIdIfCreatedWithDefaultProvider() throws Exception { + ECPublicKey publicKey = mock(ECPublicKey.class); + ECPrivateKey privateKey = mock(ECPrivateKey.class); + ECDSAKeyProvider provider = ECDSAAlgorithm.providerForKeys(publicKey, privateKey); + Algorithm algorithm = new ECDSAAlgorithm("some-alg", "some-algorithm", 32, provider); + + assertThat(algorithm.getSigningKeyId(), is(nullValue())); + } + + @Test + public void shouldReturnSigningKeyIdFromProvider() throws Exception { + ECDSAKeyProvider provider = mock(ECDSAKeyProvider.class); + when(provider.getPrivateKeyId()).thenReturn("keyId"); + Algorithm algorithm = new ECDSAAlgorithm("some-alg", "some-algorithm", 32, provider); + + assertThat(algorithm.getSigningKeyId(), is("keyId")); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfDoesNotStartWithCorrectSequenceByte() throws Exception { + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + String content256 = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + byte[] signature = algorithm256.sign(content256.getBytes()); + signature[0] = (byte) 0x02; + algorithm256.DERToJOSE(signature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + int received = (int) derSignature[1]; + received--; + derSignature[1] = (byte) received; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfRNumberDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + derSignature[3] = (byte) 34; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnDERSignatureConversionIfSNumberDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] derSignature = createDERSignature(32, false, false); + derSignature[4 + 32 + 1] = (byte) 34; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid DER signature format."); + + algorithm256.DERToJOSE(derSignature); + } + + @Test + public void shouldThrowOnJOSESignatureConversionIfDoesNotHaveExpectedLength() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + byte[] joseSignature = new byte[32 * 2 - 1]; + exception.expect(SignatureException.class); + exception.expectMessage("Invalid JOSE signature format."); + + algorithm256.JOSEToDER(joseSignature); + } + + @Test + public void shouldSignAndVerifyWithECDSA256() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + String content256 = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm256.sign(content256.getBytes()); + String signature256 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content256 + "." + signature256; + algorithm256.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldSignAndVerifyWithECDSA384() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + String content384 = "eyJhbGciOiJFUzM4NCJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm384.sign(content384.getBytes()); + String signature384 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content384 + "." + signature384; + algorithm384.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldSignAndVerifyWithECDSA512() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + String content512 = "eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhdXRoMCJ9"; + + for (int i = 0; i < 10; i++) { + byte[] signature = algorithm512.sign(content512.getBytes()); + String signature512 = Base64.encodeBase64URLSafeString((signature)); + + String jwt = content512 + "." + signature512; + algorithm512.verify(JWT.decode(jwt)); + } + } + + @Test + public void shouldDecodeECDSA256JOSE() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(32, false, false); + byte[] derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, false, false); + + //With R padding + joseSignature = createJOSESignature(32, true, false); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, true, false); + + //With S padding + joseSignature = createJOSESignature(32, false, true); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, false, true); + + //With both paddings + joseSignature = createJOSESignature(32, true, true); + derSignature = algorithm256.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 32, true, true); + } + + @Test + public void shouldDecodeECDSA256DER() throws Exception { + ECDSAAlgorithm algorithm256 = (ECDSAAlgorithm) Algorithm.ECDSA256((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_256, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_256, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(32, false, false); + byte[] joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, false, false); + + //With R padding + derSignature = createDERSignature(32, true, false); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, true, false); + + //With S padding + derSignature = createDERSignature(32, false, true); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, false, true); + + //With both paddings + derSignature = createDERSignature(32, true, true); + joseSignature = algorithm256.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 32, true, true); + } + + @Test + public void shouldDecodeECDSA384JOSE() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(48, false, false); + byte[] derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, false, false); + + //With R padding + joseSignature = createJOSESignature(48, true, false); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, true, false); + + //With S padding + joseSignature = createJOSESignature(48, false, true); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, false, true); + + //With both paddings + joseSignature = createJOSESignature(48, true, true); + derSignature = algorithm384.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 48, true, true); + } + + @Test + public void shouldDecodeECDSA384DER() throws Exception { + ECDSAAlgorithm algorithm384 = (ECDSAAlgorithm) Algorithm.ECDSA384((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_384, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_384, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(48, false, false); + byte[] joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, false, false); + + //With R padding + derSignature = createDERSignature(48, true, false); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, true, false); + + //With S padding + derSignature = createDERSignature(48, false, true); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, false, true); + + //With both paddings + derSignature = createDERSignature(48, true, true); + joseSignature = algorithm384.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 48, true, true); + } + + @Test + public void shouldDecodeECDSA512JOSE() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + + //Without padding + byte[] joseSignature = createJOSESignature(66, false, false); + byte[] derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, false, false); + + //With R padding + joseSignature = createJOSESignature(66, true, false); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, true, false); + + //With S padding + joseSignature = createJOSESignature(66, false, true); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, false, true); + + //With both paddings + joseSignature = createJOSESignature(66, true, true); + derSignature = algorithm512.JOSEToDER(joseSignature); + assertValidDERSignature(derSignature, 66, true, true); + } + + @Test + public void shouldDecodeECDSA512DER() throws Exception { + ECDSAAlgorithm algorithm512 = (ECDSAAlgorithm) Algorithm.ECDSA512((ECPublicKey) readPublicKeyFromFile(PUBLIC_KEY_FILE_512, "EC"), (ECPrivateKey) readPrivateKeyFromFile(PRIVATE_KEY_FILE_512, "EC")); + + //Without padding + byte[] derSignature = createDERSignature(66, false, false); + byte[] joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, false, false); + + //With R padding + derSignature = createDERSignature(66, true, false); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, true, false); + + //With S padding + derSignature = createDERSignature(66, false, true); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, false, true); + + //With both paddings + derSignature = createDERSignature(66, true, true); + joseSignature = algorithm512.DERToJOSE(derSignature); + assertValidJOSESignature(joseSignature, 66, true, true); + } + +} From ecdc9f8dc36574ccbd4114e53ec6c9b802381496 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Mon, 6 Nov 2017 11:29:32 -0300 Subject: [PATCH 2/2] bump dependencies --- lib/build.gradle | 8 ++++---- .../com/auth0/jwt/impl/JsonNodeClaim.java | 20 +++++++++++-------- .../com/auth0/jwt/impl/JsonNodeClaimTest.java | 18 ++++++++++++----- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/build.gradle b/lib/build.gradle index 54c414ef..fb782d71 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -34,13 +34,13 @@ compileJava { } dependencies { - compile 'com.fasterxml.jackson.core:jackson-databind:2.8.4' - compile 'commons-codec:commons-codec:1.10' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.2' + compile 'commons-codec:commons-codec:1.11' testCompile 'org.bouncycastle:bcprov-jdk15on:1.58' testCompile 'junit:junit:4.12' - testCompile 'net.jodah:concurrentunit:0.4.2' + testCompile 'net.jodah:concurrentunit:0.4.3' testCompile 'org.hamcrest:java-hamcrest:2.0.0.0' - testCompile 'org.mockito:mockito-core:2.2.8' + testCompile 'org.mockito:mockito-core:2.11.0' } jacocoTestReport { diff --git a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java index 0f27656a..98e76a21 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java +++ b/lib/src/main/java/com/auth0/jwt/impl/JsonNodeClaim.java @@ -2,6 +2,7 @@ import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Claim; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; @@ -66,11 +67,10 @@ public T[] asArray(Class tClazz) throws JWTDecodeException { return null; } - ObjectMapper mapper = new ObjectMapper(); T[] arr = (T[]) Array.newInstance(tClazz, data.size()); for (int i = 0; i < data.size(); i++) { try { - arr[i] = mapper.treeToValue(data.get(i), tClazz); + arr[i] = getObjectMapper().treeToValue(data.get(i), tClazz); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); } @@ -84,11 +84,10 @@ public List asList(Class tClazz) throws JWTDecodeException { return null; } - ObjectMapper mapper = new ObjectMapper(); List list = new ArrayList<>(); for (int i = 0; i < data.size(); i++) { try { - list.add(mapper.treeToValue(data.get(i), tClazz)); + list.add(getObjectMapper().treeToValue(data.get(i), tClazz)); } catch (JsonProcessingException e) { throw new JWTDecodeException("Couldn't map the Claim's array contents to " + tClazz.getSimpleName(), e); } @@ -102,11 +101,12 @@ public Map asMap() throws JWTDecodeException { return null; } - ObjectMapper mapper = new ObjectMapper(); try { TypeReference> mapType = new TypeReference>() { }; - return mapper.treeAsTokens(data).readValueAs(mapType); + ObjectMapper thisMapper = getObjectMapper(); + JsonParser thisParser = thisMapper.treeAsTokens(data); + return thisParser.readValueAs(mapType); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to Map", e); } @@ -114,9 +114,8 @@ public Map asMap() throws JWTDecodeException { @Override public T as(Class tClazz) throws JWTDecodeException { - ObjectMapper mapper = new ObjectMapper(); try { - return mapper.treeAsTokens(data).readValueAs(tClazz); + return getObjectMapper().treeAsTokens(data).readValueAs(tClazz); } catch (IOException e) { throw new JWTDecodeException("Couldn't map the Claim value to " + tClazz.getSimpleName(), e); } @@ -151,4 +150,9 @@ static Claim claimFromNode(JsonNode node) { } return new JsonNodeClaim(node); } + + //Visible for testing + ObjectMapper getObjectMapper() { + return new ObjectMapper(); + } } diff --git a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java index 3f48ca1d..944860f9 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/JsonNodeClaimTest.java @@ -3,6 +3,8 @@ import com.auth0.jwt.UserPojo; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.Claim; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; @@ -14,6 +16,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentMatchers; import java.io.IOException; import java.util.*; @@ -24,8 +27,7 @@ import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class JsonNodeClaimTest { @@ -267,11 +269,17 @@ public void shouldGetMapValue() throws Exception { public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap() throws Exception { JsonNode value = mock(ObjectNode.class); when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT); - when(value.fields()).thenThrow(IOException.class); - Claim claim = claimFromNode(value); + + JsonNodeClaim claim = (JsonNodeClaim) claimFromNode(value); + JsonNodeClaim spiedClaim = spy(claim); + ObjectMapper mockedMapper = mock(ObjectMapper.class); + when(spiedClaim.getObjectMapper()).thenReturn(mockedMapper); + JsonParser mockedParser = mock(JsonParser.class); + when(mockedMapper.treeAsTokens(value)).thenReturn(mockedParser); + when(mockedParser.readValueAs(ArgumentMatchers.any(TypeReference.class))).thenThrow(IOException.class); exception.expect(JWTDecodeException.class); - claim.asMap(); + spiedClaim.asMap(); } @Test